探秘Tomcat——一个简易的Servlet容器
即便再簡陋的服務(wù)器也是服務(wù)器,今天就來循著書本的第二章來看看如何實現(xiàn)一個servlet容器。
背景知識
既然說到servlet容器這個名詞,我們首先要了解它到底是什么。
servlet
相比你或多或少有所了解。servlet是用java編寫的服務(wù)器端程序,主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動態(tài)Web內(nèi)容。狹義的Servlet是指Java語言實現(xiàn)的一個接口,廣義的Servlet是指任何實現(xiàn)了這個Servlet接口的類,一般情況下,人們將Servlet理解為后者。
容器
容器的概念很大,在這里可以理解為能夠管理對象(servlet)的生命周期,對象與對象之間的依賴關(guān)系。
基于對以上兩個概念的解釋,那么對于serelvet容器的概念也就不再那么陌生了。
servlet容器
就是創(chuàng)建、管理servlet規(guī)范中相關(guān)對象、生命周期的應(yīng)用程序。
?
Servlet接口
servlet是一種編程規(guī)范,要實現(xiàn)servlet編程需要用到j(luò)avax.servlet和javax.servlet.http。所有的servlet程序都需要實現(xiàn)或繼承自實現(xiàn)了javax.servlet.servlet接口。
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
Servlet接口的方法
- init():servlet容器的初始化方法,該方法只會被調(diào)用一次;
- service():不同于init只會觸發(fā)一次,service在客戶端請求后就會被調(diào)用。同時需要傳入?yún)?shù)servletRequest和servletResponse。從字面意思就能知道,servletRequest攜帶了客戶端發(fā)送的HTTP請求的信息,而servletResponse則用于封裝servlet的響應(yīng)信息。
- destroy():當servlet實例調(diào)用完畢要被移除時,destroy方法將被調(diào)用。
- getServletConfig():該方法用于取得<servlet> <init-param>配置的參數(shù)
- getServletInfo():該方法提供有關(guān)servlet的信息,如作者、版本、版權(quán)。
?
servlet容器的職責(zé)
- 第一次調(diào)用servlet時,需要載入serlvet類并調(diào)用init方法;
- 針對客戶端的request請求,創(chuàng)建一個servletRequest對象和一個servletResponse對象;
- 傳參servletRequest和servletResponse,調(diào)用service方法;
- 當關(guān)閉servlet類時,調(diào)用destroy方法。
?
?
簡陋的servlet容器
之所以說是簡陋的servlet容器,因為這里并沒有實現(xiàn)servlet所有的方法,該容器只能支持很簡單的servlet,也沒有init方法和destroy方法。主要實現(xiàn)功能如下:
- 等待HTTP請求;
- 創(chuàng)建serlvetRequest和servletResponse對象;
- 能夠分別處理靜態(tài)資源和servlet,當客戶端請求靜態(tài)資源時,則調(diào)用StaticResourceProcessor對象的process方法;當請求為serlvet則載入請求的servlet類并調(diào)用service方法。
?
主要包括6個類
- HttpServer1:程序的入口,負責(zé)創(chuàng)建Request和Response對象,并根據(jù)HTTP請求類型將其轉(zhuǎn)給相應(yīng)的處理器處理;
- Request:用于封裝客戶端HTTP請求信息;
- Response:用于封裝服務(wù)器響應(yīng)信息;
- StaticResourceProcessor:靜態(tài)資源處理器;
- ServletProcessor1:servlet處理器;
- Constants:用于定義一些常量,如WEB_ROOT
?
HttpServer1
package day0522;import java.net.Socket; import java.net.ServerSocket; import java.net.InetAddress; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException;public class HttpServer1 {/** WEB_ROOT is the directory where our HTML and other files reside.* For this package, WEB_ROOT is the "webroot" directory under the working* directory.* The working directory is the location in the file system* from where the java command was invoked.*/// shutdown commandprivate static final String SHUTDOWN_COMMAND = "/SHUTDOWN";// the shutdown command receivedprivate boolean shutdown = false;public static void main(String[] args) {HttpServer1 server = new HttpServer1();server.await();}public void await() {ServerSocket serverSocket = null;int port = 8080;try {serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));}catch (IOException e) {e.printStackTrace();System.exit(1);}// Loop waiting for a requestwhile (!shutdown) {Socket socket = null;InputStream input = null;OutputStream output = null;try {socket = serverSocket.accept();input = socket.getInputStream();output = socket.getOutputStream();// create Request object and parseRequest request = new Request(input);request.parse();// create Response objectResponse response = new Response(output);response.setRequest(request);// check if this is a request for a servlet or a static resource// a request for a servlet begins with "/servlet/"if (request.getUri().startsWith("/servlet/")) {ServletProcessor1 processor = new ServletProcessor1();processor.process(request, response);}else {StaticResourceProcessor processor = new StaticResourceProcessor();processor.process(request, response);}// Close the socketsocket.close();//check if the previous URI is a shutdown commandshutdown = request.getUri().equals(SHUTDOWN_COMMAND);}catch (Exception e) {e.printStackTrace();System.exit(1);}}} }
從代碼可以看出,該類主要內(nèi)容與上篇的HttpServer類似,不同點有:
- await會一直等待HTTP請求,如果等到請求,該方法會根據(jù)請求類型分發(fā)給對應(yīng)的處理器來處理;
- 支持靜態(tài)資源的請求,可以通過類似http://localhost:8080/index.html這樣的請求來訪問
- index.html頁面;
- 支持servlet的請求和解析,可以通過類似http://localhost:8080/PrimitiveServlet來訪問PrimitiveServlet
?
Request與上篇介紹的Request無異,不再介紹,但是需要說明一點,這里的Request實現(xiàn)了ServletRequest接口。
package day0522;import java.io.InputStream; import java.io.IOException; import java.io.BufferedReader; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest;public class Request implements ServletRequest {private InputStream input;private String uri;public Request(InputStream input) {this.input = input;}public String getUri() {return uri;}private 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 + 1, index2);}return null;}public void parse() {// Read a set of characters from the socketStringBuffer request = new StringBuffer(2048);int i;byte[] buffer = new byte[2048];try {i = input.read(buffer);}catch (IOException e) {e.printStackTrace();i = -1;}for (int j=0; j<i; j++) {request.append((char) buffer[j]);}System.out.print(request.toString());uri = parseUri(request.toString());}/* implementation of the ServletRequest*/public Object getAttribute(String attribute) {return null;}public Enumeration getAttributeNames() {return null;}public String getRealPath(String path) {return null;}public RequestDispatcher getRequestDispatcher(String path) {return null;}public boolean isSecure() {return false;}public String getCharacterEncoding() {return null;}public int getContentLength() {return 0;}public String getContentType() {return null;}public ServletInputStream getInputStream() throws IOException {return null;}public Locale getLocale() {return null;}public Enumeration getLocales() {return null;}public String getParameter(String name) {return null;}public Map getParameterMap() {return null;}public Enumeration getParameterNames() {return null;}public String[] getParameterValues(String parameter) {return null;}public String getProtocol() {return null;}public BufferedReader getReader() throws IOException {return null;}public String getRemoteAddr() {return null;}public String getRemoteHost() {return null;}public String getScheme() {return null;}public String getServerName() {return null;}public int getServerPort() {return 0;}public void removeAttribute(String attribute) {}public void setAttribute(String key, Object value) {}public void setCharacterEncoding(String encoding)throws UnsupportedEncodingException {}@Override public int getRemotePort() {// TODO Auto-generated method stubreturn 0; }@Override public String getLocalName() {// TODO Auto-generated method stubreturn null; }@Override public String getLocalAddr() {// TODO Auto-generated method stubreturn null; }@Override public int getLocalPort() {// TODO Auto-generated method stubreturn 0; }}
Response
同理,這里的Response也不在贅述。
package day0522;import java.io.OutputStream; import java.io.IOException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.File; import java.io.PrintWriter; import java.util.Locale; import javax.servlet.ServletResponse; import javax.servlet.ServletOutputStream;public class Response implements ServletResponse {private static final int BUFFER_SIZE = 1024;Request request;OutputStream output;PrintWriter writer;public Response(OutputStream output) {this.output = output;}public void setRequest(Request request) {this.request = request;}/* This method is used to serve a static page */public void sendStaticResource() throws IOException {byte[] bytes = new byte[BUFFER_SIZE];FileInputStream fis = null;try {/* request.getUri has been replaced by request.getRequestURI */File file = new File(Constants.WEB_ROOT, request.getUri());fis = new FileInputStream(file);/*HTTP Response = Status-Line*(( general-header | response-header | entity-header ) CRLF)CRLF[ message-body ]Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF*/int ch = fis.read(bytes, 0, BUFFER_SIZE);while (ch!=-1) {output.write(bytes, 0, ch);ch = fis.read(bytes, 0, BUFFER_SIZE);}}catch (FileNotFoundException e) {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());}finally {if (fis!=null)fis.close();}}/** implementation of ServletResponse */public void flushBuffer() throws IOException {}public int getBufferSize() {return 0;}public String getCharacterEncoding() {return null;}public Locale getLocale() {return null;}public ServletOutputStream getOutputStream() throws IOException {return null;}public PrintWriter getWriter() throws IOException {// autoflush is true, println() will flush,// but print() will not.writer = new PrintWriter(output, true);return writer;}public boolean isCommitted() {return false;}public void reset() {}public void resetBuffer() {}public void setBufferSize(int size) {}public void setContentLength(int length) {}public void setContentType(String type) {}public void setLocale(Locale locale) {}@Override public String getContentType() {// TODO Auto-generated method stubreturn null; }@Override public void setCharacterEncoding(String charset) {// TODO Auto-generated method stub} }
這里的getWriter方法中新建了PrintWriter,其中第二個參數(shù)是一個boolean類型,表示是否啟動autoFlush。
?
StaticResourceProcessor
package day0522;import java.io.IOException;public class StaticResourceProcessor {public void process(Request request, Response response) {try {response.sendStaticResource();}catch (IOException e) {e.printStackTrace();}} }
看代碼可以看出:
該類相較上篇是新建的類,主要實現(xiàn)的方法有sendStaticResource,實際上這個方法在上篇中也有,只是直接放在Response中出現(xiàn),并在HttpServer中聲明調(diào)用,而這里是將兩種請求類型分別封裝成類。
?
ServletProcessor
package day0522;import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.io.File; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse;public class ServletProcessor1 {public void process(Request request, Response response) {String uri = request.getUri();String servletName = uri.substring(uri.lastIndexOf("/") + 1);URLClassLoader loader = null;try {// create a URLClassLoaderURL[] urls = new URL[1];URLStreamHandler streamHandler = null;File classPath = new File(Constants.WEB_ROOT);// the forming of repository is taken from the createClassLoader method in// org.apache.catalina.startup.ClassLoaderFactoryString repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;// the code for forming the URL is taken from the addRepository method in// org.apache.catalina.loader.StandardClassLoader class.urls[0] = new URL(null, repository, streamHandler);loader = new URLClassLoader(urls);}catch (IOException e) {System.out.println(e.toString() );}Class myClass = null;try {myClass = loader.loadClass(servletName);}catch (ClassNotFoundException e) {System.out.println(e.toString());}Servlet servlet = null;try {servlet = (Servlet) myClass.newInstance();servlet.service((ServletRequest) request, (ServletResponse) response);}catch (Exception e) {System.out.println(e.toString());}catch (Throwable e) {System.out.println(e.toString());}} }
從代碼看出:
- 該類只有一個方法process,接收Request和Response兩個參數(shù);
- 通過uri.substring來獲取請求的servlet名;
- 通過新建一個類加載器來裝載請求的servlet類,用的類加載器為java.net.URLClassLoader;
- 有了類加載器后,通過loadClass方法載入serlvet類;
- 創(chuàng)建一個載入類的實例,并調(diào)用其service方法。
?
至此,我們明白了:
- servlet容器會等待http請求;
- request負責(zé)封裝http請求信息;
- response負責(zé)封裝相應(yīng)信息;
- staticResourceProcessor負責(zé)靜態(tài)資源請求處理;
- servletProcessor負責(zé)servlet的請求處理;
- 一個簡易的servlet容器的運作原理。
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續(xù)關(guān)注我的文章,請掃描二維碼,關(guān)注JackieZheng的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優(yōu)質(zhì)文章。
友情贊助
如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續(xù)努力,寫出更好的文章^^。
1. 支付寶 2. 微信
轉(zhuǎn)載于:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ3.html
總結(jié)
以上是生活随笔為你收集整理的探秘Tomcat——一个简易的Servlet容器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高性能Cordova App开发学习笔记
- 下一篇: mysql数据库主从同步配置教程--数据