使用netty下载文件

时间:2015-08-07 11:10:25

标签: java download netty

我正在使用netty和java创建一个非常基本的Web服务器。我将拥有基本功能。它的主要职责是以JSON格式提供从客户端(例如浏览器或我正在构建的控制台应用程序)完成的API调用的响应,或者发送zip文件。出于这个原因,我创建了HttpServerHanddler类,它负责获取请求,解析它以查找命令并调用相应的api调用。它扩展了SimpleChannelInboundHandler

并覆盖以下功能;

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    LOG.debug("channelActive");
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
    LOG.debug("In channelComplete()");
    ctx.flush();
}

@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg)
        throws IOException {
    ctx = processMessage(ctx, msg);
    if (!HttpHeaders.isKeepAlive(request)) {
        // If keep-alive is off, close the connection once the content is
        // fully written.
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(
                ChannelFutureListener.CLOSE);
    }

}


private ChannelHandlerContext processMessage(ChannelHandlerContext ctx, Object msg){
    if (msg instanceof HttpRequest) {
        HttpRequest request = this.request = (HttpRequest) msg;

        if (HttpHeaders.is100ContinueExpected(request)) {
            send100Continue(ctx);
        }
        //parse message to find command, parameters and cookies
        ctx = executeCommand(command, parameters, cookies)
    }
    if (msg instanceof LastHttpContent) {
        LOG.debug("msg is of LastHttpContent");
        if (!HttpHeaders.isKeepAlive(request)) {
            // If keep-alive is off, close the connection once the content is
            // fully written.
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(
                ChannelFutureListener.CLOSE);
        } 
    }
    return ctx;
}

private ChanndelHandlerContext executeCommand(String command, HashMap<String, List<String>>> parameters, Set<Cookie> cookies>){
    //switch case to see which command has to be invoked
    switch(command){
    //many cases
    case "/report":
        ctx = myApi.getReport(parameters, cookies); //This is a member var of ServerHandler
        break;
    //many more cases
    }
    return ctx;
}

在我的具有getReport函数的Api类中。

getReport

public ChannelHandlerContext getReportFile(Map<String, List<String>> parameters,
                                           Set<Cookie> cookies) {

    //some initiliazations. Actual file handing happens bellow
    File file = new File(fixedReportPath);
    RandomAccessFile raf = null;
    long fileLength = 0L;

    try {
        raf = new RandomAccessFile(file, "r");
        fileLength = raf.length();
        LOG.debug("creating response for file");
        this.response = Response.createFileResponse(fileLength);
        this.ctx.write(response);

        this.ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0,
                                                            fileLength,
                                                            8192)),
                this.ctx.newProgressivePromise());

    } catch (FileNotFoundException fnfe) {

        LOG.debug("File was not found", fnfe);
        this.response = Response.createStringResponse("failure");
        this.ctx.write(response);
    } catch (IOException ioe) {

        LOG.debug("Error getting file size", ioe);
        this.response = Response.createStringResponse("failure");

        this.ctx.write(response);
    } finally {
        try {
            raf.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    return this.ctx;
}

响应类负责处理各种类型的响应创建(JsonString JsonArray JsonInteger文件等)

public static FullHttpResponse createFileResponse(long fileLength) {
    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
    HttpHeaders.setContentLength(response, fileLength);
    response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
    return response;
}

我的Api非常适合我的Json响应(更容易实现),但它不能很好地处理我的json响应,但不适用于我的文件响应。从例如chrome发出请求时,它只会挂起并且不会下载文件。使用netty下载文件时我应该做些什么吗?我知道它不是最好的代码,我仍然认为我完全理解代码时会遗漏一些零碎的东西,但是我想知道如何处理我的代码下载。对于我的代码,我考虑了thisthis

1 个答案:

答案 0 :(得分:1)

首先,对您的代码进行一些评论......

我宁愿为最后一个命令返回最后一个Future,而不是返回ctx,这样你的最后一个事件(没有保持活动状态)可以直接使用它。

public void channelRead0(ChannelHandlerContext ctx, Object msg)
    throws IOException {
  ChannelFuture future = processMessage(ctx, msg);
  if (future != null && !HttpHeaders.isKeepAlive(request)) {
    // If keep-alive is off, close the connection once the content is
    // fully written.
    future.addListener(ChannelFutureListener.CLOSE);
  }
}

这样做可以直接关闭而不需要任何&#34;伪&#34;发送,甚至是空的。

重要事项:请注意,在Http中,管理响应,以便在第一个HttpResponse项之后为所有数据发送块发送,直到最后一个为空(LastHttpContent)。发送另一个空的(空块而不是LastHttpContent)可能会破坏内部逻辑。

此外,您已经两次完成工作(一次在read0,一次在processMessage),这可能会导致一些问题。

此外,由于您检查了KeepAlive,因此应确保在响应中将其设置回来:

if (HttpHeaders.isKeepAlive(request)) {
  response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}

在你的发送中,你有两个选择(取决于SSL的使用与否):你只选择了第二个,这是更一般的,所以当然在所有情况下都有效,但效率较低。< / p>

// Write the content.
ChannelFuture sendFileFuture;
ChannelFuture lastContentFuture;
if (ctx.pipeline().get(SslHandler.class) == null) {
  sendFileFuture =
    ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
  // Write the end marker.
  lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); // <= last writeAndFlush
} else {
  sendFileFuture =
    ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)),
      ctx.newProgressivePromise()); // <= last writeAndFlush
  // HttpChunkedInput will write the end marker (LastHttpContent) for us.
  lastContentFuture = sendFileFuture;
}

这是lastContentFuture,你可以回到调用者来检查KeepAlive。

但是请注意你没有包含一次刷新(除了你的 EMPTY_BUFFER,但这可能是你问题的主要原因 < / strong>那里!),与示例相反(我从中复制了源代码)。 请注意,两者都使用writeAndFlush进行最后一次调用(或唯一的调用)。