第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();