日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

netty系列之:搭建自己的下载文件服务器

發布時間:2024/2/28 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 netty系列之:搭建自己的下载文件服务器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • 文件的content-type
  • 客戶端緩存文件
  • 其他HTTP中常用的處理
  • 文件內容展示處理
  • 文件傳輸進度
  • 總結

簡介

上一篇文章我們學習了如何在netty中搭建一個HTTP服務器,討論了如何對客戶端發送的請求進行處理和響應,今天我們來討論一下在netty中搭建文件服務器進行文件傳輸中應該注意的問題。

文件的content-type

客戶端向服務器端請求一個文件,服務器端在返回的HTTP頭中會包含一個content-type的內容,這個content-type表示的是返回的文件類型。這個類型應該怎么確認呢?

一般來說,文件類型是根據文件的的擴展名來確認的,根據 RFC 4288的規范,所有的網絡媒體類型都必須注冊。apache也提供了一個文件MIME type和擴展名的映射關系表。

因為文件類型比較多,我們看幾個比較常用到的類型如下:

MIME type擴展名
image/jpegjpg
image/jpegjpeg
image/pngpng
text/plaintxt text conf def list log in
image/webpwebp
application/vnd.ms-excelxls
application/vnd.openxmlformats-officedocument.spreadsheetml.sheetxlsx
application/msworddoc
application/vnd.openxmlformats-officedocument.wordprocessingml.documentdocx
application/vnd.openxmlformats-officedocument.presentationml.presentationpptx
application/vnd.ms-powerpointppt
application/pdfpdf

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 OKDate: Mon, 23 Aug 2021 17:52:30 GMT+08:00Last-Modified: Tue, 10 Aug 2021 18:05:35 GMT+08:00Expires: Mon, 23 Aug 2021 17:53:30 GMT+08:00Cache-Control: private, max-age=60

一般來說如果客戶端是現代瀏覽器的話,就會把A.txt緩存起來。在下次調用的時候只需要在head中添加If-Modified-Since,詢問服務器該文件是否被修改了即可,如果文件沒有被修改,則服務器會返回一個304 Not Modified,客戶端得到該狀態之后就會使用本地的緩存文件。

步驟3:客戶端再次請求該文件===================GET /file1.txt HTTP/1.1If-Modified-Since: Mon, 23 Aug 2021 17:55:30 GMT+08:00步驟4:服務器端響應該請求===================HTTP/1.1 304 Not ModifiedDate: 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));// 日期 headerCalendar time = new GregorianCalendar();log.info(dateFormatter.format(time.getTime()));response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime()));// 緩存 headerstime.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() {@Overridepublic void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {if (total < 0) {log.info(future.channel() + " 傳輸進度: " + progress);} else {log.info(future.channel() + " 傳輸進度: " + progress + " / " + total);}}@Overridepublic void operationComplete(ChannelProgressiveFuture future) {log.info(future.channel() + " 傳輸完畢.");}});

總結

我們考慮了一個HTTP文件服務器最基本的一些考慮因素,現在可以使用這個文件服務器來提供服務啦!

本文的例子可以參考:learn-netty4

本文已收錄于 http://www.flydean.com/20-netty-fileserver/

最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!

總結

以上是生活随笔為你收集整理的netty系列之:搭建自己的下载文件服务器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。