HTTP是基于TCP協(xié)議的。TCP負(fù)責(zé)數(shù)據(jù)傳輸,而HTTP只是規(guī)范了TCP傳輸?shù)臄?shù)據(jù)的格式,而這個具體的格式,請見后面給出的資料。
HTTP服務(wù)的底層實(shí)現(xiàn)就是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 很重要,默認(rèn)情況下in.readLine的結(jié)果中`\r\n`被去掉了
requestHeader = requestHeader + s;
if (s.equals(CRLF)){ // 3 此處HTTP請求頭我們都得到了;如果從請求頭中判斷有請求正文,則還需要繼續(xù)獲取數(shù)據(jù)
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后會按照什么方式判斷?漢字的個數(shù)?字節(jié)數(shù)?
System.out.println("響應(yīng)頭:");
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("啟動服務(wù),綁定端口: " + 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);
}
}
}
}
~~~
這是一個實(shí)現(xiàn)HTTP 1.0的服務(wù)器,對于所有的HTTP請求,會把HTTP請求頭響應(yīng)回去。 這個程序說明了web服務(wù)器處理請求的基本流程,JSP、Servlet、Spring MVC等只是在 這個基礎(chǔ)上嫁了許多方法,以讓我們更方面的編寫web應(yīng)用。web服務(wù)器不僅可以基于多線程, 也可以基于多進(jìn)程、Reactor模型等。
**測試程序:**
運(yùn)行上面的程序。我們使用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程序輸出:
~~~
啟動服務(wù),綁定端口: 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: */*
響應(yīng)頭:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106
~~~
**程序解析:**
`// 1`:定義了HTTP頭的換行符。
`// 2`:in.readLine()的結(jié)果默認(rèn)不帶換行符,這里把它加上。(這不是強(qiáng)制的,主要看你的程序邏輯需不需要, 這個程序的目標(biāo)是把HTTP請求頭響應(yīng)回去)。
`// 3`:此時s是一個空行,根據(jù)HTTP協(xié)議,整個請求頭都得到了。
`// 4`:Content-Length的值是字節(jié)的數(shù)量。
`// 5`:線程池。
`// 6`:這個循環(huán)不停監(jiān)聽socket連接,使用SocketHandler處理連入的socket,而這個處理是放在線程池中的。
**HTTP 1.1:**
HTTP 1.1也是在這個思路的基礎(chǔ)上實(shí)現(xiàn)的,即多個HTTP請求都在一個TCP連接中傳輸。對于HTTP 1.1,如何區(qū)分出每個HTTP請求很重要, 比較簡單的可以是用過`Content-Length`判斷一條請求是否結(jié)束。如果一個HTTP請求數(shù)據(jù)較多,往往采用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相關(guān)資料)HTTP相關(guān)資料
**網(wǎng)絡(luò)教程:**
[菜鳥教程-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權(quán)威指南](https://github.com/someus/another-tutorial-about-java-web/blob/master)?[計算機(jī)網(wǎng)絡(luò)](https://github.com/someus/another-tutorial-about-java-web/blob/master)?謝希仁HTTP是基于TCP協(xié)議的。TCP負(fù)責(zé)數(shù)據(jù)傳輸,而HTTP只是規(guī)范了TCP傳輸?shù)臄?shù)據(jù)的格式,而這個具體的格式,請見后面給出的資料。
HTTP服務(wù)的底層實(shí)現(xiàn)就是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 很重要,默認(rèn)情況下in.readLine的結(jié)果中`\r\n`被去掉了
requestHeader = requestHeader + s;
if (s.equals(CRLF)){ // 3 此處HTTP請求頭我們都得到了;如果從請求頭中判斷有請求正文,則還需要繼續(xù)獲取數(shù)據(jù)
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后會按照什么方式判斷?漢字的個數(shù)?字節(jié)數(shù)?
System.out.println("響應(yīng)頭:");
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("啟動服務(wù),綁定端口: " + 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);
}
}
}
}
~~~
這是一個實(shí)現(xiàn)HTTP 1.0的服務(wù)器,對于所有的HTTP請求,會把HTTP請求頭響應(yīng)回去。 這個程序說明了web服務(wù)器處理請求的基本流程,JSP、Servlet、Spring MVC等只是在 這個基礎(chǔ)上嫁了許多方法,以讓我們更方面的編寫web應(yīng)用。web服務(wù)器不僅可以基于多線程, 也可以基于多進(jìn)程、Reactor模型等。
**測試程序:**
運(yùn)行上面的程序。我們使用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程序輸出:
~~~
啟動服務(wù),綁定端口: 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: */*
響應(yīng)頭:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106
~~~
**程序解析:**
`// 1`:定義了HTTP頭的換行符。
`// 2`:in.readLine()的結(jié)果默認(rèn)不帶換行符,這里把它加上。(這不是強(qiáng)制的,主要看你的程序邏輯需不需要, 這個程序的目標(biāo)是把HTTP請求頭響應(yīng)回去)。
`// 3`:此時s是一個空行,根據(jù)HTTP協(xié)議,整個請求頭都得到了。
`// 4`:Content-Length的值是字節(jié)的數(shù)量。
`// 5`:線程池。
`// 6`:這個循環(huán)不停監(jiān)聽socket連接,使用SocketHandler處理連入的socket,而這個處理是放在線程池中的。
**HTTP 1.1:**
HTTP 1.1也是在這個思路的基礎(chǔ)上實(shí)現(xiàn)的,即多個HTTP請求都在一個TCP連接中傳輸。對于HTTP 1.1,如何區(qū)分出每個HTTP請求很重要, 比較簡單的可以是用過`Content-Length`判斷一條請求是否結(jié)束。如果一個HTTP請求數(shù)據(jù)較多,往往采用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相關(guān)資料)HTTP相關(guān)資料
**網(wǎng)絡(luò)教程:**
[菜鳥教程-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權(quán)威指南](https://github.com/someus/another-tutorial-about-java-web/blob/master)?[計算機(jī)網(wǎng)絡(luò)](https://github.com/someus/another-tutorial-about-java-web/blob/master)?謝希仁
- JSP & Servlet
- 00-00、序
- 00-01、相關(guān)軟件的安裝
- 00-02、理解HTTP
- 00-03、從JSP開始
- 00-04、理解Servlet
- 00-05、過濾器與監(jiān)聽器
- 00-06、使用velocity模板引擎
- 00-07、使用數(shù)據(jù)庫連接池
- 00-08、Tomcat的運(yùn)行機(jī)制
- Spring MVC
- 01-00、Spring與依賴注入
- 01-01、Spring與面向切面編程
- 01-02、使用Spring MVC構(gòu)建Hello World
- 01-03、JdbcTemplate
- 01-04、基于注解的URL映射
- 01-05、JSON
- 01-06、校驗(yàn)器
- 01-07、國際化
- 01-08、攔截器
- 01-09、文件上傳
- 01-10、轉(zhuǎn)換器與格式化
- Book
- Online Tutorial
- Q & A
- Learn More
- Supplement
