HTTP是基于TCP协议的。TCP负责数据传输,而HTTP只是规范了TCP传输的数据的格式,而这个具体的格式,请见后面给出的资料。 HTTP服务的底层实现就是socket编程。 下面基于socket编写一个简单的HTTP server。 ~~~ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; class SocketHandler implements Runnable { final static String CRLF = "\r\n"; // 1 private Socket clientSocket; public SocketHandler(Socket clientSocket) { this.clientSocket = clientSocket; } public void handleSocket(Socket clientSocket) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream()) ); PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())), true ); String requestHeader = ""; String s; while ((s = in.readLine()) != null) { s += CRLF; // 2 很重要,默认情况下in.readLine的结果中`\r\n`被去掉了 requestHeader = requestHeader + s; if (s.equals(CRLF)){ // 3 此处HTTP请求头我们都得到了;如果从请求头中判断有请求正文,则还需要继续获取数据 break; } } System.out.println("客户端请求头:"); System.out.println(requestHeader); String responseBody = "客户端的请求头是:\n"+requestHeader; String responseHeader = "HTTP/1.0 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "Content-Length: "+responseBody.getBytes().length+"\r\n" + "\r\n"; // 4 问题来了:1、浏览器如何探测编码 2、浏览器受到content-length后会按照什么方式判断?汉字的个数?字节数? System.out.println("响应头:"); System.out.println(responseHeader); out.write(responseHeader); out.write(responseBody); out.flush(); out.close(); in.close(); clientSocket.close(); } @Override public void run() { try { handleSocket(clientSocket); } catch(Exception ex) { ex.printStackTrace(); } } } public class MyHTTPServer { public static void main(String[] args) throws Exception { int port = 8000; ServerSocket serverSocket = new ServerSocket(port); System.out.println("启动服务,绑定端口: " + port); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30); // 5 while (true) { // 6 Socket clientSocket = serverSocket.accept(); System.out.println("新的连接" + clientSocket.getInetAddress() + ":" + clientSocket.getPort()); try { fixedThreadPool.execute(new SocketHandler(clientSocket)); } catch (Exception e) { System.out.println(e); } } } } ~~~ 这是一个实现HTTP 1.0的服务器,对于所有的HTTP请求,会把HTTP请求头响应回去。 这个程序说明了web服务器处理请求的基本流程,JSP、Servlet、Spring MVC等只是在 这个基础上嫁了许多方法,以让我们更方面的编写web应用。web服务器不仅可以基于多线程, 也可以基于多进程、Reactor模型等。 **测试程序:** 运行上面的程序。我们使用curl访问`http://127.0.0.1`(也可以使用浏览器): ~~~ $ curl -i http://127.0.0.1:8000 HTTP/1.0 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 106 客户端的请求头是: GET / HTTP/1.1 User-Agent: curl/7.35.0 Host: 127.0.0.1:8000 Accept: */* ~~~ Java程序输出: ~~~ 启动服务,绑定端口: 8000 新的连接/127.0.0.1:36463 新的连接/127.0.0.1:36463客户端请求头: GET / HTTP/1.1 User-Agent: curl/7.35.0 Host: 127.0.0.1:8000 Accept: */* 响应头: HTTP/1.0 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 106 ~~~ **程序解析:** `// 1`:定义了HTTP头的换行符。 `// 2`:in.readLine()的结果默认不带换行符,这里把它加上。(这不是强制的,主要看你的程序逻辑需不需要, 这个程序的目标是把HTTP请求头响应回去)。 `// 3`:此时s是一个空行,根据HTTP协议,整个请求头都得到了。 `// 4`:Content-Length的值是字节的数量。 `// 5`:线程池。 `// 6`:这个循环不停监听socket连接,使用SocketHandler处理连入的socket,而这个处理是放在线程池中的。 **HTTP 1.1:** HTTP 1.1也是在这个思路的基础上实现的,即多个HTTP请求都在一个TCP连接中传输。对于HTTP 1.1,如何区分出每个HTTP请求很重要, 比较简单的可以是用过`Content-Length`判断一条请求是否结束。如果一个HTTP请求数据较多,往往采用Chunked方式, 可以参考[Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding)。 ## [](https://github.com/someus/another-tutorial-about-java-web/blob/master/00-02.md#http相关资料)HTTP相关资料 **网络教程:** [菜鸟教程-HTTP教程](http://www.runoob.com/http/http-tutorial.html) [List of HTTP header fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields) [14 Header Field Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) [HTTP header should use what character encoding?](http://stackoverflow.com/questions/4400678/http-header-should-use-what-character-encoding) **书籍:** [HTTP权威指南](https://github.com/someus/another-tutorial-about-java-web/blob/master) [计算机网络](https://github.com/someus/another-tutorial-about-java-web/blob/master) 谢希仁HTTP是基于TCP协议的。TCP负责数据传输,而HTTP只是规范了TCP传输的数据的格式,而这个具体的格式,请见后面给出的资料。 HTTP服务的底层实现就是socket编程。 下面基于socket编写一个简单的HTTP server。 ~~~ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; class SocketHandler implements Runnable { final static String CRLF = "\r\n"; // 1 private Socket clientSocket; public SocketHandler(Socket clientSocket) { this.clientSocket = clientSocket; } public void handleSocket(Socket clientSocket) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream()) ); PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())), true ); String requestHeader = ""; String s; while ((s = in.readLine()) != null) { s += CRLF; // 2 很重要,默认情况下in.readLine的结果中`\r\n`被去掉了 requestHeader = requestHeader + s; if (s.equals(CRLF)){ // 3 此处HTTP请求头我们都得到了;如果从请求头中判断有请求正文,则还需要继续获取数据 break; } } System.out.println("客户端请求头:"); System.out.println(requestHeader); String responseBody = "客户端的请求头是:\n"+requestHeader; String responseHeader = "HTTP/1.0 200 OK\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "Content-Length: "+responseBody.getBytes().length+"\r\n" + "\r\n"; // 4 问题来了:1、浏览器如何探测编码 2、浏览器受到content-length后会按照什么方式判断?汉字的个数?字节数? System.out.println("响应头:"); System.out.println(responseHeader); out.write(responseHeader); out.write(responseBody); out.flush(); out.close(); in.close(); clientSocket.close(); } @Override public void run() { try { handleSocket(clientSocket); } catch(Exception ex) { ex.printStackTrace(); } } } public class MyHTTPServer { public static void main(String[] args) throws Exception { int port = 8000; ServerSocket serverSocket = new ServerSocket(port); System.out.println("启动服务,绑定端口: " + port); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30); // 5 while (true) { // 6 Socket clientSocket = serverSocket.accept(); System.out.println("新的连接" + clientSocket.getInetAddress() + ":" + clientSocket.getPort()); try { fixedThreadPool.execute(new SocketHandler(clientSocket)); } catch (Exception e) { System.out.println(e); } } } } ~~~ 这是一个实现HTTP 1.0的服务器,对于所有的HTTP请求,会把HTTP请求头响应回去。 这个程序说明了web服务器处理请求的基本流程,JSP、Servlet、Spring MVC等只是在 这个基础上嫁了许多方法,以让我们更方面的编写web应用。web服务器不仅可以基于多线程, 也可以基于多进程、Reactor模型等。 **测试程序:** 运行上面的程序。我们使用curl访问`http://127.0.0.1`(也可以使用浏览器): ~~~ $ curl -i http://127.0.0.1:8000 HTTP/1.0 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 106 客户端的请求头是: GET / HTTP/1.1 User-Agent: curl/7.35.0 Host: 127.0.0.1:8000 Accept: */* ~~~ Java程序输出: ~~~ 启动服务,绑定端口: 8000 新的连接/127.0.0.1:36463 新的连接/127.0.0.1:36463客户端请求头: GET / HTTP/1.1 User-Agent: curl/7.35.0 Host: 127.0.0.1:8000 Accept: */* 响应头: HTTP/1.0 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 106 ~~~ **程序解析:** `// 1`:定义了HTTP头的换行符。 `// 2`:in.readLine()的结果默认不带换行符,这里把它加上。(这不是强制的,主要看你的程序逻辑需不需要, 这个程序的目标是把HTTP请求头响应回去)。 `// 3`:此时s是一个空行,根据HTTP协议,整个请求头都得到了。 `// 4`:Content-Length的值是字节的数量。 `// 5`:线程池。 `// 6`:这个循环不停监听socket连接,使用SocketHandler处理连入的socket,而这个处理是放在线程池中的。 **HTTP 1.1:** HTTP 1.1也是在这个思路的基础上实现的,即多个HTTP请求都在一个TCP连接中传输。对于HTTP 1.1,如何区分出每个HTTP请求很重要, 比较简单的可以是用过`Content-Length`判断一条请求是否结束。如果一个HTTP请求数据较多,往往采用Chunked方式, 可以参考[Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding)。 ## [](https://github.com/someus/another-tutorial-about-java-web/blob/master/00-02.md#http相关资料)HTTP相关资料 **网络教程:** [菜鸟教程-HTTP教程](http://www.runoob.com/http/http-tutorial.html) [List of HTTP header fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields) [14 Header Field Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) [HTTP header should use what character encoding?](http://stackoverflow.com/questions/4400678/http-header-should-use-what-character-encoding) **书籍:** [HTTP权威指南](https://github.com/someus/another-tutorial-about-java-web/blob/master) [计算机网络](https://github.com/someus/another-tutorial-about-java-web/blob/master) 谢希仁