第2章 连接管理
2.1 持久连接
一个主机与另一端建立连接是十分复杂的,并且两个终端间要交换多个信息包,这会耗费不少时间。对于低级的HTTP消息来说握手连接是尤其重要的。如果在执行多个请求时重复使用公共的连接,那么就能大大提高数据吞吐率。HTTP/1.1默认允许HTTP连接可以被多个请求复用。HTTP/1.0也兼容终端为了多个请求去使用一个明确的机制来优先保持活跃的连接。HTTP代理也能在一定的同期时间里保持活跃的空闲连接,以免同样的目标主机随后还要请求。这种保持活跃连接的能力通常都会涉及持续性连接。HttpClient完全支持“持续性连接”。
2.2 Http连接路由
HttpClient可以建立连接给主机或路由[包含复杂的中间连接——也被称为hops(弹跳)]。HttpClient会区分不同的路由连接(平坦、路径和分层)。使用多个中间代理服务去打通目标主机连接的方式称为代理链接。正在连接中、第一次连接或只用代理连接都会建立“平坦路由”。第一次连接和通过代理链接都会建立“通道路由”。路由离开代理是不能产生路径的。当一个分层协议结束一个存在的连接就会建立“分层路由”。当结束一个目标路径或结束一个不再代理的连接后,协议就会建立分层。
2.2.1 路由计算
RouteInfo接口代表一个确定的目标主机路径的信息,涉及一个或更多的中间步骤或hops(弹跳)。HttpRoute是一个具体的RouteInfo实现,它是不能被改变的(是不可变的)。HttpTracker是一个可变的RouteInfo执行情况,用于HttpClient在内部追踪剩余的指向最终路由目标的hops(弹跳)。如果下一次向着目标的hop(弹跳)执行成功,HttpTracker会被更新。HttpRouteDirector是一个帮助类,它可以用来计算路由的下一个步骤。这个类在HttpClient内部被使用。HttpRoutePlanner是一个接口代表着一个策略,用于计算一个基于执行上下文的完整的路线。HttpClient包含了两种默认HttpRoutePlanner的实现。SystemDefaultRoutePlanner是基于java.net.ProxySelector的。默认情况下,他会接载JVM的代理设置(会在系统特性或应用上运行的浏览器选择其中一个设置)。DefaultProxyRoutePlanner实现不会利用任何Java系统特性,也不会使用任何系统或浏览器的代理设置。它总是通过相同的默认代理服务来计算路由。
2.2.2 安全的HTTP连接
如果两个终端间正在传输着的信息不能被未授权的人读取或篡改,那么HTTP连接就被认为是安全的。SSL/TLS协议被广泛用在HTTP传输安全上。然而,其他的加密手段也有被使用。通常,HTTP传输在SSL/TLS加密连接上是被分层的。2.3 HTTP连接管理
2.3.1 管理连接和连接管理者
HTTP连接是复杂的、状态性强的、线程不安全的,它需要适当地去管理。HTTP连接每次只能被一个执行线程使用。HttpClientConnectionManager 接口是HttpClient用来管理HTTP连接的特别实体。HTTP连接管理器的目的是为新建HTTP连接充当一个工厂,以管理持续连接的生命周期和同步入口以持续连接,确保每次只有一个线程可以进入一个连接。内部HTTP连接管理器与ManagedHttpClientConnection实例一起工作,为一个真实连接去充当一个代理服务,以管理连接状态和控制执行I/O制作。如何一个管理连接被消费者释放或被明确地关闭了,底层连接会从代理服务里分离,并返回给管理器。尽管这个服务消费者会保持代理服务实例的引用,但是不再允许执行任何I/O操作,也不会有意或无意得去改变真实连接的状态。这是从连接管理器里获得一个连接的例子:
- HttpClientContext context = HttpClientContext.create();
- HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();
- HttpRoute route = new HttpRoute(new HttpHost("localhost", 80));
- // Request new connection. This can be a long process
- ConnectionRequest connRequest = connMrg.requestConnection(route, null);
- // Wait for connection up to 10 sec
- HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS);
- try {
- // If not open
- if (!conn.isOpen()) {
- // establish connection based on its route info
- connMrg.connect(conn, route, 1000, context);
- // and mark it as route complete
- connMrg.routeComplete(conn, route, context);
- }
- // Do useful things with the connection.
- } finally {
- connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES);
- }
如果需要的话这个连接可以被ConnectionRequest#cancel()提早中止。这将使得在ConnectionRequest#get()方法内会解除线程阻塞。
2.3.2 简单的连接管理
BasicHttpClientConnectionManager是一个简单的连接管理器,它每次只能保持一个连接。尽管这个类是线程安全的,但它只能被一个执行的线程使用。BasicHttpClientConnectionManager会为随后的同样路由的请求尝试重用这个连接。如果这个持续连接的路由与连接请求不匹配,它会为了指定的路由而关闭现有的连接并重新打开它。如何这个连接已经被分配,就会抛出java.lang.IllegalStateException异常。连接管理器的实现应该在一个EJB容器内使用。
2.3.3 池连接管理器
PoolingHttpClientConnectionManager是一个更复杂的实现,它管理一个客户端连接池,并为线程的连接请求提供服务。连接都被汇集在每个路由基础上。对于一个路由请求,如果管理器在池里已有一个可用的持续连接,则不会创建一个新的,而是租用池里的这个连接。PoolingHttpClientConnectionManager维护着在每个路由基础上连接数目的上限。每个默认的实现不会创建超过2个并行连接,每个指定的路由总共不会超过20个连接。对于许多现实的应用来说,这些限制可能会过于约束,尤其是当他们为他们的服务器使用HTTP传输协议时。
这个例子演示了如何调整连接池参数:
- PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
- // Increase max total connection to 200
- cm.setMaxTotal(200);
- // Increase default max connection per route to 20
- cm.setDefaultMaxPerRoute(20);
- // Increase max connections for localhost:80 to 50
- HttpHost localhost = new HttpHost("locahost", 80);
- cm.setMaxPerRoute(new HttpRoute(localhost), 50);
- CloseableHttpClient httpClient = HttpClients.custom()
- .setConnectionManager(cm)
- .build();
2.3.4 连接管理器关闭
当一个HttpClient实例不再需要并且即将离开其作用范围时,要关闭它的连接管理器以确保让所有连接在管理器被关闭后保持活跃,并且这些连接的系统资源会被释放掉。
- CloseableHttpClient httpClient = <...>
- httpClient.close();
2.4 线程请求执行
当配备一个池连接管理器后,如PoolingClientConnectionManager,HttpClient就能使用执行着的多线程去执行并行的多请求。PoolingClientConnectionManager会基于它的配置去分配连接。如果一个指定的路由连接已经被租用了,连接请求会被阻塞直到有一个连接被释放回池里。你可以给'http.conn-manager.timeout'设定一个正值以确保连接管理器在连接请求操作里不会无限期地阻塞下去。如果连接请求不能在指定的时间里获得服务就会抛出ConnectionPoolTimeoutException异常。
- PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
- CloseableHttpClient httpClient = HttpClients.custom()
- .setConnectionManager(cm)
- .build();
- // URIs to perform GETs on
- String[] urisToGet = {
- "http://www.domain1.com/",
- "http://www.domain2.com/",
- "http://www.domain3.com/",
- "http://www.domain4.com/"
- };
- // create a thread for each URI
- GetThread[] threads = new GetThread[urisToGet.length];
- for (int i = 0; i < threads.length; i++) {
- HttpGet httpget = new HttpGet(urisToGet[i]);
- threads[i] = new GetThread(httpClient, httpget);
- }
- // start the threads
- for (int j = 0; j < threads.length; j++) {
- threads[j].start();
- }
- // join the threads
- for (int j = 0; j < threads.length; j++) {
- threads[j].join();
- }
- static class GetThread extends Thread {
- private final CloseableHttpClient httpClient;
- private final HttpContext context;
- private final HttpGet httpget;
- public GetThread(CloseableHttpClient httpClient, HttpGet httpget) {
- this.httpClient = httpClient;
- this.context = HttpClientContext.create();
- this.httpget = httpget;
- }
- @Override
- public void run() {
- try {
- CloseableHttpResponse response = httpClient.execute(
- httpget, context);
- try {
- HttpEntity entity = response.getEntity();
- } finally {
- response.close();
- }
- } catch (ClientProtocolException ex) {
- // Handle protocol errors
- } catch (IOException ex) {
- // Handle I/O errors
- }
- }
- }
2.5 连接回收策略
经典的I/O阻塞模式有一个主要的缺点,就是当I/O操作被阻塞时,网络socket只对I/O事件影响。当一个连接被释放回管理器,它会保持活跃,然而它不会监听socket的状态和任何I/O事件。如果这个连接在服务器端被关闭,客户端的连接在连接状态(和由于结束时正在关闭而作出的适当响应)下不会检测出这个改变。HttpClient通过测试连接是否为“陈腐的”而尝试去缓解这个问题,“陈腐的”是指不再是有效的,因为它会被服务器端关闭掉,并会在这之前为了正执行中的HTTP请求去使用连接。“陈腐的”连接检测不是百分之百有效的,并且会给每个请求执行增加10到10毫秒。为了空闲连接,唯一有效的解决办法是在每个socket模型里不包含一个线程,有一个专门的监听线程是被用来驱逐已过期的不活跃的长连接的。这个监听线程会周期性地调用ClientConnectionManager#closeExpiredConnections()方法去关闭所有已过期的连接并从池里驱逐已关闭的连接。在超过指定的时期后,也可以随意地调用 ClientConnectionManager#closeIdleConnections()方法来关闭所有连接。
- public static class IdleConnectionMonitorThread extends Thread {
- private final HttpClientConnectionManager connMgr;
- private volatile boolean shutdown;
- public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
- super();
- this.connMgr = connMgr;
- }
- @Override
- public void run() {
- try {
- while (!shutdown) {
- synchronized (this) {
- wait(5000);
- // Close expired connections
- connMgr.closeExpiredConnections();
- // Optionally, close connections
- // that have been idle longer than 30 sec
- connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
- }
- }
- } catch (InterruptedException ex) {
- // terminate
- }
- }
- public void shutdown() {
- shutdown = true;
- synchronized (this) {
- notifyAll();
- }
- }
- }
2.6 连接保持活跃策略
HTTP规范没有明确指定一个持续连接最多可以保持活跃有多久。一些HTTP服务器会使用一个非标准的Keep-Alive(保持活跃)标头来告诉客户端,他们计划在服务器端保持连接活跃的时间(以秒为单位)。如果可以获得的话,HttpClient就会使用这些信息。如果Keep-Alive标头没有出现在应答里,HttpClient会假定这个连接可以无限期地保持活跃。然而,许多HTTP服务器通常会在一段不活跃时期后被配置成放弃持续连接,为了保存系统,这经常不会通知客户端。默认的策略似乎太过于乐观了,你可能会想提供一个自定义保持活跃策略。- ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
- public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
- // Honor 'keep-alive' header
- HeaderElementIterator it = new BasicHeaderElementIterator(
- response.headerIterator(HTTP.CONN_KEEP_ALIVE));
- while (it.hasNext()) {
- HeaderElement he = it.nextElement();
- String param = he.getName();
- String value = he.getValue();
- if (value != null && param.equalsIgnoreCase("timeout")) {
- try {
- return Long.parseLong(value) * 1000;
- } catch(NumberFormatException ignore) {
- }
- }
- }
- HttpHost target = (HttpHost) context.getAttribute(
- HttpClientContext.HTTP_TARGET_HOST);
- if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
- // Keep alive for 5 seconds only
- return 5 * 1000;
- } else {
- // otherwise keep alive for 30 seconds
- return 30 * 1000;
- }
- }
- };
- CloseableHttpClient client = HttpClients.custom()
- .setKeepAliveStrategy(myStrategy)
- .build();
2.7 连接socket工厂
HTTP连接使用内部java.net.Socket对象去处理从电线传输过来的数据,然而他们依靠接口去创建、初始化和连接socket。在运行时允许HttpClient用户装备指定的socket初始化代码。PlainConnectionSocketFactory是一个默认的工作,用于创建和初始化平坦(未加密的)socket。
创建socket的过程和将它连接去一个主机是脱钩的,所以当正在一个连接操作里阻塞的时候,应该闭关掉socket。
- HttpClientContext clientContext = HttpClientContext.create();
- PlainConnectionSocketFactory sf = PlainConnectionSocketFactory.getSocketFactory();
- Socket socket = sf.createSocket(clientContext);
- int timeout = 1000; //ms
- HttpHost target = new HttpHost("localhost");
- InetSocketAddress remoteAddress = new InetSocketAddress(
- InetAddress.getByAddress(new byte[] {127,0,0,1}), 80);
- sf.connectSocket(timeout, socket, target, remoteAddress, null, clientContext);
2.7.1 安全的socket分层
LayeredConnectionSocketFactory 是一个ConnectionSocketFactory 接口的扩展。分层的socket工厂有能力在一个现存的平坦socket上创建分层的socket。分层的socket会首先会被代理服务使用来创建安全的socket。HttpClient包含了SSLSocketFactory,以实现SSL/TLS分层。请注意,HttpClient不会使用任何自定义的加密功能。它是完全依赖于标准的Java Cryptography (JCE) and Secure Sockets (JSEE)扩展。
2.7.2 整合连接管理
自定义的连接socket工厂可以关联一个特别的协议体系,如HTTP或HTTPS,进而用来创建自定义的连接管理。- ConnectionSocketFactory plainsf = <...>
- LayeredConnectionSocketFactory sslsf = <...>
- Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
- .register("http", plainsf)
- .register("https", sslsf)
- .build();
- HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
- HttpClients.custom()
- .setConnectionManager(cm)
- .build();
2.7.3 SSL/TLS定制
HttpClient利用SSLConnectionSocketFactory来创建SSL连接。SSLConnectionSocketFactory允许高度的定制。它可以把javax.net.ssl.SSLContext的实例看作是一个参数,并用它来创建自定义的SSL连接配置。
- KeyStore myTrustStore = <...>
- SSLContext sslContext = SSLContexts.custom()
- .useTLS()
- .loadTrustMaterial(myTrustStore)
- .build();
- SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
定制SSLConnectionSocketFactory要对SSL/TLS协议有更深入的掌握,这已超出了本文档的说明范围。javax.net.ssl.SSLContext详细的说明和相关的工具使用,请参考Java Secure Socket Extension(链接:http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html)。
2.7.4 主机名检验
除了在SSL/TLS协议级上托管检验和客户身份鉴定外,一旦连接被创建,HttpClient还能选择性地检验是否目标主机名与存放在服务器上的X.509证书相匹配。这个检验可以为服务器相信材料的可靠性提供额外的保证。X509HostnameVerifier 接口代表一个用于主机名检验的策略。HttpClient包含了三个 X509HostnameVerifier实现。注意:主机名检验不要被SSL托管检验给搞混淆了。
StrictHostnameVerifier(精确主机名的检验器):精确的主机名检验器工作在类似于Sun Java 1.4, Sun Java 5, Sun Java 6里。它与IE6的关系也相当紧密。这个实现符合RFC 2818,因为要处理通配符。主机名必须要么匹配第一个CN,要么匹配任意的subject-alts。通配符可以出现在CN里,和任意的subject-alts里。
BrowserCompatHostnameVerifier(浏览器兼容主机名的检验器):这个主机名检验器工作在类似于Curl和火狐浏览器里。主机名必须要么匹配第一个CN,要么匹配任意的subject-alts。通配符可以出现在CN里,和任意的subject-alts里。BrowserCompatHostnameVerifier和 StrictHostnameVerifier唯一的不同是,BrowserCompatHostnameVerifier的通配符(如"*.foo.com")会匹配所有的子域,包括"a.b.foo.com"。
AllowAllHostnameVerifier(充许所有主机名的检验器):这个主机名检验器在本质上会关掉主机检验。这个实现是无操作的(no-op),并且永远会抛出javax.net.ssl.SSLException异常。
默认的HttpClient使用BrowserCompatHostnameVerifier实现。如要需要,你可以指定不同的主机名检验器。
- SSLContext sslContext = SSLContexts.createSystemDefault();
- SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
- sslContext,
- SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
2.8 HttpClient代理服务器配置
尽管HttpClient知道复杂的路由体系和代理服务链接,但它只支持简单的定位或一个离开的跳跃(hop)代理连接。
- HttpRoutePlanner routePlanner = new HttpRoutePlanner() {
- public HttpRoute determineRoute(
- HttpHost target,
- HttpRequest request,
- HttpContext context) throws HttpException {
- return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
- "https".equalsIgnoreCase(target.getSchemeName()));
- }
- };
- CloseableHttpClient httpclient = HttpClients.custom()
- .setRoutePlanner(routePlanner)
- .build();
- }
- }
第3章 HTTP状态管理
最初的HTTP被设计成以状态、请求/应答为导向的协议,它被制作成是没有特殊条款的,以便在状态会话中能交换逻辑关系请求/应答。HTTP协议越来越受欢迎和被采用,越来越多的系统会在应用程序里使用它,这是以前所没有想过的,例如电子商务应用程序。因此,对状态管理的支持就十分有必然了。
(Netscape Communications)网景公司是当时web客户端和服务器软件开发的领航者,在他们的基于专有规格的产品上实现了对HTTP状态管理的支持。后来,网景公司尝试通过出版规格草案去标准化这个机制。这些努力最终通过 theRFC standard track促成了正式的规范定义。然而,应用程序的重要数字仍然是大部分地建立在网景公司那份草案的基础上的,并且和官方的规格不兼容。所有主要的web浏览器开发者感到不得不去保持对这些应用的最大兼容性,进而促进形成了顺从这些标准的碎片。
3.1 HTTP cookies
HTTP cookie是一个标记或者状态信息包, HTTP代理人和服务器可以通过传输它来保持一个会话。网景工程师常常将它称为"magic cookie"。
HttpClient使用Cookie接口去表现一个抽象的cookie标记。在这个最简单的HTTP cookie结构里是一对名字/值(name/value)。通常一个HTTP cookie也包含许多属性,如:版本、有效的域名、在源服务器上的给cookie 宴请所指定URLs子集的路径、最大的cookie有效期。
SetCookie
接口表现一个Set-Cookie应答标头,它会被源服务器发送给HTTP代理人,为了保持一个会话状态。SetCookie2
接口是SetCookie的扩展,它增加了一些特殊的方法。
ClientCookie 接口扩展了Cookie
接口,它加入了额外的客户功能,如能够完全地恢复原始的cookie属性,就像被源服务器指定的一样。这对于生成Cookie标头是十分重要的,因为一些cookie规格请求,只有当它们被Set-Cookie
或Set-Cookie2
标头规定时,Cookie标头应该包含确定的属性。
3.1.1 Cookie版本
Cookies兼容网景规范草案,不过不兼容版本为0的官方规范,但标准兼容cookies 版本为1的,HttpClient可能会由于版本的不同而导致处理cookies的方法不一样。
这是一个重构网景cookie的例子:
- BasicClientCookie netscapeCookie = new BasicClientCookie("name", "value");
- netscapeCookie.setVersion(0);
- netscapeCookie.setDomain(".mycompany.com");
- netscapeCookie.setPath("/");
这是一个重构标准cookie的例子。请注意,标准兼容cookie必须要保持所有从源服务器发送过来的属性。
- BasicClientCookie stdCookie = new BasicClientCookie("name", "value");
- stdCookie.setVersion(1);
- stdCookie.setDomain(".mycompany.com");
- stdCookie.setPath("/");
- stdCookie.setSecure(true);
- // Set attributes EXACTLY as sent by the server
- stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1");
- stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");
这是一个重构Set-Cookie2cookie兼容cookie的例子。请注意,标准兼容cookie必须要保持所有从源服务器发送过来的属性。
- BasicClientCookie2 stdCookie = new BasicClientCookie2("name", "value");
- stdCookie.setVersion(1);
- stdCookie.setDomain(".mycompany.com");
- stdCookie.setPorts(new int[] {80,8080});
- stdCookie.setPath("/");
- stdCookie.setSecure(true);
- // Set attributes EXACTLY as sent by the server
- stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1");
- stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");
- stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
3.2 Cookie规范
CookieSpec
接口代表一个cookie管理规范。Cookie管理规范是要严格执行的:
1、Set-Cookie和任意的Set-Cookie2
标头分析规则;
2、分析cookies规则;
3、为指定的主机、端口和源路径格式化Cookie标头。
HttpClient包含几个CookieSpec
实现:
1
、
Netscape draft(网景草案):一个顺应了网景公司早期发行的草案的规范。它对兼容传统的代码来说是不可或缺的;
2、Standard(标准的): RFC 2965 HTTP 状态管理规范;
3、Browser compatibility(浏览器兼容):这个实现尝试努力去模仿普通浏览器(如IE和火狐)的行为。
4、Best match(最好的匹配): 'Meta' cookie规范,它是一个以发送HTTP应答结构作为依据捡起cookie策略。它的所有实现都集中在一个类(class)里。
5、Ignore cookies:(忽略cookies) :所有cookie被忽略掉。
强烈建议使用Best Match策略让HttpClient以执行上下文(execution context) 作为依据来捡起一个适当的级别。
3.3 选择cookie策略
Cookie策略可以被设置,如果有请求,也可以被HTTP请求重载。
- RequestConfig globalConfig = RequestConfig.custom()
- .setCookieSpec(CookieSpecs.BEST_MATCH)
- .build();
- CloseableHttpClient httpclient = HttpClients.custom()
- .setDefaultRequestConfig(globalConfig)
- .build();
- RequestConfig localConfig = RequestConfig.copy(globalConfig)
- .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
- .build();
- HttpGet httpGet = new HttpGet("/");
- httpGet.setConfig(localConfig);
3.4自定义cookie策略
为了实现自定义的cookie策略需要实现CookieSpec
的定制
接口,创建一个CookieSpecProvider
的
实现以创建并初始化定制的规范,最后注册进HttpClient里。一旦自定义的规范被注册,它就能以标准的cookie规范相同的方式被激活。
- CookieSpecProvider easySpecProvider = new CookieSpecProvider() {
- public CookieSpec create(HttpContext context) {
- return new BrowserCompatSpec() {
- @Override
- public void validate(Cookie cookie, CookieOrigin origin)
- throws MalformedCookieException {
- // Oh, I am easy
- }
- };
- }
- };
- Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create()
- .register(CookieSpecs.BEST_MATCH,
- new BestMatchSpecFactory())
- .register(CookieSpecs.BROWSER_COMPATIBILITY,
- new BrowserCompatSpecFactory())
- .register("easy", easySpecProvider)
- .build();
- RequestConfig requestConfig = RequestConfig.custom()
- .setCookieSpec("easy")
- .build();
- CloseableHttpClient httpclient = HttpClients.custom()
- .setDefaultCookieSpecRegistry(r)
- .setDefaultRequestConfig(requestConfig)
- .build();
3.5 Cookie的持久性
HttpClient可通过实现CookieStore的接口以支持任意物理表述的持久性cookie仓库。默认情况下,CookieStore 的实现会调用CookieStore(一个简单的被java.util.ArrayList所支持的实现)。当容器对象执行垃圾回收时,存储在BasicClientCookie
对象里的cookies会丢失。如果有需要,用户可以提供更复杂的实现。
- // Create a local instance of cookie store 译:创建本地cookie仓库实例
- CookieStore cookieStore = new BasicCookieStore();
- // Populate cookies if needed 译:填入cookies
- BasicClientCookie cookie = new BasicClientCookie("name", "value");
- cookie.setVersion(0);
- cookie.setDomain(".mycompany.com");
- cookie.setPath("/");
- cookieStore.addCookie(cookie);
- // Set the store
- CloseableHttpClient httpclient = HttpClients.custom()
- .setDefaultCookieStore(cookieStore)
- .build();
3.6 HTTP状态管理和执行上下文
在HTTP请求执行过程中,HttpClient加入了以下与状态管理相关的对象去执行上下文:
1) Lookup
实例代表实际的cookie规范注册表。这个属性的值在本地上下文件里面优先于默认的;
2)CookieSpec
代表实际的cookie规格;
3)CookieOrigin
实例代表实际的关于源服务器的详细信息;
4)CookieStore
实例代表实际的cookie仓库。这个属性的值在本地上下文件里面优先于默认的。
本地的HttpContext
对象可以在请求执行之前被用来定制HTTP状态管理上下文,或者在请求执行完后检查它的状态。你可以使用分离的执行上下文来实现每个用户(或每个线程)的状态管理。cookie规范注册表和cookie仓库被定义在本地上下文里会比默认的被设置在HTTP客户端(HTTP client)级别里的有更高的优先级。
- CloseableHttpClient httpclient = <...>
- Lookup<CookieSpecProvider> cookieSpecReg = <...>
- CookieStore cookieStore = <...>
- HttpClientContext context = HttpClientContext.create();
- context.setCookieSpecRegistry(cookieSpecReg);
- context.setCookieStore(cookieStore);
- HttpGet httpget = new HttpGet("http://somehost/");
- CloseableHttpResponse response1 = httpclient.execute(httpget, context);
- <...>
- // Cookie origin details
- CookieOrigin cookieOrigin = context.getCookieOrigin();
- // Cookie spec used
- CookieSpec cookieSpec = context.getCookieSpec();