閱讀本文建議從第一篇開始往後看
本系列文章
- 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
來模擬的,如下: