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

45次阅读

共计 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/

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

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

正文完
 0