大飞带你深入理解Tomcat(四)

接上篇,代码已经能处理简单的请求,但走读一下代码,会发现这些代码非常粗糙,可改动的地方非常多,本篇先对Request类进行优化

原代码:
    // 解析浏览器发起的请求
    public void parseRequest() {
        // 暂时忽略文件上传的请求,假设都字符型请求
        byte[] buff = new byte[2048];
        StringBuffer sb = new StringBuffer(2048);
        int len = 0;
        //请求内容
        try {
            len = in.read(buff);
            sb.append(new String(buff, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.print(sb.toString());
        //解析请求行中uri信息
        uri = this.parseUri(sb.toString());
    }

问题1:上面代码是代码是上上篇代码接收客户端发起的请求信息,在极端情况下, 比如请求信息大小超过2kb,in.read(buff) 会读不全, 如果加上while循环读,会发现缓存的buff大小稍微设置不当,请求将被阻塞了,无法响应。
问题2:当前方法仅仅解析请求行中的url, 忽略对于请求行中,请求头中其他数据的解析。

分析:
针对问题1:解决这个问题, 可以采用下面的设计
设计图
思路:
1:先创建一个对象,里面设置3个char[]类型数据, 分别存储请求行中 请求方法, 请求uri,请求协议
2:再创一个类SocketInputStream对inputStream做增强。
1> SocketInputStream 继承inputStrean,然后重写其中的read方法
2> 定义一个char数组buff缓存数据,跟2个游标, count 表示一次读流读到的字节个数,pos表示操作缓存buf位置(index)
3:读socket流时,将数据缓存到buf中,记录pos=0, count为读取个数,循环解析,
1>当buf[pos]==‘ ’ 表示解析请求行中的请求方法结束, 使用requestLine.method 保存读取到的方法。
2>当buf[pos]==‘ ’ 表示解析请求行中的请求uri结束, 使用requestLine.uri保存读取到的方法。
2>当buf[pos]==‘\r’ || “\n” 表示解析请求行中的请求协议结束, 使用requestLine.protocal保存读取到的方法。

针对问题2:比较简单,只要问题1解决, 获取到uri 就可以解析uri ? 后面的数据就是查询参数

居于上面的思路,代码如下:

新建类:HttpRequestLine
/**
 * 
 * 请求行中包括3种数据
 *  请求方法  请求路径  请求协议
 */
public class HttpRequestLine {

    //-----------------------------
    //模拟tomcat所有请求数据以char[]类型存在
    public char[] method;
    public int methodEnd;
    public char[] uri;
    public int uriEnd;
    public char[] protocol;
    public int protocolEnd;

    //默认长度
    public static final int INITIAL_METHOD_SIZE = 8;
    public static final int INITIAL_URI_SIZE = 64;
    public static final int INITIAL_PROTOCOL_SIZE = 8;

    //最长
    public static final int MAX_METHOD_SIZE = 1024;
    public static final int MAX_URI_SIZE = 32768;
    public static final int MAX_PROTOCOL_SIZE = 1024;
    //-----------------------------

    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;
    }

    //uri搜索----------------------------------------------------

    public int indexOf(String str) {
        return indexOf(str.toCharArray(), str.length());
    }

    public int indexOf(char[] buf, int end) {
        char firstChar = buf[0];
        int pos = 0;
        while (pos < uriEnd) {
            pos = indexOf(firstChar, pos);
            if (pos == -1)
                return -1;
            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(char c, int start) {
        for (int i = start; i < uriEnd; i++) {
            if (uri[i] == c)
                return i;
        }
        return -1;
    }
    //----------------------------------------------------
}

新建类:SocketInuptStream

/**
 * 请求输入流处理: 解析Socket中的输入流, 获取请求行信息
 */
public class SocketInputStream extends InputStream {

    // 读取缓存信息缓存
    protected byte buf[];
    // 一次read()读取个数
    protected int count;
    // 缓存数据数组索引
    protected int pos;
    protected InputStream is;

    // 初始化同时制定缓存区大小
    public SocketInputStream(InputStream is, int bufferSize) {
        this.is = is;
        buf = new byte[bufferSize];
    }

    // 读取请求数据,封装成功requestLine 对象
    public void readRequestLine(HttpRequestLine requestLine) throws IOException {

        // 请求方法不为空表示第二次请求,重来
        if (requestLine.methodEnd != 0) {
            requestLine.recycle();
        }

        // 读取Socket流数据,遇到回车表明请求行读取结束
        int chr = 0;
        do {
            try {
                chr = read();
            } catch (Exception e) {
                chr = -1;
            }
        } while (chr == '\r' || chr == '\n');

        if (chr == -1) {
            throw new RuntimeException("读请求数据出错");
        }
        pos--;
        // 解析请求行方法-------------------------------------------
        int maxRead = requestLine.method.length;
        int readStart = pos;
        int readCount = 0;

        boolean space = false; // 结束标志
        while (!space) {
            // 如果方法数据无法接受数据,按2倍长度拓展
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.method, 0, newBuffer, 0, maxRead);
                    requestLine.method = newBuffer;
                    maxRead = requestLine.method.length;
                } else {
                    throw new RuntimeException("读取数据过大");
                }
            }

            // 当缓存数据中数据全部读取完后,重新再读
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new RuntimeException("读请求数据出错");
                }
                // 读取数据游标复位
                pos = 0;
                readStart = 0;
            }

            // 当缓存数据中读取到空格, 表示请求行中方法读取完毕
            if (buf[pos] == ' ') {
                space = true; // 结束循环
            }

            // 将读取数据保存到预设好的请求行对象的请求方法数组中
            requestLine.method[readCount] = (char) buf[pos];
            readCount++;
            pos++;
        }

        // 读取请求方法结束后, 标记结束位置
        requestLine.methodEnd = readCount - 1;

        // 同样道理解析请求行uri-------------------------------------------
        maxRead = requestLine.uri.length;
        readStart = pos;
        readCount = 0;
        space = false;

        while (!space) {
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.uri, 0, newBuffer, 0, maxRead);
                    requestLine.uri = newBuffer;
                    maxRead = requestLine.uri.length;
                } else {
                    throw new RuntimeException("读取数据过大");
                }
            }

            // 当缓存数据中数据全部读取完后,重新再读
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new RuntimeException("读请求数据出错");
                }
                // 读取数据游标复位
                pos = 0;
                readStart = 0;
            }

            // 当缓存数据中读取到空格, 表示请求行中方法读取完毕
            if (buf[pos] == ' ') {
                space = true; // 结束循环
            }

            requestLine.uri[readCount] = (char) buf[pos];
            readCount++;
            pos++;

        }
        requestLine.uriEnd = readCount - 1;

        // 同样道理解析请求行协议-------------------------------------------
        maxRead = requestLine.uri.length;
        readStart = pos;
        readCount = 0;
        space = false;
        while (!space) {
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.protocol, 0, newBuffer, 0, maxRead);
                    requestLine.protocol = newBuffer;
                    maxRead = requestLine.protocol.length;
                } else {
                    throw new RuntimeException("读取数据过大");
                }
            }
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new RuntimeException("读请求数据出错");
                }
                pos = 0;
                readStart = 0;
            }
            if (buf[pos] == '\r') {
                // Skip CR.
            } else if (buf[pos] == '\n') {
                space = true;
            } else {
                requestLine.protocol[readCount] = (char) buf[pos];
                readCount++;
            }
            pos++;
        }
        requestLine.protocolEnd = readCount;
    }

    // 重写inpustream的read方法
    public int read() throws IOException {
        // 上一次读取位置
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return buf[pos++] & 0xff;
    }

    // 一次读取多少数据
    protected void fill() throws IOException {
        pos = 0;
        count = 0;
        int nRead = is.read(buf, 0, buf.length);
        if (nRead > 0) {
            count = nRead;
        }
    }

}

改动:Request
原先实现ServletRequest 改为实现HttpServletRequest, 满足http请求处理

/**
 * 请求信息封装对象
 */
public class Request implements HttpServletRequest{
    private String method;
    private String protocol;
    //请求行信息信息中的uri
    private String requestURI;
    // 浏览器socket连接的读流
    private InputStream in;
    //URL上面的参数
    private String queryString;
    public Request(InputStream in) {
        this.in = in;
    }
    /**省略一堆目前暂时没用到的ServletRequest需要实现的方法*/
}

改动:HttpServer
添加了解析请求行的操作

/**
 * 模拟tomcat的核心类
 */
public class HttpServer {
    // 模拟tomcat关闭命令
    private static final String SHUTDOWN_CMD = "/SHUTDOWN";
    private boolean shutdown = false;
    private HttpRequestLine requestLine = new HttpRequestLine();
    private Request request;
    //持续监听端口
    @SuppressWarnings("resource")
    public void accept() {
        ServerSocket serverSocket = null;
        try {
            // 启动socket服务, 监听8080端口,
            serverSocket =  new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("启动myTomcat服务器失败:" + e.getMessage(), e);
        }
        // 没接收到关闭命令前一直监听
        while (!shutdown) {
            Socket socket = null;
            InputStream in = null;
            OutputStream out = null;
            SocketInputStream input = null;
            try {
                // 接收请求
                socket = serverSocket.accept();
                in = socket.getInputStream();
                out = socket.getOutputStream();
                // 将浏览器发送的请求信息封装成请求对象
                input = new SocketInputStream(in, 2048);
                request = new Request(input);
                parseRequest(input);
                // 将相应信息封装相应对象
                Response response = new Response(out);
                //实现约定:servlet请求路径必须以/servlet开头,以servlet简单类名结束
                if(request.getRequestURI().startsWith("/servlet")) {
                    ServletProcessor processor = new ServletProcessor();
                    processor.process(request, response);
                }else {
                    //此处简单响应一个静态资源文件
                    StaticSourceProcessor processor = new StaticSourceProcessor();
                    processor.process(request, response);
                }
                socket.close();
                //如果是使用关闭命令,停止监听退出
                shutdown = request.getRequestURI().equals(SHUTDOWN_CMD);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    private void parseRequest(SocketInputStream input)  throws IOException {
        //读取请求数据,封装成reauestLine对象
        input.readRequestLine(requestLine);
        String method =
          new String(requestLine.method, 0, requestLine.methodEnd);
        String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

        if (method.length() < 1 || requestLine.uriEnd < 1) {
          throw new RuntimeException("解析请求出错");
        }
        request.setMethod(method);
        request.setProtocol(protocol);

        //对uri进行拆分,分uri + 查询参数
        String uri = null;
        int question = requestLine.indexOf("?");
        if (question >= 0) {
          request.setQueryString(new String(requestLine.uri, question + 1,
            requestLine.uriEnd - question - 1));
          uri = new String(requestLine.uri, 0, question);
        }
        else {
          request.setQueryString(null);
          uri = new String(requestLine.uri, 0, requestLine.uriEnd);
        }

        request.setRequestURI(uri);

        System.out.println("method:" + request.getMethod());
        System.out.println("protocol:" + request.getProtocol());
        System.out.println("uri:" + request.getRequestURI());
        System.out.println("queryString:" + request.getQueryString());
    }

    public static void main(String[] args) {
        new HttpServer().accept();
    }
}

到这, 本篇就结束了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值