使用NanoHttpd實現簡易WebServer
來自: 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}。