Netty在Android開發中的應用實戰系列(五)——— 創建Web服務 | 作爲HTTP服務器

閱讀本文建議從第一篇開始往後看

本系列文章

不得不感嘆Netty的強大除了可以處理Socket的需求,竟然也還可以創建Web服務讓Android充當一個Web服務器處理GETPOST等等請求…

一、創建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代碼可以發現添加的DecoderEncoder不一致;這裏是用的是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來模擬的,如下:
    在這裏插入圖片描述

六、運行效果

在這裏插入圖片描述

總的來說使用Netty創建Http服務還是很easy的,有興趣的同學可以嘗試下;代碼都已經貼在文章中了就不上傳Demo了(上傳Demo還要下載積分emmmm…)

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