文章目錄
- 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 : 負責分發與處理連接
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 負責 method
、 uri
、 protocol
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;}
}