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响应报文的格式:
响应首部一般包含如下内容:
/** * {@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
微信公众号: 架构师日常笔记 欢迎关注!