我正在使用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下载文件时我应该做些什么吗?我知道它不是最好的代码,我仍然认为我完全理解代码时会遗漏一些零碎的东西,但是我想知道如何处理我的代码下载。对于我的代码,我考虑了this和this
答案 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进行最后一次调用(或唯一的调用)。