Jodd 是一個開源的 Java 工具集, 包含一些實用的工具類和小型框架。簡單,卻很強大!html
jodd-http是一個輕巧的HTTP客戶端。如今咱們以一個簡單的示例從源碼層看看是如何實現的?服務器
HttpRequest httpRequest = HttpRequest.get("http://jodd.org"); //1. 構建一個get請求 HttpResponse response = httpRequest.send(); //2.發送請求並接受響應信息 System.out.println(response);//3.打印響應信息
先複習一下http請求報文的格式:併發
下圖展現通常請求所帶有的屬性框架
調用get方法構建http請求:socket
/** * Builds a GET request. */ public static HttpRequest get(String destination) { return new HttpRequest() .method("GET") .set(destination); }
method方法以下:ide
/** * 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解析出一下幾個部分:ui
方法:HTTP1.1支持7種請求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。this
協議:http或者https.net
主機:請求的服務器地址
端口:請求的服務器端口
/** * 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; }
/** * 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; }
判斷是否有鏈接,若沒有鏈接則建立一個新的鏈接。
/** * 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); }
根據協議的不一樣,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的鏈接,而後經過鏈接的輸出流和輸入流來進行通訊。
參考文獻: