您的位置:首页 > 理论基础 > 计算机网络

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

2020-11-28 21:48 1331 查看

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请求报文的格式:

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

调用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响应报文的格式:

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

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

/**
* {@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;
}

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

  1. 创建连接实现
/**
* 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);
}
  1. 创建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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: