使用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");

       
        //设置短连接 addListener 写完马上关闭连接
        ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

    }

    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放在ChannelPipelineHttpObjectDecoder之后

图片描述

  • LastHttpContent 标识Http请求结束
  • HttpDataFactory 上传文件的处理方式
  • HttpPostRequestDecoder post请求体的解析类

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

0 声望
1 粉丝
0 条评论
推荐阅读
【小白必学】文件上传的漏洞介绍及常见防御方法
在文件上传的功能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致恶意用户上传恶意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传漏洞。

代码熬夜敲阅读 1k

一分钟学会、三分钟上手、五分钟应用,快速上手开源责任链框架详解 | 京东云技术团队
责任链模式是开发过程中常用的一种设计模式,在SpringMVC、Netty等许多框架中均有实现。我们日常的开发中如果要使用责任链模式,通常需要自己来实现,但自己临时实现的责任链既不通用,也很容易产生框架与业务代...

京东云开发者阅读 891

封面图
《跟闪电侠学Netty》阅读笔记 - 开篇入门Netty
和 《Netty In Action》 不同,这本书直接从Netty入门程序代码开始引入Netty框架,前半部分教你如何用Netty搭建简易的通讯系统,整体难度比较低,后半部分直接从服务端源码、客户端源码、ChannelPipeline开始介绍...

Xander阅读 639

Netty入门
进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select操作上,select/poll是顺序扫描fd是否就绪,需要扫描所有的客户端是否就绪epoll使用基于事件驱动方式替代顺序扫描,当有fd就绪时,立即回调函数r...

journey1阅读 412

uniapp实现文件选择上传,支持App/小程序/H5
lsj-upload插件地址:[链接]不清楚使用方式可点击右侧导入示例项目运行完整示例此次更新2.0与1.0使用方式略有差异,已使用1.0的同学自行斟酌是否更新到2.0版本!!!使用插件有任何问题欢迎加入QQ讨论群:群1:70...

shanjunLi阅读 376

长连接Netty服务内存泄漏,看我如何一步步捉“虫”解决 | 京东云技术团队
老板说: 长连接吗? 我说:是的! 老板说:该来的还是要来的,最终还是来了,快,赶紧先把服务重启下! 我说:已经重启了! 老板说: 这问题必须给我解决了! 我说:必须的!

京东云开发者阅读 210

封面图
Netty服务端开发及性能优化 | 京东云技术团队
Netty是一个异步基于事件驱动的高性能网络通信框架,可以看做是对NIO和BIO的封装,并提供了简单易用的API、Handler和工具类等,用以快速开发高性能、高可靠性的网络服务端和客户端程序。

京东云开发者阅读 204

封面图
0 声望
1 粉丝
宣传栏