手写一个民用Tomcat (06)

发布于:2024-04-19 ⋅ 阅读:(17) ⋅ 点赞:(0)

我们这次是引入获取参数,比如你的GET 请求 或者post 请求 如何吧请求参数进行封装 成map 集合 。

先看下erquest。请求类里边改造

private void parseRequestLine()  这个方法 改造成 依据 ?进行分割处理因为
http://localhost:8080/servlet/com.yixin.HelloWorldServlet?name=jxd&age=18请求 要把参数拿出来 name=jxd&age=18

新增了protected void parseParameters()  解析参数方方法

新增了 private static void putMapEntry 存入参数的方法

这里post 的解析只给出application/x-www-form-urlencoded 格式的请求,至于我们常用的json 请求 后期找机会给出,因为相对简单,就是解析 json 格式 的数据 。

具体的实现逻辑如下(给出关键性代码):

public class JxdRequest implements HttpServletRequest {
    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();

    private boolean parsed = false;

    private String queryString;

    public void parse(Socket socket) {
        try {
            input = socket.getInputStream();
            this.sis = new SocketInputStream(this.input, 2048);

            parseConnection(socket);
            this.sis.readRequestLine(requestLine);
            parseRequestLine();//解析数据
            parseHeaders();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }


    private void parseRequestLine() {
        int question = requestLine.indexOf("?");
        if (question >= 0) {
            queryString=new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1);
                    uri = new String(requestLine.uri, 0, question);
        } else {
            queryString = null;
            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);
            }
        }
    }

    protected void parseParameters() {
        String encoding = getCharacterEncoding();
        System.out.println(encoding);
        if (encoding == null) {
            encoding = "ISO-8859-1";
        }
        String qString = getQueryString();
        System.out.println("getQueryString:"+qString);
        if (qString != null) {
            byte[] bytes ;
            try {
                bytes = qString.getBytes(encoding);
                parseParameters(this.parameters, bytes, encoding);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();;
            }
        }
        String contentType = getContentType();
        if (contentType == null)
            contentType = "";
        int semicolon = contentType.indexOf(';');
        if (semicolon >= 0) {
            contentType = contentType.substring(0, semicolon).trim();
        }
        else {
            contentType = contentType.trim();
        }

        if ("POST".equals(getMethod()) && (getContentLength() > 0) && "application/x-www-form-urlencoded".equals(contentType)) {
            try {
                int max = getContentLength();
                int len = 0;
                byte buf[] = new byte[getContentLength()];
                ServletInputStream is = getInputStream();
                while (len < max) {
                    int next = is.read(buf, len, max - len);
                    if (next < 0) {
                        break;
                    }
                    len += next;
                }
                is.close();
                if (len < max) {
                    throw new RuntimeException("Content length mismatch");
                }
                parseParameters(this.parameters, buf, encoding);
            }
            catch (UnsupportedEncodingException ue) {
            }
            catch (IOException e) {
                throw new RuntimeException("Content read fail");
            }
        }
    }

    /**
     *
     * parseParameters 这个方法 举例 例如data name=jxd&age=18
     * 他会从0位置遍历到最后一位 同时设置两个指针一个ix 一个ox
     * ix 就是从char 数组0位开始遍历到最后
     * ox 是一个查找指针 ,当遇到 = 或者&时候,进行取舍,=前边表示key ,& 前边表示value 如果没有& 表示结尾
     * 要注意一个细节 当遇到 =或者& 时候会把ox 赋值0 ,但是为啥要    default: data[ox++] = c;
     * ,当我们遇到第一个=的时候 下一个是value=jxd 那么之前那个key(name) 就没有用了,因为已经赋值到map里边了 ,所以读取
     * jxd 时候覆盖掉前边的nam,然后 ox指针因为是从0开始 等遇到jxd后变边的&时候
     * 照样能把value=jxd 取出来 这样 一个数组 就能完成了,虽然data 原数组被改变 这样看似不太好但是,但是节省空间 不然你就要2个数组才能完成,一个取值一个
     * 放值 不得不说设计的很巧妙 。
     */
    public void parseParameters(Map<String,String[]> map, byte[] data, String encoding)
            throws UnsupportedEncodingException {
        if (parsed)
            return;
        System.out.println(data);
        if (data != null && data.length > 0) {
            int    pos = 0;
            int    ix = 0;
            int    ox = 0;
            String key = null;
            String value = null;
            while (ix < data.length) {
                byte c = data[ix++];
                switch ((char) c) {
                    case '&':
                        value = new String(data, 0, ox, encoding);
                        if (key != null) {
                            putMapEntry(map,key, value);
                            key = null;
                        }
                        ox = 0;
                        break;
                    case '=':
                        key = new String(data, 0, ox, encoding);
                        ox = 0;
                        break;
                    case '+':
                        data[ox++] = (byte)' ';
                        break;
                    case '%':
                        data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
                                + convertHexDigit(data[ix++]));
                        break;
                    default:
                        data[ox++] = c;
                }
            }
            //The last value does not end in '&'.  So save it now.
            //最后一个参数没有&结尾
            if (key != null) {
                value = new String(data, 0, ox, encoding);
                putMapEntry(map,key, value);
            }
        }
        parsed = true;
    }

    private byte convertHexDigit(byte b) {
        if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
        if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
        if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
        return 0;
    }

    /**
     *
     * 这个方式是 存入map集合 因为有的value值对应多个key 所以是数组形式存储value
     */
    private static void putMapEntry( Map<String,String[]> map, String name, String value) {
        String[] newValues = null;
        String[] oldValues = (String[]) map.get(name);
        if (oldValues == null) {
            newValues = new String[1];
            newValues[0] = value;
        } else {
            newValues = new String[oldValues.length + 1];
            System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
            newValues[oldValues.length] = value;
        }
        map.put(name, newValues);
    }


    public String getUri() {
        return uri;
    }

 

    @Override
    public String getMethod() {
       return new String(this.requestLine.method, 0, this.requestLine.methodEnd);
    }


    @Override
    public Collection<Part> getParts() throws IOException, ServletException {
        return null;
    }

    @Override
    public Part getPart(String s) throws IOException, ServletException {
        return null;
    }

    @Override
    public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
        return null;
    }

    @Override
    public Object getAttribute(String s) {
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {

    }

    @Override
    public int getContentLength() {
        return Integer.parseInt(headers.get(DefaultHeaders.CONTENT_LENGTH_NAME));
    }

    @Override
    public long getContentLengthLong() {
        return 0;
    }

    @Override
    public String getContentType() {
        return headers.get(DefaultHeaders.CONTENT_TYPE_NAME);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return this.sis;
    }

    @Override
    public String getParameter(String name) {
        parseParameters();
        String values[] = parameters.get(name);
        if (values != null)
            return (values[0]);
        else
            return (null);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        parseParameters();
        return (Collections.enumeration(parameters.keySet()));
    }

    @Override
    public String[] getParameterValues(String name) {
        parseParameters();
        String values[] = (String[]) parameters.get(name);
        if (values != null)
            return (values);
        else
            return null;
    }

}

这个类里边我们增加了

//这段代码是测试用,可以获取的 请求参数 支持get 和post
Map<String, String[]> map = requestFacade.getParameterMap();

这段代码 可以运行 测试的main 方法进行测试 看一下 map 里边的数据。

public class JxdServletProcessor {


    public void process(JxdRequest request, JxdResponse response) {
//首先根据uri最后一个/号来定位,后面的字符串认为是servlet名字
        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;
        try {
// create a URLClassLoader
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(JxdHttpServer.WEB_ROOT);
            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) {
            System.out.println(e.toString());
        }

        //由上面的URLClassLoader加载这个servlet
        Class<?> servletClass = null;
        Servlet servlet = null;
        try {

            HttpRequestFacade requestFacade = new HttpRequestFacade(request);
            HttpResponseFacade responseFacade = new HttpResponseFacade(response);
            servletClass = loader.loadClass(servletName);
            response.setCharacterEncoding("UTF-8");
            response.addHeader(DefaultHeaders.CONTENT_TYPE_NAME,"text/html;charset=UTF-8");

            response.sendHeaders();//发送响应头
            //这段代码是测试用,可以获取的 请求参数 支持get 和post
            Map<String, String[]> map = requestFacade.getParameterMap();

            servlet = (Servlet) servletClass.newInstance();
            servlet.service(requestFacade, responseFacade);
        } catch (ClassNotFoundException | IOException e) {
            System.out.println(e.toString());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
}