简介
上一篇文章咱们学习了如何在netty中搭建一个HTTP服务器,探讨了如何对客户端发送的申请进行解决和响应,明天咱们来讨论一下在netty中搭建文件服务器进行文件传输中应该留神的问题。
文件的content-type
客户端向服务器端申请一个文件,服务器端在返回的HTTP头中会蕴含一个content-type的内容,这个content-type示意的是返回的文件类型。这个类型应该怎么确认呢?
一般来说,文件类型是依据文件的的扩展名来确认的,依据 RFC 4288的标准,所有的网络媒体类型都必须注册。apache也提供了一个文件MIME type和扩展名的映射关系表。
因为文件类型比拟多,咱们看几个比拟罕用到的类型如下:
MIME type | 扩展名 |
---|---|
image/jpeg | jpg |
image/jpeg | jpeg |
image/png | png |
text/plain | txt text conf def list log in |
image/webp | webp |
application/vnd.ms-excel | xls |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx |
application/msword | doc |
application/vnd.openxmlformats-officedocument.wordprocessingml.document | docx |
application/vnd.openxmlformats-officedocument.presentationml.presentation | pptx |
application/vnd.ms-powerpoint | ppt |
application/pdf |
JDK提供了一个MimetypesFileTypeMap的类,这个类提供了一个getContentType办法,能够依据申请的文件path信息,来推断其MIME type类型:
private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); }
客户端缓存文件
对于HTTP的文件申请来说,为了保障申请的速度,会应用客户端缓存的机制。比方客户端向服务器端申请一个文件A.txt。服务器在接管到该申请之后会将A.txt文件发送给客户端。
其申请流程如下:
步骤1:客户端申请服务器端的文件 =================== GET /file1.txt HTTP/1.1
步骤2:服务器端返回文件,并且附带额定的文件工夫信息: =================== HTTP/1.1 200 OK Date: Mon, 23 Aug 2021 17:52:30 GMT+08:00 Last-Modified: Tue, 10 Aug 2021 18:05:35 GMT+08:00 Expires: Mon, 23 Aug 2021 17:53:30 GMT+08:00 Cache-Control: private, max-age=60
一般来说如果客户端是古代浏览器的话,就会把A.txt缓存起来。在下次调用的时候只须要在head中增加If-Modified-Since,询问服务器该文件是否被批改了即可,如果文件没有被批改,则服务器会返回一个304 Not Modified,客户端失去该状态之后就会应用本地的缓存文件。
步骤3:客户端再次申请该文件 =================== GET /file1.txt HTTP/1.1 If-Modified-Since: Mon, 23 Aug 2021 17:55:30 GMT+08:00 步骤4:服务器端响应该申请 =================== HTTP/1.1 304 Not Modified Date: Mon, 23 Aug 2021 17:55:32 GMT+08:00
在服务器的代码层面,咱们首先须要返回一个响应中通常须要的日期字段,如Date、Last-Modified、Expires、Cache-Control等:
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); // 日期 header Calendar time = new GregorianCalendar(); log.info(dateFormatter.format(time.getTime())); response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime())); // 缓存 headers time.add(Calendar.SECOND, HTTP_CACHE_SECONDS); response.headers().set(HttpHeaderNames.EXPIRES, dateFormatter.format(time.getTime())); response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); response.headers().set( HttpHeaderNames.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
而后在收到客户端的二次申请之后,须要比拟文件的最初批改工夫和If-Modified-Since中自带的工夫,如果没有发送变动,则发送304状态:
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED, Unpooled.EMPTY_BUFFER); setDateHeader(response);
其余HTTP中罕用的解决
咱们探讨了文件类型和缓存,对于一个通用的HTTP服务器来说,还须要思考很多其余罕用的解决,比方异样、重定向和Keep-Alive设置。
对于异样,咱们须要依据异样的代码来结构一个DefaultFullHttpResponse,并且设置相应的CONTENT_TYPE头即可,如下所示:
FullHttpResponse response = new DefaultFullHttpResponse( HTTP_1_1, status, Unpooled.copiedBuffer("异样: " + status + "\r\n", CharsetUtil.UTF_8)); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
重定向同样须要构建一个DefaultFullHttpResponse,其状态是302 Found,并且在响应头中设置location为要跳转的URL地址即可:
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND, Unpooled.EMPTY_BUFFER); response.headers().set(HttpHeaderNames.LOCATION, newUri);
Keep-Alive是HTTP中为了防止每次申请都建设连贯而做的一个优化形式。在HTTP/1.0中默认是的keep-alive是false,在HTTP/1.1中默认的keep-alive是true。如果在header中手动设置了connection:false,则server端申请返回也须要同样设置connection:false。
另外,因为HTTP/1.1中默认的keep-alive是true,如果通过HttpUtil.isKeepAlive判断通过之后,还须要判断是否是HTTP/1.0,并显示设置keep-alive为true。
final boolean keepAlive = HttpUtil.isKeepAlive(request); HttpUtil.setContentLength(response, response.content().readableBytes()); if (!keepAlive) { response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); } else if (request.protocolVersion().equals(HTTP_1_0)) { response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); }
文件内容展现解决
文件内容展现解决是http服务器的外围,也是比拟难以了解的中央。
首先要设置的是ContentLength,也就是响应的文件长度,这个能够应用file的length办法来获取:
RandomAccessFile raf;raf = new RandomAccessFile(file, "r");long fileLength = raf.length();HttpUtil.setContentLength(response, fileLength);
而后咱们须要依据文件的扩展名设置对应的CONTENT_TYPE,这个在第一大节曾经介绍过了。
而后再设置date和缓存属性。这样咱们就失去了一个只蕴含响应头的DefaultHttpResponse,咱们先把这个只蕴含响应头的respose写到ctx中。
写完HTTP头,接下来就是写HTTP的Content了。
对于HTTP传递的文件来说,有两种解决形式,第一种形式状况下如果晓得整个响应的content大小,则能够在后盾间接进行整个文件的拷贝传输。如果服务器自身反对零拷贝的话,则能够应用DefaultFileRegion的transferTo办法将File或者Channel的文件进行转移。
sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); // 完结局部 lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
如果并不知道整个响应的context大小,则能够将大文件拆分成为一个个的chunk,并且在响应的头中设置transfer-coding为chunked,netty提供了HttpChunkedInput和ChunkedFile,用来将大文件拆分成为一个个的Chunk进行传输。
sendFileFuture = ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), ctx.newProgressivePromise());
如果向channel中写入ChunkedFile,则须要增加相应的ChunkedWriteHandler对chunked文件进行解决。
pipeline.addLast(new ChunkedWriteHandler());
留神,如果是残缺文件传输,则须要手动增加last content局部:
lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
如果是ChunkedFile,last content局部曾经蕴含在了chunkedFile中,不须要再手动增加了。
文件传输进度
ChannelFuture能够增加对应的listner,用来监控文件传输的进度,netty提供了一个ChannelProgressiveFutureListener,用于监控文件的过程,能够重写operationProgressed和operationComplete办法对进度监控进行定制:
sendFileFuture.addListener(new ChannelProgressiveFutureListener() { @Override public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) { if (total < 0) { log.info(future.channel() + " 传输进度: " + progress); } else { log.info(future.channel() + " 传输进度: " + progress + " / " + total); } } @Override public void operationComplete(ChannelProgressiveFuture future) { log.info(future.channel() + " 传输结束."); } });
总结
咱们思考了一个HTTP文件服务器最根本的一些思考因素,当初能够应用这个文件服务器来提供服务啦!
本文的例子能够参考:learn-netty4
本文已收录于 http://www.flydean.com/20-netty-fileserver/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!