HttpClient API 文档:3. 状态管理

最初,HTTP 被设计为无状态的,面向请求/响应的协议,对于跨越几个逻辑相关的请求/响应交换的有状态会话并没有特殊规定。随着 HTTP 协议的普及,越来越多的系统开始将其用于它从未打算用于的应用程序,例如电子商务应用程序的内容传输。因此,支持 HTTP 状态管理变得非常有必要。

NetScape(网景)公司曾经引领网页客户端和服务端软件的发展,在他们的产品中基于专有的规范,提供了 HTTP 状态管理的支持。之后,NetScape 尝试通过发布规范草案来标准化这种机制。这些努力通过 RFC 标准促进了正式规范的定义。但是,状态管理在很多应用程序中仍然支持 NetScape 的草案而不兼容官方的标准。很多 Web 浏览器的主要开发人员觉得有必要保留这些极大地促进标准兼容性的草案。

3.1 HTTP Cookies

一个 HTTP Cookie 是 HTTP 代理或者目标服务器保持会话和交流状态信息的一个令牌或者短数据包。NetScape 工程师过去称它为 “magic cookie”,一个非常贴切的名字。
HttpClient 使用 Cookie 接口来表示一个抽象 cookie 令牌。在 HTTP 中,最简单的形式是指一个键/值对。通常一个 HTTP Cookie 也包含一系列的属性, 例如一个域名是否可用,cookie 请求的源服务器上指定的 url 子集路径,cookie 最大有效时间等等。

SetCookie 接口表示由源服务器发送给 HTTP 代理的响应头中的 Set-Cookie,以此来维持一个会话状态。
ClientCookie 接口继承自 Cookie 接口并且自身扩展了客户端特有的功能,例如从源服务器中恢复原始的 cookie。这对于生成 Cookie 的消息头是非常重要的,因为一些特殊的 cookie 规范需要 Cookie 消息头中必须包含特定的属性,这些需要在 Set-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 的解析规则
  • 验证解析 cookies 的规则
  • 给定的主机,端口和源路径来格式化 Cookie 消息头

HttpClient 附带了一些 CookieSpec 的实现规范:

  • Standard strict: 状态管理策略符合 RFC 6265 第 4 节定义的行为良好的配置文件的语法和语义。

  • Standard: 状态管理策略符合 RFC 6265 第 4 节定义的更宽松的配置文件,旨在与不符合良好行为的配置文件的现有服务器进行互操作。

  • Netscape draft (已废弃): This policy conforms to the original draft specification published by Netscape Communications. It should be avoided unless absolutely necessary for compatibility with legacy code.

  • RFC 2965 (已废弃): State management policy compliant with the obsolete state management specification defined by RFC 2965. Please do not use in new applications.

  • RFC 2109 (已废弃): State management policy compliant with the obsolete state management specification defined by RFC 2109. Please do not use in new applications.

  • Browser compatibility (已废弃): This policy strives to closely mimic the (mis)behavior of older versions of browser applications such as Microsoft Internet Explorer and Mozilla FireFox. Please do not use in new applications.

  • Default: 默认 cookie 策略是一种综合策略,它根据 HTTP 响应发送的 cookie 的属性(比如 version 属性,现在已经过时了)选择 RFC 2965、RFC 2109 或符合 Netscape draft 的实现。此策略将在 HttpClient 的下一个小版本中被弃用,取而代之的是标准(RFC 6265 兼容)实现。

  • Ignore cookies: All cookies are ignored.

在新的应用实现中,强烈建议使用 Standard 或者 Standard strict 两种规范。过时的规范仅仅在一些遗留的系统上为了兼容性而使用。支持的过时规范将在下一个发布的 HttpClient 主版本中被移除。

3.3 选择 Cookie 策略

Cookie 策略可以在 HTTP 客户端中设置,如果需要也可以在 HTTP 请求中覆盖重写。

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 中的 Cookies 对象也会丢失。如果需要的话,使用者可以提供一个更加复杂的实现。

// 创建 cookie 存储的本地实例
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 请求执行的过程中,HttpClient 将以下这些状态管理相关的对象添加到执行的上下文中:

  • Lookup 实例表示实际的 cookie 规范注册表。在本地上下文中设置的此属性的值优先于默认值。
  • CookieSpec 实例表示实际的 cookie 规范。
  • CookieOrigin 实例表示原始服务器的实际详细信息。
  • CookieStore 实例表示实际的 cookie 存储。在本地上下文中设置的此属性的值优先于默认值。

本地 HttpContext 对象可以用于在请求执行之前自定义 HTTP 状态管理的上下文,或者在请求执行后检查它的状态。开发者仍然可以使用分开的上下文来实现每一个用户(或者每一个线程)的状态管理。cookie 规范的注册,在本地上下文中定义存储的 cookie 优先于那些在 HTTP 客户端设置的默认上下文。

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

推荐阅读更多精彩内容