Jodd 是一个开源的 Java 工具集, 包含一些实用的工具类和小型框架。简单,却很强大!
jodd-http是一个轻巧的HTTP客户端。现在我们以一个简单的示例从源码层看看是如何实现的?
Sy<a href="https://www.jb51.cc/tag/stem/" target="_blank" class="keywords">stem</a>.out.println(response);//3.打印响应信息</span></pre>
构建一个get请求
先复习一下http请求报文的格式:
下图展示一般请求所带有的属性
method方法如下:
set方法如下:
</span><span style="color: #ff0000;">// http method</span>
<span style="color: #0000ff;">int</span> ndx = destination.indexOf(' '<span style="color: #000000;">);
</span><span style="color: #0000ff;">if</span> (ndx != -1<span style="color: #000000;">) {
method </span>= destination.substring(0<span style="color: #000000;">,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;">);
}
</span><span style="color: #ff0000;">// protocol</span>
<span style="color: #000000;">
ndx
</span><span style="color: #0000ff;">if</span> (ndx != -1<span style="color: #000000;">) {
protocol </span>= destination.substring(0<span style="color: #000000;">,ndx);
destination </span>= destination.substring(ndx + 3<span style="color: #000000;">);
}
</span><span style="color: #ff0000;">// host</span>
<span style="color: #000000;">
ndx
</span><span style="color: #0000ff;">if</span> (ndx == -1<span style="color: #000000;">) {
ndx </span>=<span style="color: #000000;"> destination.length();
}
</span><span style="color: #0000ff;">if</span> (ndx != 0<span style="color: #000000;">) {
host </span>= destination.substring(0<span style="color: #000000;">,ndx);
destination </span>=<span style="color: #000000;"> destination.substring(ndx);
</span><span style="color: #ff0000;">// port</span>
<span style="color: #000000;">
ndx
</span><span style="color: #0000ff;">if</span> (ndx == -1<span style="color: #000000;">) {
port </span>=<span style="color: #000000;"> DEFAULT_PORT;
} </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
port </span>= Integer.parseInt(host.substring(ndx + 1<span style="color: #000000;">));
host </span>= host.substring(0<span style="color: #000000;">,ndx);
}
}
</span><span style="color: #ff0000;">// path + query</span>
<span style="color: #000000;">
path(destination);
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">this</span><span style="color: #000000;">;
}</span></pre>
上述方法,根据destination解析出一下几个部分:
1. 方法:HTTP1.1支持7种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。
2. 协议:http或者https
3. 主机:请求的服务器地址
4. 端口:请求的服务器端口
5. 路径+查询参数,其中参数以“?”开头,使用“&”连接
<span style="color: #0000ff;">if</span> (path.startsWith(StringPool.SLASH) == <span style="color: #0000ff;">false</span><span style="color: #000000;">) {
path </span>= StringPool.SLASH +<span style="color: #000000;"> path;
}
</span><span style="color: #0000ff;">int</span> ndx = path.indexOf('?'<span style="color: #000000;">);
</span><span style="color: #0000ff;">if</span> (ndx != -1<span style="color: #000000;">) {
String queryString </span>= path.substring(ndx + 1<span style="color: #000000;">);
path </span>= path.substring(0<span style="color: #000000;">,ndx);
query </span>= HttpUtil.parseQuery(queryString,<span style="color: #0000ff;">true</span><span style="color: #000000;">);
} </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
query </span>=<span style="color: #000000;"> HttpValuesMap.ofObjects();
}
</span><span style="color: #0000ff;">this</span>.path =<span style="color: #000000;"> path;
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">this</span><span style="color: #000000;">;
}</span></pre>
发送请求
先熟悉一下http响应报文的格式:
响应首部一般包含如下内容:
</span><span style="color: #008000;">//</span><span style="color: #008000;"> prepare http connection</span>
<span style="color: #0000ff;">if</span> (timeout != -1<span style="color: #000000;">) {
<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>.setTimeout(timeout);
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> sends data</span>
<span style="color: #000000;"> HttpResponse httpResponse;
OutputStream outputStream =<span style="color: #000000;"> httpconnection.getoutputStream();
sendTo(outputStream);
InputStream inputStream </span>=<span style="color: #000000;"> <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>.getInputStream();
httpResponse </span>=<span style="color: #000000;"> HttpResponse.readFrom(inputStream);
httpResponse.assignHttpRequest(</span><span style="color: #0000ff;">this</span><span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (IOException ioex) {
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpException(ioex);
}
</span><span style="color: #0000ff;">boolean</span> keepAlive =<span style="color: #000000;"> httpResponse.isConnectionPersistent();
</span><span style="color: #0000ff;">if</span> (keepAlive == <span style="color: #0000ff;">false</span><span style="color: #000000;">) {
</span><span style="color: #008000;">//</span><span style="color: #008000;"> closes connection if keep alive is false,or if counter reached 0</span>
<span style="color: #000000;"> httpconnection.close();
httpconnection
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> httpResponse;
}</span></pre>
1. 打开httpconnection
</span><span style="color: #008000;">/**</span><span style="color: #008000;">
* Opens a new {</span><span style="color: #808080;">@link</span><span style="color: #008000;"> jodd.http.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a> connection}
* using given {</span><span style="color: #808080;">@link</span><span style="color: #008000;"> jodd.http.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider}.
</span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span><span style="color: #000000;"> 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;">if</span> (<span style="color: #0000ff;">this</span>.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a> != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> HttpException("Connection already opened"<span style="color: #000000;">);
}
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
</span><span style="color: #0000ff;">this</span>.<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider =<span style="color: #000000;"> <a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>Provider;
</span><span style="color: #ff0000;">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;"><span style="color: #ff0000;">);</span>
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (IOException ioex) {
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpException(ioex);
}
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">this</span><span style="color: #000000;">;
}</span></pre>
判断是否有连接,若没有连接则创建一个新的连接。
2. 创建连接实现
</span><span style="color: #0000ff;">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;">)) {
SSLSocket sslSocket </span>=<span style="color: #000000;"> createSSLSocket(httpRequest.host(),httpRequest.port());
sslSocket.startHandshake();
socket </span>=<span style="color: #000000;"> sslSocket;
} </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
socket </span>=<span style="color: #000000;"> createSocket(httpRequest.host(),httpRequest.port());
}
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> 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时需要进行握手。
</span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">final</span><span style="color: #000000;"> Socket socket;
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> Socket<a href="https://www.jb51.cc/tag/httpconnection/" target="_blank" class="keywords">httpconnection</a>(Socket socket) {
</span><span style="color: #0000ff;">this</span><a href="https://www.jb51.cc/tag/so/" target="_blank" class="keywords">.so</a>cket =<span style="color: #000000;"> socket;
}
</span><span style="color: #0000ff;">public</span> OutputStream g<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>utputStream() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException {
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> socket.g<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>utputStream();
}
</span><span style="color: #0000ff;">public</span> InputStream getInputStream() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException {
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> socket.getInputStream();
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> close() {
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
socket.close();
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (IOException ig<a href="https://www.jb51.cc/tag/nor/" target="_blank" class="keywords">nor</a>e) {
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> setTimeout(<span style="color: #0000ff;">int</span><span style="color: #000000;"> milliseconds) {
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
socket.setSoTimeout(milliseconds);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (SocketException sex) {
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpException(sex);
}
}
</span><span style="color: #008000;">/**</span><span style="color: #008000;">
* Returns <code>Socket</code> used by this connection.
</span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span><span style="color: #000000;"> Socket getSocket() {
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> socket;
}
}
打开Connection的输出流发送信息,打开connection的输入流接受返回信息。
<span style="color: #ff0000;">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;">/**</span><span style="color: #008000;">
* Sends request or response to output stream.
</span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> sendTo(OutputStream out) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException {
Buffer buffer </span>= buffer(<span style="color: #0000ff;">true</span><span style="color: #000000;">);
</span><span style="color: #0000ff;">if</span> (httpProgressListener == <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
buffer.writ<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>(out);
}
</span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
buffer.writ<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>(out,httpProgressListener);
}
out.flush();
}</span></pre>
将缓冲区的数据写入输出流,并发送。
接受数据并读取报文内容:
HttpResponse httpResponse </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> HttpResponse();
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 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;
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;">if</span> (line != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {
line </span>=<span style="color: #000000;"> line.trim();
</span><span style="color: #0000ff;">int</span> ndx = line.indexOf(' '<span style="color: #000000;">);
httpResponse.httpVersion(line.substring(</span>0<span style="color: #000000;">,ndx));
</span><span style="color: #0000ff;">int</span> ndx2 = line.indexOf(' ',ndx + 1<span style="color: #000000;">);
</span><span style="color: #0000ff;">if</span> (ndx2 == -1<span style="color: #000000;">) {
ndx2 </span>=<span style="color: #000000;"> 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;">return</span><span style="color: #000000;"> httpResponse;
}</span></pre>
小结
从上面的代码,我们可以看出http使用socket来建立和destination的连接,然后通过连接的输出流和输入流来进行通信。
参考文献:
【1】http://www.it165.net/admin/html/201403/2541.html
【2】http://jodd.org/doc/http.html