整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          神奇!明明是 socket,被我玩成了 http

          神奇!明明是 socket,被我玩成了 http

          在,我們已經充分了解了 HTTP 和 Socket 的關系,也了解了 HTTP 報文的格式,為了讓小伙伴能夠加深對這兩個概念的理解,本文我們來看看如何利用 Socket 模擬 HTTP 請求。如果小伙伴們對 HTTP 和 Socket 的關系、HTTP 報文格式尚不熟悉的話,可以參考前面的文章 Http 和 Socket 到底是哪門子親戚?。

          由于 HTTP 是基于 TCP 協議的應用層協議,因此我們可以用更為底層的方式來訪問 HTTP 服務,即直接使用 Socket 完成 HTTP 的請求和響應。我們前面說過,HTTP 的任務就是完成數據的包裝, Socket 提供了網絡的傳輸能力,所以我們只需要按照 HTTP 報文的格式來組裝數據,然后利用 Socket 將數據發送出去,就能得到回應。

          POST 請求上傳數據

          假設我現在有一個數據接口 http://localhost/hello,該接口每次接收一個參數 name ,調用成功之后返回給用戶一個 hello:name 字符串,那我們用 Socket 來實現這樣一個 HTTP 請求。

          首先,我們要先組裝出 HTTP 的請求頭,如下(如果小伙伴對下面這個請求頭有疑問,請復習 Http 和 Socket 到底是哪門子親戚?一文):

          POST /hello HTTP/1.1
          Accept:text/html
          Accept-Language:zh-cn
          Host:localhost
          
          name=張三
          

          我這里為了簡單,只添加了三個請求頭,然后我們通過 Socket 將上面這個字符串發送出去:

          Socket socket=new Socket(InetAddress.getByName("localhost"), 80);
          OutputStream os=socket.getOutputStream();
          String data="name=張三";
          int dataLen=data.getBytes().length;
          String contentType="Content-Length:" + dataLen+"\r\n";
          os.write("POST /hello HTTP/1.1\r\n".getBytes());
          os.write("Accept:text/html\r\n".getBytes());
          os.write("Accept-Language:zh-cn\r\n".getBytes());
          os.write(contentType.getBytes());
          os.write("Host:localhost\r\n".getBytes());
          os.write("\r\n".getBytes());
          os.write(data.getBytes());
          os.write("\r\n".getBytes());
          os.flush();
          

          我在 Serlvet 中接收這個請求并作簡單處理,如下:

          BufferedReader br=new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
          StringBuffer sb=new StringBuffer();
          String str;
          while ((str=br.readLine()) !=null) {
              sb.append(str).append("\r\n");
          }
          System.out.println("sb:"+sb.toString());
          resp.setContentType("text/html;charset=utf-8");
          PrintWriter out=resp.getWriter();
          out.write(sb.toString());
          out.flush();
          out.close();
          

          然后通過 Socket 中的輸入流我就能拿到響應結果,如下:

          BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
          StringBuffer sb=new StringBuffer();
          String str;
          while ((str=br.readLine()) !=null) {
              sb.append(str).append("\r\n");
          }
          System.out.println(sb.toString());
          

          響應結果如下:

          HTTP/1.1 200 
          Content-Type: text/html;charset=utf-8
          Transfer-Encoding: chunked
          Date: Sun, 03 Dec 2017 10:46:52 GMT
          
          name=張三
          

          這是一個簡單的通過 POST 請求下載文本的案例。接下來我們再來一個 GET 請求下載圖片的案例,來加深對 Socket 的理解。

          GET 請求下載圖片

          這個實際上也不難,但是要實現圖片的下載需要我們首先熟悉HTTP響應的數據格式,不熟悉的小伙伴可以閱讀 Http 和 Socket 到底是哪門子親戚?一文。  

          下載圖片,響應頭是文本文件,響應數據是二進制文件,我們要想辦法通過空行將這兩塊數據分開,分別處理。為了解決這個問題,我首先提供一個工具類,這個工具類用來實現一行一行的解析字節流,如下:  

          public class BufferedLineInputStream {
              private InputStream is;
          
              public BufferedLineInputStream(InputStream is) {
                  this.is=is;
              }
          
              /**
               * @param buf    將數據讀入到byte數組中
               * @param offset 數組偏移量
               * @param len    數組長度
               * @return 返回值表示讀取到的數據長度 -1表示數據讀取完畢,0表示其他異常情況
               */
              public int readLine(byte[] buf, int offset, int len) throws IOException {
                  if (len < 1) {
                      return 0;
                  }
                  //count用來統計已經向數組中存儲了多少數據
                  int count=0, c;
                  while ((c=is.read()) !=-1) {
                      buf[offset++]=(byte) c;
                      count++;
                      //如果一行已經讀完或者數組已滿
                      if (c=='\n' || count==len) {
                          break;
                      }
                  }
                  return count > 0 ? count : -1;
              }
          }
          

          然后將響應中的頭信息和圖片分別保存在不同的文件中,數據解析的核心思路就是一行一行讀取響應數據,當遇到 \r\n 表示頭信息已經讀取完了,要開始讀取二進制數據了,二進制數據讀取到之后,將之保存成圖片即可。核心代碼如下:

          fos=new FileOutputStream("E:\333.png");
          pw=new PrintWriter(new OutputStreamWriter(new FileOutputStream("E:\222.txt")));
          socket=new Socket(InetAddress.getByName("localhost"), 80);
          OutputStream out=socket.getOutputStream();
          out.write("GET /1.png HTTP/1.1\r\n".getBytes());
          out.write("Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n".getBytes());
          out.write("Accept-Language:zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\n".getBytes());
          out.write("Host:localhost\r\n".getBytes());
          out.write("\r\n".getBytes());
          
          BufferedLineInputStream blis=new BufferedLineInputStream(socket.getInputStream());
          int len;
          byte[] buf=new byte[1024];
          while ((len=blis.readLine(buf, 0, buf.length)) !=-1) {
              String s=new String(buf, 0, len);
              System.out.println(s);
              if (s.equals("\r\n")) {//表示頭信息讀取完畢
                  break;
              }
          }
          //開始解析二進制的圖片數據
          while ((len=blis.readLine(buf, 0, buf.length)) !=-1) {
              fos.write(buf, 0, len);
          }
          

          OK,Socket 模擬 HTTP 請求我們就先說到這里,兩個案例,希望能夠加深小伙伴對 Socket 和 HTTP 的理解。

          HTTPS (HyperText Transfer Protocol Secure) 是一種通過 SSL/TLS 加密保護數據傳輸安全的 HTTP 協議。HTTPS 的加密機制是保證數據在傳輸過程中不會被竊取、篡改或冒充的關鍵。

          本文將詳細介紹 HTTPS 的加密過程及其工作原理。

          HTTPS 的加密過程

          HTTPS 的加密過程可以分為以下步驟:

          1. 客戶端向服務器發送 HTTPS 請求。
          2. 服務器將公鑰證書發送給客戶端。
          3. 客戶端驗證服務器的證書。
          4. 如果驗證通過,客戶端生成一個用于會話的對稱密鑰。
          5. 客戶端使用服務器的公鑰對對稱密鑰進行加密,并將加密后的密鑰發送給服務器。
          6. 服務器使用私鑰對客戶端發送的加密密鑰進行解密,得到對稱密鑰。
          7. 服務器和客戶端使用對稱密鑰進行加密和解密數據傳輸。

          下面我們將詳細介紹每個步驟的細節。

          步驟1:客戶端向服務器發送 HTTPS 請求

          當客戶端需要從服務器獲取數據時,它會向服務器發送一個 HTTPS 請求。這個請求包括請求的 URL、HTTP 請求頭和請求體。例如:

          GET / HTTP/1.1
          Host: www.example.com
          User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
          Accept-Language: en-US,en;q=0.5
          Accept-Encoding: gzip, deflate, br
          Connection: keep-alive
          Upgrade-Insecure-Requests: 1
          

          步驟2:服務器將公鑰證書發送給客戶端

          當服務器接收到 HTTPS 請求后,它會將公鑰證書發送給客戶端。公鑰證書中包含了服務器的公鑰、服務器的域名、證書頒發機構、證書有效期等信息。

          客戶端接收到證書后,會從中提取出服務器的公鑰。

          步驟3:客戶端驗證服務器的證書

          客戶端接收到服務器的證書后,會對其進行驗證,以確保該證書是由可信任的證書頒發機構頒發的,并且證書中的域名和服務器的實際域名一致。

          如果證書驗證失敗,客戶端會中斷連接。如果驗證通過,客戶端會生成一個用于會話的對稱密鑰。

          步驟4:客戶端生成一個用于會話的對稱密鑰

          客戶端生成一個用于會話的對稱密鑰。對稱密鑰是一種加密方式,它使用相同的密鑰進行加密和解密。這個密鑰只存在于客戶端和服務器之間,因此被稱為“對稱”。

          步驟5:客戶端使用服務器的公鑰對對稱密鑰進行加密,并將加密后的密鑰發送給服務器

          客戶端使用服務器的公鑰對對稱密鑰進行加密,并將加密后的密鑰發送給服務器。在這個過程中,客戶端和服務器都知道對稱密鑰,但是只有客戶端知道對稱密鑰的值。

          步驟6:服務器使用私鑰對客戶端發送的加密密鑰進行解密,得到對稱密鑰

          服務器使用私鑰對客戶端發送的加密密鑰進行解密,得到對稱密鑰。由于私鑰只在服務器端保存,因此只有服務器才能解密客戶端發送的加密密鑰,并得到對稱密鑰的值。

          步驟7:服務器和客戶端使用對稱密鑰進行加密和解密數據傳輸

          服務器和客戶端使用對稱密鑰進行加密和解密數據傳輸。這個對稱密鑰只存在于客戶端和服務器之間,因此對數據的加密和解密只有客戶端和服務器可以進行。

          HTTPS 的工作原理

          HTTPS 的加密過程基于公鑰密碼學和對稱密鑰密碼學。

          在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,而對稱密鑰密碼學用于加密和解密數據傳輸。

          公鑰密碼學是一種密碼學技術,它使用一對密鑰(公鑰和私鑰)來加密和解密數據。公鑰可以被任何人獲得并用于加密數據,但是只有私鑰的擁有者才能解密數據。

          在 HTTPS 的加密過程中,服務器使用公鑰加密對稱密鑰,客戶端使用私鑰解密對稱密鑰。這樣就可以保證對稱密鑰在傳輸過程中不會被竊取、篡改或冒充。

          對稱密鑰密碼學是一種密碼學技術,它使用相同的密鑰進行加密和解密數據。在 HTTPS 的加密過程中,客戶端和服務器都知道對稱密鑰的值,因此可以使用對稱密鑰對數據進行加密和解密。

          對稱密鑰密碼學的優點是加密和解密速度快,但是對稱密鑰的安全傳輸是一個問題。因此,在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,保證數據在傳輸過程中不會被竊取、篡改或冒充。

          結論

          HTTPS 加密是保證數據傳輸安全的關鍵。在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,對稱密鑰密碼學用于加密和解密數據傳輸。

          HTTPS 加密的過程中,客戶端和服務器之間建立了一個安全的通信通道。在這個通道中,數據被加密傳輸,確保了數據在傳輸過程中的機密性、完整性和真實性。

          在今天的互聯網時代,數據安全越來越受到重視。HTTPS 加密作為一種保證數據傳輸安全的技術,已經成為現代互聯網通信的標準之一。通過了解 HTTPS 加密的工作原理,我們可以更好地理解互聯網通信的安全性,并且能夠更好地保護自己的數據安全。

          、Tomcat原理理論介紹

          1.1 Tomcat簡介

          Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器,屬于輕量級應用服務器,在中小型系統和并發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程序的首選。對于一個初學者來說,可以這樣認為,當在一臺機器上配置好Apache 服務器,可利用它響應HTML(標準通用標記語言下的一個應用)頁面的訪問請求。實際上Tomcat是Apache 服務器的擴展,但運行時它是獨立運行的,所以當你運行tomcat 時,它實際上作為一個與Apache 獨立的進程單獨運行的。

          1.1.1Tomcat目錄結構

          目錄說明bin主要存放編譯后的代碼的地方,startup.bat、shutdown.bat分別對應windows版本的啟動和關閉conf主要存放配置文件目錄,context.xml 存放上下文的配置信息,logging.properties存放日志配置信息,server.xml指定服務端的一些配置信息比如端口號,web.xml指定一些Servlet的配置文件lib存儲一些依賴的jar包(tomcat也是java開發的)Logs存放日志相關的包temp存放一些臨時文件的包webapps主要存放解壓之后的war的項目信息Work工作相關的信息

          1.2 請求處理流程

          1.2.1計算機的網絡通信模型



          應用層

          不同計算機上應用之間的通信,來解決通信雙方數據傳輸的問題。或者說不同計算機上對應的應用進程之間的通信。支持的協議有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS DHCP等。(協議)

          表示層

          用來規定傳輸的數據格式以及加解密,格式有,JPEG、ASCll、DECOIC、加密格式等。【在四層模型里面已經合并到了應用層】

          會話層

          用來規定開始、控制和終止一個會話。對應主機的進程,指本地主機與遠程主機正在進行的會話。【在四層模型里面已經合并到了應用層】

          傳輸層

          規定傳輸數據的協議端口號,以及流控和差錯校驗。支持的協議有:TCP UDP等,數據包一旦離開網卡即進入網絡傳輸層。指定IO模型 BIO NIO AIO等

          網絡層

          進行邏輯地址尋址,實現不同網絡之間的路徑選擇。(路由選擇) 協議有:ICMP IGMP IP(IPV4 IPV6) ARP RARP。

          數據鏈路層

          建立邏輯連接、進行硬件地址尋址、差錯校驗等功能。(由底層網絡定義協議,ATM,FDDI等)將比特組合成字節進而組合成幀,用MAC地址訪問介質,能錯誤發現但不能糾正。

          物理層

          建立、維護、斷開物理連接。(由底層網絡定義協議,RJ45,802.3等)【在四層模型里面已經合并到了數據鏈路層】

          1.2.2瀏覽器訪問服務器流程

          http請求處理過程



          第2步是先建立連接,進行3次握手。

          1.3 Tomcat 系統總體架構

          1.3.1Tomcat處理請求大致流程

          因為Tomcat可以處理Http請求,因此稱Tomcat為Http服務器。

          如果我們直接從瀏覽器發送請求,Tomcat直接接收請求,并將請求轉發到對應的java程序的話,也可以實現請求處理,但是耦合度非常高,因此Tomcat的做法是在接收到http請求之后,將請求轉發給servlet容器,由servlet容器在進行處理并轉發到對應的java程序。

          因此在Tomcat內部也實現了Servlet規范。(Servlet接?和Servlet容器這?整套內容叫作Servlet規范。

          因此Tomcat有兩個身份:

          1、Http服務器

          2、Servlet容器



          1.3.2Tomcat Servlet容器處理過程


          步驟說明:

          1)Tomcat中Http服務器模塊會接收到原始的請求參數Request,封裝ServletRequest準備請求Servlet容器

          2)將請求轉發到Servlet容器中定位Servlet(URL和Servlet的映射關系,找到相應的Servlet)

          3)如果Servlet還未加載,利用反射機制創建Servlet,并調用Servlet的init初始化方法

          4)獲取到具體的Servlet實例之后直接調用對應的業務處理方法執行業務

          5)將處理好的響應結果封裝成ServletResponse

          6)Http服務器在將ServletResponse對象封裝成原生的Response相應到瀏覽器

          1.3.3Tomcat系統總體架構總結


          Tomcat中包括兩大核心組件:

          連接器(Connector)組件,容器(Container)組件。(還包括其他組件)

          連接器(Connector)組件主要作用:處理Socket連接,負責?絡字節流與Request和Response對象的轉化。【與客戶端交互】

          容器(Container)組件主要作用:加載和管理Servlet,處理Request請求;

          1.4Tomcat組件

          1.4.1連接器組件 Coyote

          1)Coyote簡介

          Coyote是Tomcat中連接器的組件名稱,是對應的接口。客戶端通過Coyote與服務器建立連接、發送請求并接受響應。

          2)主要職責

          1)Coyote封裝了底層網絡通信(Socket請求及相應處理)

          2)Coyote使Container容器組件與具體的請求協議及IO操作?式完全解耦

          3)Coyote將Socket輸入轉換封裝為Request對象,并進一步封裝成ServletRequest交給Container容器進行處理,處理完成后返回ServletResponse給Coyote,Coyote將對象轉換成Response對象將結果寫入輸出流。

          4)Coyote負責的是具體協議(應?層)和IO(傳輸層)相關內容。

          3)Tomcat Coyote支持的IO模型與協議

          支持的協議

          應用層應用層描述描述HTTP/1.1大部分Web應用采用的協議【Tomcat8.x默認協議】AJPAJP定向包協議,實現對靜態資源的優化以及集群的部署。HTTP/2HTTP2.0大幅度提升了web性能,屬于下一代的HTTP協議但是用的很少。

          支持的IO

          傳輸層IO模型描述NIO同步非阻塞I/O、采用javaNIO類庫實現【Tomcat8默認IO模型】NIO2異步非阻塞I/O、采用jdk7的NIO類庫實現APR采用Apache可移植運行庫實現,是C/C++編寫的本地庫,如果使用需要單獨安裝APR庫。

          Tomcat在8.0之前默認使用的是BIO。如果使用APR性能可能達到Apache Http Server的性能。

          4)Coyote內部組件



          Coyote組件其中包括EndPoint組件、Processor組件、Adapter組件。

          EndPoint:EndPoint 是 Coyote 通信端點,即通信監聽的接?,是具體Socket接收和發送處理器,是對傳輸層的抽象,因此EndPoint?來實現TCP/IP協議的。

          Processor:Processor 是Coyote 協議處理接?,如果說EndPoint是?來實現TCP/IP協議的,那么Processor?來實現HTTP協議,Processor接收來?EndPoint的Socket,讀取字節流解析成Tomcat的 Request和Response原生對象。

          Adapter:Tomcat Request對象不是標準的ServletRequest,不能?Tomcat Request作為參數來調?容器。Tomcat設計者的解決?案是引?CoyoteAdapter,將參數轉換成ServlerRequest對象。


          ProtocolHandler:由Endpoint 和 Processor組成,Tomcat 按照協議和I/O 提供了6個實現類 : AjpNioProtocol,AjpAprProtocol,AjpNio2Protocol,Http11NioProtocol,Http11Nio2Protocol,Http11AprProtocol。

          1.4.2Tomcat Catalina組件


          1)Catalina組件地位

          本來Catalina組件只是Servlet容器的一個組件,而Tomcat是由一些列組件組成,組件可以在conf/server.xml文件中配置。Catalina在Tomcat中的地位非常的核心,因此經常把tomcat的一個實例當作一個Catalina實例。



          2)Catalina結構



          整個Tomcat就相當于一個Catalina實例,Tomcat啟動的時候會先初始化這個實例Catalina,Catalina實例對象通過加載server.xml完成其他實例的創建,創建并管理?個Server,Server創建并管理多個服務,每個服務?可以有多個Connector和?個Container。


          對應關系:一個Tomcat對應一個Catalina,對應一個Server,對應多個Service。每一個Service實例有多個Connector和一個Container實例。

          Catalina:負責解析Tomcat的配置?件(server.xml) , 以此來創建服務器Server組件并進?管理

          Server:Server是整個Catalina Servlet容器以及其它組件,作用負責組裝并啟動Servlaet引擎,Tomcat連接器。Server通過實現Lifecycle接?,提供了?種優雅的啟動和關閉整個系統的?式。

          Service:Service是Server內部的組件,?個Server包含多個Service。它將若?個Connector組件綁定到?個Container。

          Container:容器,負責處理?戶的servlet請求,并返回對象給web?戶的模塊。


          3)Container 組件介紹

          Container組件其中包括Engine、Host、Context、Wrapper4種組件,他們之間是父子關系。Tomcat通過分層的架構,讓Servlet容器具有很好的靈活性。



          Engine:表示整個Servlet容器的引擎,用來管理多個虛擬站點,一個Service只能有一個Engine。

          Host:代表一個虛擬主機,或者說一個站點,可以配置多個虛擬主機地址,一個Engine可以有多個Host。

          Context:表示一個Web應用服務器,一個Host下可以包含多個Context。

          Wrapper:表示一個Servlet, 一個Context中可以包括多個Wrapper。

          2、Tomcat核心配置

          思考:

          去哪兒配置?->conf/server.xml中

          怎么配置?

          2.1server.xml配置詳解

          2.1.1server配置文件概述

          server.xml中包括Server根標簽,Listener,GlobalNamingResources,Service

          Server標簽:主要用來創建一個server實體對象

          Listener標簽:定義監聽器

          GlobalNamingResources 標簽:定義服務器的全局JNDI(Java Naming and Directory Interface 標準的Java命名系統接口)資源。

          Service標簽:定義?個Service服務,?個Server標簽可以有多個Service服務實例


          Server.xml整體結構

          <?xml version="1.0" encoding="UTF-8"?>
          <!--
          port:關閉服務器的監聽端?
          shutdown:關閉服務器的指令字符串
          -->
          <Server port="8005" shutdown="SHUTDOWN">
              <!-- 以?志形式輸出服務器 、操作系統、JVM的版本信息 -->
              <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
              <!-- Security listener. Documentation at /docs/config/listeners.html
              <Listener className="org.apache.catalina.security.SecurityListener" />
              -->
              <!--APR library loader. Documentation at /docs/apr.html -->
              <!-- 加載(服務器啟動) 和 銷毀 (服務器停?) APR。 如果找不到APR庫, 則會輸出?志, 并
              不影響 Tomcat啟動 -->
              <Listener className="org.apache.catalina.core.AprLifecycleListener"
                        SSLEngine="on" />
              <!-- Prevent memory leaks due to use of particular java/javax APIs-->
              <!-- 避免JRE內存泄漏問題 -->
              <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
              <!-- 加載(服務器啟動) 和 銷毀(服務器停?) 全局命名服務 -->
              <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
              <!-- 在Context停?時重建 Executor 池中的線程, 以避免ThreadLocal 相關的內存泄漏 -->
              <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
              <!-- Global JNDI resources
              Documentation at /docs/jndi-resources-howto.html
              GlobalNamingResources 中定義了全局命名服務
              -->
              <GlobalNamingResources>
                  <!-- Editable user database that can also be used by
                  UserDatabaseRealm to authenticate users
                  -->
                  <Resource name="UserDatabase" auth="Container"
                            type="org.apache.catalina.UserDatabase"
                            description="User database that can be updated and saved"
                            factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                            pathname="conf/tomcat-users.xml" />
              </GlobalNamingResources>
              <!-- A "Service" is a collection of one or more "Connectors" that share
              a single "Container" Note: A "Service" is not itself a "Container",
              so you may not define subcomponents such as "Valves" at this level.
              Documentation at /docs/config/service.html
              -->
              <Service name="Catalina">
                  ...
              </Service>
          </Server>


          2.1.2Service標簽

          <Server port="8005" shutdown="SHUTDOWN">
             ....
              <Service name="Catalina">
                  <!--為Connector創建一個線程池-->
                  <!--
                  <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
                      maxThreads="150" minSpareThreads="4"/>
                  -->
                  <!--創建一個監聽8080的連接器組件 -->
                  <Connector port="8080" protocol="HTTP/1.1"
                             connectionTimeout="20000"
                             redirectPort="8443" />
                  <!-- 創建連接池的連接器 -->
                  <!--
                  <Connector executor="tomcatThreadPool"
                             port="8080" protocol="HTTP/1.1"
                             connectionTimeout="20000"
                             redirectPort="8443" />
                  -->
          
                  <!-- 創建一個監聽8009的ajp的connector -->
                  <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
          
                  <!--創建引擎 管理 多個Host-->
                  <Engine name="Catalina" defaultHost="localhost">
                      <!--配置集群-->
                      <!--
                      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
                      -->
                      <Realm className="org.apache.catalina.realm.LockOutRealm">
                          <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                                 resourceName="UserDatabase"/>
                      </Realm>
          
                      <!--host來管理各個Servlet-->
                      <Host name="localhost"  appBase="webapps"
                            unpackWARs="true" autoDeploy="true">
                          <!--servlet-->
                          <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                                 prefix="localhost_access_log" suffix=".txt"
                                 pattern="%h %l %u %t "%r" %s %b" />
          
                      </Host>
                  </Engine>
              </Service>
          </Server>

          Service標簽包括Executor 、Connector、Engine、Host等子標簽。


          Executor標簽

          	<Executor 
                    name="commonThreadPool"  <!--指定當前線程的名字 相當于Id-->
                    namePrefix="thread-exec-" <!-- namePrefix 給線程指定前綴名字 -->
                    maxThreads="200"  <!--指定最大線程數 -->
                    minSpareThreads="100" <!-- 指定核心線程數 -->
                    maxIdleTime="60000" <!-- 指定線程空閑時間 超過該時間線程被自動銷毀單位是毫秒 -->
                    maxQueueSize="Integer.MAX_VALUE"  <!-- 阻塞隊列的大小最大值 -->
                    prestartminSpareThreads="false" <!--確定線程池時是否啟動核心線程-->
                    threadPriority="5" <!--線程中的優先級默認是5 取值范圍是1到10 -->
                    className="org.apache.catalina.core.StandardThreadExecutor" <!-- 自定義線程池類路徑 -->
                   />

          Executor主要作用是來創建一個線程池,用于連接器Connector使用。

          Connector標簽

          <Connector 
                  port="8080" <!--監聽的端口號--> 
                  protocol="HTTP/1.1"  
                  connectionTimeout="20000" <!--連接超時時間單位毫秒-->
                  redirectPort="8443" />

          protocol="HTTP/1.1": 當前Connector?持的訪問協議。 默認為 HTTP/1.1 ,并采??動切換機制選擇?個基于 JAVANIO 的鏈接器或者基于本地APR的鏈接器(根據本地是否含有Tomcat的本地庫判定)

          redirectPort="8443":當前Connector 不?持SSL請求, 接收到了?個請求, 并且也符合security-constraint 約束,需要SSL傳輸,Catalina?動將請求重定向到指定的端?。

          URIEncoding="UTF-8":?于指定編碼URI的字符編碼, Tomcat8.x版本默認的編碼為 UTF-8 , Tomcat7.x版本默認為ISO-8859-1。

          executor="commonThreadPool":指定共享線程池的名稱,也可以通過maxThreads、minSpareThreads 等屬性配置內部線程池。

          內部線程Demo

          	<Connector port="8080"
                     protocol="HTTP/1.1"
                     executor="commonThreadPool"
                     maxThreads="1000"
                     minSpareThreads="100"
                     acceptCount="1000"
                     maxConnections="1000"
                     connectionTimeout="20000"
                     compression="on"  <!--是否壓縮數據, on是開啟壓縮 off關閉-->
                     compressionMinSize="2048" <!--壓縮的最小容量-->
                     disableUploadTimeout="true" <!--是否允許Servlet容器,正在執行使用一個較長的連接超時-->
                     redirectPort="8443"
                     URIEncoding="UTF-8" />

          Engine 標簽

          	<Engine name="Catalina" defaultHost="localhost">
              ...
          	</Engine>

          name屬性: ?于指定Engine 的名稱, 默認為Catalina

          defaultHost:默認使?的虛擬主機名稱, 當客戶端請求指向的主機?效時, 將交由默認的虛擬主機處

          理, 默認為localhost。

          Host標簽

          <Host name="localhost2"  appBase="webapps2"
                unpackWARs="true" autoDeploy="true">
          
               <!--用來指定日志配置文件地址 valve-->
              <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                     prefix="localhost_access_log" suffix=".txt"
                     pattern="%h %l %u %t "%r" %s %b" />
          
          </Host>

          Host標簽屬于engine的子標簽,主要配置虛擬主機的。默認可以配置多個,appBase假如不配置context默認訪問ROOT文件下的應用。

          name:用于指定虛擬主機。

          appBase:對應的配置路徑

          unpackWARs:解壓war包默認為true

          autoDeploye:自動提交


          Context 標簽

          <Context docBase="/Users/demo/web_demo" path="/web3"></Context>

          Context屬于Host的子標簽,主要用來映射應用所在的文件,比如上邊的訪問uri是web3映射到 /Users/demo/web_demo文件夾下。

          docBase:Web應??錄或者War包的部署路徑。可以是絕對路徑,也可以是相對于Host appBase的相對路徑。

          path:Web應?的Context 路徑。如果我們Host名為localhost, 則該web應?訪問的根路徑為:localhost:8080/web3。

          3、手寫簡易版本Tomcat

          3.1分析

          手寫一個Tomcat名稱叫做Minicat,需求:可以接收瀏覽器的http請求,并進行請求處理,處理之后將結果返回給瀏覽器客戶端。

          1)提供服務,接收請求(Socket通信)

          2)請求信息封裝成Request對象(Response對象)

          3)客戶端請求資源,資源分為靜態資源和動態資源

          4)將資源返回給客戶端

          3.2 V1版本Code

          public class Bootstrap {
          
              //自定義端口號
              public static final int port=8080;
          
              //啟動方法
              private void start() throws IOException {
                  ServerSocket serverSocket=new ServerSocket(port);
                  System.out.println("啟動成功,端口號:" + port);
                  while (true) {
                      Socket accept=serverSocket.accept();
          
                      OutputStream outputStream=accept.getOutputStream();
                      String content="Hello Word !";
                      String retResult=HttpProtocolUtil.getHeader200(content.getBytes().length) + content;
                      outputStream.write(retResult.getBytes());
          
                      outputStream.close();
                  }
          
              }
          
              public static void main(String[] args) {
                  Bootstrap bootstrap=new Bootstrap();
                  try {
                      bootstrap.start();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }


          封裝相應頭工具類

          public class HttpProtocolUtil {
           /**
               * 獲取200響應結果
               */
           public static String getHeader200(int size){
                  return "HTTP/1.1 200 OK \n" +
                          "Content-Type: text/html \n" +
                          "Content-Length: " + size + " \n" +
                          "\r\n";
              }
          
           /**
               * 獲取404響應結果
               */
           public static String getHeader404(){
                  String str404="<p1>404 Not Found</p1>";
                  return "HTTP/1.1 404 Not Found \n" +
                          "Content-Type: text/html \n" +
                          "Content-Length: " + str404.length() + " \n" +
                          "\r\n" + str404;
              }
          }

          總結:V1版本的代碼比較簡單,就是簡單在頁面上寫輸入一個8080返回一個Hello Minicat但是需要注意在返回結果中必須添加上html響應頭信息瀏覽器才能顯示

          3.3 V2版本Code

          要求:能夠輸出靜態資源文件。

          3.3.1 觀察請求參數

          private void start() throws IOException {
              ServerSocket serverSocket=new ServerSocket(port);
              while (true){
                  Socket socket=serverSocket.accept();
                  InputStream inputStream=socket.getInputStream();
          
                  //防止網絡波動讀取不到參數
                  int count=0;
                  while (count==0) {
                      count=inputStream.available();
                  }
          
                  byte[] bytes=new byte[count];
                  inputStream.read(bytes);
                  System.out.println(new String(bytes));
          
                  socket.close();
              }
          }

          打印的參數

          GET / HTTP/1.1

          Host: localhost:8080

          Connection: keep-alive

          Cache-Control: max-age=0

          Upgrade-Insecure-Requests: 1

          User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36

          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

          Sec-Fetch-Site: none

          Sec-Fetch-Mode: navigate

          Sec-Fetch-User: ?1

          Sec-Fetch-Dest: document

          Accept-Encoding: gzip, deflate, br

          Accept-Language: zh-CN,zh;q=0.9

          只提取一些有用的請求信息,比如GET、/ 路徑信息、參數信息。來封裝成Request對象和Response對象。

          3.3.2完成V2版本的Code

          Bootstrap啟動類

          public class Bootstrap {
              private static final int port=8080;
          
              public static void main(String[] args) {
                  Bootstrap bootstrap=new Bootstrap();
                  try {
                      bootstrap.start();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          
           /**
               * V2.0版本
               */
           private void start() throws IOException {
                  ServerSocket serverSocket=new ServerSocket(port);
                  System.out.println("啟動成功");
          
                  while (true){
                      Socket socket=serverSocket.accept();
                      InputStream inputStream=socket.getInputStream();
                      OutputStream outputStream=socket.getOutputStream();
          
                      //獲取到輸入輸出對象
                      Request request=new Request(inputStream);
                      Response response=new Response(outputStream);
          
                      //將結果寫到輸出流中
                      response.outputHtml(request.getUrl());
          
                      //一定要把socket關閉
                      socket.close();
                  }
              }
          }

          Request對象

          @Data
          @NoArgsConstructor
          public class Request {
              //請求方式GET、POST
              private String methodType;
              //請求URL
              private String url;
              //輸入流
              private InputStream inputStream;
          
          
           /**
               * 構造器
               */
           public Request(InputStream inputStream) throws IOException {
                  this.inputStream=inputStream;
                  parseInputParam(inputStream);
              }
          
              //解析參數
              private void parseInputParam(InputStream inputStream) throws IOException {
                  //只提取第一行
                  int length=0;
                  while (length==0){
                      length=inputStream.available();
                  }
                  byte[] bytes=new byte[length];
                  inputStream.read(bytes);
                  String inputStr=new String(bytes);
                  //截取出來 GET / HTTP/1.1
                  String[] split=inputStr.split("\\n");
                  String[] infoArr=split[0].split(" ");
                  this.methodType=infoArr[0];
                  this.url=infoArr[1];
          
                  System.out.println("=====>>method:" + methodType);
                  System.out.println("=====>>url:" + url);
              }
          }

          Response響應對象

          @Data
          @AllArgsConstructor
          @NoArgsConstructor
          public class Response {
              private OutputStream outputStream;
          
              //V2版本只輸出靜態資源文件
              public void outputHtml(String path) throws IOException {
                  //獲取到文件的絕對路徑
                  String absolutePath=StaticResourceUtil.getAbsolutePath(path);
                  File file=new File(absolutePath);
                  if (file.exists() && file.isFile()) {
          
                      //調用工具類輸出文件
                      StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
                  }else{
                      //404 Not Found
                      output(HttpProtocolUtil.getHeader404());
                  }
              }
          
              //輸出文件
              private void output(String path) throws IOException {
                  outputStream.write(path.getBytes());
              }
          }


          靜態資源處理工具

          public class StaticResourceUtil {
          
           /**
               * 獲取到文件的絕對路徑并且替換\\為/方便linux識別
               */
           public static String getAbsolutePath(String path) {
                  //獲取到當前的絕對路徑
                  String absolutePath=StaticResourceUtil.class.getResource("/").getPath();
                  return (absolutePath + path).replaceAll("\\\\", "/");
              }
          
           /**
               * 根據輸入流 對文件進行輸出
               *
               * @param inputStream  輸入流
               * @param outputStream 輸出流
               */
           public static void outputStaticResource(InputStream inputStream, 
                                                         OutputStream outputStream) throws IOException {
                  //緩沖區
                  int buffer=1024;
                  int actualOutSize=0;
          
                  //獲取需要輸出文件流的長度
                  int outputSize=0;
                  while (outputSize==0){
                      outputSize=inputStream.available();
                  }
          
                  //輸出請求頭
                  outputStream.write(HttpProtocolUtil.getHeader200(outputSize).getBytes());
          
                  byte[] bytes=new byte[buffer];
                  while (actualOutSize < outputSize){
                      //如果最后不夠一個緩沖區的話需要獲取到最后的數據length
                      if (actualOutSize + buffer > outputSize) {
                          buffer=outputSize - actualOutSize;
                          bytes=new byte[buffer];
                      }
                      //從輸入流中讀取
                      inputStream.read(bytes);
                      //寫出到輸出流
                      outputStream.write(bytes);
                      //刷新輸出流
                      outputStream.flush();
                      actualOutSize +=buffer;
                  }
              }
          }

          總結: 底層使用的是JavaSocket編程,就是對應輸入和輸出流進行了封裝,截取了需要的信息。

          3.4 V3版本Code

          需求,能夠接收靜態和動態資源,使用線程池接收。

          基于V2版本Request和Response進行開發。

          添加Pom Dom4j用來配置servlet

          <dependencies>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.12</version>
              </dependency>
          
              <dependency>
                  <groupId>dom4j</groupId>
                  <artifactId>dom4j</artifactId>
                  <version>1.6.1</version>
              </dependency>
              <dependency>
                  <groupId>jaxen</groupId>
                  <artifactId>jaxen</artifactId>
                  <version>1.1.6</version>
              </dependency>
          </dependencies>


          web.xml配置文件

          <?xml version="1.0" encoding="UTF-8"?>
          <web-app>
          
              <servlet>
                  <servlet-name>myServlet</servlet-name>
                  <servlet-class>com.tomcat.demo.servlet.MyServlet</servlet-class>
              </servlet>
          
              <servlet-mapping>
                  <servlet-name>myServlet</servlet-name>
                  <url-pattern>/hello</url-pattern>
              </servlet-mapping>
          
          </web-app>

          Servlet

          public interface Servlet {
             //初始化
             void init() throws Exception;
             //銷毀
             void destroy() throws Exception;
             //執行請求
             void service(Request request, Response response) throws Exception;
          }
          public abstract class HttpServlet implements Servlet {
          
              public abstract void doGet(Request request, Response response)throws Exception;
              public abstract void doPost(Request request, Response response)throws Exception;
          
              @Override
              public void service(Request request, Response response) throws Exception {
                  if ("GET".equalsIgnoreCase(request.getMethodType())) {
                      doGet(request,response);
                  }else {
                      doPost(request,response);
                  }
              }
          }
          
          public class MyServlet extends HttpServlet {
          
              @Override
              public void doGet(Request request, Response response) throws Exception {
                  doPost(request,response);
              }
          
              @Override
              public void doPost(Request request, Response response) throws Exception {
                  String content="<H1>Hello Servlet</H1>";
                  String header200=HttpProtocolUtil.getHeader200(content.getBytes().length);
                  response.output(header200 + content);
              }
          
              @Override
              public void init() throws Exception {
                  System.out.println("初始化方法...");
              }
          
              @Override
              public void destroy() throws Exception {
                  System.out.println("銷毀方法...");
          
              }
          }

          BootStrap啟動類

          public class Bootstrap {
          
              private static final int port=8080;
              private Map<String, Servlet> servletMap=new HashMap<>();
          
          
              public static void main(String[] args) {
                  Bootstrap bootstrap=new Bootstrap();
                  try {
                      bootstrap.start();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          
           /**
               * 啟動tomcat
               */
           private void start() throws IOException {
                  ServerSocket serverSocket=new ServerSocket(port);
          
                  //解析web.xml
                  parseWebXml();
          
                  //創建一個線程池
                  ThreadPoolExecutor threadPool=new ThreadPoolExecutor(
                          20,//指定核心線程數
                          100,//指定最大線程數
                          100L,//指定存活時間
                          TimeUnit.MILLISECONDS,//指定時間格式
                          new LinkedBlockingDeque<>(1000));//設置阻塞隊列大小
          
                  while (true){
                      //獲取到socket
                      Socket socket=serverSocket.accept();
          
                      //通過線程池去執行
                      threadPool.execute(new RequestProcessor(socket, servletMap));
                  }
              }
          
              private void parseWebXml() {
                  try {
                      InputStream resourceAsStream=this.getClass().getResourceAsStream("/web-apps/WEB-INF/web.xml");
                      Document document=new SAXReader().read(resourceAsStream);
                      Element rootElement=document.getRootElement();
                      Element servletElement=(Element) rootElement.selectSingleNode("servlet");
                      String servletName=servletElement.selectSingleNode("servlet-name").getStringValue();
                      String servletClass=servletElement.selectSingleNode("servlet-class").getStringValue();
          
                      Element servletMapping=(Element) rootElement.selectSingleNode("servlet-mapping[servlet-name='" + servletName + "']");
                      String urlPattern=servletMapping.selectSingleNode("url-pattern").getStringValue();
          
                      servletMap.put(urlPattern, (Servlet) Class.forName(servletClass).newInstance());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          RequestProcessor請求處理類

          @Data
          @AllArgsConstructor
          @NoArgsConstructor
          public class RequestProcessor implements Runnable {
              private Socket socket;
              private Map<String, Servlet> servletMap;
          
              @Override
              public void run() {
                  try {
                      InputStream inputStream=socket.getInputStream();
                      OutputStream outputStream=socket.getOutputStream();
          
                      Request request=new Request(inputStream);
                      Response response=new Response(outputStream);
          
                      //動態資源
                      if (servletMap.containsKey(request.getUrl())) {
                          servletMap.get(request.getUrl()).service(request, response);
                      }else {
                          //靜態資源
                          response.outputHtml(request.getUrl());
                      }
          
                      //關閉當前socket
           socket.close();
                  }catch (Exception e){
                      e.printStackTrace();
                  }
              }
          }


          總結:書寫Mini版本的Tomcat感受一下Tomcat整體的處理思路。


          主站蜘蛛池模板: 色系一区二区三区四区五区| 日韩视频在线一区| 性色av一区二区三区夜夜嗨| 亚洲av无码一区二区三区四区| 3d动漫精品啪啪一区二区中文| 无码人妻精品一区二区三区久久 | 丰满岳妇乱一区二区三区| 国产香蕉一区二区三区在线视频 | 日韩综合无码一区二区| 成人免费一区二区三区| 中文字幕在线观看一区二区 | 在线精品亚洲一区二区小说| 激情内射日本一区二区三区| 精品国产一区二区三区免费| 久久国产精品视频一区| 亚洲午夜电影一区二区三区 | 国产精品视频一区| 国产亚洲福利精品一区二区| 精品无码中出一区二区| 国产成人片视频一区二区| 麻豆aⅴ精品无码一区二区| 国产日韩一区二区三区| 怡红院一区二区在线观看| 一区二区免费国产在线观看| 久久一区二区三区99| 日本午夜精品一区二区三区电影| 97精品一区二区视频在线观看| 国产成人欧美一区二区三区 | 中文字幕一区二区三区四区 | 中文字幕一区二区免费| 色欲综合一区二区三区| 精品无码国产AV一区二区三区| 国产精品一区二区av不卡| 无码免费一区二区三区免费播放| 3D动漫精品啪啪一区二区下载 | 91福利国产在线观一区二区| 午夜一区二区在线观看| 欧洲精品无码一区二区三区在线播放| 少妇无码一区二区三区| 精品一区二区三区在线播放| 日本一区二区不卡视频|