在,我們已經充分了解了 HTTP 和 Socket 的關系,也了解了 HTTP 報文的格式,為了讓小伙伴能夠加深對這兩個概念的理解,本文我們來看看如何利用 Socket 模擬 HTTP 請求。如果小伙伴們對 HTTP 和 Socket 的關系、HTTP 報文格式尚不熟悉的話,可以參考前面的文章 Http 和 Socket 到底是哪門子親戚?。
由于 HTTP 是基于 TCP 協議的應用層協議,因此我們可以用更為底層的方式來訪問 HTTP 服務,即直接使用 Socket 完成 HTTP 的請求和響應。我們前面說過,HTTP 的任務就是完成數據的包裝, Socket 提供了網絡的傳輸能力,所以我們只需要按照 HTTP 報文的格式來組裝數據,然后利用 Socket 將數據發送出去,就能得到回應。
假設我現在有一個數據接口 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 的理解。
這個實際上也不難,但是要實現圖片的下載需要我們首先熟悉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 請求。這個請求包括請求的 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
當服務器接收到 HTTPS 請求后,它會將公鑰證書發送給客戶端。公鑰證書中包含了服務器的公鑰、服務器的域名、證書頒發機構、證書有效期等信息。
客戶端接收到證書后,會從中提取出服務器的公鑰。
客戶端接收到服務器的證書后,會對其進行驗證,以確保該證書是由可信任的證書頒發機構頒發的,并且證書中的域名和服務器的實際域名一致。
如果證書驗證失敗,客戶端會中斷連接。如果驗證通過,客戶端會生成一個用于會話的對稱密鑰。
客戶端生成一個用于會話的對稱密鑰。對稱密鑰是一種加密方式,它使用相同的密鑰進行加密和解密。這個密鑰只存在于客戶端和服務器之間,因此被稱為“對稱”。
客戶端使用服務器的公鑰對對稱密鑰進行加密,并將加密后的密鑰發送給服務器。在這個過程中,客戶端和服務器都知道對稱密鑰,但是只有客戶端知道對稱密鑰的值。
服務器使用私鑰對客戶端發送的加密密鑰進行解密,得到對稱密鑰。由于私鑰只在服務器端保存,因此只有服務器才能解密客戶端發送的加密密鑰,并得到對稱密鑰的值。
服務器和客戶端使用對稱密鑰進行加密和解密數據傳輸。這個對稱密鑰只存在于客戶端和服務器之間,因此對數據的加密和解密只有客戶端和服務器可以進行。
HTTPS 的加密過程基于公鑰密碼學和對稱密鑰密碼學。
在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,而對稱密鑰密碼學用于加密和解密數據傳輸。
公鑰密碼學是一種密碼學技術,它使用一對密鑰(公鑰和私鑰)來加密和解密數據。公鑰可以被任何人獲得并用于加密數據,但是只有私鑰的擁有者才能解密數據。
在 HTTPS 的加密過程中,服務器使用公鑰加密對稱密鑰,客戶端使用私鑰解密對稱密鑰。這樣就可以保證對稱密鑰在傳輸過程中不會被竊取、篡改或冒充。
對稱密鑰密碼學是一種密碼學技術,它使用相同的密鑰進行加密和解密數據。在 HTTPS 的加密過程中,客戶端和服務器都知道對稱密鑰的值,因此可以使用對稱密鑰對數據進行加密和解密。
對稱密鑰密碼學的優點是加密和解密速度快,但是對稱密鑰的安全傳輸是一個問題。因此,在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,保證數據在傳輸過程中不會被竊取、篡改或冒充。
HTTPS 加密是保證數據傳輸安全的關鍵。在 HTTPS 的加密過程中,公鑰密碼學用于保護對稱密鑰的安全傳輸,對稱密鑰密碼學用于加密和解密數據傳輸。
HTTPS 加密的過程中,客戶端和服務器之間建立了一個安全的通信通道。在這個通道中,數據被加密傳輸,確保了數據在傳輸過程中的機密性、完整性和真實性。
在今天的互聯網時代,數據安全越來越受到重視。HTTPS 加密作為一種保證數據傳輸安全的技術,已經成為現代互聯網通信的標準之一。通過了解 HTTPS 加密的工作原理,我們可以更好地理解互聯網通信的安全性,并且能夠更好地保護自己的數據安全。
Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器,屬于輕量級應用服務器,在中小型系統和并發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程序的首選。對于一個初學者來說,可以這樣認為,當在一臺機器上配置好Apache 服務器,可利用它響應HTML(標準通用標記語言下的一個應用)頁面的訪問請求。實際上Tomcat是Apache 服務器的擴展,但運行時它是獨立運行的,所以當你運行tomcat 時,它實際上作為一個與Apache 獨立的進程單獨運行的。
目錄說明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工作相關的信息
不同計算機上應用之間的通信,來解決通信雙方數據傳輸的問題。或者說不同計算機上對應的應用進程之間的通信。支持的協議有: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等)【在四層模型里面已經合并到了數據鏈路層】
http請求處理過程
第2步是先建立連接,進行3次握手。
因為Tomcat可以處理Http請求,因此稱Tomcat為Http服務器。
如果我們直接從瀏覽器發送請求,Tomcat直接接收請求,并將請求轉發到對應的java程序的話,也可以實現請求處理,但是耦合度非常高,因此Tomcat的做法是在接收到http請求之后,將請求轉發給servlet容器,由servlet容器在進行處理并轉發到對應的java程序。
因此在Tomcat內部也實現了Servlet規范。(Servlet接?和Servlet容器這?整套內容叫作Servlet規范。)
因此Tomcat有兩個身份:
1、Http服務器
2、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相應到瀏覽器
Tomcat中包括兩大核心組件:
連接器(Connector)組件,容器(Container)組件。(還包括其他組件)
連接器(Connector)組件主要作用:處理Socket連接,負責?絡字節流與Request和Response對象的轉化。【與客戶端交互】
容器(Container)組件主要作用:加載和管理Servlet,處理Request請求;
Coyote是Tomcat中連接器的組件名稱,是對應的接口。客戶端通過Coyote與服務器建立連接、發送請求并接受響應。
1)Coyote封裝了底層網絡通信(Socket請求及相應處理)
2)Coyote使Container容器組件與具體的請求協議及IO操作?式完全解耦
3)Coyote將Socket輸入轉換封裝為Request對象,并進一步封裝成ServletRequest交給Container容器進行處理,處理完成后返回ServletResponse給Coyote,Coyote將對象轉換成Response對象將結果寫入輸出流。
4)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的性能。
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。
本來Catalina組件只是Servlet容器的一個組件,而Tomcat是由一些列組件組成,組件可以在conf/server.xml文件中配置。Catalina在Tomcat中的地位非常的核心,因此經常把tomcat的一個實例當作一個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?戶的模塊。
Container組件其中包括Engine、Host、Context、Wrapper4種組件,他們之間是父子關系。Tomcat通過分層的架構,讓Servlet容器具有很好的靈活性。
Engine:表示整個Servlet容器的引擎,用來管理多個虛擬站點,一個Service只能有一個Engine。
Host:代表一個虛擬主機,或者說一個站點,可以配置多個虛擬主機地址,一個Engine可以有多個Host。
Context:表示一個Web應用服務器,一個Host下可以包含多個Context。
Wrapper:表示一個Servlet, 一個Context中可以包括多個Wrapper。
思考:
去哪兒配置?->conf/server.xml中
怎么配置?
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>
<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
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
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 name="Catalina" defaultHost="localhost">
...
</Engine>
name屬性: ?于指定Engine 的名稱, 默認為Catalina
defaultHost:默認使?的虛擬主機名稱, 當客戶端請求指向的主機?效時, 將交由默認的虛擬主機處
理, 默認為localhost。
<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 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。
手寫一個Tomcat名稱叫做Minicat,需求:可以接收瀏覽器的http請求,并進行請求處理,處理之后將結果返回給瀏覽器客戶端。
1)提供服務,接收請求(Socket通信)
2)請求信息封裝成Request對象(Response對象)
3)客戶端請求資源,資源分為靜態資源和動態資源
4)將資源返回給客戶端
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響應頭信息瀏覽器才能顯示
要求:能夠輸出靜態資源文件。
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對象。
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();
}
}
}
@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);
}
}
@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編程,就是對應輸入和輸出流進行了封裝,截取了需要的信息。
需求,能夠接收靜態和動態資源,使用線程池接收。
基于V2版本Request和Response進行開發。
<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>
<?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>
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("銷毀方法...");
}
}
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();
}
}
}
@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整體的處理思路。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。