netty简单实现文件上传接收 (OSS对象仓库) 基于Http 协议.

时间:2021-04-22 20:34:31

1.gradle配置(mvn童鞋自行分解)让其支持json :

 compile group: 'io.netty', name: 'netty-all', version: '4.1.9.Final'
 compile 'com.google.code.gson:gson:2.8.2'2.


2.构建netty文件服务主类Server:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;

import java.net.InetSocketAddress;

public class Server {


    public static void main(String[] args) {
        server();
    }


    private static void server() {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(Common.PORT))//绑定监听的本地端口号
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new HttpRequestDecoder())
                                    .addLast(new HttpResponseEncoder())
                                    .addLast(new ChunkedWriteHandler())
                                    .addLast(new HttpFileServerHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind().sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

3.实现文件路径管理类PathUtil(对win平台和Linux(mac)平台分别支持):

import java.io.File;

public class PathUtil {
    private static final ClassLoader classLoader = PathUtil.class.getClassLoader();

    public static String getImageBasePath() {
        String os = System.getProperty("os.name");
        String basePath;
        if (os.toLowerCase().startsWith("win")) {
            basePath = "C:/Users/你的win系统用户名路径/warehouse";
        } else {
            basePath = "/home/workspace/warehouse";
        }
        basePath = basePath.replace("/", File.separator);
        return basePath;
    }

    public static String getSourcePath(String name) {
        return classLoader.getResource(name).getPath();
    }
}

4.得到简单的Gson实例:

import com.google.gson.Gson;

public class GsonUtil {
    private static Gson gson;

    public static Gson getGson() {
        if (gson == null) {
            gson = new Gson();
        }
        return gson;
    }
}

5.ResponseModel<T> pojo类 (回执类):

import java.util.Date;



public class ResponseModel<T> {
    //成功
    public static final int SUCCEED = 1;
    //未知错误
    public static final int ERROR_UNKNOWN = 0;
    //错误的请求
    public static final int BAD_REQUEST = 4041;
    //验证错误
    public static final int ERROR_NO_PERMISSION = 2010;
    //服务器错误
    public static final int SERVICE_ERROR = 2010;

    private int code;
    private String message;
    private long time = new Date().getTime();
    private T result;

    public ResponseModel(T result) {
        this();
        this.result = result;
    }

    public ResponseModel() {
        message = "OK";
        code = SUCCEED;
    }

    public ResponseModel(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public static <T> ResponseModel<T> buildOk() {
        return new ResponseModel<T>();
    }

    public static <T> ResponseModel<T> buildOk(T result) {
        return new ResponseModel<T>(result);
    }

    public static <M> ResponseModel<M> buildNoPermissionError() {
        return new ResponseModel<M>(ERROR_NO_PERMISSION, "你没有操作权限!");
    }

    public static <M> ResponseModel<M> buildBadRequestError() {
        return new ResponseModel<M>(BAD_REQUEST, "错误的请求!");
    }

    public static <M> ResponseModel<M> buildOtherError() {
        return new ResponseModel<M>(ERROR_UNKNOWN, "其他错误!");
    }

    public static <M> ResponseModel<M> buildServiceError() {
        return new ResponseModel<M>(SERVICE_ERROR, "服务器错误!");
    }


    public boolean success() {
        return code == SUCCEED;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }
}

6.文件存放Handler-HttpFileServerHandler:

import com.sun.istack.internal.NotNull;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URI;
import java.net.URISyntaxException;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpResponseStatus.*;

@ChannelHandler.Sharable
public class HttpFileServerHandler extends SimpleChannelInboundHandler<HttpObject> {


    private static final String BASE_PATH = PathUtil.getImageBasePath();

    private ChannelHandlerContext ctx;

    private HttpRequest request;

    private static String KEY = "YOU_KEY";

    private static final HttpDataFactory factory =
            new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);

    private HttpPostRequestDecoder decoder;


    static {
        DiskFileUpload.deleteOnExitTemporaryFile = true;
        DiskFileUpload.baseDirectory = null;
        DiskAttribute.deleteOnExitTemporaryFile = true;
        DiskAttribute.baseDirectory = null;
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
        System.out.println("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            decoder.cleanFiles();
        }
        System.out.println("channelUnregistered");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) throws Exception {
        this.ctx = ctx;

        if (httpObject instanceof HttpRequest) {
            request = (HttpRequest) httpObject;
        } else if (httpObject instanceof HttpContent) {
            doHttpContent(ctx, ((HttpContent) httpObject));
            return;
        }

        if (!request.decoderResult().isSuccess()) {
            sendError(ctx, BAD_REQUEST);
            return;
        }

        String uri = request.uri();
        if (uri == null) {
            sendError(ctx, BAD_REQUEST);
            return;
        }

        HttpMethod method = request.method();

        if (HttpMethod.GET.equals(method)) {

            File file = new File(BASE_PATH + uri);

            if (file.exists()) {
                if (file.isFile()) {
                    RandomAccessFile accessFile = new RandomAccessFile(file, "r");
                    long length = accessFile.length();
                    DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK);
                    HttpUtil.setContentLength(request, length);
                    setContentTypeHeader(response, file);
                    if (HttpUtil.isKeepAlive(request)) {
                        response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                    }
                    ctx.write(response);
                    ChannelFuture future = ctx.write(new ChunkedFile(accessFile, 0, length, 2 << 16), ctx.newProgressivePromise());
//                    future.addListener(new ChannelProgressiveFutureListener() {
//                        @Override
//                        public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
//                            if (total < 0) {
//                                //TODO
//                            } else {
//                                //TODO
//                            }
//                        }
//
//                        @Override
//                        public void operationComplete(ChannelProgressiveFuture future) throws Exception {
//
//                        }
//                    });
                    ChannelFuture lastFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
                    lastFuture.addListener(ChannelFutureListener.CLOSE);
                } else {
                    sendError(ctx, FORBIDDEN);
                    return;
                }
            } else {
                sendError(ctx, NOT_FOUND);
                return;
            }
        } else if (HttpMethod.POST.equals(method)) {
            try {
                decoder = new HttpPostRequestDecoder(factory, request);

            } catch (Exception e) {
                writeResponse(ResponseModel.buildOtherError());
                ctx.channel().close();
            }
        }
    }

    private void doHttpContent(ChannelHandlerContext ctx,
                               HttpContent httpContent) throws URISyntaxException {
        URI uri = new URI(request.uri());
        if (uri.getPath().startsWith("/upload")) {
            if (decoder != null) {
                try {
                    decoder.offer(httpContent);
                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }

                if (httpContent instanceof LastHttpContent) {
                    readHttpDataChunkByChunk();
                    reset();
                }
            }
        } else {
            writeResponse(ResponseModel.buildBadRequestError());
        }
    }

    private void reset() {
        if (decoder != null) {
            request = null;
            decoder.destroy();
            decoder = null;
        }
    }

    private void readHttpDataChunkByChunk() {
        while (decoder.hasNext()) {
            InterfaceHttpData data = decoder.next();
            try {
                writeHttpData(data);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                data.release();
            }
        }
    }

    private void writeHttpData(InterfaceHttpData data) throws IOException {
        if (data.getHttpDataType().equals(InterfaceHttpData.HttpDataType.FileUpload)) {
            if (!KEY.equals(data.getName())) {
                writeResponse(ResponseModel.buildNoPermissionError());
                return;
            }
            FileUpload fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {
                String filename = fileUpload.getFilename();
                try {
                    File file = new File(BASE_PATH + filename);
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                    if (!file.exists()) {
                        file.createNewFile();
                    } else {
                        writeResponse(ResponseModel.buildOk(filename));
                        return;
                    }
                    fileUpload.renameTo(file);
                    decoder.removeHttpDataFromClean(fileUpload);
                    writeResponse(ResponseModel.buildOk(filename));
                } catch (Exception e) {
                    writeResponse(ResponseModel.buildOtherError());
                }
            } else {
                writeResponse(ResponseModel.buildNoPermissionError());
            }
        }
    }


    private void writeResponse(@NotNull ResponseModel<String> responseModel) {
        String resp = GsonUtil.getGson().toJson(responseModel);
        ByteBuf buf = Unpooled.copiedBuffer(resp, CharsetUtil.UTF_8);
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
        ctx.write(response);
        ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
    }

    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, status, copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.channel().close();
    }
}