手写一个民用Tomcat (04)

我们继续来 写 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 方法 运行一下,结果是一样的。

相关推荐
真的很上进2 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人34 分钟前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.36 分钟前
Mybatis-Plus
java·开发语言
不良人天码星36 分钟前
lombok插件不生效
java·开发语言·intellij-idea
守护者1701 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云1 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络1 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。1 小时前
Docker学习
java·开发语言·学习
如若1231 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
初晴~2 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·