阅读本文建议从第一篇开始往后看
本系列文章
- Netty在Android开发中的应用实战系列(一)——— 搭建服务端与客户端
- Netty在Android开发中的应用实战系列(二)——— Encoder | Decoder | Handler 的使用
- Netty在Android开发中的应用实战系列(三)——— 心跳处理 | 断线重连
- Netty在Android开发中的应用实战系列(四)——— 粘包 | 拆包 处理
- Netty在Android开发中的应用实战系列(五)——— 创建Web服务 | 作为HTTP服务器
不得不感叹Netty的强大除了可以处理Socket的需求,竟然也还可以创建Web服务让Android充当一个Web服务器处理GET
、POST
等等请求…
一、创建Http服务
public class HttpServer {
private static final String TAG = "HttpServer";
//服务开启在的端口
public static final int PORT = 7020;
public void startHttpServer() {
try {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(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(65536));
// 处理发起的请求
pipeline.addLast(new HttpServerHandler());
}
});
//绑定服务在7020端口上
b.bind(new InetSocketAddress(PORT)).sync();
Log.d(TAG, "HTTP服务启动成功 PORT=" + PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 启动HTTP服务
new HttpServer().startHttpServer();
与我们之前写的连接Socket代码可以发现添加的
Decoder
、Encoder
不一致;这里是用的是Netty封装好的专门针对HTTP请求的解码器和编码器
- 运行的效果
二、在HttpServerHandler
中处理收到的HTTP请求
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private static final String TAG = "HttpServerHandler";
@Override
public void channelRead(ChannelHandlerContext ctx, Object 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();
Log.d(TAG, "==================接收到了请求==================");
Log.d(TAG, "route = " + route);
Log.d(TAG, "method = " + method);
}
}
- 这里我是把项目跑在Android模拟器上的,所以需要在控制台输入如下命令做一个端口转发,这样在我们的电脑浏览器就可以使用
localhost:7020
就可以访问到HTTP服务了
//7020 就是你要转发的端口
adb forward tcp:7020 tcp:7020
- 在浏览器输入
localhost:7020/test
来看下效果
- 上面我们只接收了请求但是没有对请求进行返回结果,这也就导致请求一直无法完成一直处与
请求中
状态
三、响应HTTP请求
- 只需要向连接中写入
FullHttpResponse
即可,如下代码
ByteBuf byteBuf = Unpooled.copiedBuffer(Result.ok("请求成功").getBytes());
response(ctx, "text/json;charset=UTF-8", byteBuf, HttpResponseStatus.OK);
/**
* 响应请求结果
*
* @param ctx 返回
* @param contentType 响应类型
* @param content 消息
* @param status 状态
*/
private void response(ChannelHandlerContext ctx, String contentType, ByteBuf content, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
到这一个基本的HTTP服务就已经跑起来了,剩下的就是解析发起的参数和处理请求了
四、下面给出一些示例,展示如何获取请求的参数和响应json数据或者响应图片数据
- HttpServerHandler类
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private static final String TAG = "HttpServerHandler";
@Override
public void channelRead(ChannelHandlerContext ctx, Object 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(Result.error("不支持的请求方式").getBytes());
response(ctx, "text/json;charset=UTF-8", byteBuf, HttpResponseStatus.BAD_REQUEST);
}
Log.d(TAG, "==================接收到了请求==================");
Log.d(TAG, "route = " + route);
Log.d(TAG, "method = " + method);
Log.d(TAG, "params = " + params.toString());
handlerRequest(ctx, route, params);
}
/**
* 处理每个请求
*/
private void handlerRequest(ChannelHandlerContext ctx, String route, Map<String, Object> params) throws Exception {
switch (route) {
case "login":
ByteBuf login;
if ("admin".equals(params.get("name")) && "123".equals(params.get("psd"))) {
login = Unpooled.copiedBuffer(Result.ok("登录成功").getBytes());
} else {
login = Unpooled.copiedBuffer(Result.error("登录失败").getBytes());
}
response(ctx, "text/json;charset=UTF-8", login, HttpResponseStatus.OK);
break;
case "getImage":
//返回一张图片
ByteBuf image = getImage(new File("/storage/emulated/0/Android/data/com.azhon.nettyhttp/cache/test.jpg"));
response(ctx, "image/jpg", image, HttpResponseStatus.OK);
break;
case "json":
ByteBuf json = Unpooled.copiedBuffer(Result.ok("测试post请求成功").getBytes());
response(ctx, "text/json;charset=UTF-8", json, HttpResponseStatus.OK);
break;
default:
ByteBuf error = Unpooled.copiedBuffer(Result.error("未实现的请求地址").getBytes());
response(ctx, "text/json;charset=UTF-8", error, HttpResponseStatus.BAD_REQUEST);
break;
}
}
/**
* 解析Get请求参数
*/
private void parseGetParams(Map<String, Object> params, String path) {
//拼接成全路径好取参数
Uri uri = Uri.parse("http://127.0.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));
}
}
/**
* 解析Post请求参数,以提交的body为json为例
*/
private void parsePostParams(Map<String, Object> params, FullHttpRequest httpRequest) throws JSONException {
ByteBuf content = httpRequest.content();
String body = content.toString(CharsetUtil.UTF_8);
JSONObject object = new JSONObject(body);
Iterator<String> keys = object.keys();
while (keys.hasNext()) {
String key = keys.next();
params.put(key, object.opt(key));
}
}
/**
* 解析调用的接口(路由地址)
*/
private String parseRoute(String path) {
if (path.contains("?")) {
String uri = path.split("\\?")[0];
return uri.substring(1);
} else {
return path.substring(1);
}
}
/**
* 返回图片
*/
private ByteBuf getImage(File file) throws Exception {
ByteBuf byteBuf = Unpooled.buffer();
FileInputStream stream = new FileInputStream(file);
int len;
byte[] buff = new byte[1024];
while ((len = stream.read(buff)) != -1) {
byteBuf.writeBytes(buff, 0, len);
}
return byteBuf;
}
/**
* 响应请求结果
*
* @param ctx 返回
* @param contentType 响应类型
* @param content 消息
* @param status 状态
*/
private void response(ChannelHandlerContext ctx, String contentType, ByteBuf content, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
- Result类,返回的json数据工具类
public class Result {
/**
* 响应请求c成功
*/
public static String ok(String msg) {
JSONObject object = new JSONObject();
try {
object.put("code", 100);
object.put("msg", msg);
} catch (JSONException e) {
e.printStackTrace();
}
return object.toString();
}
/**
* 响应请求失败
*/
public static String error(String msg) {
JSONObject object = new JSONObject();
try {
object.put("code", 101);
object.put("msg", msg);
} catch (JSONException e) {
e.printStackTrace();
}
return object.toString();
}
}
五、上面使用到的测试接口地址
GET http://localhost:7020/login?name=admin&psd=123 登录
GET http://localhost:7020/getImage 获取图片
POST http://localhost:7020/json 提交json数据
- POST方式这里使用
postman
来模拟的,如下: