【Netty4 简单项目实践】五、Netty4接收HTTP文件上传

12 篇文章 0 订阅

又要开一个接收文件上传的服务,找了官方的样例代码,把不需要的东西删了一圈,很容易就实现了。

Bootstrap没什么变化,所以只写上initChannel需要加载的处理器

.childHandler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        // TODO Auto-generated method stub
                        ChannelPipeline pipeline = ch.pipeline();  
                        pipeline.addLast(new HttpRequestDecoder());
                        pipeline.addLast(new HttpResponseEncoder());
                        pipeline.addLast("compressor", new HttpContentCompressor());
                        pipeline.addLast(new HttpFileHandler());
                    }
                });

然后是HttpFileHandler.java这个超级大的处理器,把import也贴出来了,因为像Cookie类的位置有了变化,可能需要在import里面确认一下来消除warning。

这个处理器的原理是接收HttpObject对象,按照HttpRequest,HttpContent来做处理,文件内容是在HttpContent消息带来的。

在这个样例中,只响应feed的Post请求,其他的请求都被

if (!path.startsWith("/feed") ||request.getMethod().equals(HttpMethod.GET))

滤掉了

  • 针对HttpRequest只做了两件事,构造一个解码器decoder,看请求中是否含multi-part。
  • 初始化静态代码块设置文件下载的特性

    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

    }

大意就是下载后删除缓存文件,中断的话删除缓存文件,采用默认的缓存目录。

  • 然后在HttpContent中一个chunk一个chunk读吧,读到LastHttpContent对象处理完之后返回即可。  

HttpFileHandler.java



/**
 * 接收HTTP文件上传的处理器
 */
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.Set;

import org.json.JSONObject;

import com.seeplant.util.Property;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.CharsetUtil;

public class HttpFileHandler extends SimpleChannelInboundHandler<HttpObject>{
    private HttpRequest request;
    private boolean readingChunks;
    private final StringBuilder responseContent = new StringBuilder();
    
    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
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        // TODO Auto-generated method stub
        JSONObject jObject = new JSONObject().put("code", "404").put("msg", "page not found");
        
        if (msg instanceof HttpRequest) {
            this.request = (HttpRequest) msg;
            URI uri = new URI(request.getUri());
            String path = uri.getPath();
            
            setCookie(this.request);
            
            /* url区分的入口在这里 */
            if (!path.startsWith("/feed") || request.getMethod().equals(HttpMethod.GET)) {
                writeResponseString(ctx, jObject.toString());
                return;
            } else {
                try {
                decoder = new HttpPostRequestDecoder(factory, request);
                } catch (ErrorDataDecoderException e1) {
                    e1.printStackTrace();
                    responseContent.append(e1.getMessage());
                    writeResponseString(ctx, jObject.toString());
                    ctx.channel().close();
                    return;
                } catch (IncompatibleDataDecoderException e1) {
                    // 既然Get不需要创建解码器,自己实现业务的时候,流程不要走在这里面
                    // GET Method: should not try to create a HttpPostRequestDecoder
                    // So OK but stop here
                    writeResponseString(ctx, jObject.toString());
                    return;
                }
                
                readingChunks = HttpHeaders.isTransferEncodingChunked(request);
                //这里可以看到是否含有上传文件部分
                if (readingChunks) {
                    // Chunk version
                    readingChunks = true;
                }
            }
        } else if (msg instanceof HttpContent) {
            if (decoder != null && readingChunks) {
                HttpContent chunk = (HttpContent) msg;
                try{
                    decoder.offer(chunk);
                } catch (ErrorDataDecoderException e1) {
                    writeResponseString(ctx, jObject.toString());
                    reset();
                    ctx.channel().close();
                    return;
                }
                readHttpDataChunkByChunk(); //从解码器decoder中读出数据
                if (chunk instanceof LastHttpContent) {
                    writeResponseString(ctx, "{\"code\":0,\"msg\":\"ok\"}");
                    readingChunks = false;
                    reset();
                }
            } else {
                writeResponseString(ctx, jObject.toString());
                ctx.channel().close();
                return;
            }
        }
    }

    /**
     * Example of reading request by chunk and getting values from chunk to chunk
     * 从decoder中读出数据,写入临时对象,然后写入...哪里?
     * 这个封装主要是为了释放临时对象
     */
    private void readHttpDataChunkByChunk() {
        try {
            while (decoder.hasNext()) {
                InterfaceHttpData data = decoder.next();
                if (data != null) {
                    try {
                        // new value
                        writeHttpData(data);
                    } finally {
                        data.release();
                    }
                }
            }
        } catch (EndOfDataDecoderException e1) {
            // end
            responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
        }
    }
    
    /**
     * 设置cookie
     */
    private void setCookie(HttpRequest request) {
        Set<Cookie> cookies;
        String value = request.headers().get(COOKIE);
        if (value == null) {
            cookies = Collections.emptySet();
        } else {
            cookies = ServerCookieDecoder.LAX.decode(value);
        }
        for (Cookie cookie : cookies) {
            responseContent.append("COOKIE: " + cookie + "\r\n");
        }
        responseContent.append("\r\n\r\n");
    }
    /**
     * 封装应答的回写
     * @param ctx
     * @param message String的消息体Header中已经设置为Application/json
     */
    private void writeResponseString(ChannelHandlerContext ctx, String message) {
        // Convert the response content to a ChannelBuffer.
        responseContent.setLength(0);
        responseContent.append(message);

        ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
        // Build the response object.
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

        response.headers().set(CONTENT_TYPE, "application/json; charset=UTF-8");
        response.headers().set(CONTENT_LENGTH, buf.readableBytes());

        // Write the response.
        ctx.channel().writeAndFlush(response);
    }

    private void reset() {
        request = null;

        // destroy the decoder to release all resources
        decoder.destroy();
        decoder = null;
    }
    
    private void writeHttpData(InterfaceHttpData data) {
        // Attribute就是form表单里带的各种 name= 的属性
        if (data.getHttpDataType() == HttpDataType.Attribute) {
        } else if (data.getHttpDataType() == HttpDataType.InternalAttribute){
        }else{
            String uploadFileName = getUploadFileName(data);
            FileUpload fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {
                // fileUpload.isInMemory();// tells if the file is in Memory
                // or on File
                // fileUpload.renameTo(dest); // enable to move into another
                // File dest
                // decoder.removeFileUploadFromClean(fileUpload); //remove
                // the File of to delete file
                File dir = new File(Property.getSaveFileDir() + "download" + File.separator);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                File dest = new File(dir, uploadFileName);
                try {
                    fileUpload.renameTo(dest);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    
    private String getUploadFileName(InterfaceHttpData data) {
        String content = data.toString();
        String temp = content.substring(0, content.indexOf("\n"));
        content = temp.substring(temp.lastIndexOf("=") + 2, temp.lastIndexOf("\""));
        return content;
    }
}

帮助类Property.java



import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class Property {
    private static Properties properties = new Properties();
    static {
        File file = new File(System.getProperty("user.dir")+"setting.properties");
        if (!file.exists())
        {
            file = new File(Property.class.getResource("/").getPath()+"setting.properties");
        }
         
        try (FileInputStream fis = 
                new FileInputStream(file)){
            properties.load(fis);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            
            e.printStackTrace();
        }
    }
    
    private Property(){
    }
   
    public static String getProperty(String key) {
        return properties.getProperty(key);
    }
    
    public static String getSaveFileDir(){
        return System.getProperty("user.dir") + File.separator;
    }
}


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值