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请求的会话信息进行了广播。
日拱一卒无有尽,功不唐捐终入海