手寫 Tomcat

文章目錄

  • 02 初出茅廬:構造一個極簡的 HttpServer
    • Request
    • Response
    • HttpServer
  • 03 動態 Response : 按照規范構造返回流
  • 04 各司其職的 Server : 拆分響應模塊與處理模塊
    • HttpConnector
    • HttpProcessor
  • 05 Server 性能提升: 設計多個 Processor
    • HttpConnector
    • HttpProcessor
  • 06 規范化: 引入 HttpRequest 與 HttpResponse
    • HttpRequest
    • SocketInputStream
  • 07 對內的保護: 引入門面模式封裝內部實現類
    • HttpRequestFacade
    • HttpResponseFacade
  • 08 解析參數:通過引入 Cookie 和 Session 避免反復登錄
  • 09 有狀態的 Response: 實現 Session 傳遞與 keep-alive
    • bug
  • 10 Servlet Wrapper: 如何維護 Servlet 生命周期及實現容器管理?
  • 11 多層容器:如何通過實現 Context 與 Wrapper 形成多層容器?
  • 12 Pipeline 與 Valve: 如何實現容器間的調用、事務管理、權限驗證?
  • Filter 與 Listener: 如何實現過濾和持續監聽?
    • 過濾器

02 初出茅廬:構造一個極簡的 HttpServer

使用 Socket 簡單實現

Request

public class Request {InputStream input;String uri;public Request(InputStream input) {this.input = input;}public void parse() {int i = 0;byte[] buffer = new byte[2024];try {i = input.read(buffer);} catch (IOException e) {i = -1;throw new RuntimeException(e);}StringBuilder sb = new StringBuilder();for (int j = 0; j < i; j++) {sb.append((char) buffer[j]);}uri = parseUir(sb.toString());}public String parseUir(String str) {int index1 = 0, index2 = 0;index1 = str.indexOf(' ');index2 = str.indexOf(' ', index1 + 1);if (index1 == -1 || index2 == -1) {throw new RuntimeException("請求格式異常");}return str.substring(index1 + 1, index2);}public String getUri() {return uri;}
}

Response

public class Response {Request request;OutputStream out;int BUFFER_SIZE = 1024;public Response(Request request, OutputStream out) {this.request = request;this.out = out;}public void sendStaticResource() {byte[] bytes = new byte[BUFFER_SIZE];FileInputStream fis = null;try {File file = new File(HttpServer.WEB_ROOT, request.getUri());if (file.exists()) {// 在發送文件內容前,先發送成功的HTTP響應頭String successHeader = "HTTP/1.1 200 OK\r\n"+ "Content-Type: text/html\r\n"  // 注意: 這里可以根據文件類型動態改變+ "Content-Length: " + file.length() + "\r\n"+ "\r\n"; // 重要的空行,分隔頭和體out.write(successHeader.getBytes(StandardCharsets.UTF_8));fis = new FileInputStream(file);int ch = fis.read(bytes, 0, BUFFER_SIZE);while (ch != -1) {out.write(bytes, 0, ch);ch = fis.read(bytes, 0, BUFFER_SIZE);}out.flush();} else {// file not foundString errorMessage = """HTTP/1.1 404 File Not Found\rContent-Type: text/html\rContent-Length: 23\r\r<h1>File Not Found</h1>""";out.write(errorMessage.getBytes());}} catch (Exception e) {// thrown if cannot instantiate a File objectSystem.out.println(e.toString());} finally {if (fis != null){try {fis.close();} catch (IOException ignored) {}}}}
}

HttpServer

public class HttpServer {public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";public static void main(String[] args) {HttpServer httpServer = new HttpServer();System.out.println(WEB_ROOT);httpServer.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) {throw new RuntimeException(e);}while (true) {Socket socket = null;InputStream inputStream = null;OutputStream outputStream = null;try {socket = serverSocket.accept();inputStream = socket.getInputStream();Request request = new Request(inputStream);request.parse();outputStream = socket.getOutputStream();Response response = new Response(request, outputStream);response.sendStaticResource();socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}
}

03 動態 Response : 按照規范構造返回流

    private String composeResponseHead() {HashMap<String, String> headers = new HashMap<>();headers.put("StatusCode", "200");headers.put("StatusName", "ok");headers.put("ContentType", "text/html;charset=utf-8");headers.put("ZonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME.format(ZonedDateTime.now()));return new StrSubstitutor(headers).replace(OKMessage);}//下面的字符串是當文件沒有找到時返回的 404 錯誤描述private final static String fileNotFoundMessage = """HTTP/1.1 404 File Not Found\rContent-Type: text/html\r\r<h1>File Not Found</h1>""";//下面的字符串是正常情況下返回的,根據http協議,里面包含了相應的變量。private final static String OKMessage = """HTTP/1.1 ${StatusCode} ${StatusName}\rContent-Type: ${ContentType}\rServer: miniTomcat\rDate: ${ZonedDateTime}\r\r""";

04 各司其職的 Server : 拆分響應模塊與處理模塊

HttpServer
HttpConnector
HttpProcessor

HttpServer 拆分成兩個部分

  • HttpConnector : 負責與客戶端進行連接
  • HttpProcessor : 負責分發與處理連接

HttpConnector

public class HttpConnector implements Runnable {private static final Logger log = LoggerFactory.getLogger(HttpConnector.class);@Overridepublic void run() {ServerSocket serverSocket;int port = 8080;try {serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));log.info("服務器啟動成功");} catch (IOException e) {throw new RuntimeException(e);}while (true) {try {Socket socket = serverSocket.accept();HttpProcessor httpProcessor = new HttpProcessor();httpProcessor.process(socket);socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}public void start() {Thread thread = new Thread(this);thread.start();}
}

HttpConnector 實現 Runnable 接口,可以創建多個 HttpConnector 線程,提高并發量

HttpProcessor

public class HttpProcessor {private static final Logger log = LoggerFactory.getLogger(HttpProcessor.class);public void process(Socket socket) {try {InputStream inputStream = socket.getInputStream();Request request = new Request(inputStream);request.parse();OutputStream outputStream = socket.getOutputStream();Response response = new Response(request, outputStream);if (request.getUri().startsWith("/servlet/")) {log.info("訪問動態資源");ServletProcessor servletProcessor = new ServletProcessor();servletProcessor.process(request, response);} else {StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();staticResourceProcessor.process(request, response);}socket.close();} catch (IOException e) {throw new RuntimeException(e);}}
}

05 Server 性能提升: 設計多個 Processor

在上一節中,雖然可以開多個 HttpConnector 線程,但是一個 HttpConnector 只能處理一個 HttpProcessor

在這一節要將 HttpProcessor 異步化

HttpConnector

public class HttpConnector implements Runnable {private static final Logger log = LoggerFactory.getLogger(HttpConnector.class);int minProcessors = 3;int maxProcessors = 10;int curProcessor = 0;final Deque<HttpProcessor> processors = new ArrayDeque<>();@Overridepublic void run() {ServerSocket serverSocket;int port = 8080;try {serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));for (int i = 0; i < minProcessors; i++) {HttpProcessor processor = new HttpProcessor(this);processor.start();processors.add(processor);}curProcessor = minProcessors;log.info("服務器啟動成功");} catch (IOException e) {throw new RuntimeException(e);}while (true) {try {Socket socket = serverSocket.accept();HttpProcessor processor = getProcessor();if (processor == null) {socket.close();log.error("processor 已耗盡");} else {processor.assign(socket);}} catch (IOException e) {throw new RuntimeException(e);}}}public HttpProcessor getProcessor() {synchronized (processors) {if (!processors.isEmpty()) {return processors.poll();} else {if (curProcessor < maxProcessors) {curProcessor++;return new HttpProcessor(this);}}}return null;}void recycle(HttpProcessor processor) {processors.push(processor);}public void start() {Thread thread = new Thread(this);thread.start();}
}

HttpProcessor

public class HttpProcessor implements Runnable {private static final Logger log = LoggerFactory.getLogger(HttpProcessor.class);Socket socket;boolean available = false;HttpConnector connector;public HttpProcessor(HttpConnector connector) {this.connector = connector;}public void start() {Thread thread = new Thread(this);thread.start();}@Overridepublic void run() {while (true) {Socket socket = await();if (socket == null) {continue;}process(socket);try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}connector.recycle(this);}}public void process(Socket socket) {try {InputStream inputStream = socket.getInputStream();Request request = new Request(inputStream);request.parse();OutputStream outputStream = socket.getOutputStream();Response response = new Response(request, outputStream);if (request.getUri().startsWith("/servlet/")) {log.info("訪問動態資源");ServletProcessor servletProcessor = new ServletProcessor();servletProcessor.process(request, response);} else {StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();staticResourceProcessor.process(request, response);}socket.close();} catch (IOException e) {throw new RuntimeException(e);}}synchronized void assign(Socket socket) {while (available) {try {wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}this.socket = socket;available = true;notifyAll();}private synchronized Socket await() {while (!available) {try {wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}Socket socket = this.socket;available = false;notifyAll();return (socket);}
}

首先看 assign(socket) 方法,在這里,我們用一個標志available來標記,如果標志為true, Connetor線程就繼續死等。到了某個時候,Processor線程把這個標志設置為false,Connector線 程就跳出死等的循環,然后把接收到的Socket交給Processor。然后要立刻重新把available標志設 置為true,再調用 notifyAll() 通知其他線程。

再看 await() ,這是作為接收者Processor的線程使用的方法。反過來,如果avaliable標志為 false,那么Processor線程繼續死等。到了某個時候,Connector線把這個標志設置為true,那么 Processor線程就跳出死等的循環,拿到Socket。然后要立刻重新把avaiable標志設置為false,再調 用 notifyAll() 通知其他線程。 這個線程互鎖機制保證了兩個線程之間的同步協調。圖示如下:

在這里插入圖片描述
我們再回顧一下HttpProcessor類中的assign方法與await方法。在HttpProcessor的線程啟動之后, available的標識一直是false,這個時候這個線程會一直等待。在HttpConnector類里構造 Processor,并且調用 processor.assign(socket) 給HttpProcessor分配Socket之后,標識符 available改成true,并且調用notifyAll這個本地方法通知喚醒所有等待的線程。

而在await方法里,HttpProcessor拿到HttpConnector傳來的Socket之后,首先會接收Socket,并 且立即把available由true改為false,最后以拿到的這個Socket為基準繼續進行Processor中的處理 工作。

這也意味著,一旦Connector分配了一個Socket給到Processor,后者就能立即結束等待,拿到 Socket后調用Process方法繼續后面的工作。這時available的狀態立刻修改,進而用notifyAll方法喚 醒 Connector的等待線程,Connector就可以全身而退,去處理下一個HttpProcessor了。

T omcat中兩個線程互鎖的這種機制很經典,在后續版本的NIO和Servlet協調的設計中都用到了。

這樣也就做到了HttpProcessor的異步化,也正因為做到了異步化,我們就不能再利用Connector去 關閉Socket了,因為Connector是不知道Processor何時處理完畢的,Socket的關閉任務就交給 Processor自己處理了。

06 規范化: 引入 HttpRequest 與 HttpResponse

在這里插入圖片描述

HttpRequestLine 負責 methoduriprotocol
eq:GET /hello.txt HTTP/1.1

HttpHeader 負責其他請求頭

SocketInputStream 負責解析請求頭

HttpRequest 負責存儲請求頭

HttpRequest

public class HttpRequest implements HttpServletRequest {private static final Logger log = LoggerFactory.getLogger(HttpRequest.class);private InputStream input;private SocketInputStream sis;private String uri;InetAddress address;int port;protected HashMap<String, String> headers = new HashMap<>();protected Map<String, String> parameters = new ConcurrentHashMap<>();HttpRequestLine requestLine = new HttpRequestLine();public HttpRequest(InputStream input) {this.input = input;this.sis = new SocketInputStream(this.input, 2048);}public void parse(Socket socket) {try {parseConnection(socket);this.sis.readRequestLine(requestLine);parseHeaders();} catch (IOException | ServletException e) {log.error(e.getMessage());}this.uri = new String(requestLine.uri, 0, requestLine.uriEnd);}private void parseConnection(Socket socket) {address = socket.getInetAddress();port = socket.getPort();}private void parseHeaders() throws IOException, ServletException {while (true) {HttpHeader header = new HttpHeader();sis.readHeader(header);if (header.nameEnd == 0) {if (header.valueEnd == 0) {return;} else {throw new ServletException("httpProcessor.parseHeaders.colon");}}String name = new String(header.name,0, header.nameEnd);String value = new String(header.value, 0, header.valueEnd);// Set the corresponding request headersif (name.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.HOST_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONNECTION_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {headers.put(name, value);} else {headers.put(name, value);}}}}

SocketInputStream

public class SocketInputStream extends InputStream {private static final byte CR = (byte) '\r';private static final byte LF = (byte) '\n';private static final byte SP = (byte) ' ';private static final byte HT = (byte) '\t';private static final byte COLON = (byte) ':';private static final int LC_OFFSET = 'A' - 'a';private static final Logger log = LoggerFactory.getLogger(SocketInputStream.class);protected byte[] buf;protected int count;protected int pos;protected InputStream is;public SocketInputStream(InputStream is, int bufferSize) {this.is = is;this.buf = new byte[bufferSize];}public void readRequestLine(HttpRequestLine requestLine)throws IOException {int chr = 0;do {try {chr = read();} catch (IOException e) {log.error(e.getMessage(), e);}} while ((chr == CR) || (chr == LF));pos--;int maxRead = requestLine.method.length;int readStart = pos;int readCount = 0;boolean space = false;while (!space) {if (pos >= count) {int val = read();if (val == -1) {throw new IOException("requestStream.readline.error");}pos = 0;readStart = 0;}if (buf[pos] == SP) {space = true;}requestLine.method[readCount] = (char) buf[pos];readCount++;pos++;}requestLine.methodEnd = readCount - 1;maxRead = requestLine.uri.length;readStart = pos;readCount = 0;space = false;boolean eol = false;while (!space) {if (pos >= count) {int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == SP) {space = true;}requestLine.uri[readCount] = (char) buf[pos];readCount++;pos++;}requestLine.uriEnd = readCount - 1;maxRead = requestLine.protocol.length;readStart = pos;readCount = 0;while (!eol) {if (pos >= count) {int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == CR) {// Skip CR.} else if (buf[pos] == LF) {eol = true;} else {requestLine.protocol[readCount] = (char) buf[pos];readCount++;}pos++;}requestLine.protocolEnd = readCount;}public void readHeader(HttpHeader header)throws IOException {int chr = read();if ((chr == CR) || (chr == LF)) { // Skipping CRif (chr == CR)read(); // Skipping LFheader.nameEnd = 0;header.valueEnd = 0;return;} else {pos--;}// Reading the header nameint maxRead = header.name.length;int readStart = pos;int readCount = 0;boolean colon = false;while (!colon) {// We're at the end of the internal bufferif (pos >= count) {int val = read();if (val == -1) {throw new IOException("requestStream.readline.error");}pos = 0;readStart = 0;}if (buf[pos] == COLON) {colon = true;}char val = (char) buf[pos];if ((val >= 'A') && (val <= 'Z')) {val = (char) (val - LC_OFFSET);}header.name[readCount] = val;readCount++;pos++;}header.nameEnd = readCount - 1;// Reading the header value (which can be spanned over multiple lines)maxRead = header.value.length;readStart = pos;readCount = 0;int crPos = -2;boolean eol = false;boolean validLine = true;while (validLine) {boolean space = true;// Skipping spaces// Note : Only leading white spaces are removed. Trailing white// spaces are not.while (space) {// We're at the end of the internal bufferif (pos >= count) {// Copying part (or all) of the internal buffer to the line// bufferint val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if ((buf[pos] == SP) || (buf[pos] == HT)) {pos++;} else {space = false;}}while (!eol) {// We're at the end of the internal bufferif (pos >= count) {// Copying part (or all) of the internal buffer to the line// bufferint val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == CR) {} else if (buf[pos] == LF) {eol = true;} else {// FIXME : Check if binary conversion is working fineint ch = buf[pos] & 0xff;header.value[readCount] = (char) ch;readCount++;}pos++;}int nextChr = read();if ((nextChr != SP) && (nextChr != HT)) {pos--;validLine = false;} else {eol = false;header.value[readCount] = ' ';readCount++;}}header.valueEnd = readCount;}@Overridepublic int available() throws IOException {return (count - pos) + is.available();}@Overridepublic void close() throws IOException {if (is == null) {return;}is.close();is = null;buf = null;}@Overridepublic int read() throws IOException {if (pos >= count) {fill();if (pos >= count) {return -1;}}return buf[pos++] & 0xFF;}protected void fill() {int nRead;try {nRead = is.read(buf, 0, buf.length);} catch (IOException e) {throw new RuntimeException(e);}pos = 0;count = 0;if (nRead > 0) {count = nRead;}}
}

pos:在 buf 中要讀的位置
count: 在 buf 的末尾
buf 是一個緩存,is 會不斷將數據輸入到 buf 中
read() 方法中,當 pos >= count 時,說明 buf 中的數據已經使用完畢,通過 is 讀取下一批數據并緩存在 buf 中,否則會將返回當前位置的數據,并將 pos++

07 對內的保護: 引入門面模式封裝內部實現類

在這里插入圖片描述
在HttpProcessor類里,我們直接使用的是HttpRequest與HttpResponse, 這兩個對象要傳入Servlet里,但在這兩個類中我們也定義了許多內部的方法,一旦被用戶知曉我們 的實現類,那么這些內部方法就暴露在用戶面前了,這是我們不愿看到的,也是我們需要規避的。 因此這節課我們計劃用?面(Facade)設計模式來解決這個問題

HttpRequestFacade

public class HttpRequestFacade implements HttpServletRequest {private HttpServletRequest request;public HttpRequestFacade(HttpRequest request) {this.request = request;}/* implementation of the HttpServletRequest*/public Object getAttribute(String name) {return request.getAttribute(name);}public Enumeration getAttributeNames() {return request.getAttributeNames();}public String getAuthType() {return request.getAuthType();}public String getCharacterEncoding() {return request.getCharacterEncoding();}public int getContentLength() {return request.getContentLength();}public String getContentType() {return request.getContentType();}public String getContextPath() {return request.getContextPath();}public Cookie[] getCookies() {return request.getCookies();}public long getDateHeader(String name) {return request.getDateHeader(name);}public Enumeration getHeaderNames() {return request.getHeaderNames();}public String getHeader(String name) {return request.getHeader(name);}public Enumeration getHeaders(String name) {return request.getHeaders(name);}public ServletInputStream getInputStream() throws IOException {return request.getInputStream();}public int getIntHeader(String name) {return request.getIntHeader(name);}public Locale getLocale() {return request.getLocale();}public Enumeration getLocales() {return request.getLocales();}public String getMethod() {return request.getMethod();}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 name) {return request.getParameterValues(name);}public String getPathInfo() {return request.getPathInfo();}public String getPathTranslated() {return request.getPathTranslated();}public String getProtocol() {return request.getProtocol();}public String getQueryString() {return request.getQueryString();}public BufferedReader getReader() throws IOException {return request.getReader();}public String getRealPath(String path) {return request.getRealPath(path);}
}

HttpResponseFacade

public class HttpResponseFacade implements HttpServletResponse {private HttpServletResponse response;public HttpResponseFacade(HttpResponse response) {this.response = response;}public void addDateHeader(String name, long value) {response.addDateHeader(name, value);}public void addHeader(String name, String value) {response.addHeader(name, value);}public void addIntHeader(String name, int value) {response.addIntHeader(name, value);}public boolean containsHeader(String name) {return response.containsHeader(name);}public String encodeRedirectURL(String url) {return response.encodeRedirectURL(url);}public String encodeRedirectUrl(String url) {return response.encodeRedirectUrl(url);}public String encodeUrl(String url) {return response.encodeUrl(url);}public String encodeURL(String url) {return response.encodeURL(url);}public void flushBuffer() throws IOException {response.flushBuffer();}public int getBufferSize() {return response.getBufferSize();}public String getCharacterEncoding() {return response.getCharacterEncoding();}
}

最后修改 ServletProcessor

    public void process(HttpRequest request, HttpResponse response) {String uir = request.getUri();String ServletName = uir.substring(uir.lastIndexOf('/') + 1);URLClassLoader loader;try {URL[] urls = new URL[1];File classPath = new File(HttpServer.WEB_ROOT);String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();URLStreamHandler urlStreamHandler = null;urls[0] = new URL(null, repository, urlStreamHandler);loader = new URLClassLoader(urls);} catch (IOException e) {throw new RuntimeException(e);}ServletName = "com.lbwxxc.test.HelloServlet";Class<?> servletClass;ClassLoader classLoader = this.getClass().getClassLoader();try {servletClass = classLoader.loadClass(ServletName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}PrintWriter writer;try {writer = response.getWriter();writer.println(composeResponseHead());} catch (IOException e) {throw new RuntimeException(e);}HttpRequestFacade httpRequestFacade = new HttpRequestFacade(request);HttpResponseFacade httpResponseFacade = new HttpResponseFacade(response);Servlet servlet;try {servlet = (Servlet) servletClass.newInstance();servlet.service(httpRequestFacade, httpResponseFacade);} catch (InstantiationException | IllegalAccessException | ServletException | IOException e) {throw new RuntimeException(e);}}

這樣在Servlet中,我們看到的只是Facade,看不?內部方法,應用程序員想進行強制轉化也不行, 這樣既簡單又安全。

還有,按照Servlet的規范,客戶自定義的Servlet是要繼承HttpServlet的,在調用的service方法 內,它的實際行為是通過method判斷調用的是哪一個方法,如果是Get方法就調用doGet(),如果是 Post方法調用的就是doPost(),其他的方法也是一樣的道理。

所以在我們自定義的HttpRequest里,一定要實現getMethod方法,我們來調整一下。

    public String getMethod() {return new String(requestLine.method, 0, requestLine.methodEnd);}

08 解析參數:通過引入 Cookie 和 Session 避免反復登錄

Cookie 可能存放在請求行或者請求頭,所以在解析 HttpRequest 時,要分別處理

    public void parseRequestLine() {int queryStart = requestLine.indexOf("?");if (queryStart >= 0) {queryString = new String(requestLine.uri, queryStart + 1, requestLine.uriEnd - queryStart - 1);uri = new String(requestLine.uri, 0, queryStart);int semicolon = uri.indexOf(DefaultHeaders.JSESSIONID_NAME);if (semicolon >= 0) {sessionid = uri.substring(semicolon + DefaultHeaders.JSESSIONID_NAME.length());uri = uri.substring(0, semicolon);}} else {queryString = null;uri = new String(requestLine.uri, 0, requestLine.uriEnd);int  semicolon = uri.indexOf(DefaultHeaders.JSESSIONID_NAME);if (semicolon >= 0) {sessionid = uri.substring(semicolon + DefaultHeaders.JSESSIONID_NAME.length());uri = uri.substring(0, semicolon);}}}
    private void parseHeaders() throws IOException, ServletException {while (true) {HttpHeader header = new HttpHeader();sis.readHeader(header);if (header.nameEnd == 0) {if (header.valueEnd == 0) {return;} else {throw new ServletException("httpProcessor.parseHeaders.colon");}}String name = new String(header.name,0, header.nameEnd);String value = new String(header.value, 0, header.valueEnd);// Set the corresponding request headersif (name.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.HOST_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONNECTION_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.COOKIE_NAME)) {headers.put(name, value);this.cookies = parseCookieHeader(value);for (int i = 0; i < cookies.length; i++) {if (cookies[i].getName().equals("jsessionid")) {this.sessionid = cookies[i].getValue();}}} else {headers.put(name, value);}}}

解析完 HttpRequest,會嘗試獲取 session,如果沒有最會創建,并存放在 HttpConnect

    public void process(Socket socket) {try {InputStream inputStream = socket.getInputStream();HttpRequest httpRequest = new HttpRequest(inputStream);httpRequest.parse(socket);if (httpRequest.getSessionid() == null || httpRequest.getSessionid().isEmpty()) {// 嘗試獲取 session,如果沒有則創建httpRequest.getSession(true);}OutputStream outputStream = socket.getOutputStream();HttpResponse httpResponse = new HttpResponse(outputStream);httpResponse.setRequest(httpRequest);if (httpRequest.getUri().startsWith("/servlet/")) {log.info("訪問動態資源");ServletProcessor servletProcessor = new ServletProcessor();servletProcessor.process(httpRequest, httpResponse);} else {StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();staticResourceProcessor.process(httpRequest, httpResponse);}socket.close();} catch (IOException e) {throw new RuntimeException(e);}}
    public HttpSession getSession(boolean b) {if (sessionFacade != null)return sessionFacade;if (sessionid != null) {session = HttpConnector.sessions.get(sessionid);if (session != null) {sessionFacade = new SessionFacade(session);return sessionFacade;} else {session = HttpConnector.createSession();sessionFacade = new SessionFacade(session);return sessionFacade;}} else {session = HttpConnector.createSession();sessionFacade = new SessionFacade(session);sessionid = session.getId();return sessionFacade;}}
    public static Session createSession() {Session session = new Session();session.setValid(true);session.setCreationTime(System.currentTimeMillis());String sessionId = generateSessionId();session.setId(sessionId);sessions.put(sessionId, session);return (session);}

09 有狀態的 Response: 實現 Session 傳遞與 keep-alive

    public void sendHeaders() throws IOException {PrintWriter outputWriter = getWriter();outputWriter.print(this.getProtocol());outputWriter.print(" ");outputWriter.print(status);if (message != null) {outputWriter.print(" ");outputWriter.print(message);}outputWriter.print("\r\n");if (getContentType() != null) {outputWriter.print("Content-Type: " + getContentType() + "\r\n");}if (getContentLength() >= 0) {outputWriter.print("Content-Length: " + getContentLength() + "\r\n");}Iterator<String> names = headers.keySet().iterator();while (names.hasNext()) {String name = names.next();String value = headers.get(name);outputWriter.print(name);outputWriter.print(": ");outputWriter.print(value);outputWriter.print("\r\n");}HttpSession session = this.request.getSession(false);if (session != null) {Cookie cookie = new Cookie(DefaultHeaders.JSESSIONID_NAME, session.getId());cookie.setMaxAge(-1);addCookie(cookie);}synchronized (cookies) {Iterator<Cookie> items = cookies.iterator();while (items.hasNext()) {Cookie cookie = items.next();outputWriter.print(CookieTools.getCookieHeaderName(cookie));outputWriter.print(": ");StringBuffer sbValue = new StringBuffer();CookieTools.getCookieHeaderValue(cookie, sbValue);log.info("set cookie jsessionid string : {}", sbValue);outputWriter.print(sbValue);outputWriter.print("\r\n");}}outputWriter.print("\r\n");outputWriter.flush();}

在正式處理請求前,會先把 response 頭寫入到流中

bug

在 ServletProcessor 多添加了一個響應頭

    public void process(HttpRequest request, HttpResponse response) {String uir = request.getUri();String ServletName = uir.substring(uir.lastIndexOf('/') + 1);URLClassLoader loader = HttpConnector.loader;ServletName = "com.lbwxxc.test.HelloServlet";Class<?> servletClass;ClassLoader classLoader = this.getClass().getClassLoader();try {servletClass = classLoader.loadClass(ServletName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}//        PrintWriter writer;
//        try {
//            writer = response.getWriter();
//            writer.println(composeResponseHead());
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }HttpRequestFacade httpRequestFacade = new HttpRequestFacade(request);HttpResponseFacade httpResponseFacade = new HttpResponseFacade(response);Servlet servlet;try {servlet = (Servlet) servletClass.newInstance();servlet.service(httpRequestFacade, httpResponseFacade);} catch (InstantiationException | IllegalAccessException | ServletException | IOException e) {throw new RuntimeException(e);}}

10 Servlet Wrapper: 如何維護 Servlet 生命周期及實現容器管理?

Wrapper 是對 Servlet 的封裝

public class ServletWrapper {private Servlet instance = null;private String servletClass;private ClassLoader loader;private String name;protected ServletContainer parent = null;public ServletWrapper(String servletClass, ServletContainer parent) {this.parent = parent;this.servletClass = servletClass;//loadServlet();}public ClassLoader getLoader() {if (loader != null)return loader;return parent.getLoader();}public String getServletClass() {return servletClass;}public void setServletClass(String servletClass) {this.servletClass = servletClass;}public ServletContainer getParent() {return parent;}public void setParent(ServletContainer container) {parent = container;}public Servlet getServlet(){return this.instance;}public Servlet loadServlet() throws ServletException {if (instance!=null)return instance;Servlet servlet = null;String actualClass = servletClass;if (actualClass == null) {throw new ServletException("servlet class has not been specified");}ClassLoader classLoader = getLoader();Class classClass = null;try {if (classLoader!=null) {classClass = classLoader.loadClass(actualClass);}}catch (ClassNotFoundException e) {throw new ServletException("Servlet class not found");}try {servlet = (Servlet) classClass.newInstance();}catch (Throwable e) {throw new ServletException("Failed to instantiate servlet");}try {servlet.init(null);}catch (Throwable f) {throw new ServletException("Failed initialize servlet.");}instance = servlet;return servlet;}public void invoke(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {if (instance != null) {instance.service(request, response);}}
}

創建 ServletContainer 專門管理 Wrapper

public class ServletContainer {HttpConnector connector;ClassLoader loader;Map<String, String> servletClsMap = new ConcurrentHashMap<>();Map<String, ServletWrapper> servletInstanceMap = new ConcurrentHashMap<>();public ServletContainer() {URL[] urls = new URL[1];URLStreamHandler streamHandler = null;File classPath = new File("target/classes/com/lbwxxc/test");try {String repository = (new URL("file", null,  classPath.getCanonicalPath() + File.separator)).toString();urls[0] = new URL(null, repository, streamHandler);loader = new URLClassLoader(urls);} catch (IOException e) {throw new RuntimeException(e);}}public void invoke(HttpRequest request, HttpResponse response) {ServletWrapper servlet = null;ClassLoader loader = getLoader();String uri = request.getUri();String servletName = uri.substring(uri.lastIndexOf("/") + 1);String servletClassName = servletName;servlet = servletInstanceMap.get(servletName);if (servlet == null) {Class<?> servletClass = null;try {servletClass =  loader.loadClass("com.lbwxxc.test.HelloServlet");} catch (ClassNotFoundException e) {throw new RuntimeException(e);}try {servlet = new ServletWrapper(servletClassName, this);servlet.setInstance((Servlet) servletClass.newInstance());} catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}servletClsMap.put(servletName, servletClassName);servletInstanceMap.put(servletName, servlet);}try {HttpRequestFacade requestFacade = new HttpRequestFacade(request);HttpResponseFacade responseFacade = new HttpResponseFacade(response);System.out.println("Call service()");servlet.invoke(requestFacade, responseFacade);} catch (ServletException | IOException e) {throw new RuntimeException(e);}}
}

讓 HttpConnector 與 ServletContainer 相互引用

        HttpConnector httpConnector = new HttpConnector();ServletContainer servletContainer = new ServletContainer();httpConnector.setContainer(servletContainer);servletContainer.setConnector(httpConnector);httpConnector.start();

在 ServletProcessor 直接調用 ServletContainer ,實現責任分離

    private HttpConnector connector;public ServletProcessor(HttpConnector connector) {this.connector = connector;}

11 多層容器:如何通過實現 Context 與 Wrapper 形成多層容器?

public abstract class ContainerBase implements Container {protected Map<String, Container> children = new ConcurrentHashMap<>();protected ClassLoader loader = null;protected String name = null;protected Container parent = null;public abstract String getInfo();public ClassLoader getLoader() {if (loader != null)return (loader);if (parent != null)return (parent.getLoader());return (null);}public synchronized void setLoader(ClassLoader loader) {ClassLoader oldLoader = this.loader;if (oldLoader == loader) {return;}this.loader = loader;}public String getName() {return (name);}public void setName(String name) {this.name = name;}public Container getParent() {return (parent);}public void setParent(Container container) {Container oldParent = this.parent;this.parent = container;}public void addChild(Container child) {addChildInternal(child);}private void addChildInternal(Container child) {synchronized(children) {if (children.get(child.getName()) != null)throw new IllegalArgumentException("addChild:  Child name '" +child.getName() +"' is not unique");child.setParent((Container) this);  // May throw IAEchildren.put(child.getName(), child);}}public Container findChild(String name) {if (name == null)return (null);synchronized (children) {       // Required by post-start changesreturn ((Container) children.get(name));}}public Container[] findChildren() {synchronized (children) {Container results[] = new Container[children.size()];return ((Container[]) children.values().toArray(results));}}public void removeChild(Container child) {synchronized(children) {if (children.get(child.getName()) == null)return;children.remove(child.getName());}child.setParent(null);

多層容器

12 Pipeline 與 Valve: 如何實現容器間的調用、事務管理、權限驗證?

使用責任鏈

在這里插入圖片描述

Filter 與 Listener: 如何實現過濾和持續監聽?

過濾器

final class ApplicationFilterChain implements FilterChain {public ApplicationFilterChain() {super();}private ArrayList<ApplicationFilterConfig> filters = new ArrayList<>();private Iterator<ApplicationFilterConfig> iterator = null;private Servlet servlet = null;public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {System.out.println("FilterChain doFilter()");internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// Construct an iterator the first time this method is calledif (this.iterator == null)this.iterator = filters.iterator();// Call the next filter if there is oneif (this.iterator.hasNext()) {ApplicationFilterConfig filterConfig =(ApplicationFilterConfig) iterator.next();Filter filter = null;try {filter = filterConfig.getFilter();System.out.println("Filter doFilter()");filter.doFilter(request, response, this);} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {throw new ServletException("filterChain.filter", e);}return;}// We fell off the end of the chain -- call the servlet instancetry {HttpServletRequest requestFacade = new HttpRequestFacade((HttpRequestImpl) request);HttpServletResponse responseFacade = new HttpResponseFacade((HttpResponseImpl) response);servlet.service(requestFacade, responseFacade);} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {throw new ServletException("filterChain.servlet", e);}}void addFilter(ApplicationFilterConfig filterConfig) {this.filters.add(filterConfig);}void release() {this.filters.clear();this.iterator = iterator;this.servlet = null;}void setServlet(Servlet servlet) {this.servlet = servlet;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96052.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96052.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96052.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式ARM架構學習3——啟動代碼

一 匯編補充&#xff1a;area reset, code, readonlycode32entry;mov r0, #4 ; r0 4;mov r1, r0 ; r1 r0;mov r2, r1, lsl #1 ;r2 r1 << 1 乘2;mov r3, r1, lsr #1 ;r3 r1 >> 1 除2;mov r4, r1, ror #2;mov r0, #100 ;100是十進制 轉為16進制賦值給十進制;mov …

PNPM庫離線安裝方案

以下是幾種可行的方案&#xff0c;推薦優先使用方案一。 方案一&#xff1a;使用離線鏡像&#xff08;Offline Mirror&#xff09; - 最優雅、最PNPM的方式 這是 PNPM 官方推薦的處理離線環境的方式。它會在內網電腦上創建一個所有依賴包的壓縮文件&#xff08;tarball&#x…

[Wit]CnOCR模型訓練全流程簡化記錄(包括排除BUG)

stepfile:step 00 創建數據集 目錄結構 yourproject -data --myset ---images #存放訓練圖片 ---dev.tsv #測試標簽 tsv格式 圖片文件名\t內容 ---train.tsv #訓練標簽 tsv格式 圖片文件名\t內容 -train_config.json -train_config_gpu.json -fix_cnocr_encoding.py step 01 創…

Sklearn(機器學習)實戰:鳶尾花數據集處理技巧

1.數據集的使用&#xff1a;先使用load導入鳶尾花數據&#xff1a;from sklearn.datasets import load_iris然后定義一個函數來查看鳶尾花數據集&#xff1a;數據集的獲取&#xff1a;iris load_iris()print(鳶尾花的數據集&#xff1a;\n,iris)使用iris[DESCR]來查看數據及里…

【企業微信】接口報錯:javax.net.ssl.SSLHandshakeException

詳細報錯信息 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target原因 關于qyapi…

光子芯片驅動的胰腺癌早期檢測:基于光學子空間神經網絡的高效分割方法

光子芯片驅動的胰腺癌早期檢測:基于光學子空間神經網絡的高效分割方法 1 論文核心概念 本文提出了一種基于集成光子芯片的光學子空間神經網絡(Optical Subspace Neural Network, OSNN),用于胰腺癌的早期檢測與圖像分割。其核心思想是利用光子芯片的高并行性、低延遲和低能…

Scikit-learn Python機器學習 - 特征降維 壓縮數據 - 特征提取 - 主成分分析 (PCA)

鋒哥原創的Scikit-learn Python機器學習視頻教程&#xff1a; 2026版 Scikit-learn Python機器學習 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 課程介紹 本課程主要講解基于Scikit-learn的Python機器學習知識&#xff0c;包括機器學習概述&#xff0c;特征工程(數據…

【Python】pytorch安裝(使用conda)

# 創建 PyTorch 虛擬環境 conda create -n pytorch_env python3.10# 激活環境 conda activate pytorch_env# 安裝 PyTorch&#xff08;CPU版本&#xff09; conda install pytorch torchvision torchaudio cpuonly -c pytorch# 或者安裝 GPU 版本&#xff08;如果有NVIDIA顯卡&…

ThreeJS骨骼示例

<html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>骨骼動畫混合演示</title><style>body {margin: 0;padding: …

python + Flask模塊學習 1 基礎用法

目錄 Flask 的主要作用 常用擴展 Flask 基本用法 1. 安裝 Flask&#xff08;再安裝個postman用來調試測試API哈 2. 最小化應用示例 3. 運行應用 Flask 是一個輕量級的 Python Web 框架&#xff0c;它簡潔靈活&#xff0c;適合快速開發 Web 應用和 API。它被稱為 "微…

python數據可視化之Matplotlib(8)-Matplotlib樣式系統深度解析:從入門到企業級應用

作者&#xff1a;浪浪山齊天大圣 描述&#xff1a;深入探索Matplotlib樣式系統的核心機制&#xff0c;掌握從基礎樣式到企業級樣式管理的完整解決方案引言 在數據可視化的世界里&#xff0c;一個優秀的圖表不僅要準確傳達數據信息&#xff0c;更要具備專業的視覺效果。Matplotl…

3.HTTP/HTTPS:報文格式、方法、狀態碼、緩存、SSLTLS握手

HTTP/HTTPS&#xff1a;報文格式、方法、狀態碼、緩存、SSL/TLS握手 1. HTTP報文格式 1.1 HTTP請求報文(Request) GET /api/v1/users HTTP/1.1 // 請求行&#xff1a;方法、URI、協議版本 Host: api.example.com // 請求頭 (Headers) User-Agent: Mozil…

【慢教程】Ollama4:ollama命令匯總

??教程說明 Ollama 是一款輕量級本地大模型部署工具&#xff0c;使用廣泛&#xff0c;且容易上手&#xff0c;適合作為AI技術的入門。 &#x1f9e9;教程各部分鏈接&#xff1a; 第一課&#xff1a;ollama運行原理介紹及同類工具對比 ollama運行原理介紹及同類工具對比&am…

JAVA Predicate

簡單來說&#xff0c;當我明確知道此次判斷的邏輯時就可以直接使用if&#xff0c;但是我這次的判斷邏輯可能會隨著某個參數變化的時候使用Predicate比如當我想要判斷某長段文字中是否包含list<String> 中的元素&#xff0c;并且包含的元素個數大于 list<String>最后…

什么是PFC控制器

一句話概括PFC控制器是一種智能芯片&#xff0c;它通過控制電路中的電流波形&#xff0c;使其與電壓波形保持一致&#xff0c;從而減少電力浪費&#xff0c;提高電能的利用效率。PFC控制器IC的核心作用就是控制一顆功率MOSFET的開關&#xff0c;通過特定的電路拓撲&#xff08;…

【P03_AI大模型測試之_定制化 AI 應用程序開發】

git clone https://gitee.com/winner21/aigc-test.git 類似于joycoder的&#xff0c;可以安裝在vscode上的通義靈碼&#xff1a;https://lingma.aliyun.com/ 1、VSCODE上配置通義靈碼 2、創建前后端文件&#xff0c;并引用AI編碼代碼 3、指定文件&#xff0c;利用AI進行代碼優…

人工智能機器學習——決策樹、異常檢測、主成分分析(PCA)

一、決策樹(Decision Tree) 決策樹&#xff1a;一種對實例進行分類的樹形結構&#xff0c;通過多層判斷區分目標所屬類別 本質&#xff1a;通過多層判斷&#xff0c;從訓練數據集中歸納出一組分類規則 優點&#xff1a; 計算量小&#xff0c;運算速度快易于理解&#xff0c;可…

服務器文件同步用哪個工具?介紹一種安全高效的文件同步方案

服務器作為企業核心數據和應用的載體&#xff0c;服務器文件同步已成為IT運維、數據備份、業務協同中不可或缺的一環。然而&#xff0c;面對多樣的場景和嚴苛的需求&#xff0c;選擇一個既高效又安全的服務器文件同步工具并非易事。本文將首先探討服務器文件同步的常見場景、需…

LeetCode 004. 尋找兩個正序數組的中位數 - 二分切分與分治詳解

一、文章標題 LeetCode 004. 尋找兩個正序數組的中位數 - 二分切分與分治詳解 二、文章內容 1. 題目概述 題目描述&#xff1a;給定兩個已按非降序排列的整數數組 nums1、nums2&#xff0c;設它們長度分別為 m 和 n&#xff0c;要求返回這兩個數組合并后有序序列的中位數。…

預閃為什么可以用來防紅眼?

打閃拍照紅眼產生的原因 預閃可以用來防紅眼&#xff0c;是基于人眼的生理特性和紅眼現象的產生原理。在光線較暗時&#xff0c;人眼的瞳孔會放大。當使用閃光燈拍攝時&#xff0c;如果直接進行高強度閃光&#xff0c;由于瞳孔來不及縮小&#xff0c;閃光燈的光線會反射在眼球血…