Android cookieManager & OKHttp以及EasyPlayerPro的媒体流的认证的实现

来源:互联网 发布:阿里云关机还收费吗 编辑:程序博客网 时间:2024/06/14 08:49

Android通过CookManager来管理Cookie。在发送\响应过一个HTTP请求之后,
CookManager首先将响应里的Set-cookie字段parse成一个Cookie列表,并存储到本地;然后每次请求时,都从本地获取到这个Cookie列表,并将其组装成字符串(Cookie=”’),设到请求的header里。

  • OKHttp发送请求时从本地读取Cookie并添加到请求的Header里:
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {  requestBuilder.header("Cookie", cookieHeader(cookies));}

最终组成的HTTP header里的Cookie内容,即cookieHeader(cookies)的返回值类似如下格式:

io=UBoJHl_2LzpacG6gAAOS; connect.sid=s%3AQzYtBFCTyCKZdZT2EHZZasQIZEIE1fXz.9Lsgche%2ByEslmlsI9j1wjUGEQvCXOl0sIn0DrvKS0fk

在HTTP响应返回后,OKHttp会解析服务器返回的Set-Cookie,并存储到本地。

  • OKHttp接收到响应后将Cookie保存下来.HttpHeaders.receiveHeaders
  public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {    if (cookieJar == CookieJar.NO_COOKIES) return;    List<Cookie> cookies = Cookie.parseAll(url, headers);    if (cookies.isEmpty()) return;    cookieJar.saveFromResponse(url, cookies);  }
  • Cookie.parseAll:
  /** Returns all of the cookies from a set of HTTP response headers. */  public static List<Cookie> parseAll(HttpUrl url, Headers headers) {    List<String> cookieStrings = headers.values("Set-Cookie");    List<Cookie> cookies = null;    for (int i = 0, size = cookieStrings.size(); i < size; i++) {      Cookie cookie = Cookie.parse(url, cookieStrings.get(i));      if (cookie == null) continue;      if (cookies == null) cookies = new ArrayList<>();      cookies.add(cookie);    }    return cookies != null        ? Collections.unmodifiableList(cookies)        : Collections.<Cookie>emptyList();  }

可见Cookie.parseAll会从header里取出Set-Cookie,并parse成一个Cookie列表返回。注意这里可能有多个Cookie,比如如下情况:

  • 多个Cookie
set-cookie=[connect.sid=s%3AppUUhZKKrfLTG5VSXwKzmwRukTurkqZe.yMBYSkgDwR%2Buci1cdpQYhRLdAgYHrescudjAC88pxHs; Path=/; Expires=Thu, 08 Jun 2017 11:44:44 GMT; HttpOnly, connect.sid=s%3AFVird0AnMWAtTPvl1fPQXBiNpUUIj3jX.KjX%2Fw435wY1B8L6eUCMYQ4Yl5j5S0U9IYoKWvGW2iVU; Path=/; Expires=Wed, 07 Jun 2017 11:54:42 GMT]

接下来,将列表的Cookie持久化.调用saveFromResponse函数.
- cookieJar.saveFromResponse

  @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {    if (cookieHandler != null) {      List<String> cookieStrings = new ArrayList<>();      for (Cookie cookie : cookies) {        cookieStrings.add(cookie.toString(true));      }      Map<String, List<String>> multimap = Collections.singletonMap("Set-Cookie", cookieStrings);      try {        cookieHandler.put(url.uri(), multimap);      } catch (IOException e) {        Platform.get().log(WARN, "Saving cookies failed for " + url.resolve("/..."), e);      }    }  }

这个函数里又绕了个弯路,把List转成了Map,并存储到CookieHandler里面.参考cookieHandler.put(url.uri(), multimap);

  • cookieHandler.put(url.uri(), multimap);
// 又把map转成HttpCookie ListList<HttpCookie> cookies = parseCookie(responseHeaders);for (HttpCookie cookie : cookies) {    // if the cookie doesn't have a domain, set one. The policy will do validation.    if (cookie.getDomain() == null) {        cookie.setDomain(uri.getHost());    }    // if the cookie doesn't have a path, set one. If it does, validate it.    if (cookie.getPath() == null) {        cookie.setPath(pathToCookiePath(uri.getPath()));    } else if (!HttpCookie.pathMatches(cookie, uri)) {        continue;    }    // if the cookie has the placeholder port list "", set the port. Otherwise validate it.    if ("".equals(cookie.getPortlist())) {        cookie.setPortlist(Integer.toString(uri.getEffectivePort()));    } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) {        continue;    }    // if the cookie conforms to the policy, add it into the store    if (policy.shouldAccept(uri, cookie)) {        store.add(uri, cookie);    }}

这个函数里根据Cookie本身的特性以及持久化策略来决定是否保存cookie.如果保存的话,最终保存到了CookieStore里.

A CookieStore object represents a storage for cookie. Can store and retrieve cookies.
CookieManager will call CookieStore.add to save cookies for every incoming HTTP response, and call CookieStore.get to retrieve cookie for every outgoing HTTP request. A CookieStore is responsible for removing HttpCookie instances which have expired.

CookieStore负责为外部提供Set\Get Cookie的接口,并且在Cookie过期时删除Cookie.我们可继承该Cookie用以实现Cookie的自定义存储策略.比如需要进行重定向业务时,类似于客户端首先向服务器A请求,A可能会向B请求,并把B的响应再响应给客户端.此后客户端再直接向B请求.那我们客户端就可以把第一步响应的Cookie存储下来,做个偷梁换柱,作为B服务器的Cookie,后续向B请求时使用.

// A服务器的相应Headers headers = response.headers();Map<String, List<String>> headersValue = headers.toMultimap();CookieManager cm = (CookieManager) CookieHandler.getDefault();// b_url是B服务器的http地址。// 下面的代码相当于一个“偷梁换柱”的实现// 把A服务器的响应当成是B服务器的响应,存到CookieStore里。cm.put(cookiesUri(URI.create(b_url)), headersValue);

这里同时需要A服务器响应给客户端的HTTP header里附带着B服务器的Set-Cookie头。

总之,以上方案有一定的现实意义,可以实现A服务器完成认证后,在B服务器同时认证的功能,十分适用于流媒体行业。通常流媒体行业都有专门的CMS管理服务器与流分发服务器,而客户端在CMS认证成功后,再向流媒体服务器请求时,没必要再去认证一遍。

作为一款NB的播放器,EasyPlayerPro,就用到了该方案。其在CMS请求成功后,获取一系列媒体流的链接。在后续播放时,通过将之前的Cookie设置到媒体流的请求的Header里,以实现在流分发服务器端的认证。

EasyPlayerPro下载地址:https://fir.im/EasyPlayerPro

相关介绍见:http://www.easydarwin.org/article/news/117.html

原创粉丝点击