Multiple request sequences that represent a logically related session should be executed with the same HttpContext instance to ensure automatic propagation of conversation context and state information between requests.
上面這段話摘自httpclient官網,大體意思是邏輯會話相關的多個請求序列應該使用同一個HttpContext實例,這樣就可以讓會話信息和狀態信息在多個請求之間自動廣播。
官網上還給出一段示例代碼 ,我們仿着它的示例代碼,重新整一個,以便於觀察。
(1) 使用springboot快迅搭建一個目標服務
@RestController public class RequestController { @PostMapping("/request") public void request(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); //1. 從session中獲取username String username = (String) session.getAttribute("username"); String ret; if (username == null) { // 2. 從請求參數中獲取username的值 username = request.getParameter("username"); session.setAttribute("username", username); ret = "login success!"; } else { ret = "Having been logined " + username; } // 將ret 內容寫回到response響應體中 ServletOutputStream outputStream = null; PrintWriter pw = null; try { outputStream = response.getOutputStream(); pw = new PrintWriter(outputStream); pw.write(ret); } catch (IOException e) { e.printStackTrace(); } finally { if (pw != null) { pw.close(); } try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
啟功類省略,server.port = 9999
(2) HttpClient測試類
import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class HttpContextTest { public static void main(String[] args) throws IOException { HttpContext httpContext = new BasicHttpContext(); HttpClientContext httpClientContext = HttpClientContext.adapt(httpContext); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost("http://localhost:9999/request"); // 模仿form表單請求,設置請求參數 List<NameValuePair> nvp = new ArrayList<>(); nvp.add(new BasicNameValuePair("username", "admin")); // 第一次請求時,設置請求參數 httpPost.setEntity(new UrlEncodedFormEntity(nvp)); CloseableHttpResponse response = null; try { response = httpclient.execute(httpPost, httpClientContext); HttpEntity entity = response.getEntity(); if (entity != null) { String ret = EntityUtils.toString(entity); System.out.println("第一次請求響應:"+ ret); } }finally { response.close(); } System.out.println("=================第二次請求===================="); // 重新創建一個HttpPost對象,但是此次該對象中不設置請求參數 httpPost = new HttpPost("http://localhost:9999/request"); try { response = httpclient.execute(httpPost, httpClientContext); HttpEntity entity = response.getEntity(); if (entity != null) { String ret = EntityUtils.toString(entity); System.out.println("第二次請求響應:"+ ret); } }finally { response.close(); } } }
(3)啟動目標項目,然后再運行測試代碼,打印結果如下
第一次請求響應:login success!
=================第二次請求====================
第二次請求響應:Having been logined admin
Process finished with exit code 0
感覺瀏覽器請求一模一樣了, 保存了請求會話信息,事實上確實如此,此處就是保存jsessionid
(4) 簡單看下源碼
<1> ProtocolExec#execute()
<2> ResponseProcessCookies#process(final HttpResponse response, final HttpContext context)
@Override public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException { final HttpClientContext clientContext = HttpClientContext.adapt(context); // Obtain actual CookieSpec instance final CookieSpec cookieSpec = clientContext.getCookieSpec(); // Obtain cookie store final CookieStore cookieStore = clientContext.getCookieStore(); //....... // see if the cookie spec supports cookie versioning. if (cookieSpec.getVersion() > 0) { // process set-cookie2 headers. // Cookie2 will replace equivalent Cookie instances // 就是將cookie信息拷到cookieStore中 it = response.headerIterator(SM.SET_COOKIE2); processCookies(it, cookieSpec, cookieOrigin, cookieStore); } }
<3> 斷點查看第二次請求時HttpContext對象
通過debug可以很明顯看到,HttpContext將諸多的Http請求的會話信息進行了廣播。