簡介
上一篇的文章中,我們講到了如何從HTTP伺服器中下載檔案,和搭建下載檔案伺服器應該注意的問題,使用的GET方法。本文將會討論一下常用的向伺服器提交資料的POST方法和如何向伺服器上傳檔案。
GET方法上傳資料
按照HTTP的規範,PUT一般是向伺服器上傳資料,雖然不提倡,但是也可以使用GET向伺服器端上傳資料。
先看下GET客戶端的構建中需要注意的問題。
GET請求實際上就是一個URI,URI後面帶有請求的引數,netty提供了一個QueryStringEncoder專門用來構建引數內容:
// HTTP請求
QueryStringEncoder encoder = new QueryStringEncoder(get);
// 新增請求引數
encoder.addParam("method", "GET");
encoder.addParam("name", "flydean");
encoder.addParam("site", "www.flydean.com");
URI uriGet = new URI(encoder.toString());
有了請求URI,就可以建立HttpRequest了,當然這個HttpRequest中還需要有對應的HTTP head資料:
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriGet.toASCIIString());
HttpHeaders headers = request.headers();
headers.set(HttpHeaderNames.HOST, host);
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
headers.set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP + "," + HttpHeaderValues.DEFLATE);
headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set(HttpHeaderNames.REFERER, uriSimple.toString());
headers.set(HttpHeaderNames.USER_AGENT, "Netty Simple Http Client side");
headers.set(HttpHeaderNames.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
headers.set(
HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode(
new DefaultCookie("name", "flydean"),
new DefaultCookie("site", "www.flydean.com"))
);
我們知道HttpRequest中只有兩部分資料,分別是HttpVersion和HttpHeaders。HttpVersion就是HTTP協議的版本號,HttpHeaders就是設定的header內容。
對於GET請求來說,因為所有的內容都包含在URI中,所以不需要額外的HTTPContent,直接傳送HttpRequest到伺服器就可以了。
channel.writeAndFlush(request);
然後看下伺服器端接收GET請求之後怎麼進行處理。
伺服器端收到HttpObject物件的msg之後,需要將其轉換成HttpRequest物件,就可以通過protocolVersion(),uri()和headers()拿到相應的資訊。
對於URI中的引數,netty提供了QueryStringDecoder類可以方便的對URI中引數進行解析:
//解析URL中的引數
QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
Map<String, List<String>> uriAttributes = decoderQuery.parameters();
for (Entry<String, List<String>> attr: uriAttributes.entrySet()) {
for (String attrVal: attr.getValue()) {
responseContent.append("URI: ").append(attr.getKey()).append('=').append(attrVal).append("\r\n");
}
}
POST方法上傳資料
對於POST請求,它比GET請求多了一個HTTPContent,也就是說除了基本的HttpRequest資料之外,還需要一個PostBody。
如果只是一個普通的POST,也就是POST內容都是key=value的形式,則比較簡單,如果POST中包含有檔案,那麼會比較複雜,需要用到ENCTYPE="multipart/form-data"。
netty提供了一個HttpPostRequestEncoder類,用於快速對request body進行編碼,先看下HttpPostRequestEncoder類的完整建構函式:
public HttpPostRequestEncoder(
HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset,
EncoderMode encoderMode)
其中request就是要編碼的HttpRequest,multipart表示是否是"multipart/form-data"的格式,charset編碼方式,預設情況下是CharsetUtil.UTF_8。encoderMode是編碼的模式,目前有三種編碼模式,分別是RFC1738,RFC3986和HTML5。
預設情況下的編碼模式是RFC1738,這也是大多數form提交資料的編碼方式。但是它並不適用於OAUTH,如果要使用OAUTH的話,則可以使用RFC3986。HTML5禁用了multipart/form-data的混合模式。
最後,我們講講HttpDataFactory。factory主要用來建立InterfaceHttpData。它有一個minSize引數,如果建立的HttpData大小大於minSize則會存放在磁碟中,否則直接在記憶體中建立。
InterfaceHttpData有三種HttpData的型別,分別是Attribute, FileUpload和InternalAttribute。
Attribute就是POST請求中傳入的屬性值。FileUpload就是POST請求中傳入的檔案,還有InternalAttribute是在encoder內部使用的,這裡不過多討論。
因此,根據傳入的minSize引數大小,Attribute和FileUpload可以被分成下面幾種:
MemoryAttribute, DiskAttribute or MixedAttribute
MemoryFileUpload, DiskFileUpload or MixedFileUpload
在這一節我們先看一下在POST請求中並不上傳檔案的處理方式,首先建立HTTP request和PostBody encoder:
// 構建HTTP request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString());
HttpPostRequestEncoder bodyRequestEncoder =
new HttpPostRequestEncoder(factory, request, false);
向request中新增headers:
// 新增headers
for (Entry<String, String> entry : headers) {
request.headers().set(entry.getKey(), entry.getValue());
}
然後再向bodyRequestEncoder中新增form屬性:
// 新增form屬性
bodyRequestEncoder.addBodyAttribute("method", "POST");
bodyRequestEncoder.addBodyAttribute("name", "flydean");
bodyRequestEncoder.addBodyAttribute("site", "www.flydean.com");
bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false);
注意,上面我們向bodyRequestEncoder中新增了method,name和site這幾個屬性。然後新增了一個FileUpload。但是因為我們的編碼方式並不是"multipart/form-data",所以這裡傳遞的只是檔名,並不是整個檔案。
最後,我們要呼叫bodyRequestEncoder的finalizeRequest方法,返回最終要傳送的request。在finalizeRequest的過程中,還會根據傳輸資料的大小來設定transfer-encoding是否為chunked。
如果傳輸的內容比較大,則需要分段進行傳輸,這時候需要設定transfer-encoding = chunked,否則不進行設定。
最後傳送請求:
// 傳送請求
channel.write(request);
在server端,我們同樣需要構造一個HttpDataFactory,然後使用這個factory來構造一個HttpPostRequestDecoder,來對encoder出來的資料進行decode:
HttpDataFactory factory =
new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
//POST請求
decoder = new HttpPostRequestDecoder(factory, request);
因為server端收到的訊息根據傳送訊息的長度可以能是HttpContent,也可能是LastHttpContent。如果是HttpContent,我們將解析的結果放到一個StringBuilder中快取起來,等接收到LastHttpContent再一起傳送出去即可。
在收到HttpContent之後,我們呼叫decoder.offer方法,對HttpContent進行解碼:
decoder.offer(chunk);
在decoder內部有兩個儲存HttpData資料的容器,分別是:
List<InterfaceHttpData> bodyListHttpData
和
Map<String, List<InterfaceHttpData>> bodyMapHttpData
decoder.offer就是對chunk進行解析,然後將解析過後的資料填充到bodyListHttpData和bodyMapHttpData中。
解析過後,就可以對解析過後的資料進行讀取了。
可以通過decoder的hasNext和next方法對bodyListHttpData進行遍歷,從而獲取到對應的InterfaceHttpData。
通過data.getHttpDataType()可以拿到InterfaceHttpData的資料型別,上面也講過了有Attribute和FileUpload兩種型別。
POST方法上傳檔案
如果要POST檔案,客戶端在建立HttpPostRequestEncoder的時候傳入multipart=true即可:
HttpPostRequestEncoder bodyRequestEncoder =
new HttpPostRequestEncoder(factory, request, true);
然後分別呼叫setBodyHttpDatas和finalizeRequest方法,生成HttpRequest就可以向channel寫入了:
// 新增body http data
bodyRequestEncoder.setBodyHttpDatas(bodylist);
// finalize request,判斷是否需要chunk
request = bodyRequestEncoder.finalizeRequest();
// 傳送請求頭
channel.write(request);
要注意,如果是transfer-encoding = chunked,那麼這個HttpRequest只是請求頭的資訊,我們還需要手動將HttpContent寫入到channel中:
// 判斷bodyRequestEncoder是否是Chunked,傳送請求內容
if (bodyRequestEncoder.isChunked()) {
channel.write(bodyRequestEncoder);
}
在server端,通過判斷InterfaceHttpData的getHttpDataType,如果是FileUpload型別,則說明拿到了上傳的檔案,則可以通過下面的方法來讀取到檔案的內容:
FileUpload fileUpload = (FileUpload) data;
responseContent.append(fileUpload.getString(fileUpload.getCharset()));
這樣我們就可以在伺服器端拿到客戶端傳過來的檔案了。
總結
HTTP的檔案上傳需要考慮的問題比較多,大家有不明白的可以參考我的例子。或者留言給我一起討論。
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/21-netty-http-fileupload/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!