最初的HTTP呗设计成为无状态的,面向request/response的协议,并没有为跨越多个逻辑依赖的request/response交换的有状态的session做准备,随着HTTP协议被越来越广泛的使用,许多系统已经使用它来为超出预期的应用服务,如电子商务应用的传输,于是,状态管理逐渐变得有必要。
Netscape公司是当时web客户端和服务端软件开发的领导者,基于私有规范在其产品中实现了HTTP状态管理,随后,Netscape尝试通过发布规范草案来标准化该机制,这些努力有助于通过RFC形成规范文档,然而在大多数的应用里,状态管理依旧是很大程度上基于Netscape的草案并且跟官方规范不兼容,web开发者都可以感觉到强行保留与这些应用的兼容性导致了标准规范碎片化。
3.1 HTTP cookies
HTTP cookie是HTTP客户端和服务端之间交互的一小段状态信息或者标记用于维持一个会话信息,Netscape工程师用一个魔法饼干来形容它,而这一名称就被保留了下来。
HttpClient使用Cookie接口来表示抽象的cookie标记,HTTP cookie最简单的形式就是一个键值对,通常HTTP cookie包含一系列属性比如合法域名,cookie可以被接收的URL的子集,cookie保存的最大时间。
SetCookie接口代表Set-Cookie response头消息,该消息从服务端发送给客户端用于保持一个会话状态。
ClientCookie接口继承自Cookie接口,其可以实现额外的客户端特定的功能,比如精确的检索源服务器指定的源cookie属性,这对于生成Cookie头信息非常重要,因为一些cookie规范要求特定的属性仅当他们在Set-Cookie头消息指定时才能被Cookie头包含进去。
下面是一个创建客户端Cookie对象的例子:
BasicClientCookie cookie = new BasicClientCookie("name", "value"); // Set effective domain and path attributes cookie.setDomain(".mycompany.com"); cookie.setPath("/"); // Set attributes exactly as sent by the server cookie.setAttribute(ClientCookie.PATH_ATTR, "/"); cookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");
3.2 Cookie规范
CookieSpec接口代表了cookie管理的规范,cookie管理规范包含如下内容:
解析Set-Cookie头部的规则
验证已解析cookie的规则
为已给定主机,端口和源路径格式化Cookie头部
HttpClient附带多种CookieSpec实现:
Standard strict(严格):状态管理策略行为完全符合RFC6265第四章的行为定义。
Standard(标准):状态管理策略较为符合RFC6265第四章定义的行为,以期望在不是完全遵守该行为之间的服务器之间进行交互。
Netscape 草案(已过时):该策略遵守Netscape公司公布最初的规范草案,除非确实需要与旧代码兼容,否则尽量避免使用它。
RFC 2965(已过时): 状态管理策略符合过时的RFC2965定义的状态管理规范。请不要在新应用中使用。
RFC2109(已过时):状态管理策略符合过时的RFC2109定义的状态管理规范,请不要在新应用中使用。
浏览器兼容性Browser compatibility(已过时):该策略尝试尽量去模拟老旧的浏览器版本如微软IE和Mozilla FireFox,请不要在新应用中使用。
Default:默认cookie策略是一种综合性的策略,其基于HTTP response返回的cookie属性如version信息,过期信息,与RFC2965,RFC2109或者Netscape草案兼容,该策略将会在下一个HttpClient小版本(基于RFC6265)中废弃。
Ignore cookies:所有的cookie都被忽略
强烈建议在新应用中使用Standard或者Standard strict策略,过时规范应该仅仅是在与旧系统兼容时使用。下一个HttpClient版本将会停止对过时规范的支持。
3.3 选择Cookie策略
Cookie策略能够通过HTTP客户端设置,并且可以在HTTP request时被覆盖掉。
RequestConfig globalConfig = RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT) .build(); CloseableHttpClient httpclient = HttpClients.custom() .setDefaultRequestConfig(globalConfig) .build(); RequestConfig localConfig = RequestConfig.copy(globalConfig) .setCookieSpec(CookieSpecs.STANDARD_STRICT) .build(); HttpGet httpGet = new HttpGet("/"); httpGet.setConfig(localConfig);
3.4 定制cookie策略
为了实现定制cookie策略,你应该创建一个自定义的CookieSpec接口的实现,创建一个CookieSpecProvider的实现类,然后用该实现类去创建和初始化自定义规范的实例,然后使用HttpClient进行注册,一旦自定义规范被注册,它就会如同标准cookie规范一样被触发。
PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.getDefault(); Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create() .register(CookieSpecs.DEFAULT, new DefaultCookieSpecProvider(publicSuffixMatcher)) .register(CookieSpecs.STANDARD, new RFC6265CookieSpecProvider(publicSuffixMatcher)) .register("easy", new 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实现叫做BasicCookieStore,是一个基于java.util.ArrayList的简单实现,当容器进行垃圾回收时,如果BasicClientCookie对象被回收掉了,其存储的Cookie也会丢失,你可以提供更复杂的实现来满足自己的需求。
// Create a local instance of cookie store CookieStore cookieStore = new BasicCookieStore(); // Populate cookies if needed BasicClientCookie cookie = new BasicClientCookie("name", "value"); cookie.setDomain(".mycompany.com"); cookie.setPath("/"); cookieStore.addCookie(cookie); // Set the store CloseableHttpClient httpclient = HttpClients.custom() .setDefaultCookieStore(cookieStore) .build();
3.6 HTTP状态管理和运行上下文
在HTTP request执行过程中,HttpClient将如下的状态管理相关的对象添加到运行上下文中。
Lookup 实例代表实际的cookie注册规范,该属性的值的优先级为当前上下文大于默认上下文。
CookieSpec实例代表着实际的cookie规范。
CookieOrigin实例代表着源服务器的详细Cookie信息。
CookieStore实例代表着实际的Cookie存储,该属性的值的优先级为当前上下文大于默认上下文。
当前HttpContext对象能够在请求执行之前用来定制HTTP状态管理上下文,或者在请求执行之后检查其状态,你也可以通过单独的运行上下文来实现各自的状态管理,在HTTP客户端级别,当前上下文的ookie注册规范和cookie存储优先级大于默认的上下文。
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();