大飛帶你深入理解Tomcat(二)
作者:叩丁狼教育王一飛,高階講師。轉載請註明出處。
接上一篇,通過HttpServer,Request,Response個類的配合勉強可以處理瀏覽器發起的請求跟響應請求。但功能有點寒酸,只可以處理靜態網頁和404,本篇加入對servlet的支援,注意,僅僅是對servlet的簡單支援。
servlet回顧:
servlet是java web一個元件,是java動態網頁的基石,使用相對簡單,想深入學習的朋友可以騰訊課堂看任小龍老師傳送門:java大神之路java大神之路,這裡不累贅了,就回顧下servlet用法:
public class MyServlet implements Servlet{
public MyServlet() {
System.out.println("建立....");
}
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化....");
}
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
System.out.println("服務....");
}
public void destroy() {
System.out.println("銷燬....");
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
}
tomcat啟動後, 發起第一請求時,servlet執行順序
建立(構造器)----初始化(init)---[服務(service)] 迴圈----銷燬(destroy)
非第一次發起請求,直接呼叫serivce方法重複執行。
好,回顧到這,下面進入主題。
程式碼結構:
UML類圖(借用書中類圖):
相對上篇程式碼做改進:
0:建立一個常量類Consts, 持有專案中所有的靜態常量
/**
* 常量類
*/
public class Consts {
// tomcat專案絕對路徑, 所有web專案都丟在webapps目錄下
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webapps";
//請求404響應內容
public static final String RESPONSE_404_CONTENT = "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>";
//請求響應成功響應頭
public static final String RESPONSE_200_HEADER = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: #{count}\r\n" +
"\r\n";
}
1:Request遵循serlvet規範,實現ServletRequest介面
/**
* 請求資訊封裝物件
*/
public class Request implements ServletRequest{
// 瀏覽器socket連線的讀流
private InputStream in;
//請求行資訊資訊中的uri
private String uri;
public Request(InputStream in) {
this.in = in;
}
// 解析瀏覽器發起的請求
public void parseRequest() {
// 暫時忽略檔案上傳的請求,假設都字元型請求
byte[] buff = new byte[2048];
StringBuffer sb = new StringBuffer(2048);
int len = 0;
//請求內容
try {
len = in.read(buff);
sb.append(new String(buff, 0, len));
} catch (IOException e) {
e.printStackTrace();
}
System.out.print(sb.toString());
//解析請求行中uri資訊
uri = this.parseUri(sb.toString());
}
public String parseUri(String httpContent) {
//傳入的內容解析第一行的請求行即可:
//請求行格式: 請求方式 請求uri 協議版本 3個內容以空格隔開
int beginIndex = httpContent.indexOf(" ");
int endIndex;
if(beginIndex > -1) {
endIndex = httpContent.indexOf(" ", beginIndex + 1);
if(endIndex > beginIndex) {
return httpContent.substring(beginIndex, endIndex).trim();
}
}
return null;
}
public String getUri() {
return uri;
}
/**省略一堆目前暫時沒用到的ServletRequest需要實現的方法*/
public Object getAttribute(String name) {
return null;
}
//-------------------------------------------------
實現ServletRequest介面,需要重寫的方法一概不動,空實現。
2:Response遵循servlet規範,實現ServletResponse介面
/**
* 處理響應請求物件
*/
public class Response implements ServletResponse{
// 瀏覽器socket連線的寫流
private OutputStream out;
public Response(OutputStream out) {
this.out = out;
}
//跳轉
public void sendRedirect(String uri) {
File webPage = new File(Consts.WEB_ROOT, uri);
FileInputStream fis = null;
StringBuffer sb = new StringBuffer();
try {
//找得到頁面是
if(webPage.exists()&& webPage.isFile()) {
fis = new FileInputStream(webPage);
byte[] buff = new byte[2048];
int len = 0;
while( (len = fis.read(buff))!= -1) {
sb.append(new String(buff, 0, len));
}
String respHeader=Consts.RESPONSE_200_HEADER.replace("#{count}", sb.length()+"");
System.out.println(respHeader + sb);
out.write((respHeader + sb).getBytes());
}else {
//頁面找不到時
out.write(Consts.RESPONSE_404_CONTENT.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//重寫getWriter方法
public PrintWriter getWriter() throws IOException {
PrintWriter writer = new PrintWriter(out, true);
//設定響應頭,後續還會修改
writer.println(Consts.RESPONSE_200_HEADER);
return writer;
}
//--------------------------
僅僅重寫getWriter方法,其他方法空實現
3:響應處理分離:處理靜態請求使用staticSourceProcessor類,處理servlet使用ServletProcessor類,判斷依據uri中使用servlet字樣
/**
* 用於響應靜態檔案請求
*/
public class StaticSourceProcessor {
public void process(Request request, Response response) {
response.sendRedircet(request.getUri());
}
}
servlet類的處理有點麻煩,原因:servlet擺放在約定好的webapp目錄下,專案使用時,需要額外載入自定義的servlet的位元組碼到記憶體。
public class ServletProcessor {
public void process(Request request, Response response) {
String uri = request.getUri();
//從uri中獲取serlvet名稱
String servletName = uri.substring(uri.lastIndexOf("/")+1);
try {
//載入classpath路徑,預設使用webapps
//此處設一個限制,約束自定義的serlvet必須沒有包名,沒有為什麼,demo就不要那麼多要求
File classPath = new File(Consts.WEB_ROOT);
//轉換成url能識別的路徑, 簡單講加上file協議
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
URL url = new URL(repository);
//建立一個url類載入器,用於載入上面指定serlvetName的servlet類
URLClassLoader loader = new URLClassLoader(new URL[] {url});
//通過反射建立servlet類物件
Class myClass = loader.loadClass(servletName);
Servlet servlet= (Servlet) myClass.newInstance();
//使用servlet呼叫service方法,servlet處理完成
servlet.service(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:這裡做2個約定, 1:自定義的serlvet必須沒有包名,直接放在src目錄裡。 2:將編譯後的自定義位元組碼拷貝到webapps中等價於部署。
4:HttpServer類改造,實現響應分離
/**
* 模擬tomcat的核心類
*/
public class HttpServer {
// 模擬tomcat關閉命令
private static final String SHUTDOWN_CMD = "/SHUTDOWN";
private boolean shutdown = false;
//持續監聽埠
@SuppressWarnings("resource")
public void accept() {
ServerSocket serverSocket = null;
try {
// 啟動socket服務, 監聽8080埠,
serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("啟動myTomcat伺服器失敗:" + e.getMessage(), e);
}
// 沒接收到關閉命令前一直監聽
while (!shutdown) {
Socket socket = null;
InputStream in = null;
OutputStream out = null;
try {
// 接收請求
socket = serverSocket.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
// 將瀏覽器傳送的請求資訊封裝成請求物件
Request request = new Request(in);
request.parseRequest();
// 將相應資訊封裝相應物件
Response response = new Response(out);
//實現約定:servlet請求路徑必須以/servlet開頭,以servlet簡單類名結束
if(request.getUri().startsWith("/servlet")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}else {
//此處簡單響應一個靜態資原始檔
StaticSourceProcessor processor = new StaticSourceProcessor();
processor.process(request, response);
}
socket.close();
//如果是使用關閉命令,停止監聽退出
shutdown = request.getUri().equals(SHUTDOWN_CMD);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
public static void main(String[] args) {
new HttpServer().accept();
}
}
5:新增自定義的servlet類
HelloServlet:
在MyTomcat專案中加入servlet-api.jar依賴包,自定義HelloServlet實現Servlet介面,同時實現service方法,用於響應瀏覽器傳送的請求。
public class HelloServlet implements Servlet {
public void init(ServletConfig arg0) throws ServletException {}
//重寫service方法,響應請求
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello, servlet....");
}
public void destroy() {}
public ServletConfig getServletConfig() {return null; }
public String getServletInfo() {return null;}
}
再次強調, 這個類沒有包(package),編譯之後,應該將位元組碼放到webapps目錄下,否則報類找不到異常。
6:測試
執行HttpServer類,
在瀏覽器中輸入:http://localhost:8080/hello/index.html 進入靜態資源處理
在瀏覽器中輸入:http://localhost:8080/servlet/HelloServlet 進入靜態資源處理
相關文章
- 大飛帶你深入理解Tomcat(七)Tomcat
- 帶你深入理解和解剖 synchronizedsynchronized
- 深入理解 Tomcat (二) 從巨集觀上理解 Tomcat 元件及架構Tomcat元件架構
- 帶你深入理解傳遞引數
- 帶你深入理解 Flutter 中的字型“冷”知識Flutter
- 老司機帶你深入理解 Laravel 之 FacadeLaravel
- Android EventBus原始碼解析 帶你深入理解EventBusAndroid原始碼
- 玩轉Redis-老闆帶你深入理解分散式鎖Redis分散式
- 阿里大佬帶你,深入理解執行緒池底層原理阿里執行緒
- 帶你深入理解Android中的自定義屬性!!!Android
- 用例項帶你深入理解Java記憶體模型Java記憶體模型
- Tomcat深入淺出——Servlet(二)TomcatServlet
- 深入理解 GCD(二)GC
- 老當機帶你深入理解 Laravel 之驗證器下Laravel
- 從JAVA記憶體到垃圾回收,帶你深入理解JVMJava記憶體JVM
- 架構師帶你深入理解Java的介面和抽象類架構Java抽象
- 萬字常人帶你深入理解Zookeeper!為你的春招做好準備!
- tomcat深入學習(二)(1) ---- tomcat初始化Tomcat
- 曹工說Tomcat3:深入理解 Tomcat DigesterTomcat
- 萬字長文帶你深入理解netty!為你的春招做好準備!Netty
- 從程式碼生成說起,帶你深入理解 mybatis generator 原始碼MyBatis原始碼
- 帶你深入淺出理解深度學習(附資源打包下載)深度學習
- 《深入剖析Tomcat》讀書筆記(二)Tomcat筆記
- 帶你理解Lock鎖原理
- 帶你通俗理解httpsHTTP
- 老司機帶你深入理解 Laravel 中介軟體(全域性中介軟體)Laravel
- 重溫Java泛型,帶你更深入地理解它,更好的使用它!Java泛型
- 萬字長文帶你深入理解Kafka!為春招面試做好準備!Kafka面試
- 非同步(二):Generator深入理解非同步
- [譯]帶你理解 Async/awaitAI
- 3個框框帶你理解EventLoopOOP
- (圖解 HTTP)一篇文章帶你深入理解 IP、TCP 和 DNS圖解HTTPTCPDNS
- 一條update語句到底加了多少鎖?帶你深入理解底層原理
- 一篇文章帶你深入理解什麼是負載測試負載
- [深入理解Android卷二 全文-第二章]深入理解Java Binder和MessageQueueAndroidJava
- 九爺帶你瞭解Tomcat優化Tomcat優化
- 從基礎到高階,帶你深入瞭解和使用curl命令(二)
- 深入理解Java中的鎖(二)Java