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

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

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

 HttpRequest httpRequest = HttpRequest.get("http://jodd.org"一个get请求 HttpResponse response =Sy<a href="https://www.jb51.cc/tag/stem/" target="_blank" class="keywords">stem</a>.out.println(response);//3.打印响应信息</span></pre>

构建一个get请求

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

wKioL1MpX-qwK1-PAAExXPRpR8M814.jpg

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

wKiom1MphduAsu6XAAM_loPLbc0713.jpg

调用get方法构建http请求:

"GET"

method方法如下:

.method =toupperCase();

set方法如下:

= </span><span style="color: #ff0000;"&gt;// http method</span> <span style="color: #0000ff;"&gt;int</span> ndx = destination.indexOf(' '<span style="color: #000000;"&gt;); </span><span style="color: #0000ff;"&gt;if</span> (ndx != -1<span style="color: #000000;"&gt;) { method </span>= destination.substring(0<span style="color: #000000;"&gt;,ndx).<a href="https://www.jb51.cc/tag/toupper/" target="_blank" class="keywords">toupper</a>Case(); destination </span>= destination.substring(ndx + 1<span style="color: #000000;"&gt;); } </span><span style="color: #ff0000;"&gt;// protocol</span>

<span style="color: #000000;">
ndx = destination.indexOf("://"<span style="color: #000000;">);

    </span><span style="color: #0000ff;"&gt;if</span> (ndx != -1<span style="color: #000000;"&gt;) {
        protocol </span>= destination.substring(0<span style="color: #000000;"&gt;,ndx);
        destination </span>= destination.substring(ndx + 3<span style="color: #000000;"&gt;);
    }

    </span><span style="color: #ff0000;"&gt;// host</span>

<span style="color: #000000;">
ndx = destination.indexOf('/'<span style="color: #000000;">);

    </span><span style="color: #0000ff;"&gt;if</span> (ndx == -1<span style="color: #000000;"&gt;) {
        ndx </span>=<span style="color: #000000;"&gt; destination.length();
    }

    </span><span style="color: #0000ff;"&gt;if</span> (ndx != 0<span style="color: #000000;"&gt;) {

        host </span>= destination.substring(0<span style="color: #000000;"&gt;,ndx);
        destination </span>=<span style="color: #000000;"&gt; destination.substring(ndx);

        </span><span style="color: #ff0000;"&gt;// port</span>

<span style="color: #000000;">
ndx = host.indexOf(':'<span style="color: #000000;">);

        </span><span style="color: #0000ff;"&gt;if</span> (ndx == -1<span style="color: #000000;"&gt;) {
            port </span>=<span style="color: #000000;"&gt; DEFAULT_PORT;
        } </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt; {
            port </span>= Integer.parseInt(host.substring(ndx + 1<span style="color: #000000;"&gt;));
            host </span>= host.substring(0<span style="color: #000000;"&gt;,ndx);
        }
    }

    </span><span style="color: #ff0000;"&gt;// path + query</span>

<span style="color: #000000;">
path(destination);

    </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #0000ff;"&gt;this</span><span style="color: #000000;"&gt;;
}</span></pre>

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

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

2. 协议:http或者https

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

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

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

IoUs query is discarded. *
    <span style="color: #0000ff;"&gt;if</span> (path.startsWith(StringPool.SLASH) == <span style="color: #0000ff;"&gt;false</span><span style="color: #000000;"&gt;) {
        path </span>= StringPool.SLASH +<span style="color: #000000;"&gt; path;
    }

    </span><span style="color: #0000ff;"&gt;int</span> ndx = path.indexOf('?'<span style="color: #000000;"&gt;);

    </span><span style="color: #0000ff;"&gt;if</span> (ndx != -1<span style="color: #000000;"&gt;) {
        String queryString </span>= path.substring(ndx + 1<span style="color: #000000;"&gt;);

        path </span>= path.substring(0<span style="color: #000000;"&gt;,ndx);

        query </span>= HttpUtil.parseQuery(queryString,<span style="color: #0000ff;"&gt;true</span><span style="color: #000000;"&gt;);
    } </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt; {
        query </span>=<span style="color: #000000;"&gt; HttpValuesMap.ofObjects();
    }

    </span><span style="color: #0000ff;"&gt;this</span>.path =<span style="color: #000000;"&gt; path;

    </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #0000ff;"&gt;this</span><span style="color: #000000;"&gt;;
}</span></pre>

发送请求

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

wKiom1MpmHWALc2UAADu14JLceA655.jpg

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

wKiom1MprnXiYF18AALhmNtc3OE334.jpg

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. (httpconnection == open(); }
    </span><span style="color: #008000;"&gt;//</span><span style="color: #008000;"&gt; prepare http connection</span>

    <span style="color: #0000ff;"&gt;if</span> (timeout != -1<span style="color: #000000;"&gt;) {
        <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>.setTimeout(timeout);
    }

    </span><span style="color: #008000;"&gt;//</span><span style="color: #008000;"&gt; sends data</span>

<span style="color: #000000;"> HttpResponse httpResponse;
<span style="color: #0000ff;">try<span style="color: #000000;"> {
OutputStream outputStream =<span style="color: #000000;"> httpconnection.getoutputStream();

        sendTo(outputStream);

        InputStream inputStream </span>=<span style="color: #000000;"&gt; <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>.getInputStream();

        httpResponse </span>=<span style="color: #000000;"&gt; HttpResponse.readFrom(inputStream);

        httpResponse.assignHttpRequest(</span><span style="color: #0000ff;"&gt;this</span><span style="color: #000000;"&gt;);
    } </span><span style="color: #0000ff;"&gt;catch</span><span style="color: #000000;"&gt; (IOException ioex) {
        </span><span style="color: #0000ff;"&gt;throw</span> <span style="color: #0000ff;"&gt;new</span><span style="color: #000000;"&gt; HttpException(ioex);
    }

    </span><span style="color: #0000ff;"&gt;boolean</span> keepAlive =<span style="color: #000000;"&gt; httpResponse.isConnectionPersistent();

    </span><span style="color: #0000ff;"&gt;if</span> (keepAlive == <span style="color: #0000ff;"&gt;false</span><span style="color: #000000;"&gt;) {
        </span><span style="color: #008000;"&gt;//</span><span style="color: #008000;"&gt; closes connection if keep alive is false,or if counter reached 0</span>

<span style="color: #000000;"> httpconnection.close();
httpconnection = <span style="color: #0000ff;">null<span style="color: #000000;">;
}

    </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; httpResponse;
}</span></pre>

1. 打开httpconnection

httpconnection connection} using * {httpconnectionProvider default connection provider}. open() { httpconnectionProvider); }
</span><span style="color: #008000;"&gt;/**</span><span style="color: #008000;"&gt;
 * Opens a new {</span><span style="color: #808080;"&gt;@link</span><span style="color: #008000;"&gt; jodd.http.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a> connection}
 * using given {</span><span style="color: #808080;"&gt;@link</span><span style="color: #008000;"&gt; jodd.http.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider}.
 </span><span style="color: #008000;"&gt;*/</span>
<span style="color: #0000ff;"&gt;public</span><span style="color: #000000;"&gt; HttpRequest open(<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider) {
    </span><span style="color: #0000ff;"&gt;if</span> (<span style="color: #0000ff;"&gt;this</span>.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a> != <span style="color: #0000ff;"&gt;null</span><span style="color: #000000;"&gt;) {
        </span><span style="color: #0000ff;"&gt;throw</span> <span style="color: #0000ff;"&gt;new</span> HttpException("Connection already opened"<span style="color: #000000;"&gt;);
    }
    </span><span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt; {
        </span><span style="color: #0000ff;"&gt;this</span>.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider =<span style="color: #000000;"&gt; <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider;
        </span><span style="color: #ff0000;"&gt;this.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a> = <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider.create<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>(this</span><span style="color: #000000;"&gt;<span style="color: #ff0000;"&gt;);</span>
    } </span><span style="color: #0000ff;"&gt;catch</span><span style="color: #000000;"&gt; (IOException ioex) {
        </span><span style="color: #0000ff;"&gt;throw</span> <span style="color: #0000ff;"&gt;new</span><span style="color: #000000;"&gt; HttpException(ioex);
    }

    </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #0000ff;"&gt;this</span><span style="color: #000000;"&gt;;
}</span></pre>

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

2. 创建连接实现

httpconnection createhttpconnection(HttpRequest httpRequest) </span><span style="color: #0000ff;"&gt;if</span> (httpRequest.protocol().equalsIg<a href="https://www.jb51.cc/tag/nor/" target="_blank" class="keywords">nor</a>eCase("https"<span style="color: #000000;"&gt;)) { SSLSocket sslSocket </span>=<span style="color: #000000;"&gt; createSSLSocket(httpRequest.host(),httpRequest.port()); sslSocket.startHandshake(); socket </span>=<span style="color: #000000;"&gt; sslSocket; } </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt; { socket </span>=<span style="color: #000000;"&gt; createSocket(httpRequest.host(),httpRequest.port()); } </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #0000ff;"&gt;new</span><span style="color: #000000;"&gt; Socket<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>(socket); }</span></pre>

3. 创建socket

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

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

Sockethttpconnection httpconnection {
</span><span style="color: #0000ff;"&gt;protected</span> <span style="color: #0000ff;"&gt;final</span><span style="color: #000000;"&gt; Socket socket;

</span><span style="color: #0000ff;"&gt;public</span><span style="color: #000000;"&gt; Socket<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>(Socket socket) {
    </span><span style="color: #0000ff;"&gt;this</span><a href="https://www.jb51.cc/tag/so/" target="_blank" class="keywords">.so</a>cket =<span style="color: #000000;"&gt; socket;
}

</span><span style="color: #0000ff;"&gt;public</span> OutputStream g<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>utputStream() <span style="color: #0000ff;"&gt;throws</span><span style="color: #000000;"&gt; IOException {
    </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; socket.g<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>utputStream();
}

</span><span style="color: #0000ff;"&gt;public</span> InputStream getInputStream() <span style="color: #0000ff;"&gt;throws</span><span style="color: #000000;"&gt; IOException {
    </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; socket.getInputStream();
}

</span><span style="color: #0000ff;"&gt;public</span> <span style="color: #0000ff;"&gt;void</span><span style="color: #000000;"&gt; close() {
    </span><span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt; {
        socket.close();
    } </span><span style="color: #0000ff;"&gt;catch</span><span style="color: #000000;"&gt; (IOException ig<a href="https://www.jb51.cc/tag/nor/" target="_blank" class="keywords">nor</a>e) {
    }
}

</span><span style="color: #0000ff;"&gt;public</span> <span style="color: #0000ff;"&gt;void</span> setTimeout(<span style="color: #0000ff;"&gt;int</span><span style="color: #000000;"&gt; milliseconds) {
    </span><span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt; {
        socket.setSoTimeout(milliseconds);
    } </span><span style="color: #0000ff;"&gt;catch</span><span style="color: #000000;"&gt; (SocketException sex) {
        </span><span style="color: #0000ff;"&gt;throw</span> <span style="color: #0000ff;"&gt;new</span><span style="color: #000000;"&gt; HttpException(sex);
    }
}

</span><span style="color: #008000;"&gt;/**</span><span style="color: #008000;"&gt;
 * Returns <code>Socket</code> used by this connection.
 </span><span style="color: #008000;"&gt;*/</span>
<span style="color: #0000ff;"&gt;public</span><span style="color: #000000;"&gt; Socket getSocket() {
    </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; socket;
}

}

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

OutputStream outputStream =httpconnection.getoutputStream();
        <span style="color: #ff0000;"&gt;sendTo(outputStream);</span>

        InputStream inputStream </span>= <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>.getInputStream();</pre>

发送过程:

</span><span style="color: #008000;"&gt;/**</span><span style="color: #008000;"&gt; * Sends request or response to output stream. </span><span style="color: #008000;"&gt;*/</span> <span style="color: #0000ff;"&gt;public</span> <span style="color: #0000ff;"&gt;void</span> sendTo(OutputStream out) <span style="color: #0000ff;"&gt;throws</span><span style="color: #000000;"&gt; IOException { Buffer buffer </span>= buffer(<span style="color: #0000ff;"&gt;true</span><span style="color: #000000;"&gt;); </span><span style="color: #0000ff;"&gt;if</span> (httpProgressListener == <span style="color: #0000ff;"&gt;null</span><span style="color: #000000;"&gt;) { buffer.writ<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>(out); } </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt; { buffer.writ<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>(out,httpProgressListener); } out.flush(); }</span></pre>

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

接受数据并读取报文内容

} nore) { = HttpResponse httpResponse </span>= <span style="color: #0000ff;"&gt;new</span><span style="color: #000000;"&gt; HttpResponse(); </span><span style="color: #008000;"&gt;//</span><span style="color: #008000;"&gt; the f<a href="https://www.jb51.cc/tag/irs/" target="_blank" class="keywords">irs</a>t line</span>

<span style="color: #000000;"> String line;
<span style="color: #0000ff;">try<span style="color: #000000;"> {
line =<span style="color: #000000;"> reader.readLine();
} <span style="color: #0000ff;">catch<span style="color: #000000;"> (IOException ioex) {
<span style="color: #0000ff;">throw <span style="color: #0000ff;">new<span style="color: #000000;"> HttpException(ioex);
}

    </span><span style="color: #0000ff;"&gt;if</span> (line != <span style="color: #0000ff;"&gt;null</span><span style="color: #000000;"&gt;) {

        line </span>=<span style="color: #000000;"&gt; line.trim();

        </span><span style="color: #0000ff;"&gt;int</span> ndx = line.indexOf(' '<span style="color: #000000;"&gt;);
        httpResponse.httpVersion(line.substring(</span>0<span style="color: #000000;"&gt;,ndx));

        </span><span style="color: #0000ff;"&gt;int</span> ndx2 = line.indexOf(' ',ndx + 1<span style="color: #000000;"&gt;);
        </span><span style="color: #0000ff;"&gt;if</span> (ndx2 == -1<span style="color: #000000;"&gt;) {
            ndx2 </span>=<span style="color: #000000;"&gt; line.length();
        }
        httpResponse.statusCode(Integer.parseInt(line.substring(ndx,ndx2).trim()));

        httpResponse.statusPhrase(line.substring(ndx2).trim());
    }

    httpResponse.readHeaders(reader);
    httpResponse.readBody(reader);

    </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; httpResponse;
}</span></pre>

小结

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

参考文献:

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

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

相关文章

HashMap是Java中最常用的集合类框架,也是Java语言中非常典型...
在EffectiveJava中的第 36条中建议 用 EnumSet 替代位字段,...
介绍 注解是JDK1.5版本开始引入的一个特性,用于对代码进行说...
介绍 LinkedList同时实现了List接口和Deque接口,也就是说它...
介绍 TreeSet和TreeMap在Java里有着相同的实现,前者仅仅是对...
HashMap为什么线程不安全 put的不安全 由于多线程对HashMap进...