共计 6009 个字符,预计需要花费 16 分钟才能阅读完成。
简介
上一篇的文章中,咱们讲到了如何从 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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!