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

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

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

简约之美Jodd-http--深入源码理解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响应报文的格式:
简约之美Jodd-http--深入源码理解http协议

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

简约之美Jodd-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

上一篇:ActiveMQ入门代码


下一篇:go语言 panic: sql: expected 10 destination arguments in Scan, not 2