使用NanoHttpd实现简易WebServer

z317825146 8年前

来自: http://blog.csdn.net/jltxgcy/article/details/50680394


   0x00

    在介绍使用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}。