Android之利用NanoHttpd搭建服务器

NanoHttpd是一个开源库,使用Java实现,可以在非常方便地集成到Android应用中去,实现了一个轻量级的 Web Server。

项目地址: https://github.com/NanoHttpd/nanohttpd

使用方法:在下面下载链接中下载nanohttpd-2.2.0.jar导入到Android项目中的libs目录下。

https://github.com/NanoHttpd/nanohttpd/releases

 

    在介绍使用NanoHttpd实现简易WebServer之前,我们首先熟悉下局域网Socket通信。一个Client工程,代码地址为https://github.com/jltxgcy/AppVulnerability/tree/master/MyClient。一个Server工程,代码地址为https://github.com/jltxgcy/AppVulnerability/tree/master/MyServer。

    两个工程要在要同样的Wifi环境下,MyClient工程要修改连接目标的IP地址。如下:


clientSocket = new Socket("10.10.154.74",6100);
    这个IP地址可以通过设置->关于手机->状态信息->IP地址获取。如下图:
    

    具体的代码就不介绍了,大家自己分析。

   0x01

    下面介绍使用NanoHttpd实现简易WebServer。代码地址为https://github.com/jltxgcy/AppVulnerability/tree/master/NanoHttpD。

    运行NanoHttpD后,在本机的UC浏览器输入http://127.0.0.1:8088,会返回it works。在其他连接相同wifi的手机浏览器上输入http://10.10.154.12(也就是运行NanoHttpD的手机IP),也会出现it works。

    那么这个本地webServer是什么原理呢?

    我们先看主Activity,代码如下:


public class MainActivity extends Activity {
 
    private SimpleServer server;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        server = new SimpleServer();
        try {
            
            // 因为程序模拟的是html放置在asset目录下,
            // 所以在这里存储一下AssetManager的指针。
            server.asset_mgr = this.getAssets();
            
            // 启动web服务
            server.start();
            
            Log.i("Httpd", "The server started.");
        } catch(IOException ioe) {
            Log.w("Httpd", "The server could not start.");
        }
    }
       ......
}
   创建了SimpleServer对象,然后调用了它的start方法。我们来看SimpleServer类的代码:

public class SimpleServer extends NanoHTTPD {
    AssetManager asset_mgr;
    
    public SimpleServer() {
        // 端口是8088,也就是说要通过http://127.0.0.1:8088来访当问
        super(8088);
    }
 
    public Response serve(String uri, Method method, 
            Map<String, String> header,
            Map<String, String> parameters,
            Map<String, String> files)
    {
        int len = 0;  
        byte[] buffer = null;
        Log.d("jltxgcy", header.get("remote-addr"));
        
        // 默认传入的url是以“/”开头的,需要删除掉,否则就变成了绝对路径
        String file_name = uri.substring(1);
        
        // 默认的页面名称设定为index.html
        if(file_name.equalsIgnoreCase("")){
            file_name = "index.html";
        }
 
        try {
            
            //通过AssetManager直接打开文件进行读取操作
            InputStream in = asset_mgr.open(file_name, AssetManager.ACCESS_BUFFER);
            
            //假设单个网页文件大小的上限是1MB
             buffer = new byte[1024*1024];  
            
             int temp=0;
            while((temp=in.read())!=-1){
                buffer[len]=(byte)temp;  
                len++;  
            }
            in.close();  
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        // 将读取到的文件内容返回给浏览器
        return new NanoHTTPD.Response(new String(buffer,0,len));
 
    }
}
    SimpleServer继承了NanoHTTPD,server.start()实际上调用NanoHTTPD类的start方法。如下:

public void start() throws IOException {
        myServerSocket = new ServerSocket();
        myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
 
        myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                do {
                    try {
                        final Socket finalAccept = myServerSocket.accept();
                        registerConnection(finalAccept);
                        finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
                        final InputStream inputStream = finalAccept.getInputStream();
                        if (inputStream == null) {
                            safeClose(finalAccept);
                            unRegisterConnection(finalAccept);
                        } else {
                            asyncRunner.exec(new Runnable() {
                                @Override
                                public void run() {
                                    OutputStream outputStream = null;
                                    try {
                                        outputStream = finalAccept.getOutputStream();
                                        TempFileManager tempFileManager = tempFileManagerFactory.create();
                                        HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress());
                                        while (!finalAccept.isClosed()) {
                                            session.execute();
                                        }
                                    } catch (Exception e) {
                                        // When the socket is closed by the client, we throw our own SocketException
                                        // to break the  "keep alive" loop above.
                                        if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {
                                            e.printStackTrace();
                                        }
                                    } finally {
                                        safeClose(outputStream);
                                        safeClose(inputStream);
                                        safeClose(finalAccept);
                                        unRegisterConnection(finalAccept);
                                    }
                                }
                            });
                        }
                    } catch (IOException e) {
                    }
                } while (!myServerSocket.isClosed());
            }
        });
        myThread.setDaemon(true);
        myThread.setName("NanoHttpd Main Listener");
        myThread.start();
    }
    创建了一个Socket Server,myServerSocket.accept()阻塞等待连接,当在本机浏览器输入http://127.0.0.1:8088,建立连接,接下来去处理这个连接,myThread线程会继续执行到session.execute。我们来看那这个函数的代码:

@Override
        public void execute() throws IOException {
            try {
                // Read the first 8192 bytes.
                // The full header should fit in here.
                // Apache's default header limit is 8KB.
                // Do NOT assume that a single read will get the entire header at once!
                byte[] buf = new byte[BUFSIZE];
                splitbyte = 0;
                rlen = 0;
                {
                    int read = -1;
                    try {
                        read = inputStream.read(buf, 0, BUFSIZE);
                    } catch (Exception e) {
                        safeClose(inputStream);
                        safeClose(outputStream);
                        throw new SocketException("NanoHttpd Shutdown");
                    }
                    if (read == -1) {
                        // socket was been closed
                        safeClose(inputStream);
                        safeClose(outputStream);
                        throw new SocketException("NanoHttpd Shutdown");
                    }
                    while (read > 0) {
                        rlen += read;
                        splitbyte = findHeaderEnd(buf, rlen);
                        if (splitbyte > 0)
                            break;
                        read = inputStream.read(buf, rlen, BUFSIZE - rlen);
                    }
                }
 
                if (splitbyte < rlen) {
                    ByteArrayInputStream splitInputStream = new ByteArrayInputStream(buf, splitbyte, rlen - splitbyte);
                    SequenceInputStream sequenceInputStream = new SequenceInputStream(splitInputStream, inputStream);
                    inputStream = sequenceInputStream;
                }
 
                parms = new HashMap<String, String>();
                if(null == headers) {
                    headers = new HashMap<String, String>();
                }
 
                // Create a BufferedReader for parsing the header.
                BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));
 
                // Decode the header into parms and header java properties
                Map<String, String> pre = new HashMap<String, String>();
                decodeHeader(hin, pre, parms, headers);
 
                method = Method.lookup(pre.get("method"));
                if (method == null) {
                    throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
                }
 
                uri = pre.get("uri");
 
                cookies = new CookieHandler(headers);
 
                // Ok, now do the serve()
                Response r = serve(this);
                if (r == null) {
                    throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
                } else {
                    cookies.unloadQueue(r);
                    r.setRequestMethod(method);
                    r.send(outputStream);
                }
            } catch (SocketException e) {
                // throw it out to close socket object (finalAccept)
                throw e;
            } catch (SocketTimeoutException ste) {
                throw ste;
            } catch (IOException ioe) {
                Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
                r.send(outputStream);
                safeClose(outputStream);
            } catch (ResponseException re) {
                Response r = new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
                r.send(outputStream);
                safeClose(outputStream);
            } finally {
                tempFileManager.clear();
            }
        }
    这个函数解析http://127.0.0.1:8088(数据来源于finalAccept.getInputStream()),然后调用了SimpleServer的serve方法,这个server方法返回的就是显示在浏览器中的内容。
    我们根据调试,看一下public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parameters, Map<String, String> files),这些参数返回的值到底是多少?

    url为/,method为GET,head为{accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,UC/145,plugin/1,alipay/un, accept-encoding=gzip, host=127.0.0.1:8088, accept-language=zh-CN, http-client-ip=127.0.0.1, cache-control=max-age=0, x-ucbrowser-ua=dv(Nexus 6);pr(UCBrowser/10.7.0.634);ov(Android 5.1.1);ss(411*683);pi(1440*2392);bt(UM);pm(1);bv(1);nm(0);im(0);sr(0);nt(2);, remote-addr=127.0.0.1, user-agent=Mozilla/5.0 (Linux; U; Android 5.1.1; zh-CN; Nexus 6 Build/LMY47Z) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/10.7.0.634 U3/0.8.0 Mobile Safari/534.30, connection=keep-alive},parameters为{NanoHttpd.QUERY_STRING=null},files为{}。

   如果请求的地址为http://127.0.0.1:8088/adv?d=1,则url为adv,parameter为{d=1, NanoHttpd.QUERY_STRING=d=1}。

 

--------------------- 
作者:jltxgcy 
来源:CSDN 
原文:https://blog.csdn.net/jltxgcy/article/details/50680394 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值