简介在上一篇文章中,我们谈到了如何从HTTP服务器下载文件,搭建下载服务器应该注意的问题,以及使用的GET方法。本文将讨论向服务器提交数据的常见POST方法以及如何向服务器上传
在上一篇文章中,我们谈到了如何从HTTP服务器下载文件,搭建下载服务器应该注意的问题,以及使用的GET方法。本文将讨论向服务器提交数据的常见POST方法以及如何向服务器上传文件。
GET方法上传数据。根据HTTP的规范,PUT一般会将数据上传到服务器。虽然不推荐,但是GET也可以用来将数据上传到服务器。
下面我们来看看GET客户端建设中需要注意的问题。
GET实际上是一个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头数据:
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。Version是HTTP协议的版本号,HttpHeaders是set头的内容。
对于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。
如果只是普通的帖子,也就是帖子的内容都是key=value的形式,那就比较简单了。如果帖子包含文件,会比较复杂,需要使用enctype = "multipart/form-data "。
Netty提供了一个HttpPostRequestEncoder类,用于快速编码请求体。让我们先来看一下HttpPostRequestEncoder类的完整构造函数:
public HttpPostRequestEncoder( HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset, EncoderMode encoderMode)
请求是要编码的HttpRequest,multipart表示是否为“multipart/form-data”格式。字符集编码方法是CharsetUtil。默认为UTF_8。EncoderMode是一种编码模式。目前有三种编码模式,分别是RFC1738、RFC3986和HTML5。
默认情况下,编码模式是RFC1738,这也是大多数表单提交数据的编码模式。但是,它不适用于OAUTH。如果要使用OAUTH,可以使用RFC3986。HTML5禁用多部分/表单数据的混合模式。
最后说一下HttpDataFactory。工厂主要用于创建InterfaceHttpData。它有一个minSize参数。如果创建的HttpData大于minSize,则存储在磁盘中,否则直接在内存中创建。
InterfaceHttpData有三种类型的HttpData,即Attribute、FileUpload和InternalAttribute。
是在属性POST请求中传入的属性值。FileUpload是POST请求传入的文件,在编码器内部使用InternalAttribute,这里不讨论。
因此,根据传入的minSize参数的大小,可以将Attribute和FileUpload分为以下几类:
MemoryAttribute、DiskAttribute或mixed attribute
memory file upload、DiskFileUpload或MixedFileUpload
本节我们先来看看POST请求中不上传文件的处理方法。首先,创建HTTP请求和PostBody编码器:
// 构建HTTP request HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); HttpPostRequestEncoder bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, false);
向请求添加标题:
// 添加headers for (Entry<String, String> entry : headers) { request.headers().set(entry.getKey(), entry.getValue()); }
然后将form属性添加到bodyRequestEncoder:
// 添加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添加了方法、名称和站点的属性。然后添加了FileUpload。但是,因为我们的编码方法不是“multipart/form-data”,所以这里只传递文件名,而不是整个文件。
最后,我们将调用bodyRequestEncoder的finalizeRequest方法来返回要发送的最终请求。在finalizeRequest过程中,transfer-encoding是否分块将根据传输数据的大小来设置。
如果传输的内容很大,需要分段传输。这时它需要设置transfer-encoding = chunked,否则不会设置。
上次发送请求:
// 发送请求 channel.write(request);
在服务器端,我们还需要构造一个HttpDataFactory,然后使用这个工厂构造一个HttpPostRequestDecoder来解码来自编码器的数据:
HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);//POST请求decoder = new HttpPostRequestDecoder(factory, request);
因为服务器收到的消息根据发送消息的长度可以是HttpContent也可以是LastHttpContent。如果是HttpContent,我们会将解析的结果缓存在一个StringBuilder中,然后在接收到LastHttpContent后一起发送出去。
收到HttpContent后,我们调用decoder.offer方法对HttpContent进行解码:
decoder.offer(chunk);
解码器中有两个容器用于存储HttpData数据,它们是:
List<InterfaceHttpData> bodyListHttpData和Map<String, List<InterfaceHttpData>> bodyMapHttpData
Decoder.offer是解析chunk,然后把解析后的数据填入bodyListHttpData和bodyMapHttpData。
解析后,可以读取解析后的数据。
可以通过decoder的hasNext和Next方法遍历bodyListHttpData,从而获得对应的InterfaceHttpData。
可以通过data.getHttpDataType()获取InterfaceHttpData的数据类型。如上所述,有两种类型:属性和文件上传。
POST方法上传文件。如果您想要发布一个文件,客户端可以在创建HttpPostRequestEncoder时传入multipart=true:
HttpPostRequestEncoder bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, true);
然后分别调用setBodyHttpDatas和finalizeRequest方法,生成可以写入通道的HttpRequest:
// 添加body http data bodyRequestEncoder.setBodyHttpDatas(bodylist); // finalize request,判断是否需要chunk request = bodyRequestEncoder.finalizeRequest(); // 发送请求头 channel.write(request);
注意,如果是transfer-encoding = chunked,那么这个HttpRequest只是请求头的信息,我们还需要手动将HttpContent写入通道:
// 判断bodyRequestEncoder是否是Chunked,发送请求内容 if (bodyRequestEncoder.isChunked()) { channel.write(bodyRequestEncoder); }
在服务器端,通过判断InterfaceHttpData的getHttpDataType,如果是FileUpload类型,则表示获取了上传的文件,可以通过以下方法读取文件的内容:
FileUpload fileUpload = (FileUpload) data;responseContent.append(fileUpload.getString(fileUpload.getCharset()));
这样,我们可以在服务器端从客户端获取文件。
摘要上传HTTP文件要考虑很多问题。如果你不明白,请参考我的例子。或者给我留言讨论。