apache-http-client发起请求不携带cookie问题的解决

最近公司的项目在使用http接口作为其他项目的服务提供者,调用的时候需要使用apachehttp-client作为发起http请求的基础部件。使用过程中发现请求时无法携带cookie,上网查阅资料并查找源码,最终问题得以解决并记录如下。

起因


  • 目前在公司负责的项目需要作为一个服务提供者,为公司的其他项目提供http接口服务;为了方便其他项目组的调用,使用http-client封装了一个jar包,外部项目调用的时候直接使用jar包封装好的方法进行调用,降低外部系统调用的难度,并可以保证调用方式的正确性。在封装并调试的过程中,发现了一个问题,就是直接使用http-client调用的时候不会携带cookie,即使按照网上教程、官网文档的方式、依旧无法添加cookie。请求时如果不能携带cookie,则被调用方会因为没有cookie,而重新创建session;目前该项目的session是存储在redis里,虽然每个seesion占用的空间并不大,但是如果http-client发起的请求过多,那么redis中存储的session依旧有可能占满redis的内存,同时对于系统问题的排除也比较不利,故需要解决。

解决过程


  • 网上能找到最多的代码大概类似于下面这样
1
2
3
4
5
6
7
8
BasicCookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("cookieName", "cookieValue");
cookie.setDomain("test.com"); //cookie的域名
cookie.setPath("/"); //cookie的域名的存储路径
cookieStore.addCookie(cookie);
HttpClient client = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
final HttpGet requestGET = new HttpGet("http://test.com");
HttpResponse res = client.execute(requestGET);
  • 但是经过实际的测试,发现并不能添加cookie,遂继续google。直到找到了这篇blog,虽然这篇blog没有解决我的问题,但是阅读大致能够缕清http-client中对于cookie的处理方式,再结合官网doc上对于cookie处理部分的解释,大致有了思路:

    • http-client对于cookie的添加由所谓的cookie policy决定,当没有设置cookie policy是,会默认使用DefaultCookieSpecProvidercreate()方法,创建一个cookie处理策略cookieSpec

    • 默认处理策略cookieSpec创建后,会调用其中的match()方法判断cookie是否符合规则,符合规则后,再调用cookieSpec.formatCookies()方法,格式化cookie,并最终添加到请求的header里,至此完成cookie的添加。

    • 源码如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      //从上下文获取请求设置
      final RequestConfig config = clientContext.getRequestConfig();
      //获取cookie处理策略
      String policy = config.getCookieSpec();
      //cookie处理策略为空,则使用默认的处理策略
      if (policy == null) {
      policy = CookieSpecs.DEFAULT;
      }
      //此处省略无关代码
      ...
      // Get an instance of the selected cookie policy
      // 上面一行是源码带的注释,就是从 registry 获取 CookieSpecProvider
      final CookieSpecProvider provider = registry.lookup(policy);
      if (provider == null) {
      if (this.log.isDebugEnabled()) {
      this.log.debug("Unsupported cookie policy: " + policy);
      }
      return;
      }
      // 调用 CookieSpecProvider 获取 cookie 处理策略实例
      final CookieSpec cookieSpec = provider.create(clientContext);
      // Get all cookies available in the HTTP state
      // 获取cookie
      final List<Cookie> cookies = cookieStore.getCookies();
      // Find cookies matching the given origin
      final List<Cookie> matchedCookies = new ArrayList<Cookie>();
      final Date now = new Date();
      boolean expired = false;
      for (final Cookie cookie : cookies) {
      if (!cookie.isExpired(now)) {
      // 逐一比较cookie是否符合规则,符合规则的才能添加,就是这里出了问题才导致cookie没有成功添加到请求
      if (cookieSpec.match(cookie, cookieOrigin)) {
      if (this.log.isDebugEnabled()) {
      this.log.debug("Cookie " + cookie + " match " + cookieOrigin);
      }
      matchedCookies.add(cookie);
      }
      } else {
      if (this.log.isDebugEnabled()) {
      this.log.debug("Cookie " + cookie + " expired");
      }
      expired = true;
      }
      }
      // Per RFC 6265, 5.3
      // The user agent must evict all expired cookies if, at any time, an expired cookie
      // exists in the cookie store
      if (expired) {
      cookieStore.clearExpired(now);
      }
      // Generate Cookie request headers
      if (!matchedCookies.isEmpty()) {
      // 使用cookie策略实例将cookie处理为headers,至此添加cookie完成
      final List<Header> headers = cokieSpec.formatCookies(matchedCookies);
      for (final Header header : headers) {
      request.addHeader(header);
      }
      }
  • 有了上面的处理过程就好说了,结合官网doc的说明,首先创建一个类实现CookieSpecProvider接口
1
2
3
4
5
6
public class SingleCookieSpecProvider implements CookieSpecProvider {
@Override
public CookieSpec create(HttpContext context) {
return new SingleCookieSpec();
}
}

​ 然后创建一个类实现CookieSpec接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class SingleCookieSpec implements CookieSpec {

@Override
public int getVersion() {
return 0;
}

@Override
public List<Cookie> parse(Header header, CookieOrigin origin) throws MalformedCookieException {
return null;
}

@Override
public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException {

}

@Override
public boolean match(Cookie cookie, CookieOrigin origin) {
// 直接返回true,保证cookie能直接添加
return true;
}

@Override
public List<Header> formatCookies(List<Cookie> cookies) {
// 将cookie转为headers
Args.notNull(cookies, "List of cookies");
return doFormatOneHeader(cookies);
}

private List<Header> doFormatOneHeader(final List<Cookie> cookies) {
final CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size());
buffer.append(SM.COOKIE);
buffer.append(": ");
for (final Cookie cooky : cookies) {
final Cookie cookie = cooky;
formatCookieAsVer(buffer, cookie);
buffer.append("; ");
}
final List<Header> headers = new ArrayList<Header>(1);
headers.add(new BufferedHeader(buffer));
return headers;
}

protected void formatCookieAsVer(final CharArrayBuffer buffer,
final Cookie cookie) {
formatParamAsVer(buffer, cookie.getName(), cookie.getValue());
if (cookie.getPath() != null) {
if (cookie instanceof ClientCookie
&& ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) {
buffer.append("; ");
formatParamAsVer(buffer, "$Path", cookie.getPath());
}
}
if (cookie.getDomain() != null) {
if (cookie instanceof ClientCookie
&& ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) {
buffer.append("; ");
formatParamAsVer(buffer, "$Domain", cookie.getDomain());
}
}
}

protected void formatParamAsVer(final CharArrayBuffer buffer,
final String name, final String value) {
buffer.append(name);
buffer.append("=");
if (value != null) {
buffer.append(value);
}
}

@Override
public Header getVersionHeader() {
return null;
}
}

最后,http-client调用方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 设置下cookie
BasicCookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie(cookie.getName(),cookie.getValue());
cookie.setDomain(cookie.getDomain());
cookie.setPath(cookie.getPath());
cookie.setAttribute(ClientCookie.PATH_ATTR, cookie.getPath());
cookie.setAttribute(ClientCookie.DOMAIN_ATTR, cookie.getDomain());
// cookie添加到cookieStore中
cookieStore.addCookie(cookie);
// cookieStore 添加到 httpclient 实例中
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore).build();

// request context 设置
// 将cookie设置到上下文中
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(cookieStore);
// 注册cookie处理策略
Registry<CookieSpecProvider> registry = RegistryBuilder.<CookieSpecProvider>create()
.register("singleCookieSpec", new SingleCookieSpecProvider())
.build();
// 设置使用的cookie策略
RequestConfig requestConfig = RequestConfig.custom()
.setCookieSpec("singleCookieSpec")
.build();
context.setRequestConfig(requestConfig);
// 设置注册器
context.setCookieSpecRegistry(registry);
HttpGet httpget = new HttpGet(url);
// 设置超时毫秒
int timeout = 10000;
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setCookieSpec(SINGLE_COOKIE_SPECIFICATION_NAME)
.build();
httpget.setConfig(requestConfig);
// 执行get请求.
CloseableHttpResponse response = httpclient.execute(httpget, context);

总结


遇到问题的时候,如果网上的资料不能解决问题,还是得看源码,程序的工作原理都在代码里。