半点优化网 http://www.bdxc.net/
当前位置首页 > 网站技术问题> 正文

如何开发自己的httpserver-nanohttpd源码解读

现在作为一个开发人员,http server相关的内容已经是无论如何都要了解的知识了。用curl发一个请求,配置一下apache,部署一个web server对我们来说都不是很难,但要想搞清楚这些背后都发生了什么技术细节还真不是很简单的。所以新的系列将是分享我学习Http Server的过程。NanoHttpd是Github上的一个开源项目,号称只用一个java文件就能创建一个http server,我将通过分析NanoHttpd的源码解析如何开发自己的HttpServer。Github 地址:在开始前首先简单说明HttpServer的基本要素:1.能接受HttpRequest并返回HttpResponse2.满足一个Server的基本特征,能够长时间运行关于Http协议一般HttpServer都会声明支持Http协议的哪些特性,nanohttpd作为一个轻量级的httpserver只实现了最简单、最常用的功能,不过我们依然可以从中学习很多。首先看下NanoHttpd类的start函数[java] view plaincopypublic 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(); 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(); } 1.创建ServerSocket,bind制定端口2.创建主线程,主线程负责和client建立连接3.建立连接后会生成一个runnable对象放入asyncRunner中,asyncRunner.exec会创建一个线程来处理新生成的连接。4.新线程首先创建了一个HttpSession,然后while(true)的执行httpSession.exec。这里介绍下HttpSession的概念,HttpSession是java里Session概念的实现,简单来说一个Session就是一次httpClient->httpServer的连接,当连接close后session就结束了,如果没结束则session会一直存在。这点从这里的代码也能看到:如果socket不close或者exec没有抛出异常(异常有可能是client段断开连接)session会一直执行exec方法。一个HttpSession中存储了一次网络连接中server应该保存的信息,比如:URI,METHOD,PARAMS,HEADERS,COOKIES等。5.这里accept一个client的socket就创建一个独立线程的server模型是ThreadServer模型,特点是一个connection就会创建一个thread,是比较简单、常见的socket server实现。缺点是在同时处理大量连接时线程切换需要消耗大量的资源,如果有兴趣可以了解更加高效的NIO实现方式。当获得client的socket后自然要开始处理client发送的httprequest。Http Request Header的parse:[plain] view plaincopy// 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); } } 1.读取socket数据流的前8192个字节,因为http协议中头部最长为81922.通过findHeaderEnd函数找到header数据的截止位置,并把位置保存到splitbyte内。[java] view plaincopyif (splitbyte < rlen) { inputStream.unread(buf, splitbyte, rlen - splitbyte); } parms = new HashMap<String, String>(); if(null == headers) { headers = new HashMap<String, String>(); } 1.http协议规定header和body之间使用两个回车换行分割1.Http协议第一行是Method URI HTTP_VERSION2.后面每行都是KEY:VALUE格式的header3.uri需要经过URIDecode处理后才能使用4.uri中如果包含?则表示有param,httprequest的param一般表现为:/index.jsp?username=xiaoming&id=2下面是处理cookie,不过这里cookie的实现较为简单,所以跳过。之后是serve方法,serve方法提供了用户自己实现httpserver具体逻辑的很好接口。在NanoHttpd中的serve方法实现了一个默认的简单处理功能。[java] view plaincopy 发送response的步骤如下:1.设置mimeType和Time等内容。2.创建一个PrintWriter,按照HTTP协议依次开始写入内容3.第一行是HTTP的返回码4.然后是content-Type5.然后是Date时间6.之后是其他的HTTP Header7.设置Keep-Alive的Header,Keep-Alive是Http1.1的新特性,作用是让客户端和服务器端之间保持一个长链接。8.如果客户端指定了ChunkedEncoding则分块发送response,Chunked Encoding是Http1.1的又一新特性。一般在response的body比较大的时候使用,server端会首先发送response的HEADER,然后分块发送response的body,每个分块都由chunk length\r\n和chunk data\r\n组成,最后由一个0\r\n结束。9.如果没指定ChunkedEncoding则需要指定Content-Length来让客户端指定response的body的size,然后再一直写body直到写完为止。

猜你喜欢