Tomcat源码分析使用NIO接收HTTP请求(三)----解析请求行

我们在上一节中已经接收到了请求,在本节中我们将来解析请求行也就是`GET / HTTP/1.1`这一行。

了解Tomcat解析请求行

Tomcat在Http11Processor的service方法中会调用Http11InputBuffer的parseRequestLine方法来解析Tomcat的请求行,所以我们本节的重点将是实现parseRequestLine方法。

实现parseRequestLine方法

以下代码都是基于上一节代码修改或添加的。

第一步: 修改parseRequestLine方法,在parseRequestLine方法读取到数据后,会先执行do-while 循环以用来删除空格。然后根据空格标志来获取Get,获取Get后又以同样的方式获取/,此时可以运行程序 来看一下打印输出,会发现有一个明显的问题(见注释)

boolean parseRequestLine() throws IOException {
    byte chr = 0;
    if (parsingRequestLinePhase < 2) {
        // 读取数据
        wrapper.read(byteBuffer);
        System.out.println("postion="+byteBuffer.position()+";limit=" + byteBuffer.limit());
        if (byteBuffer.position() > 0) {
            // 打印接收到的请求
            System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8));
            byteBuffer.flip();
            // 这个do while循环用来清除消息开始的空行
            do{
                chr = byteBuffer.get();
                byte[] b = new byte[]{chr};
            }while((chr == (byte) '\r') || (chr == (byte) '\n'));
            // 因为在do while中多读取了一个position所以在这里需要给设置回来
            byteBuffer.position(byteBuffer.position() - 1);
            // 接下来在继续解析 拿到Get
            // 当遇到空格或制表符时被设置成true
            boolean space = false;
            // 获取GET
            while(!space) {
                chr = byteBuffer.get();
                if (chr == (byte) ' ' || chr == (byte) '\t') {
                    space = true;
                }
            }
            byte[] b = byteBuffer.array();
            byte[] c = Arrays.copyOfRange(b, 0, byteBuffer.position());
            System.out.println("postion="+byteBuffer.position()
                    +";limit=" + byteBuffer.limit()
                    +";content="+ new String(c));
            // 因为获取/与GET逻辑类似,所以直接复制上一段代码来解析/.但是在打印输出时会发现GET被重新输出
            space = false;
            // 获取
            while(!space) {
                chr = byteBuffer.get();
                if (chr == (byte) ' ' || chr == (byte) '\t') {
                    space = true;
                }
            }
            b = byteBuffer.array();
            c = Arrays.copyOfRange(b, 0, byteBuffer.position());
            System.out.println("postion="+byteBuffer.position()
                    +";limit=" + byteBuffer.limit()
                    +";content="+ new String(c));
        }
    }
    return true;
}

 第二步: 解决第一步当中的问题。在Http11InputBuffer定义一个全局 变量private int parsingRequestLineStart = 0;然后修改代码如下

boolean parseRequestLine() throws IOException {
    byte chr = 0;
    if (parsingRequestLinePhase < 2) {
        wrapper.read(byteBuffer);
        System.out.println("postion="+byteBuffer.position()+";limit=" + byteBuffer.limit());
        if (byteBuffer.position() > 0) {
            System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8));
            byteBuffer.flip();
            do{
                chr = byteBuffer.get();
            }while((chr == (byte) '\r') || (chr == (byte) '\n'));
            byteBuffer.position(byteBuffer.position() - 1);
            // 新增加的
            parsingRequestLineStart = byteBuffer.position();
            // =====================-*-__-*-===================================//
            boolean space = false;
            // 获取GET
            while(!space) {
                chr = byteBuffer.get();
                if (chr == (byte) ' ' || chr == (byte) '\t') {
                    space = true;
                }
            }
            byte[] b = byteBuffer.array();
            byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
            parsingRequestLineStart = byteBuffer.position();
            System.out.println("postion="+byteBuffer.position()
                    +";limit=" + byteBuffer.limit()
                    +";content="+ new String(c));
            // =====================-*-__-*-===================================//
            space = false;
            // 获取 /
            while(!space) {
                chr = byteBuffer.get();
                if (chr == (byte) ' ' || chr == (byte) '\t') {
                    space = true;
                }
            }
            b = byteBuffer.array();
            c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
            parsingRequestLineStart = byteBuffer.position();
            System.out.println("postion="+byteBuffer.position()
                    +";limit=" + byteBuffer.limit()
                    +";content="+ new String(c));
            // =====================-*-__-*-===================================//
            space = false;
            // 获取 HTTP/1.1
            while(!space) {
                chr = byteBuffer.get();
                // 这里和上一个有点区别,判断换行
                if ((chr == (byte) '\r') || (chr == (byte) '\n')) {
                    space = true;
                }
            }
            b = byteBuffer.array();
            c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
            System.out.println("postion="+byteBuffer.position()
                    +";limit=" + byteBuffer.limit()
                    +";content="+ new String(c));
        }
    }
    return true;
}

 第三步: 第二步中存在的一个问题是我们只尝试读取了一次数据,有可能数据没有接收全,导致后续解析出错。所以我们需要一种尝试重新读取数据的方法。在Http11InputBuffer中新建一个fill方法,并重写parseRequestLine方法。运行结果和上一步基本一样就不在贴图了。有一点需要注意的是我们是根据position和limit的关系来判断是否需要重新读取数据的,所以我们要重写init方法对position和limit进行设置。

public void init(SocketWrapperBase<?> socketWrapper) {
    wrapper = socketWrapper;
    byteBuffer.position(0).limit(0);
}
private boolean fill() throws IOException {
    int nRead = -1;
    byteBuffer.mark();
    try {
        if (byteBuffer.position() < byteBuffer.limit()) {
            byteBuffer.position(byteBuffer.limit());
        }
        byteBuffer.limit(byteBuffer.capacity());
        nRead = wrapper.read(byteBuffer);
    } finally {
        byteBuffer.limit(byteBuffer.position()).reset();
        System.out.println("postion="+byteBuffer.position() +";limit=" + byteBuffer.limit() + ";nRead=" + nRead);
    }
    return nRead > 0;
}
boolean parseRequestLine() throws IOException {
    byte chr = 0;
    do{
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
    }while((chr == (byte) '\r') || (chr == (byte) '\n'));
    byteBuffer.position(byteBuffer.position() - 1);
    parsingRequestLineStart = byteBuffer.position();
    // =====================-*-__-*-===================================//
    boolean space = false;
    // 获取GET
    while(!space) {
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
        if (chr == (byte) ' ' || chr == (byte) '\t') {
            space = true;
        }
    }
    byteBuffer.position(byteBuffer.position() - 1);
    byte[] b = byteBuffer.array();
    byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
    System.out.println("postion="+byteBuffer.position()
            +";limit=" + byteBuffer.limit()
            +";content="+ new String(c));
    parsingRequestLineStart = byteBuffer.position();
    // =====================-*-__-*-===================================//
    space = false;
    // 获取 空格
    while(!space) {
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
        if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
            space = true;
            byteBuffer.position(byteBuffer.position() - 1);
        }
    }
    b = byteBuffer.array();
    c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
    System.out.println("postion11="+byteBuffer.position()
            +";limit=" + byteBuffer.limit()
            +";content="+ new String(c));
    parsingRequestLineStart = byteBuffer.position();
    // =====================-*-__-*-===================================//
    space = false;
    // 获取 /
    while(!space) {
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
        if (chr == (byte) ' ' || chr == (byte) '\t') {
            space = true;
        }
    }
    byteBuffer.position(byteBuffer.position() - 1);
    b = byteBuffer.array();
    c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
    System.out.println("postion="+byteBuffer.position()
            +";limit=" + byteBuffer.limit()
            +";content="+ new String(c));
    parsingRequestLineStart = byteBuffer.position();
    // =====================-*-__-*-===================================//
    space = false;
    // 获取 空格
    while(!space) {
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
        if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
            space = true;
            byteBuffer.position(byteBuffer.position() - 1);
        }
    }
    b = byteBuffer.array();
    c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
    parsingRequestLineStart = byteBuffer.position();
    System.out.println("postion11="+byteBuffer.position()
            +";limit=" + byteBuffer.limit()
            +";content="+ new String(c));
    // =====================-*-__-*-===================================//
    space = false;
    // 获取 HTTP/1.1
    while(!space) {
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill()) {
                return false;
            }
        }
        chr = byteBuffer.get();
        if ((chr == (byte) '\r') || (chr == (byte) '\n')) {
            space = true;
        }
    }
    b = byteBuffer.array();
    c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
    System.out.println("postion="+byteBuffer.position()
            +";limit=" + byteBuffer.limit()
            +";content="+ new String(c));
    return true;
}
第四步: 使用 parsingRequestLinePhase来标志正确解析了请求行,修改parseRequestLine方法如下:
boolean parseRequestLine() throws IOException {
    byte chr = 0;
    if (parsingRequestLinePhase < 2) {
        do{
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
        }while((chr == (byte) '\r') || (chr == (byte) '\n'));
        byteBuffer.position(byteBuffer.position() - 1);
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 2;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 2) {
        boolean space = false;
        // 获取GET
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (chr == (byte) ' ' || chr == (byte) '\t') {
                space = true;
            }
        }
        byteBuffer.position(byteBuffer.position() - 1);
        byte[] b = byteBuffer.array();
        byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
        System.out.println("postion="+byteBuffer.position()
                +";limit=" + byteBuffer.limit()
                +";content="+ new String(c));
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 3;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 3) {
        boolean space = false;
        // 获取 空格
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
                space = true;
                byteBuffer.position(byteBuffer.position() - 1);
            }
        }
        byte[] b = byteBuffer.array();
        byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
        System.out.println("postion11="+byteBuffer.position()
                +";limit=" + byteBuffer.limit()
                +";content="+ new String(c));
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 4;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 4) {
        boolean space = false;
        // 获取 /
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (chr == (byte) ' ' || chr == (byte) '\t') {
                space = true;
            }
        }
        byteBuffer.position(byteBuffer.position() - 1);
        byte[] b = byteBuffer.array();
        byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
        System.out.println("postion="+byteBuffer.position()
                +";limit=" + byteBuffer.limit()
                +";content="+ new String(c));
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 5;
    }

    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 5) {
        boolean space = false;
        // 获取 空格
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
                space = true;
                byteBuffer.position(byteBuffer.position() - 1);
            }
        }
        byte[] b = byteBuffer.array();
        byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
        parsingRequestLineStart = byteBuffer.position();
        System.out.println("postion11="+byteBuffer.position()
                +";limit=" + byteBuffer.limit()
                +";content="+ new String(c));
        parsingRequestLinePhase = 6;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 6) {
        boolean space = false;
        // 获取 HTTP/1.1
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if ((chr == (byte) '\r') || (chr == (byte) '\n')) {
                space = true;
            }
        }
        byte[] b = byteBuffer.array();
        byte[] c = Arrays.copyOfRange(b, parsingRequestLineStart, byteBuffer.position());
        System.out.println("postion="+byteBuffer.position()
                +";limit=" + byteBuffer.limit()
                +";content="+ new String(c));
        parsingRequestLinePhase = 7;
    }
    if (parsingRequestLinePhase == 7) {
        parsingRequestLinePhase = 0;
        parsingRequestLineStart = 0;
        return true;
    }
    throw new IOException("解析错误");
}

第五步: 保存解析后的数据。在Tomcat源码中,当其解析请求后会将解析后的信息保存在Request对象中,这个Request对象是Tomcat内部对像,之后会将其转换为ServletRequest 对像。Tomcat会将接收到的数转换为一个数组,并将这个数组和数据索引封装成一个名为ByteChunk的对象,所以我们要新键一个ByteChunk类,如下: 

public final class ByteChunk {
    // 缓存数据
    private byte[] buff;
    protected int start;
    protected int end;
    public void setBytes(byte[] b, int off, int len) {
        buff = b;
        start = off;
        end = start + len;
        byte[] c = Arrays.copyOfRange(b, start, end);
        System.out.println("ByteChunk类setBytes();start=" + off + ";end=" + end +";content="+ new String(c));
    }
}

第六步: ByteChunk是用来保存字节数据的,在Tomcat源码中还有一个CharChunk是用来保存字符数据的,二者被封装在一个名为MessageBytes的类中,现在我们还用不到CharChunk所以先不管它。在新键一个MessageBytes类,如下

public final class MessageBytes implements Cloneable, Serializable {
    private final ByteChunk byteC=new ByteChunk();
    public static MessageBytes newInstance() {
        return factory.newInstance();
    }
    public void setBytes(byte[] b, int off, int len) {
        byteC.setBytes( b, off, len );
    }
    private static final MessageBytesFactory factory = new MessageBytesFactory();
    private static class MessageBytesFactory {
        protected MessageBytesFactory() {
        }
        public MessageBytes newInstance() {
            return new MessageBytes();
        }
    }
}
第七步: 新键Request类,并在Http11InputBuffer类中初始话它private Request request = new Request();
public class Request {
    // 保存请求方法 GET POST之类的
    private final MessageBytes methodMB = MessageBytes.newInstance();
    // -~-__-~-
    private final MessageBytes uriMB = MessageBytes.newInstance();
    // 使用的协议
    private final MessageBytes protoMB = MessageBytes.newInstance();

    public MessageBytes method() {
        return methodMB;
    }

    public MessageBytes requestURI() {
        return uriMB;
    }

    public MessageBytes protocol() {
        return protoMB;
    }
}

第八步: 重写parseRequestLine方法

boolean parseRequestLine() throws IOException {
    byte chr = 0;
    if (parsingRequestLinePhase < 2) {
        do{
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
        }while((chr == (byte) '\r') || (chr == (byte) '\n'));
        byteBuffer.position(byteBuffer.position() - 1);
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 2;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 2) {
        boolean space = false;
        // 获取GET
        while(!space) {
            int pos = byteBuffer.position();
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (chr == (byte) ' ' || chr == (byte) '\t') {
                space = true;
                // 保存数据
                request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
                        pos - parsingRequestLineStart);
            }
        }
        parsingRequestLinePhase = 3;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 3) {
        boolean space = false;
        // 获取 空格
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
                space = true;
                byteBuffer.position(byteBuffer.position() - 1);
            }
        }
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 4;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 4) {
        boolean space = false;
        // 获取 /
        while(!space) {
            int pos = byteBuffer.position();
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (chr == (byte) ' ' || chr == (byte) '\t') {
                space = true;
                request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
                        pos - parsingRequestLineStart);
            }
        }
        parsingRequestLinePhase = 5;
    }

    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 5) {
        boolean space = false;
        // 获取 空格
        while(!space) {
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            if (!(chr == (byte) ' ' || chr == (byte) '\t')) {
                space = true;
                byteBuffer.position(byteBuffer.position() - 1);
            }
        }
        parsingRequestLineStart = byteBuffer.position();
        parsingRequestLinePhase = 6;
    }
    // =====================-*-__-*-===================================//
    if (parsingRequestLinePhase == 6) {
        boolean space = false;
        // 获取 HTTP/1.1
        while(!space) {
            int pos = byteBuffer.position();
            if (byteBuffer.position() >= byteBuffer.limit()) {
                if (!fill()) {
                    return false;
                }
            }
            chr = byteBuffer.get();
            // 这里和上一个有点区别,判断换行
            if ((chr == (byte) '\r') || (chr == (byte) '\n')) {
                space = true;
                request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
                        pos - parsingRequestLineStart);
            }
        }
        parsingRequestLinePhase = 7;
    }
    if (parsingRequestLinePhase == 7) {
        parsingRequestLinePhase = 0;
        parsingRequestLineStart = 0;
        return true;
    }
    throw new IOException("解析错误");
}

 

结束 !!!

 


 

posted @ 2022-11-28 14:34  一十三  阅读(500)  评论(0)    收藏  举报