時(shí)間:2023-05-24 19:06:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-05-24 19:06:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)
簡(jiǎn)單實(shí)現(xiàn)一個(gè) Java Web 服務(wù)器框架: GET /index/test HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,sq;q=0.8 Connection: keep-alive Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
第一行是請(qǐng)求行,分別用空格分割了三個(gè)部分:method、url、protocol HTTP/1.1 200 Content-Type: text/html ? <h1> Hello, web Framework! </h1>
第一行是響應(yīng)行,分別用空格分割了兩個(gè)部分:protocol 和 status ServerSocket serverSocket = new ServerSocket(80);
然后呢,Web 服務(wù)器在入口處從不拒絕任何連接,我們要接收所有的 socket 請(qǐng)求:accept
方法將阻塞當(dāng)前程序,直到下一個(gè) socket 連接請(qǐng)求到來(lái),然后建立一個(gè)長(zhǎng)連接。 while (true) { Socket client = serverSocket.accept(); }
當(dāng)我們成功建立一個(gè) socket 連接,此時(shí)我們先不關(guān)注請(qǐng)求報(bào)文,而是直接對(duì)它發(fā)送一個(gè) http 響應(yīng)報(bào)文:write
向?qū)Ψ綄懸恍?shù)據(jù),flush
將強(qiáng)行寫出所有數(shù)據(jù),最后調(diào)用 close
關(guān)閉這個(gè)鏈接(http 是無(wú)狀態(tài)的,如果不主動(dòng)關(guān)閉該連接,客戶端將認(rèn)為此次請(qǐng)求并未結(jié)束)。 while (true) { Socket client = serverSocket.accept(); OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); }
import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; ? public class Main { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(80); while (true) { Socket client = serverSocket.accept(); ? OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } ?
現(xiàn)在運(yùn)行這個(gè) Java 程序,然后用瀏覽器請(qǐng)求本地的 80 端口,你將看到: Angie app = new Angie(); ? app.use("/test", (req, res) -> { res.setStatus(200) .setHeaders("Content-Type", "text/html") .send("<h1> Hello, web framework! </h1>"); }); ? app.listen(80);
倉(cāng)庫(kù) --> https://github.com/Drincann/Angie-javaAngie
,該類實(shí)例化的對(duì)象將作為一個(gè)獨(dú)立的 web 服務(wù)。use
方法將一個(gè)回調(diào)方法注冊(cè)到一個(gè)路由上,listen
方法用來(lái)開始監(jiān)端口。listen
方法,listen
用來(lái)不斷建立 socket 連接,并向客戶端(瀏覽器)發(fā)送數(shù)據(jù): public class Angie { public void listen(int port) { try { // 監(jiān)聽 port 端口 ServerSocket serverSocket = new ServerSocket(port); while (true) { // 接受連接 Socket client = serverSocket.accept(); // 創(chuàng)建新線程,發(fā)送數(shù)據(jù) new Thread(() -> { try { OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
注意這里對(duì)每個(gè) socket 請(qǐng)求都創(chuàng)建了一個(gè)新線程處理,用來(lái)提高并發(fā)性能。這時(shí)候我們就可以這樣寫:
Angie app = new Angie(); app.listen(80);
use
,注冊(cè)一個(gè)回調(diào)方法到某個(gè)路由: private final HashMap<String, Processor> routeMap = new HashMap();
然后在開發(fā)者調(diào)用 use
時(shí)記錄這個(gè)映射關(guān)系: public void use(String route, Processor processor) { routeMap.put(route, processor); }
Processor
是回調(diào)方法的函數(shù)式類型,正常來(lái)講,應(yīng)該接收一個(gè) request 參數(shù)和一個(gè) response 參數(shù)。listen
中的 while,不要再回復(fù)固定內(nèi)容了,而是向不同路由的回調(diào)方法分發(fā)消息: while (true) { // 接受連接 Socket client = serverSocket.accept(); ? // 創(chuàng)建新線程,發(fā)送數(shù)據(jù) new Thread(() -> { try { // 分發(fā)消息 Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); if (routeMap.containsKey(request.getUrl())) { routeMap.get(request.getUrl()).callback(request, response); } else { response.setStatus(404).send(request.getUrl() + " not found"); } } catch (IOException e) { e.printStackTrace(); } }).start(); }
到現(xiàn)在為止,入口類 Angie
的代碼就已經(jīng)全部完成了: package cool.gaolihai; ? import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; ? public class Angie { private final HashMap<String, Processor> routeMap = new HashMap(); ? public void use(String route, Processor processor) { routeMap.put(route, processor); } ? public void listen(int port) { try { ServerSocket serverSocket = new ServerSocket(port); while (true) { Socket client = serverSocket.accept(); new Thread(() -> { try { Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); if (routeMap.containsKey(request.getUrl())) { routeMap.get(request.getUrl()).callback(request, response); } else { response.setStatus(404).send(request.getUrl() + " not found"); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
Request
、Response
就是剛才 Processor
需要接收的參數(shù),下面我們來(lái)實(shí)現(xiàn)這些類型。Processor
非常簡(jiǎn)單,他就是一個(gè)函數(shù)式接口: package cool.gaolihai; ? @FunctionalInterface public interface Processor { void callback(Request request, Response response); }
Request
負(fù)責(zé)解析請(qǐng)求報(bào)文,在這里,我們僅解析 url、GET 請(qǐng)求的 params 和 method,我們創(chuàng)建對(duì)應(yīng)的屬性和訪問(wèn)器: private String url; private String params; private String method; ? public String getUrl() { return url; } ? public String getParams() { return params; } ? public String getMethod() { return method; }
可以看到,在入口類中,我們將 socket 的輸入流作為構(gòu)造參數(shù)進(jìn)行實(shí)例化,所以構(gòu)造函數(shù)如下:public Request(InputStream inputStream){ try { String[] requestLine = new BufferedReader(new InputStreamReader(inputStream)).readLine().split(" "); if (requestLine.length == 3 && requestLine[2].equals("HTTP/1.1")) { this.method = requestLine[0]; String fullUrl = requestLine[1]; if (fullUrl.contains("?")) { this.url = fullUrl.substring(0, fullUrl.indexOf("?")); this.params = fullUrl.substring(fullUrl.indexOf("?") + 1); } else { this.url = fullUrl; } } } catch (IOException e) { e.printStackTrace(); }}
Request
這就完成了,完整代碼如下: package cool.gaolihai; ? import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; ? public class Request { ? private String url; private String params; private String method; ? public Request(InputStream inputStream){ try { String[] requestLine = new BufferedReader(new InputStreamReader(inputStream)).readLine().split(" "); if (requestLine.length == 3 && requestLine[2].equals("HTTP/1.1")) { this.method = requestLine[0]; String fullUrl = requestLine[1]; if (fullUrl.contains("?")) { this.url = fullUrl.substring(0, fullUrl.indexOf("?")); this.params = fullUrl.substring(fullUrl.indexOf("?") + 1); } else { this.url = fullUrl; } } } catch (IOException e) { e.printStackTrace(); } } ? public String getUrl() { return url; } ? public String getParams() { return params; } ? public String getMethod() { return method; } }
Response
類中,我們需要給開發(fā)者提供設(shè)置請(qǐng)求頭、設(shè)置響應(yīng)狀態(tài)嗎和響應(yīng)數(shù)據(jù)的能力: private HashMap<String, String> headers = new HashMap<>(); public Response setHeaders(String key, String value) { this.headers.put(key, value); return this; }
對(duì)于響應(yīng)狀態(tài)碼,我們這樣實(shí)現(xiàn): private int status; public Response setStatus(int statusCode) { this.status = statusCode; return this; }
在這里,我們還要將輸出流放在內(nèi)部維護(hù): private OutputStream outputStream;
構(gòu)造函數(shù)則是直接初始化輸出流:public Response(OutputStream outputStream) { this.outputStream = outputStream;}
最后一個(gè)功能,發(fā)送數(shù)據(jù),實(shí)際上是向輸出流中寫字符串:public void send(String data) { try { StringBuilder dataBuilder = new StringBuilder(); // 拼接請(qǐng)求行 dataBuilder.append("HTTP/1.1 ").append(this.status).append("/n"); // 拼接請(qǐng)求頭 for (String key: this.headers.keySet()) { dataBuilder.append(key).append(": ").append(this.headers.get(key)).append("/n"); } // 拼接請(qǐng)求體 dataBuilder.append("/n").append(data); outputStream.write(dataBuilder.toString().getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); }}
到這里 Response
也實(shí)現(xiàn)完畢了,代碼如下: package cool.gaolihai; ? import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; ? public class Response { private OutputStream outputStream; private HashMap<String, String> headers = new HashMap<>(); private int status; ? public Response(OutputStream outputStream) { this.outputStream = outputStream; } ? public Response setHeaders(String key, String value) { this.headers.put(key, value); return this; } ? public Response setStatus(int statusCode) { this.status = statusCode; return this; } ? public void send(String data) { try { StringBuilder dataBuilder = new StringBuilder(); dataBuilder.append("HTTP/1.1 ").append(this.status).append("/n"); for (String key: this.headers.keySet()) { dataBuilder.append(key).append(": ").append(this.headers.get(key)).append("/n"); } dataBuilder.append("/n").append(data); ? outputStream.write(dataBuilder.toString().getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
Angie
,其每一個(gè)實(shí)例都是一個(gè)獨(dú)立的 Web 服務(wù)端。Processor
,用來(lái)通過(guò) use
方法接入框架的處理流程,還有與之相關(guān)的 Request
和 Response
類型,用來(lái)解析請(qǐng)求報(bào)文和提供開發(fā)者響應(yīng)客戶端的能力。 import cool.gaolihai.Angie; ? public class app { public static void main(String[] args) { Angie app = new Angie(); ? app.use("/test", (req, res) -> { res.setStatus(200) .setHeaders("Content-Type", "text/html") .send("<h1> Hello, web framework! </h1>"); }); ? app.listen(80); } }
由于筆者沒(méi)有基本的 Java 功底,對(duì)一些機(jī)制并不了解,目前這個(gè)框架偶然會(huì)玄學(xué)地在線程中拋出 NullPointerException
異常,在異常的棧軌跡中也沒(méi)有觸發(fā)任何斷點(diǎn)。關(guān)鍵詞:服務(wù),實(shí)現(xiàn),簡(jiǎn)單
客戶&案例
營(yíng)銷資訊
關(guān)于我們
客戶&案例
營(yíng)銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。