基于Java web服務(wù)器簡(jiǎn)單實(shí)現(xiàn)一個(gè)Servlet容器
上篇寫了一個(gè)簡(jiǎn)單的Java web服務(wù)器實(shí)現(xiàn),只能處理一些靜態(tài)資源的請(qǐng)求,本篇文章實(shí)現(xiàn)的Servlet容器基于前面的服務(wù)器做了個(gè)小改造,增加了Servlet請(qǐng)求的處理。
程序執(zhí)行步驟
1.創(chuàng)建一個(gè)ServerSocket對(duì)象;
2.調(diào)用ServerSocket對(duì)象的accept方法,等待連接,連接成功會(huì)返回一個(gè)Socket對(duì)象,否則一直阻塞等待;
3.從Socket對(duì)象中獲取InputStream和OutputStream字節(jié)流,這兩個(gè)流分別對(duì)應(yīng)request請(qǐng)求和response響應(yīng);
4.處理請(qǐng)求:讀取InputStream字節(jié)流信息,轉(zhuǎn)成字符串形式,并解析,這里的解析比較簡(jiǎn)單,僅僅獲取uri(統(tǒng)一資源標(biāo)識(shí)符)信息;
5.處理響應(yīng)(分兩種類型,靜態(tài)資源請(qǐng)求響應(yīng)或servlet請(qǐng)求響應(yīng)):如果是靜態(tài)資源請(qǐng)求,則根據(jù)解析出來(lái)的uri信息,從WEB_ROOT目錄中尋找請(qǐng)求的資源資源文件, 讀取資源文件,并將其寫入到OutputStream字節(jié)流中;如果是Servlet請(qǐng)求,則首先生成一個(gè)URLClassLoader類加載器,加載請(qǐng)求的servlet類,創(chuàng)建servlet對(duì)象,執(zhí)行service方法(往OutputStream寫入響應(yīng)的數(shù)據(jù));
6.關(guān)閉Socket對(duì)象;
7.轉(zhuǎn)到步驟2,繼續(xù)等待連接請(qǐng)求;
代碼實(shí)現(xiàn):
添加依賴:
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.3</version> </dependency>
服務(wù)器代碼:
package ex02.pyrmont.first; import java.net.Socket; import java.net.ServerSocket; import java.net.InetAddress; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import ex02.pyrmont.Request; import ex02.pyrmont.Response; import ex02.pyrmont.StaticResourceProcessor; public class HttpServer1 { // 關(guān)閉服務(wù)命令 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; public static void main(String[] args) { HttpServer1 server = new HttpServer1(); //等待連接請(qǐng)求 server.await(); } public void await() { ServerSocket serverSocket = null; int port = 8080; try { //服務(wù)器套接字對(duì)象 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); } // 循環(huán)等待請(qǐng)求 while (true) { Socket socket = null; InputStream input = null; OutputStream output = null; try { //等待連接,連接成功后,返回一個(gè)Socket對(duì)象 socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // 創(chuàng)建Request對(duì)象并解析 Request request = new Request(input); request.parse(); // 檢查是否是關(guān)閉服務(wù)命令 if (request.getUri().equals(SHUTDOWN_COMMAND)) { break; } // 創(chuàng)建 Response 對(duì)象 Response response = new Response(output); response.setRequest(request); if (request.getUri().startsWith("/servlet/")) { //請(qǐng)求uri以/servlet/開(kāi)頭,表示servlet請(qǐng)求 ServletProcessor1 processor = new ServletProcessor1(); processor.process(request, response); } else { //靜態(tài)資源請(qǐng)求 StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // 關(guān)閉 socket socket.close(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } }
常量類:
package ex02.pyrmont; import java.io.File; public class Constants { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes"; }
Request:
package ex02.pyrmont; 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; } /** * * requestString形式如下: * GET /index.html HTTP/1.1 * Host: localhost:8080 * Connection: keep-alive * Cache-Control: max-age=0 * ... * 該函數(shù)目的就是為了獲取/index.html字符串 */ 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; } //從InputStream中讀取request信息,并從request中獲取uri值 public void parse() { // Read a set of characters from the socket StringBuffer 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 { } }
Response:
package ex02.pyrmont; 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; } //將web文件寫入到OutputStream字節(jié)流中 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) { } }
靜態(tài)資源請(qǐng)求處理:
package ex02.pyrmont; import java.io.IOException; public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } }
Servlet請(qǐng)求處理:
package ex02.pyrmont.first; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import ex02.pyrmont.Constants; import ex02.pyrmont.Request; import ex02.pyrmont.Response; public class ServletProcessor1 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); //類加載器,用于從指定JAR文件或目錄加載類 URLClassLoader loader = null; try { URLStreamHandler streamHandler = null; //創(chuàng)建類加載器 loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler)}); } catch (IOException e) { System.out.println(e.toString()); } Class<?> myClass = null; try { //加載對(duì)應(yīng)的servlet類 myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { //生產(chǎn)servlet實(shí)例 servlet = (Servlet) myClass.newInstance(); //執(zhí)行ervlet的service方法 servlet.service((ServletRequest) request,(ServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } }
Servlet類:
import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter; public class PrimitiveServlet implements Servlet { public void init(ServletConfig config) throws ServletException { System.out.println("init"); } public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println("from service"); PrintWriter out = response.getWriter(); out.println("Hello. Roses are red."); out.print("Violets are blue."); } public void destroy() { System.out.println("destroy"); } public String getServletInfo() { return null; } public ServletConfig getServletConfig() { return null; } }
結(jié)果測(cè)試:
靜態(tài)資源請(qǐng)求:
servlet請(qǐng)求(因?yàn)橹皇堑谝粋€(gè)字符串被刷新到瀏覽器,所以你不能看到第二個(gè)字符串Violets are blue。我們將在后續(xù)完善該容器):
改進(jìn)
前面實(shí)現(xiàn)的Servlet容器有一個(gè)嚴(yán)重的問(wèn)題,用戶在servlet里可以直接將ServletRequest、ServletResponse向下轉(zhuǎn) 型為Request和Response類型,并直接調(diào)用其內(nèi)部的public方法,這是一個(gè)不好的設(shè)計(jì),改進(jìn)方法是給Request、Response 增加外觀類,這樣,用戶只能訪問(wèn)外觀類里定義的public方法。
Request外觀類
package ex02.pyrmont.second; 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; import ex02.pyrmont.Request; public class RequestFacade implements ServletRequest { private ServletRequest request = null; public RequestFacade(Request request) { this.request = request; } /* implementation of the ServletRequest */ public Object getAttribute(String attribute) { return request.getAttribute(attribute); } public Enumeration<?> getAttributeNames() { return request.getAttributeNames(); } @SuppressWarnings("deprecation") public String getRealPath(String path) { return request.getRealPath(path); } public RequestDispatcher getRequestDispatcher(String path) { return request.getRequestDispatcher(path); } public boolean isSecure() { return request.isSecure(); } public String getCharacterEncoding() { return request.getCharacterEncoding(); } public int getContentLength() { return request.getContentLength(); } public String getContentType() { return request.getContentType(); } public ServletInputStream getInputStream() throws IOException { return request.getInputStream(); } public Locale getLocale() { return request.getLocale(); } public Enumeration<?> getLocales() { return request.getLocales(); } public String getParameter(String name) { return request.getParameter(name); } public Map<?, ?> getParameterMap() { return request.getParameterMap(); } public Enumeration<?> getParameterNames() { return request.getParameterNames(); } public String[] getParameterValues(String parameter) { return request.getParameterValues(parameter); } public String getProtocol() { return request.getProtocol(); } public BufferedReader getReader() throws IOException { return request.getReader(); } public String getRemoteAddr() { return request.getRemoteAddr(); } public String getRemoteHost() { return request.getRemoteHost(); } public String getScheme() { return request.getScheme(); } public String getServerName() { return request.getServerName(); } public int getServerPort() { return request.getServerPort(); } public void removeAttribute(String attribute) { request.removeAttribute(attribute); } public void setAttribute(String key, Object value) { request.setAttribute(key, value); } public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { request.setCharacterEncoding(encoding); } }
Response外觀類
package ex02.pyrmont.second; import java.io.IOException; import java.io.PrintWriter; import java.util.Locale; import javax.servlet.ServletResponse; import javax.servlet.ServletOutputStream; import ex02.pyrmont.Response; public class ResponseFacade implements ServletResponse { private ServletResponse response; public ResponseFacade(Response response) { this.response = response; } public void flushBuffer() throws IOException { response.flushBuffer(); } public int getBufferSize() { return response.getBufferSize(); } public String getCharacterEncoding() { return response.getCharacterEncoding(); } public Locale getLocale() { return response.getLocale(); } public ServletOutputStream getOutputStream() throws IOException { return response.getOutputStream(); } public PrintWriter getWriter() throws IOException { return response.getWriter(); } public boolean isCommitted() { return response.isCommitted(); } public void reset() { response.reset(); } public void resetBuffer() { response.resetBuffer(); } public void setBufferSize(int size) { response.setBufferSize(size); } public void setContentLength(int length) { response.setContentLength(length); } public void setContentType(String type) { response.setContentType(type); } public void setLocale(Locale locale) { response.setLocale(locale); } }
處理Servlet請(qǐng)求類:
package ex02.pyrmont.second; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import ex02.pyrmont.Constants; import ex02.pyrmont.Request; import ex02.pyrmont.Response; public class ServletProcessor2 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); // 類加載器,用于從指定JAR文件或目錄加載類 URLClassLoader loader = null; try { URLStreamHandler streamHandler = null; // 創(chuàng)建類加載器 loader = new URLClassLoader(new URL[] { new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler) }); } catch (IOException e) { System.out.println(e.toString()); } Class<?> myClass = null; try { // 加載對(duì)應(yīng)的servlet類 myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; //給request、response增加外觀類,安全性考慮,防止用戶在servlet里直接將ServletRequest、ServletResponse向下轉(zhuǎn)型為Request和Response類型, //并直接調(diào)用其內(nèi)部的public方法,因?yàn)镽equestFacade、ResponseFacade里不會(huì)有parse、sendStaticResource等方法; RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } }
其它代碼與前面實(shí)現(xiàn)的Servlet容器基本一致。
驗(yàn)證程序,分別請(qǐng)求靜態(tài)資源和Servlet,發(fā)現(xiàn)結(jié)果與前面實(shí)現(xiàn)的容器一致;
參考資料:《深入剖析Tomcat》
@author 風(fēng)一樣的碼農(nóng)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Transaction事務(wù)實(shí)現(xiàn)流程源碼解析
此文就Spring 事務(wù)實(shí)現(xiàn)流程進(jìn)行源碼解析,我們可以借此對(duì)Spring框架更多一層理解,下面以xml形式創(chuàng)建一個(gè)事務(wù)進(jìn)行分析2022-09-09Springboot整合Swagger2后訪問(wèn)swagger-ui.html 404報(bào)錯(cuò)問(wèn)題解決方案
這篇文章主要介紹了Springboot整合Swagger2后訪問(wèn)swagger-ui.html 404報(bào)錯(cuò),本文給大家分享兩種解決方案,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06IDEA創(chuàng)建Maven項(xiàng)目一直顯示正在加載的問(wèn)題及解決
這篇文章主要介紹了IDEA創(chuàng)建Maven項(xiàng)目一直顯示正在加載的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12MybatisPlus批量保存原理及失效原因排查全過(guò)程
這篇文章主要介紹了MybatisPlus批量保存原理及失效原因排查全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Spring?WebMVC初始化Controller流程詳解
這篇文章主要介紹了Spring?WebMVC初始化Controller流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Java布隆過(guò)濾器的應(yīng)用實(shí)例
這篇文章主要介紹了Java布隆過(guò)濾器的應(yīng)用實(shí)例,在程序的世界中,布隆過(guò)濾器是程序員的一把利器,利用它可以快速地解決項(xiàng)目中一些比較棘手的問(wèn)題,如網(wǎng)頁(yè)?URL?去重、垃圾郵件識(shí)別、大集合中重復(fù)元素的判斷和緩存穿透等問(wèn)題,需要的朋友可以參考下2023-11-11