自己動手實現一個 Web Server
項目背景
最近在重溫WEB服務器的相關機制和原理,為了方便記憶和理解,就嘗試自己用Java寫一個簡化的WEB SERVER的實現,功能簡單,簡化了常規服務器的大部分功能和結構封裝,但仍然保留從瀏覽器發送請求到將處理結果返回響應到瀏覽器的整個流程,現在把相關內容分享出來,供大家參考。
項目環境
IDE : eclipse 4.6.3
JDK : JDK1.8.0_131
Maven : Maven 3.5.2
項目結構
項目比較簡單,就用一個普通的Java或Maven工程,引入JDK依賴即可。
工程下只有一個包,共包含六個文件。
WebServer : WEB 服務器主類,里面包含main方法,可直接運行啟動服務器。
Request: 請求包裝類,包含請求類型,請求URI。
Response:響應包裝類,包含輸出流,可向瀏覽器輸出響應信息。
RequstParser:請求信息解析類,解析完成后返回一個Request。
ServiceDispacher:服務派發器,這里類似於Srping的DispatcherServlete。(不屬於服務器部分)
TestController:模擬控制器返回信息。(不屬於服務器部分)
其中ServiceDispacher和TestController,不屬於服務器部分,這里為了方便測試,放在一個工程下。

實現流程
實現流程大致如下:
1 創建服務端ServerSocket, 綁定一個 端口號
2 循環監聽客戶端請求,連接成功后返回一個Socket
3 開啟一個新的線程,傳入Socket處理當前請求
4 Web Server調用ServiceDispacher進行服務的分發
5 ServiceDispacher根據請求查找並調用相應的控制器
6 控制器方法執行返回結果,並將結果相應到瀏覽器
代碼示例
下面給出完整的代碼實現,代碼注釋已經解釋的比較清楚了,在這里就不再多費口舌了,快來源碼見。
1 WebServer.java
package com.louis.web.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * Web Server * @author Louis */ public class WebServer { /** * 服務器啟動端口 */ private int port = 8888; /** * 服務端Socket */ private ServerSocket serverSocket; public WebServer() { init(); } /** * 初始化服務端Socket */ private void init() { try { // 創建服務端Socket serverSocket = new ServerSocket(port); System.out.println("服務端已啟動,等待客戶端連接.."); } catch (IOException e) { e.printStackTrace(); } } /** * 啟動服務器,監聽並處理客戶請求 * @throws IOException */ public void start() throws IOException { while (true) { // 偵聽並接受客戶請求 Socket socket = serverSocket.accept(); // 新啟線程,處理客戶請求 new Thread() { @Override public void run() { service(socket); } }.start(); } } /** * 處理客戶請求 * @param socket */ private void service(Socket socket) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); // 讀取請求信息內容 Request request = new RequestParser().parse(inputStream); Response response = new Response(outputStream); service(request, response); } catch (IOException e) { e.printStackTrace(); } finally { // 關閉連接 if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } System.out.println("接收到客戶端連接, " + socket.getInetAddress() + ":" + socket.getPort()); } /** * 處理客戶請求, 把請求交給框架派遣服務,類似Spring的DispatcherServlet * @param request * @param response */ private void service(Request request, Response response) { ServiceDispatcher serviceDispatcher = new ServiceDispatcher(); serviceDispatcher.dispatcher(request, response); } public static void main(String[] args) { try { new WebServer().start(); } catch (IOException e) { e.printStackTrace(); } } }
2 Request.java
package com.louis.web.server; /** * Request * @author Louis */ public class Request { /** * 請求方式: GET\POST\DELETE.. */ private String type; /** * 請求URI */ private String uri; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } }
3 Response.java
package com.louis.web.server; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; /** * Response * * @author Louis */ public class Response { private OutputStream output; public Response(OutputStream output) { this.output = output; } /** * 輸出文本信息 * @param text * @throws IOException */ public void writeText(String text) { FileInputStream fis = null; try { output.write("HTTP/1.1 200 OK\n".getBytes()); output.write("Content-Type: text/html; charset=UTF-8\n\n".getBytes()); output.write(text.getBytes()); } catch (Exception e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
4 RequestParser.java
package com.louis.web.server; import java.io.InputStream; /** * Request Parser * @author Louis */ public class RequestParser { private final static int BUFFER_SIZE = 1024; /** * 解析請求 * @param inputStream * @return Request */ public Request parse(InputStream inputStream) { Request request = new Request(); // 讀取請求信息 String requestMessage = readRequestMessage(inputStream); // 解析請求方式 String type = parseType(requestMessage); request.setType(type); // 解析請求類型 String uri = parseUri(requestMessage); request.setUri(uri); return request; } /** * 讀取請求信息 * @param input * @return */ private String readRequestMessage(InputStream input) { StringBuffer requestMessage = new StringBuffer(); int readLength = 0; byte[] buffer = new byte[BUFFER_SIZE]; try { readLength = input.read(buffer); } catch (Exception e) { e.printStackTrace(); readLength = -1; } for(int i = 0; i < readLength; i++) { requestMessage.append((char) buffer[i]); } return requestMessage.toString(); } /** * 解析請求方式 * @param requestString * @return */ private String parseType(String requestString) { int index = 0; index = requestString.indexOf(' '); if (index != -1) { return requestString.substring(0, index); } return null; } /** * 解析請求類型 * @param requestString * @return */ private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; } }
5 ServiceDispatcher.java
package com.louis.web.server; /** * 根據請求類型和URI找到對應的控制器,將請求交給控制器處理 * * @author Louis */ public class ServiceDispatcher { /** * 轉發處理請求 * @param request * @param response */ public void dispatcher(Request request, Response response) { execController(request, response); } /** * 根據請求類型及URI等請求信息,找到並執行對應的控制器方法后返回 * 此處直接返回一個控制器,模擬查找和執行控制器方法的過程 * @param request * @param response * @return */ private void execController(Request request, Response response) { String text = getControllerResult(request, response); StringBuilder sb = new StringBuilder(); sb.append("請求類型: " + request.getType()); sb.append("<br/>請求URI: " + request.getUri()); sb.append("<br/>返回結果: " + text); // 輸出控制器返回結果 response.writeText(sb.toString()); } /** * 模擬查找和執行控制器方法並返回結果 * @param request * @param response * @return */ private String getControllerResult(Request request, Response response) { String text = ""; String uri = request.getUri(); String [] uriArray = uri.split("\\/"); if(uriArray.length != 3) { text = "請求路徑沒有找到相關匹配服務. "; } else if("test".equalsIgnoreCase(uriArray[1])) { TestController testController = new TestController(); if("test1".equalsIgnoreCase(uriArray[2])) { text = testController.test1(); } else if("test2".equalsIgnoreCase(uriArray[2])) { text = testController.test2(); } else { text = "請求路徑沒有找到相關匹配服務. "; } } else { text = "請求路徑沒有找到相關匹配服務. "; } return text; } }
6 TestController.java
package com.louis.web.server; public class TestController { public String test1() { return "TestController.test1() 調用成功"; } public String test2() { return "TestController.test2() 調用成功"; } }
啟動測試
直接運行WebServer的main方法即可,控制台輸出“服務端已啟動,等待客戶端連接..”, 啟動成功。

啟動完成之后,瀏覽器訪問分別訪問不同路徑,查看響應結果。
如下圖所示:
http://localhost:8888/test/test1

http://localhost:8888/test/test2

http://localhost:8888/test/test3

http://localhost:8888/test/mack

我們看到,當調用 /test/test1 和 /test/test2 的時候,控制器 TestController 的 test1 和 test2 方法相應被調用成功。而輸入 test3 獲取其他不存在的服務的時候,將會得到“請求路徑沒有找到相關匹配服務”的響應。
源碼下載
碼雲:https://gitee.com/liuge1988/web-server
作者:朝雨憶輕塵
出處:https://www.cnblogs.com/xifengxiaoma/
版權所有,歡迎轉載,轉載請注明原文作者及出處。
