乐趣区

关于netty:netty系列之搭建HTTP上传文件服务器

简介

上一篇的文章中,咱们讲到了如何从 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版