在Android中使用netty可以很容易搭建一个web服务器;同时具有netty的优良特性:高性能,高可靠性,API易上手等;本篇文章主要介绍在Android中使用netty搭建web服务器的简单过程,对于一些复杂使用,复杂特性不做深究;不甚了解netty的可以先阅读此篇入门文章:Netty在Android中使用
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 HttpServerHandler());
//在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 HttpServerHandler());
2.实现客户端请求数据的读取:HttpServerHandler
- 详细步骤见代码
- 浏览器访问参考:
http://172.16.3.112:8080/json
http://172.16.3.112:8080/login?name=admin&psw=123456
http://172.16.3.112:8080/getImage
package me.com.testnettywebserver;
import android.net.Uri;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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.util.CharsetUtil;
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final String TAG = "HttpServerHandler";
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (!(msg instanceof FullHttpRequest)){
Log.e(TAG,"未知请求:"+msg.toString());
return;
}
FullHttpRequest httpRequest = (FullHttpRequest) msg;
String path = httpRequest.uri();
HttpMethod method = httpRequest.method();
String route = parseRoute(path);
Map<String,Object> params = new HashMap<>();
if (method == HttpMethod.GET){
parseGetParams(params,path);
}else if (method == HttpMethod.POST){
parsePostParams(params,httpRequest);
}else {
ByteBuf byteBuf = Unpooled.copiedBuffer(HttpResult.error("不支持的请求方式").getBytes());
response(ctx,"text/json;charset=UTF-8",byteBuf, HttpResponseStatus.BAD_REQUEST);
}
Log.e(TAG,"******************接收到了请求******************");
Log.e(TAG,"method:"+method);
Log.e(TAG,"route:"+route);
Log.e(TAG,"params:"+params.toString());
//路由实现
handlerRequest(ctx,route,params);
}
private void handlerRequest(ChannelHandlerContext ctx, String route, Map<String, Object> params) {
switch (route){
case "login":
ByteBuf login;
if ("admin".equals(params.get("name")) && "123456".equals(params.get("psw"))){
login = Unpooled.copiedBuffer(HttpResult.ok("登录成功").getBytes());
}else {
login = Unpooled.copiedBuffer(HttpResult.error("登录失败").getBytes());
}
response(ctx,"text/json;charset=UTF-8",login,HttpResponseStatus.OK);
break;
case "getImage":
ByteBuf imgBuf = getImage(new File("/storage/emulated/0/MagazineUnlock/1.jpg"));
response(ctx,"image/jpeg",imgBuf,HttpResponseStatus.OK);
break;
case "json":
ByteBuf byteBuf = Unpooled.copiedBuffer(HttpResult.ok("测试post请求成功").getBytes());
response(ctx,"text/json;charset=UTF-8",byteBuf,HttpResponseStatus.OK);
break;
default:
ByteBuf buf = Unpooled.copiedBuffer(HttpResult.error("未实现的请求地址").getBytes());
response(ctx,"text/json;charset=UTF-8",buf,HttpResponseStatus.BAD_REQUEST);
break;
}
}
private ByteBuf getImage(File file) {
ByteBuf byteBuf = Unpooled.buffer();
try {
FileInputStream fileInputStream = new FileInputStream(file);
int len;
byte[] buf = new byte[1024];
while ((len = fileInputStream.read(buf)) != -1){
byteBuf.writeBytes(buf,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return byteBuf;
}
private void parsePostParams(Map<String, Object> params, FullHttpRequest httpRequest) {
ByteBuf content = httpRequest.content();
String body = content.toString(CharsetUtil.UTF_8);
try {
JSONObject jsonObject = new JSONObject(body);
Iterator<String> iterator = jsonObject.keys();
while (iterator.hasNext()){
String key = iterator.next();
params.put(key,jsonObject.opt(key));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void parseGetParams(Map<String, Object> params, String path) {
Uri uri = Uri.parse("http://172.16.0.1"+path);
Set<String> names = uri.getQueryParameterNames();
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()){
String key = iterator.next();
params.put(key,uri.getQueryParameter(key));
}
}
private void response(ChannelHandlerContext ctx, String type, ByteBuf byteBuf, HttpResponseStatus status) {
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,status,byteBuf);
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,type);
ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
}
/**
* 解析调用的接口(路由地址)
*/
private String parseRoute(String path) {
if (path.contains("?")) {
String uri = path.split("\\?")[0];
return uri.substring(1);
} else {
return path.substring(1);
}
}
}
3.实现数据发送
private void response(ChannelHandlerContext ctx, String type, ByteBuf byteBuf, HttpResponseStatus status) {
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,status,byteBuf);
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,type);
ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
}
4.注意地方
- 添加权限
<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"/>
- 跨域解决(跨域原因是:浏览器的同源策略,前端使用了不同源的url访问服务器)
解决方法:在Response header中添加:
httpResponse.headers().add("Access-Control-Allow-Origin", "*");
httpResponse.headers().add("Access-Control-Allow-Methods", "GET, POST, PUT,DELETE,OPTIONS,PATCH");
httpResponse.headers().add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");