在Android中使用Netty接收Http文件上傳

瀏覽器上傳文件給服務端主要有兩種方式:1.讀取整個文件,把讀的內容放入request的body中,一次post請求上傳整個文件;2.讀取部分文件,一部分一部分上傳;對於小文件可以使用方式1,而對於大文件則使用方式2,否則瀏覽器一次上傳的文件量過大會造成等待post返回超時導致上傳失敗;本文主要記錄一次上傳整個文件的情況;

1.服務器的配置及啓動啓動

  • 在後臺線程中執行如下方法:
private void startServer() {
        try {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<io.netty.channel.socket.SocketChannel>() {
                        @Override
                        protected void initChannel(io.netty.channel.socket.SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // http服務器端對request解碼
                            pipeline.addLast(new HttpRequestDecoder());
                            // http服務器端對response編碼
                            pipeline.addLast(new HttpResponseEncoder());
                            // 在處理POST消息體時需要加上
                            pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                            // 處理髮起的請求
                            pipeline.addLast(new HttpFileHandler());
                            //在HttpResponseEncoder序列化之前會對response對象進行HttpContentCompressor壓縮
                            pipeline.addLast("compressor", new HttpContentCompressor());
                        }
                    });
            b.bind(new InetSocketAddress(PORT)).sync();
            Log.d(TAG, "HTTP服務啓動成功 PORT=" + PORT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 使用Http進行編解碼主要添加:
// http服務器端對request解碼
pipeline.addLast(new HttpRequestDecoder());
// http服務器端對response編碼
pipeline.addLast(new HttpResponseEncoder());
  • 對發起的請求進行處理:(詳細見#2中的實現方法)
pipeline.addLast(new HttpFileHandler());

2.處理文件接收請求:HttpFileHandler

/**
 * 接收HTTP文件上傳的處理器
 */
package me.com.testnettywebserver;

import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
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.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;

public class HttpFileHandler extends SimpleChannelInboundHandler<HttpObject> {

    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 {

        if (msg instanceof FullHttpRequest) {

            FullHttpRequest request = (FullHttpRequest) msg;
            URI uri = new URI(request.uri());
            String path = uri.getPath();

            if (!path.startsWith("/user")) {
                response(ctx, "", Unpooled.copiedBuffer(HttpResult.error("未實現的請求地址").getBytes()), HttpResponseStatus.OK);
            } else if (request.method().equals(HttpMethod.OPTIONS)) { //處理跨域請求
                response(ctx, "", Unpooled.copiedBuffer(HttpResult.ok("成功").getBytes()), HttpResponseStatus.OK);
            } else if (request.method().equals(HttpMethod.POST)){   //文件通過post進行上傳
                try {

                    //"multipart/form-data" : 代表在表單中進行文件上傳
                    if (!request.headers().get(HttpHeaderNames.CONTENT_TYPE).contains("multipart/form-data")){
                        return;
                    }

                    HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
                    HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request);
                    List<InterfaceHttpData> dataList = decoder.getBodyHttpDatas();

                    if (dataList == null) {
                        return;
                    }
                    for (int ni = 0; ni < dataList.size(); ni++) {
                        writeHttpData(dataList.get(ni));
                    }

                    decoder.destroy();

                    response(ctx, "", Unpooled.copiedBuffer(HttpResult.ok("接收成功").getBytes()), HttpResponseStatus.OK);
                } catch (ErrorDataDecoderException e1) {
                    e1.printStackTrace();
                    response(ctx, "", Unpooled.copiedBuffer(HttpResult.error("解碼失敗").getBytes()), HttpResponseStatus.OK);
                }


            }
        }
    }

    private void response(ChannelHandlerContext ctx, String type, ByteBuf byteBuf, HttpResponseStatus status) {
        FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, byteBuf);
        if (TextUtils.isEmpty(type)) {
            httpResponse.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/json;charset=UTF-8");
        } else {
            httpResponse.headers().add(HttpHeaderNames.CONTENT_TYPE, type);
        }
        ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
    }

    private void writeHttpData(InterfaceHttpData data) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //TODO 判斷沒有存儲權限則返回
            //int permission = ContextCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE");
        }


        if (data.getHttpDataType() == HttpDataType.FileUpload) {
            FileUpload fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {
                File dir = new File(Environment.getExternalStorageDirectory() + "/download/");
                if (!dir.exists()) {
                    dir.mkdir();
                }
                File dest = new File(dir, "record.apk");//根據接收到的文件類型來決定文件的後綴
                try {
                    fileUpload.renameTo(dest);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 使用以下代碼配置服務器對文件的緩存策略:
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
    }
  • 接收到的文件在FullHttpRequest中,需要單獨把要的文件從FullHttpRequest對象中解碼出來
HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request);
List<InterfaceHttpData> dataList = decoder.getBodyHttpDatas();

3.注意地方

  • 添加權限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  • 在配置服務器的時候要注意這個配置:
 // 在處理POST消息體時需要加上
pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));

此值設置的大小將會影響瀏覽器上傳文件的大小

4.推薦閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章