三種常見的http content-type詳解
介紹
http協議是建立在tcp/ip協議之上的應用層協議,主要包括三個部分,狀態行,頭部資訊,訊息主體。對應一個http請求就是:請求行,請求頭,請求體。
協議規定post提交的資料,必須包含在訊息主體中entity-body中,但是協議並沒有規定資料使用什麼編碼方式。開發者可以自己決定訊息主體的格式。
資料傳送出去後,需要接收的服務端解析成功,一般服務端會根據content-type欄位來獲取引數是怎麼編碼的,然後對應去解碼。
在最早的http post請求中,只支援application/x-www-form-urlencoded,引數都是通過瀏覽器的url傳遞。其實是不支援檔案上傳的,這樣有很多不便。在1995年的時候,出臺了rfc1867,也就是《RFC 1867 From-based file upload in HTML》,用以支援檔案上傳。所以content-type擴充了multipart/form-data用以支援向伺服器傳送二進位制
application/x-www-form-urlencoded
在最開始的請求方式中,請求引數都是放在url中,表單提交的時候,都是以key=&value=的方式寫在url後面。這也是瀏覽器表單提交的預設方式。
此時可以直接呼叫request.getInputStream或request.getReader獲取到請求內容,再解析出具體的引數。後者只是對前者的一個封裝,可以讓呼叫者更方便字元內容的處理。可以看到:
@Override public BufferedReader getReader() throws IOException { if (this.reader == null) { this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); } return this.reader; }
public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException { super(in); if (charsetName == null) throw new NullPointerException("charsetName"); sd = StreamDecoder.forInputStreamReader(in, this, charsetName); }
也可以通過request.getParameter獲取到引數。
但是需要注意,getInputStream、getReader、getParameter在一定的場景是互斥的。
multipart/form-data
此種方式多用於檔案上傳,表單資料都儲存在http的正文部分,各個表單項之間用boundary分開。
一次完整的抓包如下:
POST /ecard/uploadFaceImage?timestamp=1531906535406 HTTP/1.0
Host: www.example.com
X-Real-IP: 183.156.142.242
X-Forwarded-For: 183.156.142.242
Connection: close
Content-Length: 230101
sign: 9a7d3b4978979ef65a12e34ae1cf7b2d
accept: */*
user-agent: Mozilla/5.0 (Linux; U; Android 6.0.1; zh-CN; OPPO R9s Build/MMB29M)
AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108
UCBrowser/11.8.8.968 UWS/2.13.1.42 Mobile Safari/537.36 UCBS/2.13.1.42_180629181124
ChannelId(1) NebulaSDK/1.8.100112 Nebula AlipayDefined(nt:WIFI,ws:360|0|3.0)
AliApp(AP/10.1.28.560) AlipayClient/10.1.28.560 Language/zh-Hans useStatusBar/true
isConcaveScreen/false
Cookie: ssl_upgrade=0; spanner=6tlJA6NZwnkqTDN+BMhdT7lbzLPsFJUeXt2T4qEYgj0=
Accept-Encoding: gzip
Content-Type: multipart/form-data; boundary=pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
Content-Disposition: form-data; name="personCode"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit
DM1203
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G
Content-Disposition: form-data; name="DM1203"; filename="123524587.jpg"
Content-Type:
Content-Transfer-Encoding: binary
圖片二進位制資料(特別長)
--pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G--
HTTP/1.1 200
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: Origin, No-Cache, X-Requested-With,
If-Modified-Since, Pragma, Cache-Control, Expires, Content-Type
Access-Control-Allow-Credentials: true
XDomainRequestAllowed: 1
Content-Type: application/json;charset=UTF-8
Date: Wed, 18 Jul 2018 09:35:36 GMT
Connection: close
{"retCode":1,"msg":"success","data":null}
可以看到裡面有一個boundary分界,值為:pgRq9HriiaBmfSo5rfyEJPtcumxb4fd6o15f_3G,請求時,會放在Content-Type後面傳到伺服器,伺服器根據這個邊界解析資料,劃分段,每一段都是一項資料。每一項中的name屬性就是唯一的id
此時用request.getParameter是取不到資料的,這個時候需要通過request.getInputStream來獲取資料。這時取到的是一個InputStream,無法直接取到指定的表單項。但是有很多開源的元件可以直接利用,比如apache的fileupload元件。通過這些開源的upload元件,提供的api,就可以直接從request中取得指定的表單項。
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> list = upload.parseRequest(request);
上面的程式碼中,接下來就可通過遍歷list獲取引數了。
application/json
現在越來越多的應用使用application/json,用來告訴服務端訊息主體是序列化的json字串。由於json規範的流行,各大瀏覽器都開始原生支援JSON.stringfy。
而且spring對這個content-Type上傳的資料有很好的支援,可以直接通過@RequestBody進行接收。也是當前完美適配當前流行的RestApi。
參考