背景:网页丢个apk文件,客户端收到后自动更新安装应用
浏览器上传文件给服务端主要有两种方式: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));
此值设置的大小将会影响浏览器上传文件的大小
网友评论