[{"data":1,"prerenderedAt":852},["ShallowReactive",2],{"page-\u002Fnetwork\u002Frestfulapi和graphql":3},{"id":4,"title":5,"body":6,"description":12,"extension":840,"meta":841,"navigation":847,"path":848,"seo":849,"stem":850,"__hash__":851},"content\u002Fnetwork\u002FRestfulAPI和GraphQL.md","RestfulAPI和GraphQL",{"type":7,"value":8,"toc":832},"minimark",[9,13,24,28,31,42,45,48,54,57,60,63,74,77,80,107,127,130,136,139,145,148,151,154,328,331,338,458,463,667,670,676,738,741,829],[10,11,12],"p",{},"RESTful API 和 GraphQL 是两种对立的 API 设计范式 ，restfulapi的哲学是一切皆资源，用url标识资源",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","\u002Fusers          → 用户集合\n\u002Fusers\u002F123      → 单个用户\n\u002Fusers\u002F123\u002Fposts → 用户的文章\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[25,26,27],"h2",{"id":27},"介绍",[10,29,30],{},"在restfulapi中，http方法表意动词操作资源，比如GET方法读取 GET \u002Fuser\u002F123，POST创建，POST \u002Fusers，PUT表全量更新 PATCH部分更新 DELETE删除。",[10,32,33,37,38,41],{},[34,35,36],"strong",{},"方法 + URL = 完整语义","。",[21,39,40],{},"DELETE \u002Fusers\u002F123"," 一看就知道\"删除 123 号用户\"。每个请求都是独立的，服务端不会保存客户端状态，有统一接口，状态码表意。",[10,43,44],{},"REST好用是好用，也有几个缺点，一个是过渡获取，客户端要的字段少服务器返回一堆，比如你可能只要name，但是一次性拿到的是全部（其实和这个要后端端点的设计），一个页面需要多个资源可能要多个请求。后端要版本管理，加字段可能破坏兼容性。",[10,46,47],{},"GraphQL核心思想是描述客户端需求之后服务端再返回这些。只需要单一端点，客户端指定好字段就行：",[14,49,52],{"className":50,"code":51,"language":19},[17],"POST \u002Fgraphql\n\nquery {\n    user(id: 123) {\n        name\n        posts {\n            title\n        }\n    }\n}\n\n直接返回\n{\n    \"data\": {\n        \"user\": {\n            \"name\": \"Alice\",\n            \"posts\": [\n                { \"title\": \"文章 1\" },\n                { \"title\": \"文章 2\" }\n            ]\n        }\n    }\n}\n",[21,53,51],{"__ignoreMap":23},[10,55,56],{},"缺点是缓存复杂，rest可以用HTTP天然缓存，GRAPHQL要客户端缓存，查询有复杂度，嵌套查询可能触发大量数据库查询。",[10,58,59],{},"除此外，还有好几种其他的： gRPC、tRPC、WS、SOAP。",[25,61,62],{"id":62},"options请求",[10,64,65,66,69,70,73],{},"在 RESTful API 中，",[34,67,68],{},"OPTIONS 请求","主要用于",[34,71,72],{},"协商跨域资源共享和","查询资源支持的 HTTP 方法。它本身不修改资源，属于“安全”和“幂等”的方法。",[10,75,76],{},"在非简单的请求前，浏览器会先发送OPTIONS请求去询问浏览器是否允许，比如触发场景是跨域+非简单请求，这个操作叫做CORS预检请求。",[10,78,79],{},"除此以外，OPTIONS会参与到了查询Allows头，客户端询问某个资源支持哪些HTTP方法，在手动调用或者工具探测的时候会发生。还有API能力发现，获取资源的原信息，支持的格式等。",[10,81,82,83,86,87,86,90,86,93,96,97,86,100,86,103,106],{},"我们最常用的请求叫做简单请求，就比如GET HEAD POST，请求头只包含：",[21,84,85],{},"Accept","、",[21,88,89],{},"Accept-Language",[21,91,92],{},"Content-Language",[21,94,95],{},"Content-Type","（且值只能是 ",[21,98,99],{},"application\u002Fx-www-form-urlencoded",[21,101,102],{},"multipart\u002Fform-data",[21,104,105],{},"text\u002Fplain","）",[10,108,109,110,113,114,116,117,120,121,86,124,106],{},"非简单请求会触发OPTIONS，比如PUT DELETE PATCH CONNECT TRACE OPTIONS本身。 或 ",[21,111,112],{},"POST"," 但 ",[21,115,95],{}," 是 ",[21,118,119],{},"application\u002Fjson","或带有自定义请求头（如 ",[21,122,123],{},"Authorization",[21,125,126],{},"X-Custom-Header",[10,128,129],{},"那么这个说完了我们来看看浏览器在CORS预检中的请求是什么样子：",[14,131,134],{"className":132,"code":133,"language":19},[17],"OPTIONS \u002Fapi\u002Fusers\u002F1 HTTP\u002F1.1\nOrigin: https:\u002F\u002Fexample.com\nAccess-Control-Request-Method: PUT\nAccess-Control-Request-Headers: Content-Type, Authorization\n",[21,135,133],{"__ignoreMap":23},[10,137,138],{},"当浏览器发送了这个请求之后，可能返回的就是：",[14,140,143],{"className":141,"code":142,"language":19},[17],"HTTP\u002F1.1 204 No Content\nAccess-Control-Allow-Origin: https:\u002F\u002Fexample.com\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE\nAccess-Control-Allow-Headers: Content-Type, Authorization\nAccess-Control-Max-Age: 86400\n",[21,144,142],{"__ignoreMap":23},[10,146,147],{},"记住，浏览器会返回允许的源、允许的方法、允许的请求头、还有预检结果缓存时间，期间内不再重复预检。",[25,149,150],{"id":150},"安全性和幂等性",[10,152,153],{},"作为一个前端开发，我们有必要记忆清楚每个方法的安全性和幂等性：",[155,156,157,179],"table",{},[158,159,160],"thead",{},[161,162,163,167,170,173,176],"tr",{},[164,165,166],"th",{},"方法",[164,168,169],{},"用途",[164,171,172],{},"安全",[164,174,175],{},"幂等",[164,177,178],{},"典型状态码",[180,181,182,201,218,235,251,268,284,300],"tbody",{},[161,183,184,190,193,196,198],{},[185,186,187],"td",{},[21,188,189],{},"GET",[185,191,192],{},"获取资源",[185,194,195],{},"✅",[185,197,195],{},[185,199,200],{},"200",[161,202,203,207,210,213,215],{},[185,204,205],{},[21,206,112],{},[185,208,209],{},"创建资源",[185,211,212],{},"❌",[185,214,212],{},[185,216,217],{},"201",[161,219,220,225,228,230,232],{},[185,221,222],{},[21,223,224],{},"PUT",[185,226,227],{},"全量替换资源",[185,229,212],{},[185,231,195],{},[185,233,234],{},"200 \u002F 204",[161,236,237,242,245,247,249],{},[185,238,239],{},[21,240,241],{},"PATCH",[185,243,244],{},"部分更新资源",[185,246,212],{},[185,248,195],{},[185,250,234],{},[161,252,253,258,261,263,265],{},[185,254,255],{},[21,256,257],{},"DELETE",[185,259,260],{},"删除资源",[185,262,212],{},[185,264,195],{},[185,266,267],{},"204",[161,269,270,275,278,280,282],{},[185,271,272],{},[21,273,274],{},"HEAD",[185,276,277],{},"获取响应头（无 body）",[185,279,195],{},[185,281,195],{},[185,283,200],{},[161,285,286,291,294,296,298],{},[185,287,288],{},[21,289,290],{},"OPTIONS",[185,292,293],{},"查询支持的方法 \u002F CORS 预检",[185,295,195],{},[185,297,195],{},[185,299,267],{},[161,301,302,320,322,324,326],{},[185,303,304,305,310,311,316,317,319],{},"安全表示不修改资源，只读。幂等表示执行一次和多次请求的效果是相同的。",[34,306,307,309],{},[21,308,112],{}," 既不安全也不幂等","，这是它和其他方法最大的区别。 ",[34,312,313,315],{},[21,314,224],{}," 是幂等的","，多次 ",[21,318,224],{}," 同一个资源，结果一样。",[185,321],{},[185,323],{},[185,325],{},[185,327],{},[25,329,330],{"id":330},"状态码",[10,332,333,334,337],{},"记住",[34,335,336],{},"分组含义","：",[155,339,340,353],{},[158,341,342],{},[161,343,344,347,350],{},[164,345,346],{},"分组",[164,348,349],{},"含义",[164,351,352],{},"常见",[180,354,355,368,386,407,437],{},[161,356,357,362,365],{},[185,358,359],{},[21,360,361],{},"1xx",[185,363,364],{},"信息",[185,366,367],{},"很少用",[161,369,370,375,378],{},[185,371,372],{},[21,373,374],{},"2xx",[185,376,377],{},"成功",[185,379,380,86,382,86,384],{},[21,381,200],{},[21,383,217],{},[21,385,267],{},[161,387,388,393,396],{},[185,389,390],{},[21,391,392],{},"3xx",[185,394,395],{},"重定向",[185,397,398,86,401,86,404],{},[21,399,400],{},"301",[21,402,403],{},"302",[21,405,406],{},"304",[161,408,409,414,417],{},[185,410,411],{},[21,412,413],{},"4xx",[185,415,416],{},"客户端错误",[185,418,419,86,422,86,425,86,428,86,431,86,434],{},[21,420,421],{},"400",[21,423,424],{},"401",[21,426,427],{},"403",[21,429,430],{},"404",[21,432,433],{},"405",[21,435,436],{},"429",[161,438,439,444,447],{},[185,440,441],{},[21,442,443],{},"5xx",[185,445,446],{},"服务端错误",[185,448,449,86,452,86,455],{},[21,450,451],{},"500",[21,453,454],{},"502",[21,456,457],{},"503",[10,459,460,337],{},[34,461,462],{},"高频状态码",[155,464,465,476],{},[158,466,467],{},[161,468,469,471,473],{},[164,470,330],{},[164,472,349],{},[164,474,475],{},"易错点",[180,477,478,489,507,523,535,547,559,571,583,595,607,619,631,643,655],{},[161,479,480,484,486],{},[185,481,482],{},[21,483,200],{},[185,485,377],{},[185,487,488],{},"通用",[161,490,491,495,498],{},[185,492,493],{},[21,494,217],{},[185,496,497],{},"已创建",[185,499,500,502,503,506],{},[21,501,112],{}," 创建资源后返回，通常带 ",[21,504,505],{},"Location"," 头",[161,508,509,513,516],{},[185,510,511],{},[21,512,267],{},[185,514,515],{},"无内容",[185,517,518,86,520,522],{},[21,519,257],{},[21,521,224],{}," 成功后返回，无 body",[161,524,525,529,532],{},[185,526,527],{},[21,528,400],{},[185,530,531],{},"永久重定向",[185,533,534],{},"资源永久换了 URL",[161,536,537,541,544],{},[185,538,539],{},[21,540,403],{},[185,542,543],{},"临时重定向",[185,545,546],{},"资源临时换了 URL",[161,548,549,553,556],{},[185,550,551],{},[21,552,406],{},[185,554,555],{},"未修改",[185,557,558],{},"缓存命中，客户端用本地缓存",[161,560,561,565,568],{},[185,562,563],{},[21,564,421],{},[185,566,567],{},"请求错误",[185,569,570],{},"参数格式错、缺少字段",[161,572,573,577,580],{},[185,574,575],{},[21,576,424],{},[185,578,579],{},"未认证",[185,581,582],{},"没登录，或 token 无效",[161,584,585,589,592],{},[185,586,587],{},[21,588,427],{},[185,590,591],{},"无权限",[185,593,594],{},"登录了，但没权限访问",[161,596,597,601,604],{},[185,598,599],{},[21,600,430],{},[185,602,603],{},"未找到",[185,605,606],{},"资源不存在",[161,608,609,613,616],{},[185,610,611],{},[21,612,433],{},[185,614,615],{},"方法不允许",[185,617,618],{},"URL 存在，但不支持这个方法",[161,620,621,625,628],{},[185,622,623],{},[21,624,436],{},[185,626,627],{},"请求过多",[185,629,630],{},"限流",[161,632,633,637,640],{},[185,634,635],{},[21,636,451],{},[185,638,639],{},"服务器内部错误",[185,641,642],{},"代码异常",[161,644,645,649,652],{},[185,646,647],{},[21,648,454],{},[185,650,651],{},"网关错误",[185,653,654],{},"上游服务挂了",[161,656,657,661,664],{},[185,658,659],{},[21,660,457],{},[185,662,663],{},"服务不可用",[185,665,666],{},"服务维护或过载",[25,668,669],{"id":669},"内容协商和其他分类",[10,671,672,673,337],{},"客户端和服务端通过 HTTP 头协商",[34,674,675],{},"数据格式",[155,677,678,688],{},[158,679,680],{},[161,681,682,685],{},[164,683,684],{},"请求头",[164,686,687],{},"作用",[180,689,690,701,712,721,731],{},[161,691,692,696],{},[185,693,694],{},[21,695,85],{},[185,697,698,699],{},"客户端想要什么格式，如 ",[21,700,119],{},[161,702,703,707],{},[185,704,705],{},[21,706,95],{},[185,708,709,710],{},"请求体的格式，如 ",[21,711,119],{},[161,713,714,718],{},[185,715,716],{},[21,717,89],{},[185,719,720],{},"想要什么语言",[161,722,723,728],{},[185,724,725],{},[21,726,727],{},"Accept-Encoding",[185,729,730],{},"想要什么压缩格式",[161,732,733,736],{},[185,734,735],{},"ACCEPT是客户端向服务端发送自己想要的数据格式，Content-Type是发送方告诉接收方它发的数据是什么格式，如果服务端返回406，就表示Not Acceptable，无法满足Accept。",[185,737],{},[10,739,740],{},"CORS相关的有很多的头，跨域的时候服务端需要返回这些头：",[155,742,743,752],{},[158,744,745],{},[161,746,747,750],{},[164,748,749],{},"响应头",[164,751,687],{},[180,753,754,764,774,784,794,804,814],{},[161,755,756,761],{},[185,757,758],{},[21,759,760],{},"Access-Control-Allow-Origin",[185,762,763],{},"允许的源",[161,765,766,771],{},[185,767,768],{},[21,769,770],{},"Access-Control-Allow-Methods",[185,772,773],{},"允许的方法",[161,775,776,781],{},[185,777,778],{},[21,779,780],{},"Access-Control-Allow-Headers",[185,782,783],{},"允许的请求头",[161,785,786,791],{},[185,787,788],{},[21,789,790],{},"Access-Control-Allow-Credentials",[185,792,793],{},"是否允许携带 cookie",[161,795,796,801],{},[185,797,798],{},[21,799,800],{},"Access-Control-Max-Age",[185,802,803],{},"预检结果缓存时间",[161,805,806,811],{},[185,807,808],{},[21,809,810],{},"Access-Control-Expose-Headers",[185,812,813],{},"客户端能读取的响应头",[161,815,816,827],{},[185,817,818,819,822,823,826],{},"请求头也用来认证和授权，比如我们常见的Bearer Token，",[21,820,821],{},"Authorization: Bearer \u003Ctoken>","，最常用。或者",[21,824,825],{},"API KEY：X-API-Key: \u003Ckey>"," 等。",[185,828],{},[10,830,831],{},"其他的还有缓存控制、条件请求、范围请求、代理转发、连接管理、安全、追踪，这个我先不讲，HTTP笔记我会细讲。",{"title":23,"searchDepth":833,"depth":833,"links":834},2,[835,836,837,838,839],{"id":27,"depth":833,"text":27},{"id":62,"depth":833,"text":62},{"id":150,"depth":833,"text":150},{"id":330,"depth":833,"text":330},{"id":669,"depth":833,"text":669},"md",{"date":842,"tags":843},"2026-10-11",[844,845,846],"network","restful","graphql",true,"\u002Fnetwork\u002Frestfulapi和graphql",{"description":12},"network\u002FRestfulAPI和GraphQL","umFZ9vGERTnG2vaGjJi2bibv1fXRiiLRuXTUNN5O3sM",1791740440488]