超文本傳輸協(xié)議(Hyper Text Transfer Protocol)。
規(guī)范了瀏覽器和服務(wù)器交互的數(shù)據(jù)格式。
請(qǐng)求格式的結(jié)構(gòu): 請(qǐng)求頭:請(qǐng)求方式、 請(qǐng)求的地址和 HTTP 協(xié)議版本 請(qǐng)求行: 消息報(bào)頭, 一般用來(lái)說(shuō)明客戶(hù)端要使用的一些附加信息 空行:位于請(qǐng)求行和請(qǐng)求數(shù)據(jù)之間, 空行是必須的。 請(qǐng)求數(shù)據(jù): 非必須。
響應(yīng)行(狀態(tài)行): HTTP 版本、 狀態(tài)碼、 狀態(tài)消息 響應(yīng)頭: 消息報(bào)頭, 客戶(hù)端使用的附加信息 空行: 響應(yīng)頭和響應(yīng)實(shí)體之間的, 必須的。 響應(yīng)實(shí)體: 正文, 服務(wù)器返回給瀏覽器的信息
下面基于socket編寫(xiě)一個(gè)簡(jiǎn)單的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請(qǐng)求頭我們都得到了;如果從請(qǐng)求頭中判斷有請(qǐng)求正文,則還需要繼續(xù)獲取數(shù)據(jù)
break;
}
}
System.out.println("客戶(hù)端請(qǐng)求頭:");
System.out.println(requestHeader);
String responseBody = "客戶(hù)端的請(qǐng)求頭是:\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 問(wèn)題來(lái)了:1、瀏覽器如何探測(cè)編碼 2、瀏覽器受到content-length后會(huì)按照什么方式判斷?漢字的個(gè)數(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("啟動(dòng)服務(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);
}
}
}
}
這是一個(gè)實(shí)現(xiàn) HTTP 1.0 的服務(wù)器,對(duì)于所有的 HTTP 請(qǐng)求,會(huì)把 HTTP 請(qǐng)求頭響應(yīng)回去。 這個(gè)程序說(shuō)明了web服務(wù)器處理請(qǐng)求的基本流程,JSP、Servlet、Spring MVC 等只是在 這個(gè)基礎(chǔ)上嫁了許多方法,以讓我們更方面的編寫(xiě) web 應(yīng)用。web 服務(wù)器不僅可以基于多線程, 也可以基于多進(jìn)程、Reactor模型等。
測(cè)試程序:
運(yùn)行上面的程序。我們使用 curl 訪問(wèn)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
客戶(hù)端的請(qǐng)求頭是:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*
Java 程序輸出:
啟動(dòng)服務(wù),綁定端口: 8000
新的連接/127.0.0.1:36463
新的連接/127.0.0.1:36463客戶(hù)端請(qǐng)求頭:
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)制的,主要看你的程序邏輯需不需要,
這個(gè)程序的目標(biāo)是把 HTTP 請(qǐng)求頭響應(yīng)回去)。
// 3
:此時(shí)s是一個(gè)空行,根據(jù) HTTP 協(xié)議,整個(gè)請(qǐng)求頭都得到了。
// 4
:Content-Length 的值是字節(jié)的數(shù)量。
// 5
:線程池。
// 6
:這個(gè)循環(huán)不停監(jiān)聽(tīng)socket連接,使用 SocketHandler 處理連入的 socket,而這個(gè)處理是放在線程池中的。
HTTP 1.1:
HTTP 1.1 也是在這個(gè)思路的基礎(chǔ)上實(shí)現(xiàn)的,即多個(gè) HTTP 請(qǐng)求都在一個(gè) TCP 連接中傳輸。對(duì)于 HTTP 1.1,如何區(qū)分出每個(gè)HTTP請(qǐng)求很重要,
比較簡(jiǎn)單的可以是用過(guò)Content-Length
判斷一條請(qǐng)求是否結(jié)束。如果一個(gè) HTTP 請(qǐng)求數(shù)據(jù)較多,往往采用 Chunked 方式,
可以參考 Chunked transfer encoding。
更多建議: