当前位置: 首页 > news >正文

手写 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.xdnf.cn/news/1454761.html

相关文章:

  • STM32启动模式配置
  • 一个开源的企业官网简介
  • RTSP H.265 与 RTMP H.265 的差异解析:标准、扩展与增强实现
  • 设备监控系统如何为重工业实现设备预测性维护
  • 【智谱清言-GLM-4.5】StackCube-v1 任务训练结果不稳定性的分析
  • uniapp中使用echarts并且支持pc端的拖动、拖拽和其他交互事件
  • 案例精述 | 防护即智能 Fortinet赋能英科全栈安全重构实践
  • [晕事]今天做了件晕事91,glibc,rand之前必须设置种子
  • AI+Java 守护你的钱袋子!金融领域的智能风控与极速交易
  • Elasticsearch面试精讲 Day 8:聚合分析与统计查询
  • docker更新jar包,懒人执行脚本
  • 若依微服务遇到的配置问题
  • 【数据可视化-108】2025年6月新能源汽车零售销量TOP10车企分析大屏(PyEcharts炫酷黑色主题可视化)
  • JUnit 详解
  • Rust+slint实现一个登录demo
  • 一文搞懂保险中的Nominee\Beneficiary\Trustee三个角色
  • Rustdesk搭建与客户端修改与编译
  • 从零开始的云计算生活——第五十八天,全力以赴,Jenkins部署
  • MD 格式说明
  • Web与Nginx网站服务
  • 2023 arXiv MapperGPT: Large Language Models for Linking and Mapping Entities
  • # 开发中使用——鸿蒙CoreSpeechKit让文字发声后续
  • 迈威通信从送快递角度教你分清网络二层和三层
  • 美团开源龙猫大模型,与DeepSeek V3同一梯队?
  • matlab实现希尔伯特变换(HHT)
  • vue2 打包生成的js文件过大优化
  • 白平衡分块统计数据为什么需要向下采样?
  • Web应用安全入门:从OWASP Top 10理解SQL注入与纵深防御
  • GcWord V8.2 新版本:TOA/TA字段增强、模板标签管理与PDF导出优化
  • 政务级数据安全!小陌GEO引擎的私有化部署实践指南