教你動手實現Tomcat,快速從JavaSE切換到javaweb學習!
技術標籤:Javahttpjavanginxlinuxtomcat
對於JavaEE的初學者來說,大家學完JAVASE之後,馬上進入了WEB階段的學習。大家在JAVASE階段寫程式碼時,從頭到尾都是自己寫的,到了WEB階段,尤其是進入Servlet的學習階段,有人會感到困惑,怎麼我寫的程式碼看不到main函數了,服務端的Servlet是個什麼東東呢?tomcat伺服器到底底層做了哪些事情呢?為了幫助大家更好的理解tomcat伺服器,也為了幫助大家更好的步入WEB的學習,我們特意安排了本次課程,動手實現Tomcat。我們自己手動的方式搭建一個Tomcat,讓大家能夠順利的從JAVASE切換到JAVAWEB階段的學習。
我們要實現一個Tomcat需要一些技術鋪墊,這些技術分別是http協議和JavaSe的Socket程式設計。我們按照以下的順序進行我們本次課程的學習。
-
HTTP協議
-
JavaSe的Socket程式設計
-
動手搭建Tomcat伺服器
1.HTTP協議
1.1.HTTP協議概述
1.1.1協議
約定規則
1.1.2.網路協議
資料在網路上傳輸規則 http,pop3,pop,imap,ftp,流媒體協議
1.1.3.HTTP協議
htyper text transform protocal
超文字傳輸協議:如何在網際網路上傳輸超文字
HTML:超文字標記語言 htyper text markup language
1.1.4.HTTP協議格式
由於我們平時訪問網際網路上的網頁都是請求之後才響應.所以HTTP協議
基於請求-響應模型. 協議分為請求部分,響應部分.
1.1.5.HTTP協議分為請求部分,響應部分.
*_請求部分格式
請求行 請求頭 請求體
*_響應部分格式:
響應行 響應頭 響應體
1.2.請求部分(請求行/請求頭/請求體)
POST /xxx/xxxx/xxx.png http/1.1
K1:v1
K2:v2
K3:v3
K4:v4
Username=tom&password=1234
1.2.1.請求行(請求方式 本次請求路徑 協議/版本)
POST /index.html http/1.1(換行)
請求方式(POST/GET,一共設計7個方法)
本次請求哪些內容
本次請求採用的協議以及協議版本
1.2.2.請求頭
作用:1告訴伺服器對客戶端描述2對本次請求描述
格式
K1:v1(換行)
K2:v2(換行)
K3:v3(換行)
空行
1.2.3.觀察HTTP協議請求頭
火狐瀏覽器下訪問百度圖片.
利用火狐===>工具===>web開發者===>網路 觀察本次請求過程
Host:"ss0.bdstatic.com"
User-Agent:"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0"
Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
Accept-Language:"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"
Accept-Encoding:"gzip, deflate, br"
Connection:"keep-alive"
Host: 本次請求的主機路徑
User-Agent:告訴服務端本次請求客戶端所在的平臺以及本次請求採用的瀏覽器百度中搜索:網速測試
Accept:用於指定客戶端接受哪些型別的資訊.eg:Accept:image/gif,表明客戶端希望接受
GIF圖象格式的資源;Accept:text/html,表明客戶端希望接受html文字.
Accept-Language:告訴服務端,瀏覽器可以識別的語言種類
Accept-Encoding:告訴服務端,瀏覽器可以哪些型別壓縮格式資料 gzip,defalte
1.2.4.請求體(約定使用者的表單資料向服務端傳遞格式)
<form action=”http://www.baidu.com/aaa” method=”GET”>
<input type=”text” name=”username”/>
<input type=”password” name=”password”/>
<input type=”submit” value=”提交”/>
</form>
上面的表單中,當我們錄入資料之後,點選提交按鈕,資料以下述方式進行提交
GET /aaa?username=tom&pasword=123 http/1.1
請求頭
請求頭
請求頭
請求頭
空行
<form action=”www.baidu.com/aaa” method=”POST”>
<input type=”text” name=”username”/>
<input type=”password” name=”password”/>
<input type=”submit” value=”提交”/>
</form>
上面的表單中,當我們錄入資料之後,點選提交按鈕,資料以下述方式進行提交
POST /aaa http/1.1
請求頭
請求頭
請求頭
請求頭
username=tom&pasword=123
[請求體作用]存放客戶端向服務端傳遞的資料
username=tom&userage=18&password=1234
Get /index.html?username=tom&password=123 http/1.1
請求頭
請求頭
空行
POST /index.html http/1.1
請求頭
請求頭
usernam=tom&password=1234
1.3.響應部分(響應行/響應頭/響應體)
[響應部分格式]
HTTP/1.1 200 OK(換行) K1:v1(換行) K2:v2(換行) K3:v3(換行) 空行 <html> XXXX..... </html>
1.3.1.響應行
HTTP/1.1 200 OK(回車換行)
本次響應採用的協議
狀態碼:協議設計之初,用一些數字描述了本次響應
狀態描述:用文字本次響應進行簡短描述
狀態程式碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示資訊--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操作
4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
5xx:伺服器端錯誤--伺服器未能實現合法的請求
常見狀態程式碼,狀態描述說明:
200 OK //客戶端請求成功
404 Not Found //請求資源不存在,eg:輸入了錯誤的URL"
1.3.2.響應頭
作用:
1.服務端告訴瀏覽器服務端資訊
2.對本次響應描述
格式:
K1:v1
K2:v2
K3:v3
空行
響應體
1.3.3.分析響應頭
Date: Fri, 21 Apr 2017 02:04:54 GMT Content-Type: text/html Connection: keep-alive Content-Encoding:gzip Content-length Server
Date:響應時間
content-Type:本次響應內容型別
content-Encoding:本次內容採用的壓縮格式
content-length:本次內容長度
server:服務端採用的伺服器型別
1.3.4.響應體
服務端響應到客戶端部分,可能是HTML頁面,JS檔案,CSS檔案,圖片
2.Socket程式設計
關於Socket的基礎知識我們不在贅述,Socket屬於JAVASE的基礎API。我們主要結合2段程式來驗證HTTP協議傳輸規則。
2.1.案例一
2.1.1.案例需求
模擬瀏覽器向服務端起HTTP請求,接受來自服務端響應回客戶端的資料
2.1.2.圖形分析
2.1.3.程式碼實現
public class TestClient { public static void main(String[] args) throws Exception { Socket s=null; OutputStream ops=null; InputStream is=null; try { s=new Socket("www.itcast.cn",80); ops=s.getOutputStream(); ops.write("GET /subject/about/index.html HTTP/1.1\n".getBytes()); ops.write("HOST:www.itcast.cn\n".getBytes()); ops.write("\n".getBytes()); is=s.getInputStream(); int i=is.read(); while(i!=-1){ System.out.print((char)i); i=is.read(); } } catch (Exception e) { e.printStackTrace(); }finally { if(null!=is) { is.close(); is=null; } if(null!=ops) { ops.close(); ops=null; } if(null!=s) { s.close(); s=null; } } } }
2.1.4.執行結果
如果我們通過瀏覽器訪問
http://www.itcast.cn/subject/about/index.html
之後再頁面中右擊...>檢視頁面原始碼,HTML頁面原始碼中的內容和我們響應到客戶端的響應體部分是一致的。
2.2.案例二
2.2.1.案例需求
從瀏覽器的位址列輸入localhost:8080 ,向本機的8080埠發起請求,服務端向客戶端響應回去HTTP協議的響應部分
2.2.2.圖形分析
2.2.3.程式碼實現
public class TestServer { //localhost:8080 public static void main(String[] args) throws IOException { ServerSocket ss =null; Socket socket =null; OutputStream ops=null; try { ss= new ServerSocket(8080); while (true) { socket= ss.accept(); ops=socket.getOutputStream(); ops.write("HTTP/1.1 200 ok\n".getBytes()); ops.write("Content-Type:text/html;charset-utf-8\n".getBytes()); ops.write("Server:Apache-Coyote/1.1\n".getBytes()); ops.write("\n\n".getBytes()); StringBuffer buffer=new StringBuffer(); buffer.append("<html>"); buffer.append("<head>"); buffer.append("<title>"); buffer.append("我是標題"); buffer.append("</title>"); buffer.append("</head>"); buffer.append("<body>"); buffer.append("<h1>I am header </h1>"); buffer.append("<a href='http://www.baidu.com'>www.baidu.com</a>"); buffer.append("</body>"); buffer.append("</html>"); ops.write(buffer.toString().getBytes()); ops.flush(); } } catch (Exception e) { e.printStackTrace(); }finally { if(null!=ops) { ops.close(); ops=null; } if(null!=socket) { socket.close(); socket=null; } } } }
2.2.4.執行結果
3.實現Tomcat版本一
3.1.案例需求
-
在WebContent下發布靜態資源demo.html , demo02.html
-
啟動Tomcat伺服器
-
當客戶端對服務端發起不同的請求,localhost:8080/demo.html
-
服務端可以將對應的html頁面響應到客戶端
3.2.圖形分析
"
3.3.程式碼實現
3.3.1.實現服務端的準備工作
-
定義靜態變數WEB.ROOT,用於存放WebContent目錄的絕對路徑
-
定義靜態變數url,存放本次請求服務端的靜態資源的名稱
public class TestServer{ //獲取到專案下的WebContent目錄 public static final String WEB.ROOT =System.getProperty("user.dir") + "\\" + "WebContent"; // 代表客戶端要請求資源的路徑 public static String url = ""; public static void main(String[] args) {} }
3.3.2.實現啟動服務端程式碼
-
建立ServerSocket物件,監聽本機8080埠
-
等待來自客戶端的請求
public static void main(String[] args) { //System.out.println(WEB.ROOT); try { // 建立不本機器的ServerSocket,監聽本機器的80埠 ServerSocket serverSocket = new ServerSocket(8080); while (true) { Socket socket = serverSocket.accept(); } } catch (Exception e) { e.printStackTrace(); } }
3.3.3.實現服務端解析HTTP請求部分請求部分
-
讀取HTTP協議請求部分資料
-
解析請求行,獲取本次請求的靜態資源名稱
public static void main(String[] args) { //System.out.println(WEB.ROOT); try { // 建立不本機器的ServerSocket,監聽本機器的80埠 ServerSocket serverSocket = new ServerSocket(8080); while (true) { Socket socket = serverSocket.accept(); //服務端的輸入流 InputStream input = socket.getInputStream(); //服務端的輸出流 OutputStream output = socket.getOutputStream(); //獲取HTTP協議的請求部分,擷取客戶端要訪問的資源名稱, //將這個資源名稱賦值給url parse(input); } } catch (Exception e) { e.printStackTrace(); } }
public static void parse(InputStream input) throws IOException { StringBuffer content = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; // 讀取客戶端傳送過來的資料,將資料讀取到位元組陣列buffer中. //i代表讀取資料量的大小 311位元組 i = input.read(buffer); System.out.println(buffer); for (int j = 0; j < i; j++) { content.append((char) buffer[j]); } // 列印讀取到的內容 System.out.print(content.toString()); // 擷取客戶端要請求的資源路徑 demo.html String uri = parseUri(content.toString()); // 賦值給本類中靜態成員 url = uri; System.out.println("uri..............:"+uri); }
// 擷取客戶端請求資源的路徑+名稱 /demo.html public static String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1+2, index2); } return null; }
3.3.4.實現服務端向客戶端傳送HTTP協議響應部分
-
通過輸入流讀取靜態資源到服務端記憶體
-
將HTTP協議響應部分到客戶端
public static void sendStaticResource(OutputStream output) throws IOException { byte[] bytes = new byte[2048]; FileInputStream fis = null; try { //cc.jpeg File file = new File(TestServer2.WEB.ROOT, url); if (file.exists()) { output.write("HTTP/1.1 200 OK\n".getBytes()); output.write("Server: Apache-Coyote/1.1\n".getBytes()); output.write("Content-Type: text/html;charset=UTF-8\n" .getBytes()); output.write("\n".getBytes()); fis = new FileInputStream(file); int ch = fis.read(bytes); while (ch != -1) { output.write(bytes, 0, ch); ch = fis.read(bytes); } } else { // file not found String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } } catch (Exception e) { // thrown if cannot instantiate a File object e.printStackTrace(); System.out.println(e.toString()); } finally { if (fis != null) fis.close(); } }
4.實現Tomcat版本二
4.1.案例需求
當客戶端傳送請求到服務端的時候,可以執行服務端的一段JAVA程式碼,而且可以向客戶端響應資料
4.2.圖形分析
4.3.程式碼實現
4.3.1.定義一個介面Servlet
該介面是所有在服務端執行的JAVA小程式都要遵循的介面.
public interface Servlet { public void init(); public void service(InputStream is,OutputStream ops)throws IOException; public void destroy(); }
4.3.2.實現服務端的JAVA小程式 AAServlet,BBServlet
AAServlet.java
public class AAServlet implements Servlet{ @Override public void init() { System.out.println("aa...init....."); } @Override public void service(InputStream is, OutputStream ops) throws IOException { System.out.println("AA service..."); ops.write("I am from Server...AA".getBytes()); ops.flush(); } @Override public void destroy() { System.out.println("aa...destroy....."); } }
BBServlet.java
public class BBServlet implements Servlet{ @Override public void init() { System.out.println("bb...init....."); } @Override public void service(InputStream is, OutputStream ops) throws IOException { System.out.println("BB service..."); ops.write("I am from Server...BB".getBytes()); ops.flush(); } @Override public void destroy() { System.out.println("bb...destroy....."); } }
4.3.3.為服務端的2端JAVA小程式配置引數
conf.properties
aa=com.itcast.tomcateV2.AAServlet bb=com.itcast.tomcateV2.BBServlet
4.3.4.伺服器啟動就讀取配置引數
在TestServler中定義一個靜態型別的MAP,用於存放配置檔案中的引數資訊
public static Map<String,String> map=new HashMap<String,String>();
public static void readConf() throws Exception{ Properties p=new Properties(); p.load(new FileInputStream(TestServer.WEB.ROOT+"\\conf.properties")); Set keySet = p.keySet(); Iterator iterator = keySet.iterator(); while(iterator.hasNext()) { String key=(String)iterator.next(); String value = p.getProperty(key); map.put(key, value); } }
4.3.5.在版本一的基礎上實現靜態動態資源的響應
public static void sendDynamicResource(InputStream is,OutputStream ops) throws Exception{ ops.write("HTTP/1.1 200 OK\n".getBytes()); ops.write("Server:Apache-Coyote/1.1\n".getBytes()); ops.write("Content-Type:text/html;charset=UTF-8\n".getBytes()); ops.write("\n".getBytes()); if(map.containsKey(url)) { String value=map.get(url); Class clazz=Class.forName(value); Servlet ss=(Servlet)clazz.newInstance(); ss.service(is, ops); } }
五.總結
通過手動實現一個Tomcat,我們可以發現執行在服務端的JAVA小程式AAServlet或者是BBServlet本質上還是一段JAVA小程式,只不過我們只需要按照約定實現Servlet這個介面,只需要做好對應的配置資訊,那麼我們就可以通過瀏覽器向服務端傳送請求,讓服務端的這個JAVA小程式也就是AAServlet或者BBServlet小程式進行執行。最末,希望大家通過本案例可以對Tomcat有一個粗略的認識,可以順利的過度到Servlet的學習以及JavaWeb階段的學習,祝願大家天天開心!
動手實現Tomcat
完整視訊:http://yun.itheima.com/course/445.html?2010stt
配套資料:https://pan.baidu.com/s/1nmH8U4g_rNPWkX11WBhOfA 提取碼:7i9o