1. 程式人生 > >例項:如何使用 Netty 下載檔案

例項:如何使用 Netty 下載檔案

本例項主要參考的是官網的examples:點選這裡

使用場景:客戶端向Netty請求一個檔案,Netty服務端下載指定位置檔案到客戶端。

本例項使用的是Http協議,當然,可以通過簡單的修改即可換成TCP協議。

需要注意本例項的關鍵點是,為了更高效的傳輸大資料,例項中用到了ChunkedWriteHandler編碼器,它提供了以zero-memory-copy方式寫檔案。

第一步:先寫一個HttpFileServer

package NettyDemo.file.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/*******************************************************************************
 * Reserved. BidPlanStructForm.java Created on 2014-8-19 Author: <a
 * href=mailto:
[email protected]
>wanghouda</a> * @Title: HttpFileServer.java * @Package NettyDemo.file.server Description: Version: 1.0 ******************************************************************************/ public class HttpFileServer { static final int PORT = 8080; public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() {// 有連線到達時會建立一個channel @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new FileServerHandler()); } }); Channel ch = b.bind(PORT).sync().channel(); System.err.println("開啟瀏覽器,輸入: " + ("http") + "://127.0.0.1:" + PORT + '/'); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }


</pre><pre>
第二步:再寫一個FileServerHandler
package NettyDemo.file.server;

import static io.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.DATE;
import static io.netty.handler.codec.http.HttpHeaders.Names.EXPIRES;
import static io.netty.handler.codec.http.HttpHeaders.Names.IF_MODIFIED_SINCE;
import static io.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED;
import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_MODIFIED;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.SystemPropertyUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;

/*******************************************************************************
 * Created on 2014-8-19 Author:
 * <href=mailto:
[email protected]
>wanghouda</a> * * @Title: HttpFileServerHandler.java * @Package NettyDemo.file.server Description: Version: 1.0 ******************************************************************************/ public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; public static final String HTTP_DATE_GMT_TIMEZONE = "GMT"; public static final int HTTP_CACHE_SECONDS = 60; @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { // 監測解碼情況 if (!request.getDecoderResult().isSuccess()) { sendError(ctx, BAD_REQUEST); return; } final String uri = request.getUri(); final String path = sanitizeUri(uri); if (path == null) { sendError(ctx, FORBIDDEN); return; } //讀取要下載的檔案 File file = new File(path); if (file.isHidden() || !file.exists()) { sendError(ctx, NOT_FOUND); return; } if (file.isDirectory()) { if (uri.endsWith("/")) { sendListing(ctx, file); } else { sendRedirect(ctx, uri + '/'); } return; } if (!file.isFile()) { sendError(ctx, FORBIDDEN); return; } // Cache Validation String ifModifiedSince = request.headers().get(IF_MODIFIED_SINCE); if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince); // Only compare up to the second because the datetime format we send // to the client // does not have milliseconds long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; long fileLastModifiedSeconds = file.lastModified() / 1000; if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { sendNotModified(ctx); return; } } RandomAccessFile raf; try { raf = new RandomAccessFile(file, "r"); } catch (FileNotFoundException ignore) { sendError(ctx, NOT_FOUND); return; } long fileLength = raf.length(); HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); HttpHeaders.setContentLength(response, fileLength); setContentTypeHeader(response, file); setDateAndCacheHeaders(response, file); if (HttpHeaders.isKeepAlive(request)) { response.headers().set("CONNECTION", HttpHeaders.Values.KEEP_ALIVE); } // Write the initial line and the header. ctx.write(response); // Write the content. ChannelFuture sendFileFuture; if (ctx.pipeline().get(SslHandler.class) == null) { sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); } else { sendFileFuture = ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), ctx.newProgressivePromise()); } sendFileFuture.addListener(new ChannelProgressiveFutureListener() { @Override public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) { if (total < 0) { // total unknown System.err.println(future.channel() + " Transfer progress: " + progress); } else { System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total); } } @Override public void operationComplete(ChannelProgressiveFuture future) { System.err.println(future.channel() + " Transfer complete."); } }); // Write the end marker ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); // Decide whether to close the connection or not. if (!HttpHeaders.isKeepAlive(request)) { // Close the connection when the whole content is written out. lastContentFuture.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); if (ctx.channel().isActive()) { sendError(ctx, INTERNAL_SERVER_ERROR); } } private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); private static String sanitizeUri(String uri) { // Decode the path. try { uri = URLDecoder.decode(uri, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e); } if (!uri.startsWith("/")) { return null; } // Convert file separators. uri = uri.replace('/', File.separatorChar); // Simplistic dumb security check. // You will have to do something serious in the production environment. if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) { return null; } // Convert to absolute path. return SystemPropertyUtil.get("user.dir") + File.separator + uri; } private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); private static void sendListing(ChannelHandlerContext ctx, File dir) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); StringBuilder buf = new StringBuilder(); String dirPath = dir.getPath(); buf.append("<!DOCTYPE html>\r\n"); buf.append("<html><head><title>"); buf.append("Listing of: "); buf.append(dirPath); buf.append("</title></head><body>\r\n"); buf.append("<h3>Listing of: "); buf.append(dirPath); buf.append("</h3>\r\n"); buf.append("<ul>"); buf.append("<li><a href=\"../\">..</a></li>\r\n"); for (File f : dir.listFiles()) { if (f.isHidden() || !f.canRead()) { continue; } String name = f.getName(); if (!ALLOWED_FILE_NAME.matcher(name).matches()) { continue; } buf.append("<li><a href=\""); buf.append(name); buf.append("\">"); buf.append(name); buf.append("</a></li>\r\n"); } buf.append("</ul></body></html>\r\n"); ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8); response.content().writeBytes(buffer); buffer.release(); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendRedirect(ChannelHandlerContext ctx, String newUri) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND); response.headers().set(LOCATION, newUri); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8)); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * When file timestamp is the same as what the browser is sending up, send a * "304 Not Modified" * * @param ctx * Context */ private static void sendNotModified(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED); setDateHeader(response); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * Sets the Date header for the HTTP response * * @param response * HTTP response */ private static void setDateHeader(FullHttpResponse response) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); } /** * Sets the Date and Cache headers for the HTTP Response * * @param response * HTTP response * @param fileToCache * file to extract content type */ private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); // Date header Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); // Add cache headers time.add(Calendar.SECOND, HTTP_CACHE_SECONDS); response.headers().set(EXPIRES, dateFormatter.format(time.getTime())); response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); response.headers().set(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified()))); } /** * Sets the content type header for the HTTP Response * * @param response * HTTP response * @param file * file to extract content type */ private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); } }
第三步:啟動Netty服務,在瀏覽器中輸入
     http://127.0.0.1:8080/

     如圖所示:即可在瀏覽器中看到工程目錄下所有檔案,點選即可下載

    

ps:通過對本例進行簡單修改可實現各種方式的檔案下載

相關推薦

例項如何使用 Netty 下載檔案

本例項主要參考的是官網的examples:點選這裡 使用場景:客戶端向Netty請求一個檔案,Netty服務端下載指定位置檔案到客戶端。 本例項使用的是Http協議,當然,可以通過簡單的修改即可換成TCP協議。 需要注意本例項的關鍵點是,為了更高效的傳輸大資料,例項中用到了

例項Netty 基於Http協議下的資料傳輸Demo

Http/Https協議是最重要最常用到的協議之一,Netty提供了一些了的Handler來處理Http協議下的編碼工作。下面就介紹一個Netty例項: 1.通過HttpClient傳送Protobuf型別資料到服務端 2.服務端Netty負責把接收到的Http請求中的資料

轉發傳送post請求下載檔案

原文地址:https://blog.csdn.net/yunlala_/article/details/78385962 處理檔案流方案一 以下是我親試可以實現的一種方案: exportData () { const form = this.getSearc

Postman高階應用(8)檔案變成了亂碼——下載檔案

背景 在做後臺管理系統時候,涉及到匯出報表等功能,如果我們用以前的方式請求,往往會看到返回一堆亂碼,而不是我們想要的檔案。其實Postman為我們提供了檔案下載功能,同樣普通的html文字和json資料也可以返回儲存為檔案。 實戰 點發送按鈕旁邊的三角形,然後點Send a

webdriver API上傳下載檔案

上傳檔案 普通上傳:將本地檔案的路徑作為一個值放在input標籤中,通過form表單提交的時候將這個值提交給伺服器 上傳的輸入框標籤必須為input #送一個檔案的絕對路徑到上傳輸入框 dr.find_element_by_name('file').send_keys('D:\\uplo

C#實踐問題如何實現檔案的上傳下載

一種學習的方法:把握住關鍵點、重點、難點、易錯點等,以點帶面。 使用C#編寫一個簡單的檔案上傳和下載功能很簡單,只要掌握了一些關鍵點和易錯點就足夠在很短的時間內設計一個實用的文件管理頁面。   檔案上傳   前端實現: 第一部分:在前臺aspx內嵌入一條上

Idea使用HttpClicnt模擬下載檔案

感覺沒啥用,不過還是寫一下吧,回憶起來就寫一下,模擬前提是已經搭建好SpringMVC環境配置: 1、在DownFileDemo類裡寫: @Test public void downFile() throws IOException { //1、得到HttpCl

連結串列例項對英語文字檔案單詞字元出現頻率統計

1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <ctype.h> 5 6 7 #define

問題記錄執行緒池批量下載檔案

在一個視訊網上找到了喜歡的線上視訊資源,沒有下載按鈕,只能自己下載了,看了 一下network,是ts分片的檔案,好在是命名挺規範的,都是 xxxx + index + .ts的格式,方便了 我下載。 一開始我用單執行緒下載,下了半天只下了200個 分片,總共有800個,因此想到了執行緒池,

Android網路框架Retrofit2使用封裝Get/Post/檔案上傳/下載

背景 Android開發中的網路框架經過多年的發展,目前比較主流的就是Retrofit了,Retrofit2版本出現也有幾年了,為了方便使用,特封裝了一些關於Retrofit2的程式碼,分享給大家。 框架主要包括: Get請求 Post請求 檔案上傳 檔案下載

Python爬蟲例項從百度貼吧下載多頁話題內容

上週網路爬蟲課程中,留了一個實踐:從百度貼吧下載多頁話題內容。我完成的是從貼吧中一個帖子中爬取多頁內容,與老師題目要求的從貼吧中爬取多頁話題還是有一定區別的,況且,在老師講評之後,我瞬間就發現了自己跟老師程式碼之間的差距了,我在程式碼書寫上還是存在很多不規範不嚴謹的地方,而且

python爬蟲之下載檔案的方式總結以及程式例項

  python爬蟲之下載檔案的方式以及下載例項     目錄 第一種方法:urlretrieve方法下載 第二種方法:request download 第三種方法:視訊檔案、大型檔案下載 實戰演示   第一種方法:urlretrieve方法下載

katalon系列十二自動化上傳檔案下載檔案

一、下載檔案1.下載檔案時,需要先設定好Chrome/Firefox下載路徑、不彈出下載框等,大家先學習下在selenium下如何設定:https://www.cnblogs.com/fnng/p/7700620.html 在Katalon中設定Chrome的DesiredCapabilities如圖:

瀏覽器從伺服器下載檔案的Servlet例項

測試兩種下載:瀏覽器通過伺服器下載其他網站檔案(http協議);瀏覽器下載伺服器本地硬盤裡的檔案(file協議) 1.工具類downloadUtils.java的核心部分 //伺服器使客戶端可以從遠端url下載檔案 public void download(String

IIS(二)支援.apk檔案下載的設定教程

因為IIS的預設MIME型別裡沒有.apk和.ipa的檔案,所以無法通過網路直接下載。   解決辦法:既然.apk .ipa無法下載是因為沒有MIME,那麼新增一個MIME型別就可以了。   解決步驟:

Java Web基礎知識之檔案下載當你下載檔案的時候到底發生了什麼?

從網上下載檔案幾乎是每個人都會遇到的,不管是圖片、文字檔案還是一些視訊,但是我們真的知道在下載的過程中發生了什麼嗎?本文章就學習一下其中的原理。 關於檔案下載存在靜態下載和動態下載兩種,靜態下載是比較容易的,我們平常在網上對很多圖片和和視訊等的下載有很多其實就是靜態下載,那

poi讀取excel檔案模板並填入資料(合併sheet)並且下載

今天做一個到處excel的功能,涉及到多表查詢,然後讀取excel模板檔案並寫入查詢到的資料,並且要合併sheet,合併單元格,下載等功能,附上程式碼: Conreoller類: /**      * 匯出excel      */     public ModelAnd

ajax實戰(ajax非同步下載檔案)請求二進位制流進行處理

需求 管理後臺需要隨時下載資料報表,資料要實時生成後轉換為excel下載 檔案不大,頁面放置“匯出”按鈕,點選按鈕後彈出儲存檔案對話方塊儲存 說明:第一種方法使用a標籤直接可以滿足大部分人需求,第二種方法純粹是在說實現方法以及更好的操作體驗,不需要(舉一個需

HDFS簡單程式設計例項檔案合併

 下圖顯示了HDFS檔案系統中路徑為“localhost:50070/explorer.html#/user/hadoop”的目錄中所有的檔案資訊: 對於該目錄下的所有檔案,我們將執行以下操作: 首先,從該目錄中過濾出所有後綴名不為".abc"的檔案。 然後,對過濾之後的檔案進行讀取。

Python3例項使用cx_Freeze打包成exe檔案

首先可以用簡單命令列進行打包。不過我這個是接著上一篇文章的點選開啟連結,把淘寶程式打包起來。 我用的是4.3.4版本 本來有現成的程式碼。使用了setup.py檔案。這樣的好處是可以寫各種配置引數。 from cx_Freeze import setup, Executab