1. 程式人生 > 其它 >誰說 HTTP GET 就不能通過 Body 來發送資料呢?

誰說 HTTP GET 就不能通過 Body 來發送資料呢?

一、簡介

  當我們被問及 HTTP 的 GET 與 POST 兩種請求方式的區別的時候,很多答案是說 GET 的資料須通過 URL 以 Query Parameter 來傳送,而 POST 可以通過請求體來發送資料,所以因 URL 的受限,往往 GET 無法傳送太多的字元。這個回答好比在啟用了 HTTPS 時,GET 請求 URL 中的引數仍然是明文傳輸的一樣。

  GET 果真不能通過 Request Body 來傳送資料嗎?非也。如此想法多半是因循著網頁中 form 的 method 屬性只有 get 與 post 兩種而來。因為把 form 的 method 設定為 post, 表單資料會放在 body 中,而 method 為 get(預設值) 時, 提交時瀏覽器會把表單中的字元拼接到 action 的 URL 後作為 query parameter 傳送。於是乎就有了這麼一種假像:HTTP GET 必須通過 URL 的查詢引數來發送資料。

  其實 HTTP 規範並未規定說 GET 就不能傳送 body 資料,在 RFCGET中只是說:The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.只是說 GET 意味著通過 URI 來識別資源。

二、測試

  我也是本著傳統上對 GET 與 POST 區別的誤解很多年,今天突然意識到 GET 應該可以使用 body, 況且 HTTP 本身是一個純文字的協議。沒有測試就沒有 100% 的發言權,所以做了如下的測試:

  在一個 Spring Boot Web 專案中建立的 GET 請求 API

1 @RestController
2 public class DemoController {
3  
4     @RequestMapping(value = "/", method = RequestMethod.GET)
5     public String getRequest(@RequestParam("id") String id, @RequestBody String body) {
6         return id + ": " + body;
7     }
8 }

  上而建立的 GET 請求,URL 是/?id=something, 然後希望通過 request body 來獲得請求資料

  再來一個測試用例,給 GET 請求傳送 body 資料

 1 @RunWith(SpringRunner.class)
 2 @WebMvcTest
 3 public class DemoControllerTest {
 4  
 5     @Autowired
 6     private MockMvc mockMvc;
 7  
 8     @Test
 9     public void shouldReturnDefaultMessage() throws Exception {
10         this.mockMvc.perform(get("/?id=100").content("Hello, Get Body"))
11             .andDo(print())
12             .andExpect(content().string(is("100: Hello, Get Body")));
13     }
14 }

  上面的單元測試順利通過,說明對於 GET 請求我們同樣可以使用 Request Body 來發送資料,而且 Spring 的測試框架也支援 GET 傳送 body 資料

  再作一個驗證,curl 命令, 需要用 -X 指定為 GET 請求,否則 curl 在使用 -d 傳送 body 資料時自動切換為 POST 請求

  通過 curl -v 可以看到詳細的請求響應資料,兩個請求的 Content-Length 都是 8, 即 "Get Body" 的長度,它們確實是在 Request Body 中,服務端接送 GET 來的 body 資料也沒有半點問題。

  下面是通過 Wireshark 捕獲到的資料包的樣子

  如果說通過 Spring 的測試用例以及 curl 命令還有所疑問的話,看上面那張圖片就分明的告訴我們是在使用 GET 傳送 body 資料的。

  但確實有些工具或類庫不讓我們傳送 GET 請求時設定 Body, 例如著名的 Postman, 在選擇 GET 時 Body 標籤是灰色不可用的。

  而且從目前最新的 Apache Http Client 4.5 元件,它的 HttpGet 也不支援設定 Request Body, 因為 HttpGet 沒有像 HttpPost 那樣的 setEntity(entity) 方法。

  另一個 OkHttpClient 庫也不支援 GET 傳送 Request Body, 當執行下面的程式碼時

1 new Request.Builder()
2    .url("http://localhost:8080/?id=100")
3    .method("GET", RequestBody.create(MediaType.parse("application/json"), "hello body"))
4    .build();

  直接告訴我

1 java.lang.IllegalArgumentException: method GET must not have a request body

  最後再試一個 AsyncHttpClient 庫

1 Dsl.asyncHttpClient()
2    .prepareGet("http://localhost:8080/?id=100")
3    .setBody("Get Body")
4    .execute()
5    .toCompletableFuture()
6    .thenAccept(System.out::println).join();

  輸出 "100: Get Body", 證明 AsyncHttpClient 是可以 GET 時傳送 Body 資料的。

三、總結

  Apache Http Client 和 OkHttpClient 都不支援 GET 請求傳送 Body 資料,而 AsyncHttpClient 是可以的。

  那麼回過頭來想想為什麼 HTTP 並未規定不可以 GET 中傳送 Body 內容,但卻不少知名的工具不能用 GET 傳送 Body 資料,所以大致的講我們仍然不推薦使用 GET 攜帶 Body 內容,一是因為有可能某些應用伺服器也會忽略掉 GET 的 Body 資料。我想更主要是 GET 被設計來用 URI 來識別資源,如果讓它的請求體中攜帶資料,那麼通常的快取服務便失效了,URI 不能作為快取的 Key;而是http協議是支援GET請求是帶請求體的,後端也是可以接受GET請求中的請求體,但是web瀏覽器會限制GET請求不攜帶請求體

  但另一方面,如果僅僅是為了讀取資源,而需要使用 Body 傳送一大批資料時,改用 POST 請求卻與 RESTFul 的 POST 語義不相符。這時候或許可以 GET + BODY, 但是不能對該請求以 URI 作為 Key 進行快取了。

四、參考文章

https://yanbin.blog/why-http-get-cannot-sent-data-with-reuqest-body/#more-8193

https://blog.csdn.net/liushuyul/article/details/113341456?utm_term=gethttp%E6%9C%89%E6%B2%A1%E6%9C%89%E8%AF%B7%E6%B1%82%E4%BD%93&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-0-113341456&spm=3001.4430

本文來自部落格園,作者:Mr-xxx,轉載請註明原文連結:https://www.cnblogs.com/MrLiuZF/p/15133243.html