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.推荐阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343