我们继续来 写 Tomcat 这次我们做优化,先看一下一个标准的http 协议
GET /servlet/com.yixin.HelloWorldServlet HTTP/1.1
Host: localhost:8080
Connection: keep-alive
sec-ch-ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
sec-ch-ua-platform: "Windows"
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
我们这一版就要是把所需要的数据 取出来,放到map 集合里边,先看第一个 我们能获取 请求方式GET, 请求路径 以及 请求协议 HTTP/1.1 然后 空一行, 获取到 请求头。
如果根据 这个报文结构,让各位写一个 获取协议 应该不难吧, 什么 split(),indexof, 什么subString 整吧,但是 tomcat 源码中就不会这么干了。
来 我们看下 ,
HttpRequestLine 类
public class HttpRequestLine { public static final int INITIAL_METHOD_SIZE = 8; public static final int INITIAL_URI_SIZE = 128; public static final int INITIAL_PROTOCOL_SIZE = 8; public static final int MAX_METHOD_SIZE = 32; public static final int MAX_URI_SIZE = 2048; public static final int MAX_PROTOCOL_SIZE = 32; //下面的属性对应于Http Request规范,即头行格式method uri protocol //如:GET /hello.txt HTTP/1.1 //char[] 存储每段的字符串,对应的int值存储的是每段的结束位置 public char[] method; public int methodEnd; public char[] uri; public int uriEnd; public char[] protocol; public int protocolEnd; public HttpRequestLine() { this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0, new char[INITIAL_PROTOCOL_SIZE], 0); } public HttpRequestLine(char[] method, int methodEnd, char[] uri, int uriEnd, char[] protocol, int protocolEnd) { this.method = method; this.methodEnd = methodEnd; this.uri = uri; this.uriEnd = uriEnd; this.protocol = protocol; this.protocolEnd = protocolEnd; } public void recycle() { methodEnd = 0; uriEnd = 0; protocolEnd = 0; } public int indexOf(char[] buf) { return indexOf(buf, buf.length); } //这是主要的方法 /** * 这个方法遵循的 从你查到的第一个字符串开始找 * 举个例子 在helloworld 里边找or * 以第一个字符o在helloworld 开始遍历 找到符合的就返回一个pos位置 * 然后以pos 为起点 开始循环依次对比 看是否后边的满足 如果不满足 返回 pos++ * 继续开始这样找下去 一直遍历完所有helloworld */ public int indexOf(char[] buf, int end) { char firstChar = buf[0]; int pos = 0; //pos是查找字符串buf在uri[]中的开始位置 while (pos < uriEnd) { pos = indexOfChar(firstChar, pos); //首字符定位开始位置 if (pos == -1) { return -1; } //uriEnd - pos 表示url的最后剩余是否小与查找字符串的长度 //如果后边都没有了,依旧没有查到 难就查不到了。 if ((uriEnd - pos) < end) { return -1; } for (int i = 0; i < end; i++) { //从开始位置起逐个字符比对 if (uri[i + pos] != buf[i]) { break; } if (i == (end - 1)) { //每个字符都相等,则匹配上了,返回开始位置 return pos; } } pos++; } return -1; } public int indexOf(String str) { return indexOf(str.toCharArray(), str.length()); } //在uri[]中查找字符c的出现位置 public int indexOfChar(char c, int start) { for (int i = start; i < uriEnd; i++) { if (uri[i] == c) { return i; } } return -1; } }
这个比较简单,就是查找字符串位置
接下来 比较复杂了。
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'; protected byte buf[]; protected int count; protected int pos; protected InputStream is; public SocketInputStream(InputStream is, int bufferSize) { this.is = is; buf = new byte[bufferSize]; } //从输入流中解析出request line public void readRequestLine(HttpRequestLine requestLine) throws IOException { int chr = 0; //跳过空行 do { try { chr = read(); } catch (IOException e) { } } while ((chr == CR) || (chr == LF)); //第一个非空位置 pos--; int maxRead = requestLine.method.length; int readStart = pos; int readCount = 0; boolean space = false; //解析第一段method,以空格结束 解析出 GET 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; //method段的结束位置 maxRead = requestLine.uri.length; readStart = pos; readCount = 0; space = false; boolean eol = false; //解析第二段uri,以空格结束 解析出/servlet/com.yixin.HelloWorldServlet 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; //uri结束位置 maxRead = requestLine.protocol.length; readStart = pos; readCount = 0; //解析第三段protocol,以eol结尾 解析出 HTTP/1.1 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 CR if (chr == CR) read(); // Skipping LF header.nameEnd = 0; header.valueEnd = 0; return; } else { pos--; } // 正在读取 header name int maxRead = header.name.length; int readStart = pos; int readCount = 0; boolean colon = false; while (!colon) { // 我们处于内部缓冲区的末尾 if (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; // 读取 header 值(可以跨越多行) maxRead = header.value.length; readStart = pos; readCount = 0; int crPos = -2; boolean eol = false; boolean validLine = true; while (validLine) { boolean space = true; // 跳过空格 // 注意:仅删除前面的空格,后面的不删。 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) || (buf[pos] == HT)) { pos++; } else { space = false; } } 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) { } else if (buf[pos] == LF) { eol = true; } else { // FIXME:检查二进制转换是否正常 int ch = buf[pos] & 0xff; header.value[readCount] = (char) ch; readCount++; } pos++; } int nextChr = read(); //Microsoft Edge 因为有的vlaue 值中有空格的情况 if ((nextChr != SP) && (nextChr != HT)) { pos--; validLine = false; } else { eol = false; header.value[readCount] = ' '; readCount++; } } header.valueEnd = readCount; } @Override public int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) { return -1; } } return buf[pos++] & 0xff; } public int available() throws IOException { return (count - pos) + is.available(); } public void close() throws IOException { if (is == null) { return; } is.close(); is = null; buf = null; } protected void fill() throws IOException { pos = 0; count = 0; int nRead = is.read(buf, 0, buf.length); if (nRead > 0) { count = nRead; } System.out.println(new String(buf)); } }
总归就是 获取 http协议中的数据 ,readRequestLine 这个方法, 别看复杂就是获取 第一行数据,GET 请求路径,等
接下来就是获取 请求头数据放入到map 集合:
HttpHeader
public class HttpHeader { public static final int INITIAL_NAME_SIZE = 64; public static final int INITIAL_VALUE_SIZE = 512; public static final int MAX_NAME_SIZE = 128; public static final int MAX_VALUE_SIZE = 1024; public char[] name; public int nameEnd; public char[] value; public int valueEnd; protected int hashCode = 0; public HttpHeader() { this(new char[INITIAL_NAME_SIZE], 0, new char[INITIAL_VALUE_SIZE], 0); } public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) { this.name = name; this.nameEnd = nameEnd; this.value = value; this.valueEnd = valueEnd; } public HttpHeader(String name, String value) { this.name = name.toLowerCase().toCharArray(); this.nameEnd = name.length(); this.value = value.toCharArray(); this.valueEnd = value.length(); } public void recycle() { nameEnd = 0; valueEnd = 0; hashCode = 0; } }
这是一个请求头的类
JxdRequest 这个类 需要 只显示 变更的部分:
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 void parse(Socket socket) { try { input = socket.getInputStream(); this.sis = new SocketInputStream(this.input, 2048); parseConnection(socket); this.sis.readRequestLine(requestLine); parseHeaders(); } catch (IOException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } 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); // 设置相应的请求头 if (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); } } }
这个请求request 里边有各种方法 ,解析 i请求头,解析请求 路径 方式等,
其他的没有变化 ,然后 可以用上一节的main 方法 运行一下,结果是一样的。