Java模擬實(shí)現(xiàn)HTTP服務(wù)器項(xiàng)目實(shí)戰(zhàn)
一,HTTP 協(xié)議的工作過程

二、HTTP協(xié)議格式
1,抓包分析搜狗主頁
HTTP請求

首行: [方法] + [url] + [版本]
Header: 請求的屬性, 冒號(hào)分割的鍵值對;每組屬性之間使用\n分隔;遇到空行表示Header部 分結(jié)束 Body: 空行后面的內(nèi)容都是Body.
Body允許為空字符串. 如果Body存在, 則在Header中會(huì)有 一個(gè)Content-Length屬性來標(biāo)識(shí)Body的長度
HTTP響應(yīng)

首行: [版本號(hào)] + [狀態(tài)碼] + [狀態(tài)碼解釋]
Header: 請求的屬性, 冒號(hào)分割的鍵值對;每組屬性之間使用\n分隔;遇到空行表示Header部 分結(jié)束
Body: 空行后面的內(nèi)容都是Body. Body允許為空字符串. 如果Body存在, 則在Header中會(huì)有 一個(gè)Content-Length屬性來標(biāo)識(shí)Body的長度; 如果服務(wù)器返回了一個(gè)html頁面, 那么html頁 面內(nèi)容就是在body中
2,協(xié)議格式總結(jié)

三、版本V1
實(shí)現(xiàn)一個(gè)最簡單的 HTTP 服務(wù)器.
在這個(gè)版本中, 我們只是簡單解析 GET 請求, 并根據(jù)請求的路徑來構(gòu)造出不同的響應(yīng).
路徑為 /200, 返回一個(gè) "歡迎頁面".
路徑為 /404, 返回一個(gè) "沒有找到" 的頁面.
路徑為 /302, 重定向到其他的頁面
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV1 {
// HTTP 底層要基于 TCP 來實(shí)現(xiàn). 需要按照 TCP 的基本格式來先進(jìn)行開發(fā).
private ServerSocket serverSocket = null;
public HttpServerV1(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
// 1. 獲取連接
Socket clientSocket = serverSocket.accept();
// 2. 處理連接(使用短連接的方式實(shí)現(xiàn))
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
private void process(Socket clientSocket) {
// 由于 HTTP 協(xié)議是文本協(xié)議, 所以仍然使用字符流來處理.
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
// 下面的操作都要嚴(yán)格按照 HTTP 協(xié)議來進(jìn)行操作.
// 1. 讀取請求并解析
// a) 解析首行, 三個(gè)部分使用空格切分
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");
String method = firstLineTokens[0];
String url = firstLineTokens[1];
String version = firstLineTokens[2];
// b) 解析 header, 按行讀取, 然后按照冒號(hào)空格來分割鍵值對
Map<String, String> headers = new HashMap<>();
String line = "";
// readLine 讀取的一行內(nèi)容, 是會(huì)自動(dòng)去掉換行符的. 對于空行來說, 去掉了換行符, 就變成空字符串
while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
// 不能使用 : 來切分. 像 referer 字段, 里面的內(nèi)容是可能包含 : .
String[] headerTokens = line.split(": ");
headers.put(headerTokens[0], headerTokens[1]);
}
// c) 解析 body (暫時(shí)先不考慮)
// 請求解析完畢, 加上一個(gè)日志, 觀察請求的內(nèi)容是否正確.
System.out.printf("%s %s %s\n", method, url, version);
for (Map.Entry<String, String> entry : headers.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
System.out.println();
// 2. 根據(jù)請求計(jì)算響應(yīng)
// 不管是啥樣的請求, 咱們都返回一個(gè) hello 這樣的 html
String resp = "";
if (url.equals("/200")) {
bufferedWriter.write(version + " 200 OK\n");
resp = "<h1>hello</h1>";
} else if (url.equals("/404")) {
bufferedWriter.write(version + " 404 Not Found\n");
resp = "<h1>not found</h1>";
} else if (url.equals("/302")) {
bufferedWriter.write(version + " 303 Found\n");
bufferedWriter.write("Location: http://www.sogou.com\n");
resp = "";
} else {
bufferedWriter.write(version + " 200 OK\n");
resp = "<h1>default</h1>";
}
// 3. 把響應(yīng)寫回到客戶端
bufferedWriter.write("Content-Type: text/html\n");
bufferedWriter.write("Content-Length: " + resp.getBytes().length + "\n"); // 此處的長度, 不能寫成 resp.length(), 得到的是字符的數(shù)目, 而不是字節(jié)的數(shù)目
bufferedWriter.write("\n");
bufferedWriter.write(resp);
// 此處這個(gè) flush 就算沒有, 問題也不大. 緊接著
// bufferedWriter 對象就要被關(guān)閉了. close 時(shí)就會(huì)自動(dòng)觸發(fā)刷新操作.
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
HttpServerV1 server = new HttpServerV1(9090);
server.start();
}
}



四、版本V2
在這個(gè)版本中, 我們只是簡單解析 GET 請求, 并根據(jù)請求的路徑來構(gòu)造出不同的響應(yīng).
在版本1 的基礎(chǔ)上, 我們做出一些改進(jìn):
把解析請求和構(gòu)造響應(yīng)的代碼提取成單獨(dú)的類.
能夠把 URL 中的 query string 解析成鍵值對.
能夠給瀏覽器返回 Cookie.
1,創(chuàng)建 HttpRequest 類
對照著 HTTP 請求的格式, 創(chuàng)建屬性: method, url, version, headers.
創(chuàng)建 patameters, 用于存放 query string 的解析結(jié)果.
創(chuàng)建一個(gè)靜態(tài)方法 build, 用來完成解析 HTTP 請求的過程.
從 socket 中讀取數(shù)據(jù)的時(shí)候注意設(shè)置字符編碼方式
創(chuàng)建一系列 getter 方法獲取到請求中的屬性.
單獨(dú)寫一個(gè)方法 parseKV 用來解析 query string
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
// 表示一個(gè) HTTP 請求, 并負(fù)責(zé)解析.
public class HttpRequest {
private String method;
// /index.html?a=10&b=20
private String url;
private String version;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> parameters = new HashMap<>();
// 請求的構(gòu)造邏輯, 也使用工廠模式來構(gòu)造.
// 此處的參數(shù), 就是從 socket 中獲取到的 InputStream 對象
// 這個(gè)過程本質(zhì)上就是在 "反序列化"
public static HttpRequest build(InputStream inputStream) throws IOException {
HttpRequest request = new HttpRequest();
// 此處的邏輯中, 不能把 bufferedReader 寫到 try ( ) 中.
// 一旦寫進(jìn)去之后意味著 bufferReader 就會(huì)被關(guān)閉, 會(huì)影響到 clientSocket 的狀態(tài).
// 等到最后整個(gè)請求處理完了, 再統(tǒng)一關(guān)閉
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 此處的 build 的過程就是解析請求的過程.
// 1. 解析首行
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");
request.method = firstLineTokens[0];
request.url = firstLineTokens[1];
request.version = firstLineTokens[2];
// 2. 解析 url 中的參數(shù)
int pos = request.url.indexOf("?");
if (pos != -1) {
// 看看 url 中是否有 ? . 如果沒有, 就說明不帶參數(shù), 也就不必解析了
// 此處的 parameters 是希望包含整個(gè) 參數(shù) 部分的內(nèi)容
// pos 表示 ? 的下標(biāo)
// /index.html?a=10&b=20
// parameters 的結(jié)果就相當(dāng)于是 a=10&b=20
String parameters = request.url.substring(pos + 1);
// 切分的最終結(jié)果, key a, value 10; key b, value 20;
parseKV(parameters, request.parameters);
}
// 3. 解析 header
String line = "";
while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
String[] headerTokens = line.split(": ");
request.headers.put(headerTokens[0], headerTokens[1]);
}
// 4. 解析 body (暫時(shí)先不考慮)
return request;
}
private static void parseKV(String input, Map<String, String> output) {
// 1. 先按照 & 切分成若干組鍵值對
String[] kvTokens = input.split("&");
// 2. 針對切分結(jié)果再分別進(jìn)行按照 = 切分, 就得到了鍵和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
output.put(result[0], result[1]);
}
}
// 給這個(gè)類構(gòu)造一些 getter 方法. (不要搞 setter).
// 請求對象的內(nèi)容應(yīng)該是從網(wǎng)絡(luò)上解析來的. 用戶不應(yīng)該修改.
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
// 此處的 getter 手動(dòng)寫, 自動(dòng)生成的版本是直接得到整個(gè) hash 表.
// 而我們需要的是根據(jù) key 來獲取值.
public String getHeader(String key) {
return headers.get(key);
}
public String getParameter(String key) {
return parameters.get(key);
}
@Override
public String toString() {
return "HttpRequest{" +
"method='" + method + '\'' +
", url='" + url + '\'' +
", version='" + version + '\'' +
", headers=" + headers +
", parameters=" + parameters +
'}';
}
}2,創(chuàng)建 HttpResponse 類
根據(jù) HTTP 響應(yīng), 創(chuàng)建屬性: version, status, message, headers, body
另外創(chuàng)建一個(gè) OutputStream, 用來關(guān)聯(lián)到 Socket 的 OutputStream.
往 socket 中寫入數(shù)據(jù)的時(shí)候注意指定字符編碼方式.
創(chuàng)建一個(gè)靜態(tài)方法 build, 用來構(gòu)造 HttpResponse 對象.
創(chuàng)建一系列 setter 方法, 用來設(shè)置 HttpResponse 的屬性.
創(chuàng)建一個(gè) flush 方法, 用于最終把數(shù)據(jù)寫入 OutputStream
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
// 表示一個(gè) HTTP 響應(yīng), 負(fù)責(zé)構(gòu)造
// 進(jìn)行序列化操作
public class HttpResponse {
private String version = "HTTP/1.1";
private int status; // 狀態(tài)碼
private String message; // 狀態(tài)碼的描述信息
private Map<String, String> headers = new HashMap<>();
private StringBuilder body = new StringBuilder(); // 方便一會(huì)進(jìn)行拼接.
// 當(dāng)代碼需要把響應(yīng)寫回給客戶端的時(shí)候, 就往這個(gè) OutputStream 中寫就好了.
private OutputStream outputStream = null;
public static HttpResponse build(OutputStream outputStream) {
HttpResponse response = new HttpResponse();
response.outputStream = outputStream;
// 除了 outputStream 之外, 其他的屬性的內(nèi)容, 暫時(shí)都無法確定. 要根據(jù)代碼的具體業(yè)務(wù)邏輯
// 來確定. (服務(wù)器的 "根據(jù)請求并計(jì)算響應(yīng)" 階段來進(jìn)行設(shè)置的)
return response;
}
public void setVersion(String version) {
this.version = version;
}
public void setStatus(int status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
public void setHeader(String key, String value) {
headers.put(key, value);
}
public void writeBody(String content) {
body.append(content);
}
// 以上的設(shè)置屬性的操作都是在內(nèi)存中倒騰.
// 還需要一個(gè)專門的方法, 把這些屬性 按照 HTTP 協(xié)議 都寫到 socket 中.
public void flush() throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write(version + " " + status + " " + message + "\n");
headers.put("Content-Length", body.toString().getBytes().length + "");
for (Map.Entry<String, String> entry : headers.entrySet()) {
bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
}
bufferedWriter.write("\n");
bufferedWriter.write(body.toString());
bufferedWriter.flush();
}
}3,創(chuàng)建 HttpServer 類
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV2 {
private ServerSocket serverSocket = null;
public HttpServerV2(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
public void process(Socket clientSocket) {
try {
// 1. 讀取并解析請求
HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
System.out.println("request: " + request);
HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
response.setHeader("Content-Type", "text/html");
// 2. 根據(jù)請求計(jì)算響應(yīng)
if (request.getUrl().startsWith("/200")) {
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>hello</h1>");
} else if (request.getUrl().startsWith("/add")) {
// 這個(gè)邏輯要根據(jù)參數(shù)的內(nèi)容進(jìn)行計(jì)算
// 先獲取到 a 和 b 兩個(gè)參數(shù)的值
String aStr = request.getParameter("a");
String bStr = request.getParameter("b");
// System.out.println("a: " + aStr + ", b: " + bStr);
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
int result = a + b;
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1> result = " + result + "</h1>");
} else if (request.getUrl().startsWith("/cookieUser")) {
response.setStatus(200);
response.setMessage("OK");
// HTTP 的 header 中允許有多個(gè) Set-Cookie 字段. 但是
// 此處 response 中使用 HashMap 來表示 header 的. 此時(shí)相同的 key 就覆蓋
response.setHeader("Set-Cookie", "user=zhangsan");
response.writeBody("<h1>set cookieUser</h1>");
} else if (request.getUrl().startsWith("/cookieTime")) {
response.setStatus(200);
response.setMessage("OK");
// HTTP 的 header 中允許有多個(gè) Set-Cookie 字段. 但是
// 此處 response 中使用 HashMap 來表示 header 的. 此時(shí)相同的 key 就覆蓋
response.setHeader("Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
response.writeBody("<h1>set cookieTime</h1>");
} else {
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>default</h1>");
}
// 3. 把響應(yīng)寫回到客戶端
response.flush();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
} finally {
try {
// 這個(gè)操作會(huì)同時(shí)關(guān)閉 getInputStream 和 getOutputStream 對象
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
HttpServerV2 server = new HttpServerV2(9090);
server.start();
}
}



強(qiáng)化理解Cookie
Cookie就是一個(gè)字符串(里面的內(nèi)容由程序員自己決定)
Cookie從服務(wù)器來,服務(wù)器會(huì)在header中引入一個(gè)Set-Cookie字段,對應(yīng)的值就會(huì)保存在瀏覽器中
Cookie按照域名/地址來存,每個(gè)域名/地址有自己的Cookie
Cookei在后續(xù)訪問相同的域名/地址的請求,就會(huì)自動(dòng)帶上Cookie,服務(wù)器感知到這個(gè)Cookie之后就可以在服務(wù)器端進(jìn)行一些邏輯處理

五、版本V3
在版本 2 的基礎(chǔ)上, 再做出進(jìn)一步的改進(jìn).
解析請求中的 Cookie, 解析成鍵值對
解析請求中的 body, 按照 x-www-form-urlencoded 的方式解析.
根據(jù)請求方法, 分別調(diào)用 doGet / doPost
能夠返回指定的靜態(tài)頁面. 實(shí)現(xiàn)簡單的會(huì)話機(jī)制.
1. 創(chuàng)建 HttpRequest 類
屬性中新增了 cookies 和 body
新增一個(gè)方法 parseCookie, 在解析 header 完成后解析
cookie 新增了解析 body 的流程.
import javax.print.attribute.standard.RequestingUserName;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
public class HttpRequest {
private String method;
private String url;
private String version;
private Map<String, String> headers = new HashMap<>();
// url 中的參數(shù)和 body 中的參數(shù)都放到這個(gè) parameters hash 表中.
private Map<String, String> parameters = new HashMap<>();
private Map<String, String> cookies = new HashMap<>();
private String body;
public static HttpRequest build(InputStream inputStream) throws IOException {
HttpRequest request = new HttpRequest();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 1. 處理首行
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");
request.method = firstLineTokens[0];
request.url = firstLineTokens[1];
request.version = firstLineTokens[2];
// 2. 解析 url
int pos = request.url.indexOf("?");
if (pos != -1) {
String queryString = request.url.substring(pos + 1);
parseKV(queryString, request.parameters);
}
// 3. 循環(huán)處理 header 部分
String line = "";
while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
String[] headerTokens = line.split(": ");
request.headers.put(headerTokens[0], headerTokens[1]);
}
// 4. 解析 cookie
String cookie = request.headers.get("Cookie");
if (cookie != null) {
// 把 cookie 進(jìn)行解析
parseCookie(cookie, request.cookies);
}
// 5. 解析 body
if ("POST".equalsIgnoreCase(request.method)
|| "PUT".equalsIgnoreCase(request.method)) {
// 這兩個(gè)方法需要處理 body, 其他方法暫時(shí)不考慮
// 需要把 body 讀取出來.
// 需要先知道 body 的長度. Content-Length 就是干這個(gè)的.
// 此處的長度單位是 "字節(jié)"
int contentLength = Integer.parseInt(request.headers.get("Content-Length"));
// 注意體會(huì)此處的含義~~
// 例如 contentLength 為 100 , body 中有 100 個(gè)字節(jié).
// 下面創(chuàng)建的緩沖區(qū)長度是 100 個(gè) char (相當(dāng)于是 200 個(gè)字節(jié))
// 緩沖區(qū)不怕長. 就怕不夠用. 這樣創(chuàng)建的緩沖區(qū)才能保證長度管夠~~
char[] buffer = new char[contentLength];
int len = bufferedReader.read(buffer);
request.body = new String(buffer, 0, len);
// body 中的格式形如: username=tanglaoshi&password=123
parseKV(request.body, request.parameters);
}
return request;
}
private static void parseCookie(String cookie, Map<String, String> cookies) {
// 1. 按照 分號(hào)空格 拆分成多個(gè)鍵值對
String[] kvTokens = cookie.split("; ");
// 2. 按照 = 拆分每個(gè)鍵和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
cookies.put(result[0], result[1]);
}
}
private static void parseKV(String queryString, Map<String, String> parameters) {
// 1. 按照 & 拆分成多個(gè)鍵值對
String[] kvTokens = queryString.split("&");
// 2. 按照 = 拆分每個(gè)鍵和值
for (String kv : kvTokens) {
String[] result = kv.split("=");
parameters.put(result[0], result[1]);
}
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public String getBody() {
return body;
}
public String getParameter(String key) {
return parameters.get(key);
}
public String getHeader(String key) {
return headers.get(key);
}
public String getCookie(String key) {
return cookies.get(key);
}
}2,創(chuàng)建 HttpResponse 類
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
public class HttpResponse {
private String version = "HTTP/1.1";
private int status;
private String message;
private Map<String, String> headers = new HashMap<>();
private StringBuilder body = new StringBuilder();
private OutputStream outputStream = null;
public static HttpResponse build(OutputStream outputStream) {
HttpResponse response = new HttpResponse();
response.outputStream = outputStream;
return response;
}
public void setVersion(String version) {
this.version = version;
}
public void setStatus(int status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
public void setHeader(String key, String value) {
headers.put(key, value);
}
public void writeBody(String content) {
body.append(content);
}
public void flush() throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write(version + " " + status + " " + message + "\n");
headers.put("Content-Length", body.toString().getBytes().length + "");
for (Map.Entry<String, String> entry : headers.entrySet()) {
bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
}
bufferedWriter.write("\n");
bufferedWriter.write(body.toString());
bufferedWriter.flush();
}
}3,創(chuàng)建 HttpServer 類
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV3 {
static class User {
// 保存用戶的相關(guān)信息
public String userName;
public int age;
public String school;
}
private ServerSocket serverSocket = null;
// session 會(huì)話. 指的就是同一個(gè)用戶的一組訪問服務(wù)器的操作, 歸類到一起, 就是一個(gè)會(huì)話.
// 記者來采訪你, 記者問的問題就是一個(gè)請求, 你回答的內(nèi)容, 就是一個(gè)響應(yīng). 一次采訪過程中
// 涉及到很多問題和回答(請求和響應(yīng)), 這一組問題和回答, 就可以稱為是一個(gè) "會(huì)話" (整個(gè)采訪的過程)
// sessions 中就包含很多會(huì)話. (每個(gè)鍵值對就是一個(gè)會(huì)話)
private HashMap<String, User> sessions = new HashMap<>();
public HttpServerV3(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
public void process(Socket clientSocket) {
// 處理核心邏輯
try {
// 1. 讀取請求并解析
HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
// 2. 根據(jù)請求計(jì)算響應(yīng)
// 此處按照不同的 HTTP 方法, 拆分成多個(gè)不同的邏輯
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request, response);
} else {
// 其他方法, 返回一個(gè) 405 這樣的狀態(tài)碼
response.setStatus(405);
response.setMessage("Method Not Allowed");
}
// 3. 把響應(yīng)寫回到客戶端
response.flush();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void doGet(HttpRequest request, HttpResponse response) throws IOException {
// 1. 能夠支持返回一個(gè) html 文件.
if (request.getUrl().startsWith("/index.html")) {
String sessionId = request.getCookie("sessionId");
User user = sessions.get(sessionId);
if (sessionId == null || user == null) {
// 說明當(dāng)前用戶尚未登陸, 就返回一個(gè)登陸頁面即可.
// 這種情況下, 就讓代碼讀取一個(gè) index.html 這樣的文件.
// 要想讀文件, 需要先知道文件路徑. 而現(xiàn)在只知道一個(gè) 文件名 index.html
// 此時(shí)這個(gè) html 文件所屬的路徑, 可以自己來約定(約定某個(gè) d:/...) 專門放 html .
// 把文件內(nèi)容寫入到響應(yīng)的 body 中
response.setStatus(200);
response.setMessage("OK");
response.setHeader("Content-Type", "text/html; charset=utf-8");
InputStream inputStream = HttpServerV3.class.getClassLoader().getResourceAsStream("index.html");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 按行讀取內(nèi)容, 把數(shù)據(jù)寫入到 response 中
String line = null;
while ((line = bufferedReader.readLine()) != null) {
response.writeBody(line + "\n");
}
bufferedReader.close();
} else {
// 用戶已經(jīng)登陸, 無需再登陸了.
response.setStatus(200);
response.setMessage("OK");
response.setHeader("Content-Type", "text/html; charset=utf-8");
response.writeBody("<html>");
response.writeBody("<div>" + "您已經(jīng)登陸了! 無需再次登陸! 用戶名: " + user.userName + "</div>");
response.writeBody(+ user.age + "</div>");
response.writeBody("<div>" + user.school + "</div>");
response.writeBody("</html>");
}
}
}
private void doPost(HttpRequest request, HttpResponse response) {
// 2. 實(shí)現(xiàn) /login 的處理
if (request.getUrl().startsWith("/login")) {
// 讀取用戶提交的用戶名和密碼
String userName = request.getParameter("username");
String password = request.getParameter("password");
// System.out.println("userName: " + userName);
// System.out.println("password: " + password);
// 登陸邏輯就需要驗(yàn)證用戶名密碼是否正確.
// 此處為了簡單, 咱們把用戶名和密碼在代碼中寫死了.
// 更科學(xué)的處理方式, 應(yīng)該是從數(shù)據(jù)庫中讀取用戶名對應(yīng)的密碼, 校驗(yàn)密碼是否一致.
if ("zhangsan".equals(userName) && "123".equals(password)) {
// 登陸成功
response.setStatus(200);
response.setMessage("OK");
response.setHeader("Content-Type", "text/html; charset=utf-8");
// 原來登陸成功, 是給瀏覽器寫了一個(gè) cookie, cookie 中保存的是用戶的用戶名.
// response.setHeader("Set-Cookie", "userName=" + userName);
// 現(xiàn)有的對于登陸成功的處理. 給這次登陸的用戶分配了一個(gè) session
// (在 hash 中新增了一個(gè)鍵值對), key 是隨機(jī)生成的. value 就是用戶的身份信息
// 身份信息保存在服務(wù)器中, 此時(shí)也就不再有泄露的問題了
// 給瀏覽器返回的 Cookie 中只需要包含 sessionId 即可
String sessionId = UUID.randomUUID().toString();
User user = new User();
user.userName = "zhangsan";
user.age = 20;
user.school = "北京大學(xué)";
sessions.put(sessionId, user);
response.setHeader("Set-Cookie", "sessionId=" + sessionId);
response.writeBody("<html>");
response.writeBody("<div>歡迎您! " + userName + "</div>");
response.writeBody("</html>");
} else {
// 登陸失敗
response.setStatus(403);
response.setMessage("Forbidden");
response.setHeader("Content-Type", "text/html; charset=utf-8");
response.writeBody("<html>");
response.writeBody("<div>登陸失敗</div>");
response.writeBody("</html>");
}
}
}
public static void main(String[] args) throws IOException {
HttpServerV3 serverV3 = new HttpServerV3(9090);
serverV3.start();
}
}4,insex.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登錄頁面</title>
</head>
<body>
<form method="post" action="/login">
<div style="margin-bottom: 5px">
<input type="text" name="username" placeholder="請輸入名字">
</div>
<div style="margin-bottom: 5px">
<input type="password" name="password" placeholder="請輸入密碼">
</div>
<div>
<input type="submit" value="登錄">
</div>
</form>
</body>
</html>



請求中沒有Cookie



響應(yīng)中帶有Cookie字段,此時(shí)瀏覽器就會(huì)帶有Cookie
到此這篇關(guān)于Java模擬實(shí)現(xiàn)HTTP服務(wù)器項(xiàng)目實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Java HTTP服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中HTTP請求不通的原因級(jí)解決方法
HTTP 請求是指從客戶端到服務(wù)器的請求消息,對于一個(gè) Spring Boot 項(xiàng)目而言,服務(wù)器就是 Spring Boot,客戶端就是用戶本地的瀏覽器,但是會(huì)遇到SpringBoot HTTP請求不通的問題,本文介紹了一些常見問題及解決方法,需要的朋友可以參考下2025-02-02
java 隨機(jī)生成6位短信驗(yàn)證碼實(shí)例代碼
這篇文章主要介紹了java 隨機(jī)生成6位短信驗(yàn)證碼的實(shí)例代碼,文中給大家擴(kuò)展介紹了java隨機(jī)生成四位數(shù)字驗(yàn)證碼的方法,需要的朋友可以參考下2019-12-12
Java對MySQL數(shù)據(jù)庫進(jìn)行連接、查詢和修改操作方法
這篇文章主要介紹了Java對MySQL數(shù)據(jù)庫進(jìn)行連接、查詢和修改操作方法,需要的朋友可以參考下2017-07-07
JMagick實(shí)現(xiàn)基本圖像處理的類實(shí)例
這篇文章主要介紹了JMagick實(shí)現(xiàn)基本圖像處理的類,實(shí)例分析了java圖像處理的相關(guān)技巧,需要的朋友可以參考下2015-06-06
Java讀取Excel文件內(nèi)容的簡單實(shí)例
這篇文章主要介紹了Java讀取Excel文件內(nèi)容的簡單實(shí)例,有需要的朋友可以參考一下2013-11-11

