• 简约之美Jodd-http--深入源码理解http协议


    Jodd 是一个开源的 Java 工具集, 包含一些实用的工具类和小型框架。简单,却很强大!

    jodd-http是一个轻巧的HTTP客户端。现在我们以一个简单的示例从源码层看看是如何实现的?

       HttpRequest httpRequest = HttpRequest.get("http://jodd.org"); //1. 构建一个get请求
        HttpResponse response = httpRequest.send(); //2.发送请求并接受响应信息
    
        System.out.println(response);//3.打印响应信息

    构建一个get请求

    先复习一下http请求报文的格式:

    wKioL1MpX-qwK1-PAAExXPRpR8M814.jpg

    下图展示一般请求所带有的属性

    wKiom1MphduAsu6XAAM_loPLbc0713.jpg

    调用get方法构建http请求:

        /**
         * Builds a GET request.
         */
        public static HttpRequest get(String destination) {
            return new HttpRequest()
                    .method("GET")
                    .set(destination);
        }

    method方法如下:

        /**
         * Specifies request method. It will be converted into uppercase.
         */
        public HttpRequest method(String method) {
            this.method = method.toUpperCase();
            return this;
        }

    set方法如下:

    /**
         * Sets the destination (method, host, port... ) at once.
         */
        public HttpRequest set(String destination) {
            destination = destination.trim();
    
            // http method
    
            int ndx = destination.indexOf(' ');
    
            if (ndx != -1) {
                method = destination.substring(0, ndx).toUpperCase();
                destination = destination.substring(ndx + 1);
            }
    
            // protocol
    
            ndx = destination.indexOf("://");
    
            if (ndx != -1) {
                protocol = destination.substring(0, ndx);
                destination = destination.substring(ndx + 3);
            }
    
            // host
    
            ndx = destination.indexOf('/');
    
            if (ndx == -1) {
                ndx = destination.length();
            }
    
            if (ndx != 0) {
    
                host = destination.substring(0, ndx);
                destination = destination.substring(ndx);
    
                // port
    
                ndx = host.indexOf(':');
    
                if (ndx == -1) {
                    port = DEFAULT_PORT;
                } else {
                    port = Integer.parseInt(host.substring(ndx + 1));
                    host = host.substring(0, ndx);
                }
            }
    
            // path + query
    
            path(destination);
    
            return this;
        }

    上述方法,根据destination解析出一下几个部分:

    1. 方法:HTTP1.1支持7种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。

    2. 协议:http或者https

    3. 主机:请求的服务器地址

    4. 端口:请求的服务器端口

    5. 路径+查询参数,其中参数以“?”开头,使用“&”连接

        /**
         * Sets request path. Query string is allowed.
         * Adds a slash if path doesn't start with one.
         * Query will be stripped out from the path.
         * Previous query is discarded.
         * @see #query()
         */
        public HttpRequest path(String path) {
            // this must be the only place that sets the path
    
            if (path.startsWith(StringPool.SLASH) == false) {
                path = StringPool.SLASH + path;
            }
    
            int ndx = path.indexOf('?');
    
            if (ndx != -1) {
                String queryString = path.substring(ndx + 1);
    
                path = path.substring(0, ndx);
    
                query = HttpUtil.parseQuery(queryString, true);
            } else {
                query = HttpValuesMap.ofObjects();
            }
    
            this.path = path;
    
            return this;
        }

    发送请求

    先熟悉一下http响应报文的格式:

    wKiom1MpmHWALc2UAADu14JLceA655.jpg

    响应首部一般包含如下内容:

    wKiom1MprnXiYF18AALhmNtc3OE334.jpg

    /**
         * {@link #open() Opens connection} if not already open, sends request,
         * reads response and closes the request. If keep-alive mode is enabled
         * connection will not be closed.
         */
        public HttpResponse send() {
            if (httpConnection == null) {
                open();
            }
    
            // prepare http connection
    
            if (timeout != -1) {
                httpConnection.setTimeout(timeout);
            }
    
            // sends data
            HttpResponse httpResponse;
            try {
                OutputStream outputStream = httpConnection.getOutputStream();
    
                sendTo(outputStream);
    
                InputStream inputStream = httpConnection.getInputStream();
    
                httpResponse = HttpResponse.readFrom(inputStream);
    
                httpResponse.assignHttpRequest(this);
            } catch (IOException ioex) {
                throw new HttpException(ioex);
            }
    
            boolean keepAlive = httpResponse.isConnectionPersistent();
    
            if (keepAlive == false) {
                // closes connection if keep alive is false, or if counter reached 0
                httpConnection.close();
                httpConnection = null;
            }
    
            return httpResponse;
        }

    1. 打开HttpConnection

        /**
         * Opens a new {@link HttpConnection connection} using
         * {@link JoddHttp#httpConnectionProvider default connection provider}.
         */
        public HttpRequest open() {
            return open(JoddHttp.httpConnectionProvider);
        }
    
        /**
         * Opens a new {@link jodd.http.HttpConnection connection}
         * using given {@link jodd.http.HttpConnectionProvider}.
         */
        public HttpRequest open(HttpConnectionProvider httpConnectionProvider) {
            if (this.httpConnection != null) {
                throw new HttpException("Connection already opened");
            }
            try {
                this.httpConnectionProvider = httpConnectionProvider;
                this.httpConnection = httpConnectionProvider.createHttpConnection(this);
            } catch (IOException ioex) {
                throw new HttpException(ioex);
            }
    
            return this;
        }

    判断是否有连接,若没有连接则创建一个新的连接。

    2. 创建连接实现

        /**
         * Creates new connection from current {@link jodd.http.HttpRequest request}.
         *
         * @see #createSocket(String, int)
         */
        public HttpConnection createHttpConnection(HttpRequest httpRequest) throws IOException {
            Socket socket;
    
            if (httpRequest.protocol().equalsIgnoreCase("https")) {
                SSLSocket sslSocket = createSSLSocket(httpRequest.host(), httpRequest.port());
    
                sslSocket.startHandshake();
    
                socket = sslSocket;
            } else {
                socket = createSocket(httpRequest.host(), httpRequest.port());
            }
    
            return new SocketHttpConnection(socket);
        }

    3. 创建socket

      根据协议的不同,http使用SocketFactory创建socket,https使用SSLSocketFactory创建SSLSocket。最终使用SocketHttpConnection进行包装。

    SocketHttpConnection继承自HttpConnection,实现了socket的输入输出流连接。注意:https创建完SSLSocket时需要进行握手。

    public class SocketHttpConnection implements HttpConnection {
    
        protected final Socket socket;
    
        public SocketHttpConnection(Socket socket) {
            this.socket = socket;
        }
    
        public OutputStream getOutputStream() throws IOException {
            return socket.getOutputStream();
        }
    
        public InputStream getInputStream() throws IOException {
            return socket.getInputStream();
        }
    
        public void close() {
            try {
                socket.close();
            } catch (IOException ignore) {
            }
        }
    
        public void setTimeout(int milliseconds) {
            try {
                socket.setSoTimeout(milliseconds);
            } catch (SocketException sex) {
                throw new HttpException(sex);
            }
        }
    
        /**
         * Returns <code>Socket</code> used by this connection.
         */
        public Socket getSocket() {
            return socket;
        }
    }

     打开Connection的输出流发送信息,打开connection的输入流接受返回信息。

                OutputStream outputStream = httpConnection.getOutputStream();
    
                sendTo(outputStream);
    
                InputStream inputStream = httpConnection.getInputStream();

    发送过程:

        protected HttpProgressListener httpProgressListener;
    
        /**
         * Sends request or response to output stream.
         */
        public void sendTo(OutputStream out) throws IOException {
            Buffer buffer = buffer(true);
    
            if (httpProgressListener == null) {
                buffer.writeTo(out);
            }
            else {
                buffer.writeTo(out, httpProgressListener);
            }
    
            out.flush();
        }

    将缓冲区的数据写入输出流,并发送。

    接受数据并读取报文内容:

    /**
         * Reads response input stream and returns {@link HttpResponse response}.
         * Supports both streamed and chunked response.
         */
        public static HttpResponse readFrom(InputStream in) {
            InputStreamReader inputStreamReader;
            try {
                inputStreamReader = new InputStreamReader(in, StringPool.ISO_8859_1);
            } catch (UnsupportedEncodingException ignore) {
                return null;
            }
            BufferedReader reader = new BufferedReader(inputStreamReader);
    
            HttpResponse httpResponse = new HttpResponse();
    
            // the first line
            String line;
            try {
                line = reader.readLine();
            } catch (IOException ioex) {
                throw new HttpException(ioex);
            }
    
            if (line != null) {
    
                line = line.trim();
    
                int ndx = line.indexOf(' ');
                httpResponse.httpVersion(line.substring(0, ndx));
    
                int ndx2 = line.indexOf(' ', ndx + 1);
                if (ndx2 == -1) {
                    ndx2 = line.length();
                }
                httpResponse.statusCode(Integer.parseInt(line.substring(ndx, ndx2).trim()));
    
                httpResponse.statusPhrase(line.substring(ndx2).trim());
            }
    
            httpResponse.readHeaders(reader);
            httpResponse.readBody(reader);
    
            return httpResponse;
        }

    小结

      从上面的代码,我们可以看出http使用socket来建立和destination的连接,然后通过连接的输出流和输入流来进行通信。

    参考文献:

    【1】http://www.it165.net/admin/html/201403/2541.html

    【2】http://jodd.org/doc/http.html

  • 相关阅读:
    使用 Eclipse 调试 Java 程序的 10 个技巧
    oracle9i,10g再谈优化模式参数问题.
    oracle 索引
    解决IE不能在新窗口中向父窗口的下拉框添加项的问题
    获取文档的尺寸:利用Math.max的另一种方式
    揭开constructor属性的神秘面纱
    测试杂感:Windows8也许需要Account Hub
    探索式测试:探索是为了学习
    一次有教益的程序崩溃调试 (下)
    软件测试读书列表 (2013.8)
  • 原文地址:https://www.cnblogs.com/davidwang456/p/4569283.html
Copyright © 2020-2023  润新知