`

实例:如何使用Netty下载文件

阅读更多

        使用场景:客户端向Netty请求一个文件,Netty服务端下载指定位置文件到客户端。

        本实例使用的是Http协议,当然,可以通过简单的修改即可换成TCP协议。

        需要注意本实例的关键点是,为了更高效的传输大数据,实例中用到了ChunkedWriteHandler编码器,它提供了以zero-memory-copy方式写文件。

第一步:先写一个HttpFileServer

package com.bijian.netty.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;

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();
        }
    }
}

第二步:再写一个FileServerHandler

package com.bijian.netty.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;

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:通过对本例进行简单修改可实现各种方式的文件下载

 

文章来源:http://blog.csdn.net/suifeng3051/article/details/38679507

  • 大小: 11 KB
分享到:
评论

相关推荐

    用netty实现文件传输

    - 使用 Telnet 或 Netcat 工具模拟客户端连接,验证服务器端是否能正确接收文件。 - 对传输的大文件进行完整性校验,如计算 MD5 或 SHA 哈希值,确保文件传输无误。 7. **安全考虑** - **身份验证**:为了防止...

    netty简单实例

    在"ChatNetty"项目中,Maven 的 pom.xml 文件将列出所有必要的依赖项,包括 Netty、Spring 和其他可能的第三方库。 通过以上步骤,我们可以构建出一个基于 Netty 的简单聊天应用。这只是一个入门级的实例,实际应用...

    精通并发与netty 无加密视频

    第81讲:Netty引用计数的实现机制与自旋锁的使用技巧 第82讲:Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析 第83讲:AtomicIntegerFieldUpdater实例演练与volatile关键字分析 第84讲:Netty...

    使用netty实现TCP长链接消息写入kafka以及kafka批量消费数据

    6. **SpringBoot集成Kafka**:SpringBoot简化了Kafka的配置和使用,通过`application.properties`或`application.yml`文件可以轻松配置Kafka的相关属性,如bootstrap.servers、group.id等。SpringBoot还提供了...

    Netty5.0TCP/IP上传大文件

    在这个“Netty5.0TCP/IP上传大文件”的示例中,我们将探讨如何利用Netty5.x版本处理大文件的上传,特别关注拆包和合并的技术。 在TCP/IP通信中,数据通常被分成多个数据段进行传输,这是因为TCP协议是基于流的,...

    Netty大纲-同步netty专栏

    - **PRC框架**:通过实例展示如何构建一个基于Netty的简单RPC框架。 6. **源码剖析** - **启动流程**:了解Netty服务器启动过程,包括事件循环组和事件循环的创建。 - **EventLoop剖析**:深入EventLoop的工作...

    netty5权威指南中可以使用的jar

    在提供的压缩包中,有两个重要的文件:"netty-all-5.0.0.Alpha1.jar"和"netty-all-5.0.0.Alpha1-sources.jar"。这两个文件是Netty5.0.0.Alpha1版本的库文件和源码包。 1. "netty-all-5.0.0.Alpha1.jar":这是Netty5...

    使用Netty作为底层架构,开发文件上传系统

    在构建一个基于Netty的文件上传系统时,我们首先需要理解Netty的核心特性和Spring Boot在其中的作用。Netty是一个高性能、异步事件驱动的网络应用程序框架,它简化了网络应用的开发,尤其是在处理TCP、UDP协议时。而...

    NettyDemo Netty使用实例,对象传递调用

    在本文中,我们将深入探讨Netty在实际应用中的实例——对象传递调用,以及如何解决TCP粘包问题。同时,我们还会讨论Java序列化方案在Netty中的编解码对比。 首先,让我们来看看TCP粘包问题。在TCP协议中,由于其...

    jboss netty5.0

    在解压后的文件列表中,“netty5.0.0”可能是整个 Netty 5.0 发行版的打包文件,包括源码、构建脚本、文档以及示例项目等。 Netty 5.0 的关键特性可能包括: 1. **异步非阻塞 I/O**:Netty 使用 NIO(非阻塞 I/O)...

    spring+netty+mybatis整合实例

    创建一个Spring配置文件,例如`applicationContext.xml`,在这里定义Bean,包括Netty服务器、MyBatis的数据源、SqlSessionFactory等。使用Spring的注解或者XML配置来声明Bean,例如: ```xml &lt;!-- 数据库连接配置...

    netty代码demo.rar

    这个“netty代码demo.rar”文件很可能是包含了一些Netty的基础使用示例,帮助开发者理解和学习Netty的工作原理以及如何在实际项目中应用。 Netty的核心特性包括: 1. **异步I/O模型**:Netty基于Java NIO(非阻塞I...

    netty5完整配置实例

    这个“netty5完整配置实例”显然旨在帮助开发者理解和使用Netty 5版本,考虑到Netty的不同版本间的确存在显著差异,这个实例应该包含了Netty 5的关键配置和示例代码。 首先,Netty 5可能已经不被广泛使用,因为最新...

    netty-fileServer:netty实现的简单文件系统

    8. **文件上传和下载实现**: 在这个文件服务器中,Netty可能会使用`FileRegion`来高效地传输大文件,它允许将文件直接从文件系统传输到网络,而无需先将其全部加载到内存中。 9. **HTTP 协议处理**: 要实现文件服务...

    netty4完整配置及实例

    在 `pom.xml` 文件中,你需要添加 Netty 4 的依赖项,如下所示: ```xml &lt;groupId&gt;io.netty &lt;artifactId&gt;netty-all &lt;version&gt;4.x.y.z ``` 这里的 `4.x.y.z` 应替换为实际的 Netty 版本号。 3. **基本...

    netty文件传输

    总的来说,Netty 的文件传输功能强大且易于使用,结合 Java NIO 的优势,使得它成为开发高性能网络应用的理想选择。通过理解并实践 Netty 的文件传输机制,开发者可以更好地利用 Netty 构建出满足业务需求的应用。...

    Spring+Netty+WebSocket实例

    1. **配置WebSocket**:在Spring的配置文件中,我们需要定义WebSocket的处理器,设置WebSocket路径,并启用WebSocket支持。 2. **创建WebSocket端点**:使用Spring的`@ServerEndpoint`注解定义WebSocket连接的端点...

    SpringBoot_Netty实例(参照文本网址)

    1. 添加依赖:在`pom.xml`文件中,我们需要引入Netty的相关依赖,确保SpringBoot可以找到并使用Netty库。 2. 创建Netty服务器配置:通过编写一个自定义的`ServerBootstrap`配置,我们可以定制Netty服务器的行为,如...

    netty 实现长连接

    标题中的“netty 实现长连接”指的是使用Netty框架构建能够维持长时间连接的网络通信应用。Netty是一个高性能、异步事件驱动的网络应用程序框架,适用于开发服务器和客户端的高性能、高可用性协议库。在传统的HTTP或...

Global site tag (gtag.js) - Google Analytics