1. 程式人生 > >三種常見的http content-type詳解

三種常見的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用以支援向伺服器傳送二進位制

資料。後來隨著web應用的增多,增加了諸如application/json的型別。

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。

參考