乐趣区

使用netty实现文件上传服务器

使用 netty 实现文件上传服务器

代码实现根据官网提供的 example https://github.com/netty/nett…
以及 netty 官网的 api 文档 https://netty.io/4.1/api/inde…
项目地址 https://github.com/1433365571…

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<SocketChannel> {

    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<HttpObject> {

    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, "<h1> 上传成功 </h1>");

            }

        }


    }

    // 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");

        ctx.channel().writeAndFlush(response);

    }

    private String getHomeResponse() {return "<h1> welcome home </h1>";}

    private String getUploadResponse() {

        return "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "<meta charset=\"UTF-8\">\n" +
                "<title>Title</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "\n" +
                "<form action=\"http://127.0.0.1:8080/post_multipart\"enctype=\"multipart/form-data\"method=\"POST\">\n" +
                "\n" +
                "\n" +
                "<input type=\"file\"name=" +
                "" +"" +
                "\"YOU_KEY\">\n" +
                "\n" +
                "<input type=\"submit\"name=\"send\">\n" +
                "\n" +
                "</form>\n" +
                "\n" +
                "</body>\n" +
                "</html>";

    }


    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 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>nettyHttpUploadServer</groupId>
    <artifactId>http.upload</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.10.Final</version>
        </dependency>

    </dependencies>

    <!--    <build>-->
    <!--        <plugins>-->
    <!--            <plugin>-->
    <!--                <groupId>org.apache.maven.plugins</groupId>-->
    <!--                <artifactId>maven-jar-plugin</artifactId>-->

    <!--                <configuration>-->
    <!--                    <archive>-->
    <!--                        <manifest>-->
    <!--                            <addClasspath>true</addClasspath>-->
    <!--                            <mainClass>server.HttpUploadServer</mainClass>-->
    <!--                        </manifest>-->
    <!--                    </archive>-->
    <!--                </configuration>-->

    <!--            </plugin>-->
    <!--        </plugins>-->
    <!--    </build>-->
    <build>

        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>server.HttpUploadServer</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>server.HttpUploadServer</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.jolira</groupId>
                <artifactId>onejar-maven-plugin</artifactId>
                <version>1.4.4</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>one-jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>

    </build>


</project>

6 代码说明

  • ChannelHandlerContext 控制数据处理管道 ChannelPipeline 执行流程
  • HttpObject 消息处理的接口
  • HttpRequest 封装 http 请求头请求协议等
  • DefaultFullHttpResponse 构造 http 响应
  • httpcontext netty 会将请求体分组处理,获得完整的请求体可以请将 HttpObjectAggregator 放在 ChannelPipeline 中的
  • HttpObjectDecoder 之后 此文章解释较详细。
  • LastHttpContent 标识 Http 请求结束
  • HttpDataFactory 上传文件的处理方式
  • HttpPostRequestDecoder post 请求体的解析类

7 访问 http://127.0.0.1:8080/up 上传文件

退出移动版