最近非" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷資訊 > 網(wǎng)站運(yùn)營(yíng) > 簡(jiǎn)單實(shí)現(xiàn)一個(gè) Java Web 服務(wù)器框架

簡(jiǎn)單實(shí)現(xiàn)一個(gè) Java Web 服務(wù)器框架

時(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ù)器框架:

Web 服務(wù)器原理

最近剛剛?cè)腴T Java Web,僅使用過(guò) Node.js 進(jìn)行后端開發(fā),所以對(duì) Java EE 的整體設(shè)計(jì)非常模糊,很可能會(huì)想當(dāng)然地用錯(cuò)名詞、或者張冠李戴,麻煩能在評(píng)論區(qū)指出,感謝。

最近非??鄲烙?tomcat 這個(gè)黑箱子,在這里記錄一下我的認(rèn)識(shí)。

Tomcat

我們經(jīng)常將 tomcat 稱為 服務(wù)器,然而這讓我迷惑了很久,這個(gè)說(shuō)法挺通俗,就是有點(diǎn)難懂 (

我認(rèn)為 tomcat 所作的工作比較接近 Web 框架 這個(gè)概念。只不過(guò),在 Java EE 的生態(tài)中,Servlet 才應(yīng)該被稱為 框架,而 tomcat,我們可以認(rèn)為它是 框架的實(shí)現(xiàn)。

要理解這些東西的含義和 Java Web 服務(wù)器(如 tomcat)原理,我們應(yīng)該首先來(lái)談一下 Servlet。




Servlet

Servlet 本質(zhì)是一個(gè)接口,Java 并沒(méi)有對(duì)它做任何實(shí)現(xiàn),僅提供了 Servlet 這么一個(gè)規(guī)范,它作為一個(gè)抽象層,用來(lái)連接所謂 框架的實(shí)現(xiàn) 和開發(fā)者的 Web 業(yè)務(wù)邏輯。

以 tomcat 為例,tomcat 被稱為 Web 服務(wù)器,實(shí)際上他也是一個(gè) Java 程序,本質(zhì)上講則是 Servlet 相關(guān)規(guī)范的實(shí)現(xiàn)。

當(dāng)我們啟動(dòng)一個(gè) Java Web 項(xiàng)目時(shí),程序的主控制流是由 tomcat 實(shí)現(xiàn)和接管的。即我們啟動(dòng)的是 tomcat,而不是我們編寫的業(yè)務(wù)代碼,這些業(yè)務(wù)代碼更像是一個(gè) tomcat 的插件。

形象來(lái)看是下圖這樣的:







在細(xì)節(jié)中舉例的話,例如我們使用的 HttpRequest 和 HttpResponse 對(duì)象,這些類型、接口都由 tomcat 實(shí)現(xiàn)。

我們現(xiàn)在知道了,Servlet 相關(guān)規(guī)范定義了一個(gè)沒(méi)有任何實(shí)現(xiàn)的 Web 框架。

我們還應(yīng)該知道,tomcat 也是一個(gè) java 程序,啟動(dòng) Java Web 項(xiàng)目就是啟動(dòng) tomcat。

此外我們還要明白,tomcat 它并不特殊,他僅僅是實(shí)現(xiàn)了一個(gè) Web 服務(wù)器(后面我會(huì)帶著你手動(dòng)實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的 Web 服務(wù)器),然后遵循了 Servlet 規(guī)范,讓我們的應(yīng)用可以通過(guò) Servlet 接口對(duì)接到 tomcat 的主控制流中。

它并不特殊,還體現(xiàn)在這一點(diǎn),你有沒(méi)有疑惑過(guò),為什么我們?cè)?IDEA 中要?jiǎng)?chuàng)建一個(gè) Java Enterprise 項(xiàng)目?而不是一個(gè)普通的 Java Module?普通的 Java 項(xiàng)目就沒(méi)有作為服務(wù)端的能力嗎?

答案是有的,我們想要實(shí)現(xiàn)一個(gè) Web 服務(wù)器,真的跟 Java EE 關(guān)系不是很大,我們甚至可以手調(diào)匯編寫出一個(gè)服務(wù)端程序。

Java EE 只是定義了一套規(guī)范,我們不是必須要去遵循他,至少為了感知 Web 服務(wù)端,揭開這個(gè)黑盒子上的布,我們先不去考慮規(guī)范。

下面我將剔除 Servlet,為你展示如何實(shí)現(xiàn)一個(gè)最小功能的 Web 服務(wù)端開發(fā)框架。




Web 服務(wù)器的基本實(shí)現(xiàn)

如果你使用過(guò) Node.js 開發(fā) Web 后端應(yīng)用,那么一定使用過(guò)或者聽說(shuō)過(guò) express,下面我們就來(lái)開發(fā)一個(gè)類似 express 風(fēng)格的 Web 服務(wù)器框架。

http 協(xié)議

首先是實(shí)現(xiàn) http 協(xié)議。

先別著急害怕,我們只需要了解一丟丟,下面給出請(qǐng)求報(bào)文和響應(yīng)報(bào)文的例子。

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

后面是請(qǐng)求頭,最后再空一行,寫請(qǐng)求體,只不過(guò)這個(gè)實(shí)例沒(méi)有請(qǐng)求體。

HTTP/1.1 200 Content-Type: text/html ? <h1> Hello, web Framework! </h1>第一行是響應(yīng)行,分別用空格分割了兩個(gè)部分:protocol 和 status

接下來(lái)是響應(yīng)頭,這里只有一個(gè) Content-Type,最后空一行,再寫響應(yīng)體,可以看到,這里的響應(yīng)體是 html 代碼。




立刻實(shí)現(xiàn)

有了這些,我們就可以花幾行代碼,立刻實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Web 服務(wù)器。

看好了。

要知道,所有網(wǎng)絡(luò)通信均基于 TCP/IP 協(xié)議,而該協(xié)議的實(shí)現(xiàn)就是一個(gè)被稱為 socket 的東西,幾乎任何操作系統(tǒng)都對(duì)上層提供了 socket 的接口,很多編程語(yǔ)言的標(biāo)準(zhǔn)庫(kù)也對(duì)這些系統(tǒng)調(diào)用做了封裝。

在 Java 中,我們只需要這樣做,就能創(chuàng)建一個(gè)服務(wù)端 Socket,并監(jiān)聽 80 端口:

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)文:

我們先調(dià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(); }


這就結(jié)束了,所有代碼:

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 端口,你將看到:













Web 框架的基本實(shí)現(xiàn)

到這里,實(shí)際上我要表達(dá)的所有思想已經(jīng)全部說(shuō)清楚了,tomcat 底層就是做了這些事情,接收 socket 連接,然后發(fā)送報(bào)文,只不過(guò)在這中間他還調(diào)用了開發(fā)者的 Servlet 實(shí)現(xiàn)類 —— 因?yàn)樗枰赖降讘?yīng)該發(fā)送什么內(nèi)容的報(bào)文。

下面的內(nèi)容是這中間的部分的簡(jiǎn)單實(shí)現(xiàn),如果你對(duì)如何簡(jiǎn)單地分析請(qǐng)求報(bào)文,如何設(shè)計(jì)一個(gè)簡(jiǎn)單的回調(diào)機(jī)制感興趣,可以繼續(xù)看下去。

如果你有 Java Web 開發(fā)基礎(chǔ),那一定知道 web.xml 這個(gè)東西,這也是 Servlet 相關(guān)規(guī)范的一環(huán),他提示 Web 服務(wù)器應(yīng)該如何理解我們的 web 應(yīng)用配置。

例如路由(或者叫 url)對(duì)應(yīng)的處理程序,需要配置 servlet-mapping,我認(rèn)為這并不是一種好的設(shè)計(jì)。

好的設(shè)計(jì)應(yīng)該讓框架提供足夠的自由度,盡量少用配置,把邏輯盡量體現(xiàn)在代碼里。

但這也存在 Java Web 本身依賴關(guān)系的限制,如果啟動(dòng)入口是我們的程序,這種關(guān)系下設(shè)計(jì)的框架就很容易實(shí)現(xiàn)足夠的自由度,但如果入口本身就在框架中,那么框架的設(shè)計(jì)將比較受限。




框架設(shè)計(jì)

下面要設(shè)計(jì)一個(gè) express like 的框架,我們將不把框架作為程序的入口,而是在其他程序中引用該框架編寫業(yè)務(wù)代碼。

他使用起來(lái)就像這樣:

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-java

該框架將存在一個(gè)入口類 Angie,該類實(shí)例化的對(duì)象將作為一個(gè)獨(dú)立的 web 服務(wù)。

use 方法將一個(gè)回調(diào)方法注冊(cè)到一個(gè)路由上,listen 方法用來(lái)開始監(jiān)端口。




入口類

我們先來(lái)實(shí)現(xià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);





然后我們要實(shí)現(xiàn)一個(gè)另一個(gè)方法 use,注冊(cè)一個(gè)回調(diào)方法到某個(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ù)。

這時(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)這些類型。




回調(diào)方法

Processor 非常簡(jiǎn)單,他就是一個(gè)函數(shù)式接口:

package cool.gaolihai; ? @FunctionalInterface public interface Processor { void callback(Request request, Response response); }


解析請(qǐng)求報(bào)文

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ù)如下:

我們把請(qǐng)求行 "GET /test HTTP/1.1" 通過(guò)空格分割,分別得到了 method、fullUrl,在 url 中又通過(guò) "?" 得到了 url 和 params。

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; } }


響應(yīng)客戶端

Response 類中,我們需要給開發(fā)者提供設(shè)置請(qǐng)求頭、設(shè)置響應(yīng)狀態(tài)嗎和響應(yīng)數(shù)據(jù)的能力:

對(duì)于設(shè)置請(qǐng)求頭,我們使用一個(gè)哈希表來(lái)存,響應(yīng)數(shù)據(jù)時(shí)再拼接:

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í)際上是向輸出流中寫字符串:

在這個(gè)方法中,我們讓開發(fā)者提供請(qǐng)求體的字符串內(nèi)容。

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(); } } }


測(cè)試

現(xiàn)在我們有一個(gè)入口類 Angie,其每一個(gè)實(shí)例都是一個(gè)獨(dú)立的 Web 服務(wù)端。

還有一個(gè)回調(diào)方法的函數(shù)式類型 Processor,用來(lái)通過(guò) use 方法接入框架的處理流程,還有與之相關(guān)的 RequestResponse 類型,用來(lái)解析請(qǐng)求報(bào)文和提供開發(fā)者響應(yīng)客戶端的能力。

現(xiàn)在你可以隨意測(cè)試這個(gè)框架,就像這樣:

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)。

如果讀者發(fā)現(xiàn)了問(wèn)題所在,麻煩請(qǐng)不吝賜教,非常感謝!

再放一遍倉(cāng)庫(kù)地址 --> https://github.com/Drincann/Angie-java。







Java Web 服務(wù)器的基本實(shí)現(xiàn)

這里的 Java Web 服務(wù)器指符合 Servlet 規(guī)范的 Web 服務(wù)器。然而實(shí)際上其體系規(guī)模略龐大,我們不可能簡(jiǎn)單地實(shí)現(xiàn)它,但我們可以自己定義一個(gè) Servlet 類似的簡(jiǎn)單規(guī)范,然后去嘗試實(shí)現(xiàn)。這樣可以搞明白 tomcat 這種服務(wù)器到底在實(shí)現(xiàn)什么,以及他可以如何實(shí)現(xiàn)這些東西。

如果想了解,可以看這篇博客 https://blog.csdn.net/weixin_35586546/article/details/81226887,我下面的內(nèi)容大概就是對(duì)這篇博客里代碼的解釋。

手指骨折,暫時(shí)鴿了。

關(guān)鍵詞:服務(wù),實(shí)現(xiàn),簡(jiǎn)單

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉