Android使用Netty搭建Web服務器

在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");

5.推薦閱讀

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