使用netty实现文件上传服务器
1 编写 server 启动类
package server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
public final class HttpUploadServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.handler(new LoggingHandler(LogLevel.INFO));
b.childHandler(new HttpUploadServerInitializer(sslCtx));
Channel ch = b.bind(PORT).sync().channel();
System.err.println("Open your web browser and navigate to " +
(SSL ? "https" : "http") + "://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
2 绑定handler
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.ssl.SslContext;
public class HttpUploadServerInitializer extends ChannelInitializer {
private final SslContext sslCtx;
public HttpUploadServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
}
pipeline.addLast(new HttpRequestDecoder());
pipeline.addLast(new HttpResponseEncoder());
// Remove the following line if you don't want automatic content compression.
pipeline.addLast(new HttpContentCompressor());
// pipeline.addLast("http-aggregator",
// new HttpObjectAggregator(65536));// 目的是将多个消息转换为单一的request或者response对象
pipeline.addLast(new HttpUploadServerHandler());
}
}
3 上传处理的handler
package server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import java.io.File;
import java.io.IOException;
import java.net.URI;
public class HttpUploadServerHandler extends SimpleChannelInboundHandler {
private HttpRequest request;
private static final String uploadUrl = "/up";
private static final String fromFileUrl = "/post_multipart";
private static final HttpDataFactory factory =
new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed
private HttpPostRequestDecoder decoder;
static {
DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
// on exit (in normal
// exit)
DiskFileUpload.baseDirectory = null; // system temp directory
DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
// exit (in normal exit)
DiskAttribute.baseDirectory = null; // system temp directory
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
if (decoder != null) {
decoder.cleanFiles();
}
}
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpRequest) {
this.request = (HttpRequest) msg;
URI uri = new URI(request.uri());
System.out.println(uri);
urlRoute(ctx, uri.getPath());
}
if (decoder != null) {
if (msg instanceof HttpContent) {
// 接收一个新的请求体
decoder.offer((HttpContent) msg);
// 将内存中的数据序列化本地
readHttpDataChunkByChunk();
}
if (msg instanceof LastHttpContent) {
System.out.println("LastHttpContent");
reset();
writeResponse(ctx, "
上传成功
");}
}
}
// url路由
private void urlRoute(ChannelHandlerContext ctx, String uri) {
StringBuilder urlResponse = new StringBuilder();
// 访问文件上传页面
if (uri.startsWith(uploadUrl)) {
urlResponse.append(getUploadResponse());
} else if (uri.startsWith(fromFileUrl)) {
decoder = new HttpPostRequestDecoder(factory, request);
return;
} else {
urlResponse.append(getHomeResponse());
}
writeResponse(ctx, urlResponse.toString());
}
private void writeResponse(ChannelHandlerContext ctx, String context) {
ByteBuf buf = Unpooled.copiedBuffer(context, CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=utf-8");
//设置短连接 addListener 写完马上关闭连接
ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private String getHomeResponse() {
return "
welcome home
";}
private String getUploadResponse() {
return "\n" +
"\n" +
"
\n" +" \n" +
"
Title\n" +"\n" +
"
\n" +"\n" +
"
\n" +"\n" +
"\n" +
"
"" +
"\"YOU_KEY\">\n" +
"\n" +
" \n" +
"\n" +
"
\n" +"\n" +
"\n" +
"";
}
private void readHttpDataChunkByChunk() throws IOException {
while (decoder.hasNext()) {
InterfaceHttpData data = decoder.next();
if (data != null) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
FileUpload fileUpload = (FileUpload) data;
if (fileUpload.isCompleted()) {
fileUpload.isInMemory();// tells if the file is in Memory
// or on File
fileUpload.renameTo(new File(PathUtil.getFileBasePath() + fileUpload.getFilename())); // enable to move into another
// File dest
decoder.removeHttpDataFromClean(fileUpload); //remove
}
}
}
}
}
private void reset() {
request = null;
// destroy the decoder to release all resources
decoder.destroy();
decoder = null;
}
}
4 环境文件
package server;
import java.io.File;
public class PathUtil {
private static final ClassLoader classLoader = PathUtil.class.getClassLoader();
public static String getFileBasePath() {
String os = System.getProperty("os.name");
String basePath;
if (os.toLowerCase().startsWith("win")) {
basePath = "D:/warehouse/";
} else {
basePath = "/root/upload_source";
}
basePath = basePath.replace("/", File.separator);
return basePath;
}
public static String getSourcePath(String name) {
return classLoader.getResource(name).getPath();
}
}
5 pom文件
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
nettyHttpUploadServer
http.upload
1.0-SNAPSHOT
UTF-8
io.netty
netty-all
4.1.10.Final
maven-assembly-plugin
3.0.0
server.HttpUploadServer
jar-with-dependencies
make-assembly
package
single
org.apache.maven.plugins
maven-jar-plugin
server.HttpUploadServer
com.jolira
onejar-maven-plugin
1.4.4
one-jar
org.apache.maven.plugins
maven-compiler-plugin
1.8
1.8
6 代码说明
ChannelHandlerContext 控制数据处理管道 ChannelPipeline 执行流程
HttpObject 消息处理的接口
HttpRequest 封装http请求头请求协议等
DefaultFullHttpResponse 构造http响应
httpcontext netty会将请求体分块处理此文章解释较详细,直接处理完整的请求体可以请将HttpObjectAggregator放在ChannelPipelineHttpObjectDecoder之后
LastHttpContent 标识Http请求结束
HttpDataFactory 上传文件的处理方式
HttpPostRequestDecoder post请求体的解析类
7 访问 http://127.0.0.1:8080/up 上传文件