<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手寫一個(gè)簡易版的 Tomcat!

          共 9586字,需瀏覽 20分鐘

           ·

          2021-11-23 20:23


          前言

          使用tomcat的時(shí)候當(dāng)瀏覽器輸入url之后,開始發(fā)送http請求,這個(gè)請求發(fā)送到哪兒呢,Url解析的過程中

          • 1 先通過域名解析請求得到ip
          • 2 然后通過ip找到對應(yīng)的主機(jī)
          • 3 再通過響應(yīng)的端口找到進(jìn)程
          • 4 然后再去根據(jù)程序去處理這個(gè)請求,再到原路返回

          思考

          對于1,2步驟我們本地測試可以不用去扣這個(gè),明白這么回事兒就可以,本地localhost實(shí)際上對應(yīng)我們自己本機(jī)127.0.0.1

          對于第三部,我們本地可以去通過一個(gè)socket去監(jiān)聽響應(yīng)的端口,去獲取到請求,然后再響應(yīng)給客戶端讓客戶端瀏覽器去解析我們返回的http報(bào)文,從而展示數(shù)據(jù);

          具體如下步驟:

          1)提供服務(wù),接收請求(可以使用Socket通信)
          2)請求信息封裝成Request(Response)
          3)客戶端請求資源,資源分為靜態(tài)資源(html)和動態(tài)資源(Servlet)
          4)資源返回給客戶端瀏覽器

          具體實(shí)現(xiàn)

          首先新建maven工程

          maven工程

          然后定義一個(gè)啟動類Bootstrap然后實(shí)現(xiàn)一個(gè)啟動方法start,在這個(gè)方法中啟動一個(gè)socket監(jiān)聽8080端口。

          package?com.udeam.v1;

          import?com.udeam.util.HttpUtil;

          import?java.io.IOException;
          import?java.io.InputStream;
          import?java.io.OutputStream;
          import?java.net.ServerSocket;
          import?java.net.Socket;

          /**
          ?*?啟動類入庫
          ?*?用于啟動tomcat
          ?*/

          public?class?Bootstrap?{

          ????/**
          ?????*?監(jiān)聽端口號
          ?????*?用于啟動socket監(jiān)聽的端口號
          ?????*/

          ????private?int?port?=?8080;

          ????/**
          ?????*?啟動方法
          ?????*/

          ????public?void?start()?throws?IOException?{
          ????????//返回固定字符串到客戶端
          ????????ServerSocket?socket?=?new?ServerSocket(port);
          ????????System.out.println("---------?start?port?:?"?+?port);
          ????????while?(true)?{
          ????????????Socket?accept?=?socket.accept();
          ????????????//獲取輸入流
          ????????????//InputStream?inputStream?=?accept.getInputStream();
          ????????????//輸出流
          ????????????OutputStream?outputStream?=?accept.getOutputStream();
          ????????????System.out.println("?------?響應(yīng)返回內(nèi)容?:?"?+?result);
          ????????????outputStream.write("hello?world?...".getBytes());
          ????????????outputStream.flush();
          ????????????outputStream.close();
          ????????????socket.close();
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????try?{
          ????????????new?Bootstrap().start();
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          }

          通過這個(gè)Socket返回hello world...給客戶端
          我們?yōu)g覽器輸入127

          可以看到后臺代碼輸出信息

          后臺代碼輸出信息

          前臺瀏覽器顯示信息

          瀏覽器顯示信息

          返回信息瀏覽器不認(rèn),響應(yīng)無效,出現(xiàn)這種情況是瀏覽器只認(rèn)識http報(bào)文,故此需要包裝一個(gè)返回瀏覽器,然后瀏覽器才能解析

          新建一個(gè)http包裝類HttpUtil包裝響應(yīng)信息給瀏覽器
          這里我們只返回200和404狀態(tài)的

          package?com.udeam.util;

          /**
          ?*?封裝http響應(yīng)
          ?*/

          public?class?HttpUtil?{

          ????/**
          ?????*?404?page
          ?????*/

          ????private?static?final?String?content?=?"

          404?page...?

          "
          ;

          ????/**
          ?????*?添加響應(yīng)頭信息
          ?????*?


          ?????*?http響應(yīng)體格式
          ?????*?


          ?????*?響應(yīng)頭(多參數(shù)空格換行)
          ?????*?換行
          ?????*?響應(yīng)體
          ?????*/
          ????public?static?String?addHeadParam(int?len)?{
          ????????String?head?=?"HTTP/1.1?200?OK?\n";
          ????????head?+=?"Content-Type:?text/html;?charset=UTF-8?\n";
          ????????head?+=?"Content-Length:?"?+?len?+?"?\n"?+?"\r\n";
          ????????return?head;
          ????}

          ????/**
          ?????*?4040響應(yīng)
          ?????*
          ?????*?@return
          ?????*/

          ????public?static?String?resp_404()?{
          ????????String?head?=?"HTTP/1.1?404?not??found?\n";
          ????????head?+=?"Content-Type:?text/html;?charset=UTF-8?\n";
          ????????head?+=?"Content-Length:?"?+?content.length()?+?"?\n"?+?"\r\n";
          ????????return?head?+?content;
          ????}

          ????/**
          ?????*?200響應(yīng)
          ?????*
          ?????*?@param?content?響應(yīng)內(nèi)容
          ?????*?@return
          ?????*/

          ????public?static?String?resp_200(String?content)?{
          ????????return?addHeadParam(content.length())?+?content;
          ????}

          }

          然后再請求,可以看到成功返回信息

          手寫tomcat

          然后我們再去請求一個(gè)靜態(tài)頁面index.html

          新建一個(gè)html頁面

          html>
          <html?lang="en">
          <head>
          ????<meta?charset="UTF-8">
          ????<title>Titletitle>
          head>
          <body>
          hello?tomcat....
          body>
          html>

          這次前臺請求Url是http://localhost:8080/index.html
          還是從Socket進(jìn)行監(jiān)聽8080端口

          請求靜態(tài)的html,那如何去在后臺找到這個(gè)資源呢?

          通過url也就是/index.html去找到這個(gè)文件,后臺文件我們?nèi)シ诺?code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">resource下

          那如何獲取url呢?

          瀏覽器在請求后臺的時(shí)候發(fā)送的也是http請求,我們可以獲取http請求報(bào)文
          可以看一下,請求報(bào)文

          請求報(bào)文

          從請求頭中獲取到url以及method等
          獲取輸入流

          Socket?accept?=?socket.accept();
          //獲取輸入流
          InputStream?inputStream?=?accept.getInputStream();

          然后對輸入流進(jìn)行解析,通過解析http請求頭第一行得到url和method封裝到Request對象中

          /**
          ?*?封裝的請求實(shí)體類
          ?*/

          public?class?Request?{
          ????/**
          ?????*?請求方式
          ?????*/

          ????private?String?method;

          ????/**
          ?????*?請求url
          ?????*/

          ????private?String?url;

          ????/**
          ?????*?輸入流
          ?????*/

          ????public?InputStream?inputStream;

          ????public?Request()?{
          ????}

          ????//構(gòu)造器輸入流
          ????public?Request(InputStream?inputStream)?throws?IOException?{
          ????????this.inputStream?=?inputStream;

          ????????//讀取請求信息,封裝屬性
          ????????int?count?=?0;
          ????????//讀取請求信息
          ????????while?(count?==?0)?{
          ????????????count?=?inputStream.available();
          ????????}
          ????????byte[]?b?=?new?byte[count];
          ????????inputStream.read(b);
          ????????String?reqStr?=?new?String(b);
          ????????System.out.println("請求信息?:?"?+?reqStr);
          ????????//根據(jù)http請求報(bào)文?換行符截取
          ????????String[]?split?=?reqStr.split("\\n");

          ????????//獲取第一行請求頭信息
          ????????String?s?=?split[0];
          ????????//根據(jù)空格進(jìn)行截取請求方式和url
          ????????String[]?s1?=?s.split("?");
          ????????System.out.println("method?:?"?+?s1[0]);
          ????????System.out.println("url?:?"?+?s1[1]);

          ????????this.method?=?s1[0];
          ????????this.url?=?s1[1];

          ????}

          ?//....?get??set省略
          }

          然后根據(jù)請求的url從磁盤找到靜態(tài)資源讀取到然后以流的形式返回給瀏覽器

          這里封裝返回對象Response

          public?class?Response?{

          ????/**
          ?????*?響應(yīng)流
          ?????*/

          ????private?OutputStream?outputStream;

          ????public?Response(OutputStream?outputStream)?{
          ????????this.outputStream?=?outputStream;
          ????}

          ????//輸出指定字符串
          ????public?void?outPutStr(String?content)?throws?IOException?{
          ????????outputStream.write(content.getBytes());
          ????????outputStream.flush();
          ????????outputStream.close();
          ????}
          }

          根據(jù)url獲取靜態(tài)資源

          public?void?outPutHtml(String?url)?throws?IOException?{
          ????//排除瀏覽器的/favicon.ico請求
          ????if?(("/favicon.ico").equals(url)){
          ????????return;
          ????}
          ????//獲取靜態(tài)資源的絕對路徑
          ????String?abPath?=?ResourUtil.getStaticPath(url);
          ????//查詢靜態(tài)資源是否存在
          ????File?file?=?new?File(abPath);
          ????if?(file.exists())?{
          ????????//輸出靜態(tài)資源
          ????????ResourUtil.readFile(new?FileInputStream(abPath),?outputStream);
          ????}?else?{
          ????????//404
          ????????try?{
          ????????????outPutStr(HttpUtil.resp_404());
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          }

          ResourUtil 工具類,封裝解析讀取靜態(tài)資源。

          /**
          ?*?靜態(tài)資源工具類
          ?*/

          public?class?ResourUtil?{

          ????/**
          ?????*?獲取classes文件目錄
          ?????*/

          ????private?static?URL?url?=?ResourUtil.class.getClassLoader().getResource("\\\\");

          ????/**
          ?????*?獲取靜態(tài)資源文件路徑
          ?????*
          ?????*?@param?path
          ?????*?@return
          ?????*/

          ????public?static?String?getStaticPath(String?path)?throws?UnsupportedEncodingException?{

          ????????//獲取目錄的絕對路徑
          ????????try?{
          ????????????String?decode?=?URLDecoder.decode(url.getPath(),?"UTF-8");
          ????????????String?replace1?=?decode.replace("\\",?"/");
          ????????????String?replace2?=?replace1.replace("http://",?"");
          ????????????replace2?=?replace2.substring(0,replace2.lastIndexOf("/"))?+?path;
          ????????????return?replace2;
          ????????}?catch?(UnsupportedEncodingException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????return?null;
          ????}

          ????/**
          ?????*?讀取靜態(tài)資源文件輸入流
          ?????*
          ?????*?@param?inputStream
          ?????*/

          ????public?static?void?readFile(InputStream?inputStream,?OutputStream?outputStream)?throws?IOException?{

          ????????int?count?=?0;
          ????????//讀取請求信息
          ????????while?(count?==?0)?{
          ????????????count?=?inputStream.available();
          ????????}
          ????????int?content?=?0;
          ????????//讀取文件
          ????????content?=?count;


          ????????//輸出頭
          ????????outputStream.write(HttpUtil.addHeadParam(content).getBytes());
          ????????//輸出內(nèi)容
          ????????long?written?=?0;
          ????????int?byteSize?=?1024;
          ????????byte[]?b?=?new?byte[byteSize];
          ????????//讀取
          ????????while?(written?????????????if?(written?+?1024?>?content)?{
          ????????????????byteSize?=?(int)?(content?-?written);
          ????????????????b?=?new?byte[byteSize];
          ????????????}
          ????????????inputStream.read(b);
          ????????????outputStream.write(b);
          ????????????outputStream.flush();
          ????????????written?+=?byteSize;
          ????????}
          ????}
          }

          socket中完整請求代碼

          public?void?start()?throws?IOException?{
          ????//返回固定字符串到客戶端
          ????ServerSocket?socket?=?new?ServerSocket(port);
          ????System.out.println("---------?start?port?:?"?+?port);

          ????while?(true)?{
          ????????Socket?accept?=?socket.accept();
          ????????//獲取輸入流
          ????????InputStream?inputStream?=?accept.getInputStream();
          ????????//封裝請求和響應(yīng)對象
          ????????Request?request?=?new?Request(inputStream);
          ????????Response?response?=?new?Response(accept.getOutputStream());
          ????????response.outPutHtml(request.getUrl());
          ????}

          }

          瀏覽器測試,可以看到正確返回

          手寫tomcat

          接下來實(shí)現(xiàn)定義請求動態(tài)資源,具體實(shí)現(xiàn)在java web中處理一個(gè)請求是使用servlet請求

          tomcat處理servlet請求需要實(shí)現(xiàn)servlet規(guī)范

          什么是servlet規(guī)范呢?

          簡單來說就是http請求在接收到請求之后將請求交給Servlet容器來處理,Servlet容器通過Servlet接口來調(diào)用不同的業(yè)務(wù)類,這一整套稱作Servlet規(guī)范;

          接口規(guī)范

          /**
          ?*?自定義servlet規(guī)范
          ?*/

          public?interface?Servlet?{

          ????void??init()?throws?Exception;
          ????void??destory()?throws?Exception;
          ????void??service(Request?request,?Response?response)?throws?Exception;
          }

          實(shí)現(xiàn)

          /**
          ?*?實(shí)現(xiàn)servlet規(guī)范
          ?*/

          public?abstract?class?HttpServlet?implements?Servlet?{

          ????public?abstract?void?doGet(Request?request,?Response?response);

          ????public?abstract?void?doPost(Request?request,?Response?response);

          ????@Override
          ????public?void?service(Request?request,?Response?response)?throws?Exception?{
          ????????if?("GET".equalsIgnoreCase(?request.getMethod()
          ????????))?{
          ????????????doGet(request,?response);
          ????????}?else?{
          ????????????doPost(request,?response);
          ????????}
          ????}
          }

          業(yè)務(wù)請求servlet

          /**
          ?*?業(yè)務(wù)類servelt
          ?*/

          public?class?MyServlet?extends?HttpServlet?{

          ????@Override
          ????public?void?init()?throws?Exception?{
          ????}

          ????@Override
          ????public?void?doGet(Request?request,?Response?response)?{

          ????????//動態(tài)業(yè)務(wù)請求
          ????????String?content?=?"

          ?GET?業(yè)務(wù)請求

          "
          ;
          ????????try?{
          ????????????response.outPutStr(HttpUtil.resp_200(content));
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}

          ????@Override
          ????public?void?doPost(Request?request,?Response?response)?{
          ????????//動態(tài)業(yè)務(wù)請求
          ????????String?content?=?"

          ?Post?業(yè)務(wù)請求

          "
          ;
          ????????try?{
          ????????????response.outPutStr(HttpUtil.resp_200(content));
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}


          ????@Override
          ????public?void?destory()?throws?Exception?{

          ????}
          }

          定義完之后,如何請求呢,如何根據(jù)請求Ur去得到相應(yīng)的servlet
          在Java web中我們是在web.xml中進(jìn)行配置,同樣新建web.xml,配置servlet


          <web-app>

          ????
          ????

          ????
          ????<servlet>
          ????????<servlet-name>testservlet-name>
          ????????<servlet-class>com.udeam.v4.MyServletservlet-class>
          ????servlet>

          ????<servlet-mapping>
          ????????<servlet-name>testservlet-name>
          ????????<url-pattern>/testurl-pattern>
          ????servlet-mapping>

          web-app>

          解析web.xml文件

          講url和每一個(gè)servlet對應(yīng)起來存儲到map中

          /**
          ?*?加載解析web.xml,初始化Servlet
          ?*/

          private?void?loadServlet()?{
          ????InputStream?resourceAsStream?=?this.getClass().getClassLoader().getResourceAsStream("web.xml");
          ????SAXReader?saxReader?=?new?SAXReader();

          ????try?{
          ????????Document?document?=?saxReader.read(resourceAsStream);
          ????????Element?rootElement?=?document.getRootElement();

          ????????List?selectNodes?=?rootElement.selectNodes("http://servlet");
          ????????for?(int?i?=?0;?i?????????????Element?element?=??selectNodes.get(i);
          ????????????//?test
          ????????????Element?servletnameElement?=?(Element)?element.selectSingleNode("servlet-name");
          ????????????String?servletName?=?servletnameElement.getStringValue();
          ????????????Element?servletclassElement?=?(Element)?element.selectSingleNode("servlet-class");
          ????????????String?servletClass?=?servletclassElement.getStringValue();

          ????????????//?根據(jù)servlet-name的值找到url-pattern
          ????????????Element?servletMapping?=?(Element)?rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='"?+?servletName?+?"']");
          ????????????//?/test
          ????????????String?urlPattern?=?servletMapping.selectSingleNode("url-pattern").getStringValue();
          ????????????servletMap.put(urlPattern,?(HttpServlet)?Class.forName(servletClass).newInstance());

          ????????}

          ????}?catch?(DocumentException?e)?{
          ????????e.printStackTrace();
          ????}?catch?(IllegalAccessException?e)?{
          ????????e.printStackTrace();
          ????}?catch?(InstantiationException?e)?{
          ????????e.printStackTrace();
          ????}?catch?(ClassNotFoundException?e)?{
          ????????e.printStackTrace();
          ????}
          }

          啟動方法
          根據(jù)url找到servlet去執(zhí)行service方法

          private?static?final?Map?servletMap?=?new?HashMap<>();

          public?void?start()?throws?IOException?{

          ????ServerSocket?socket?=?new?ServerSocket(port);
          ????System.out.println("---------?start?port?:?"?+?port);

          ????while?(true)?{
          ????????Socket?accept?=?socket.accept();
          ????????//獲取輸入流
          ????????InputStream?inputStream?=?accept.getInputStream();
          ????????//封裝請求和響應(yīng)對象
          ????????Request?request?=?new?Request(inputStream);
          ????????Response?response?=?new?Response(accept.getOutputStream());
          ????????//靜態(tài)資源
          ????????if?(request.getUrl().contains(".html"))?{
          ????????????response.outPutHtml(request.getUrl());
          ????????}?else?{
          ????????????if?(!servletMap.containsKey(request.getUrl()))?{
          ????????????????response.outPutStr(HttpUtil.resp_200(request.getUrl()?+?"?is?not?found?...?"));
          ????????????}?else?{
          ????????????????HttpServlet?httpServlet?=?servletMap.get(request.getUrl());
          ????????????????try?{
          ????????????????????//處理請求
          ????????????????????httpServlet.service(request,?response);
          ????????????????}?catch?(Exception?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}
          ????????}
          ????}

          }

          然后請求http://localhost:8080/test可以看到正確返回

          get請求

          這里在deget方法中增加睡眠停頓模擬業(yè)務(wù)請求時(shí)間

          try?{
          ????Thread.sleep(10_000);
          }?catch?(InterruptedException?e)?{
          ????e.printStackTrace();
          }

          請求可以可以看到請求阻塞,這是因?yàn)橥粋€(gè)socket 當(dāng)前test這個(gè)沒有請求結(jié)束,第二個(gè)請求進(jìn)來然后阻塞。

          必須等到第一個(gè)請求結(jié)束后才能處理請求。

          手?jǐn)]tomcat

          然后再請求index.html

          tomcat的http請求測試

          發(fā)現(xiàn),并不是靜態(tài)資源并不是立即返回,需要等到test請求結(jié)束后才能返回

          故此需要對這個(gè)進(jìn)行改造,讓彼此請求互不干擾

          我們可以使用多線程來進(jìn)行解決,線程互不干擾,每個(gè)請求去執(zhí)行

          在Socket中添加方法

          //1?單線程處理
          MyThread?myThread?=?new?MyThread(httpServlet,?response,?request);
          new?Thread(myThread).start();

          線程是寶貴的資源,頻繁創(chuàng)建和銷毀線程對開銷很大,故此使用線程池來解決

          /**
          *?參數(shù)可以配置在xml里面
          */

          private?static?final?ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(10,?20,?1,?TimeUnit.HOURS,?new?ArrayBlockingQueue<>(500));


          //2?線程池執(zhí)行
          threadPoolExecutor.submit(myThread);
          threadPoolExecutor.shutdown();

          這樣子就可以立即返回響應(yīng),互不干擾;

          簡易版的tomcat實(shí)現(xiàn)就可以實(shí)現(xiàn)了,代碼的話沒有像tomcat那樣子可以將war包解析之類的..
          而且代碼耦合性也大,tomcat和業(yè)務(wù)代碼在一個(gè)Maven中...

          說明

          分別在指定包下如v1,v2,v3,v4每個(gè)代表一個(gè)版本

          • v1 簡單的返回指定字符串
          • v2 返回靜態(tài)頁面
          • v3 單線程處理servelt請求(多個(gè)請求會阻塞)
          • v4 多線程處理

          其中需要用到解析xml依賴

          <dependencies>
          ????<dependency>
          ????????<groupId>dom4jgroupId>
          ????????<artifactId>dom4jartifactId>
          ????????<version>1.6.1version>
          ????dependency>
          ????<dependency>
          ????????<groupId>jaxengroupId>
          ????????<artifactId>jaxenartifactId>
          ????????<version>1.1.6version>
          ????dependency>
          dependencies>

          至此,一個(gè)簡單的 tomcat 就擼完了,如需本文源碼,請加我微信:xttblog2,免費(fèi)發(fā)送!

          瀏覽 52
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品在线观看成人视频 | 极品美女口交赤裸口交赤 | 韩国经典一区二区在线 | 荫蒂添到高潮免费视频 | 日P视频免费 |