由于我们在之前所有的入门教程中,对于HTTP请求都采用了简单的接口实现。而实际使用过程中,我们的HTTP请求要复杂的多,本文主要分析HTTP请求时遇见的常见问题以及解决方法。
由于我们在之前所有的入门教程中,对于HTTP请求都采用了简单的接口实现。而实际使用过程中,我们的HTTP请求要复杂的多,比如当我们将Spring Cloud Zuul作为API网关接入网站类应用时,往往都会碰到下面这两个非常常见的问题:
- 会话无法保持
- 重定向后的HOST错误
本文将帮助大家分析问题原因并给出解决这两个常见问题的方法。
一、会话保持问题
通过跟踪一个HTTP请求经过Zuul到具体服务,再到返回结果的全过程。我们很容易就能发现,在传递的过程中,HTTP请求头信息中的Cookie和Authorization都没有被正确地传递给具体服务,所以最终导致会话状态没有得到保持的现象。
那么这些信息是在哪里丢失的呢?我们从Zuul进行路由转发的过滤器作为起点,来一探究竟。下面是RibbonRoutingFilter过滤器的实现片段:
- publicclassRibbonRoutingFilterextendsZuulFilter{
- ...
- protectedProxyRequestHelperhelper;
- @Override
- publicObjectrun(){
- RequestContextcontext=RequestContext.getCurrentContext();
- this.helper.addIgnoredHeaders();
- try{
- RibbonCommandContextcommandContext=buildCommandContext(context);
- ClientHttpResponseresponse=forward(commandContext);
- setResponse(response);
- returnresponse;
- }
- ...
- returnnull;
- }
- protectedRibbonCommandContextbuildCommandContext(RequestContextcontext){
- HttpServletRequestrequest=context.getRequest();
- MultiValueMap<String,String>headers=this.helper
- .buildZuulRequestHeaders(request);
- MultiValueMap<String,String>params=this.helper
- .buildZuulRequestQueryParams(request);
- ...
- }
- }
这里有三个重要元素:
- 过滤器的核心逻辑run函数实现,其中调用了内部函数buildCommandContext来构建上下文内容
- 而buildCommandContext中调用了helper对象的buildZuulRequestHeaders方法来处理请求头信息
- helper对象是ProxyRequestHelper类的实例
接下来我们再看看ProxyRequestHelper的实现:
- publicclassProxyRequestHelper{
- publicMultiValueMap<String,String>buildZuulRequestHeaders(
- HttpServletRequestrequest){
- RequestContextcontext=RequestContext.getCurrentContext();
- MultiValueMap<String,String>headers=newHttpHeaders();
- Enumeration<String>headerNames=request.getHeaderNames();
- if(headerNames!=null){
- while(headerNames.hasMoreElements()){
- Stringname=headerNames.nextElement();
- if(isIncludedHeader(name)){
- Enumeration<String>values=request.getHeaders(name);
- while(values.hasMoreElements()){
- Stringvalue=values.nextElement();
- headers.add(name,value);
- }
- }
- }
- }
- Map<String,String>zuulRequestHeaders=context.getZuulRequestHeaders();
- for(Stringheader:zuulRequestHeaders.keySet()){
- headers.set(header,zuulRequestHeaders.get(header));
- }
- headers.set(HttpHeaders.ACCEPT_ENCODING,"gzip");
- returnheaders;
- }
- publicbooleanisIncludedHeader(StringheaderName){
- Stringname=headerName.toLowerCase();
- RequestContextctx=RequestContext.getCurrentContext();
- if(ctx.containsKey(IGNORED_HEADERS)){
- Objectobject=ctx.get(IGNORED_HEADERS);
- if(objectinstanceofCollection&&((Collection<?>)object).contains(name)){
- returnfalse;
- }
- }
- ...
- }
- }
从上述源码中,我们可以看到构建头信息的方法buildZuulRequestHeaders通过isIncludedHeader函数来判断当前请求的各个头信息是否在忽略的头信息清单中,如果是的话就不组织到此次转发的请求中去。那么这些需要忽略的头信息是在哪里初始化的呢?在PRE阶段的PreDecorationFilter过滤器中,我们可以找到答案:
- publicclassPreDecorationFilterextendsZuulFilter{
- ...
- publicObjectrun(){
- RequestContextctx=RequestContext.getCurrentContext();
- finalStringrequestURI=this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
- Routeroute=this.routeLocator.getMatchingRoute(requestURI);
- if(route!=null){
- Stringlocation=route.getLocation();
- if(location!=null){
- ctx.put("requestURI",route.getPath());
- ctx.put("proxy",route.getId());
- //处理忽略头信息的部分
- if(!route.isCustomSensitiveHeaders()){
- this.proxyRequestHelper.addIgnoredHeaders(
- this.properties.getSensitiveHeaders()
- .toArray(newString[0]));
- }else{
- this.proxyRequestHelper.addIgnoredHeaders(
- route.getSensitiveHeaders()
- .toArray(newString[0]));
- }
- ...
- }
从上述源码中,我们可以看到有一段if/else块,通过调用ProxyRequestHelper的addIgnoredHeaders方法来添加需要忽略的信息到请求上下文中,供后续ROUTE阶段的过滤器使用。这里的if/else块分别用来处理全局设置的敏感头信息和指定路由设置的敏感头信息。而全局的敏感头信息定义于ZuulProperties中:
- @Data
- @ConfigurationProperties("zuul")
- publicclassZuulProperties{
- privateSet<String>sensitiveHeaders=newLinkedHashSet<>(
- Arrays.asList("Cookie","Set-Cookie","Authorization"));
- ...
- }
所以解决该问题的思路也很简单,我们只需要通过设置sensitiveHeaders即可,设置方法分为两种:
1. 全局设置:
- zuul.sensitive-headers=
2. 指定路由设置:
- zuul.routes..sensitive-headers=
- zuul.routes..custom-sensitive-headers=true
二、重定向问题
在使用Spring Cloud Zuul对接Web网站的时候,处理完了会话控制问题之后。往往我们还会碰到如下图所示的问题,我们在浏览器中通过Zuul发起了登录请求,该请求会被路由到某WebSite服务,该服务在完成了登录处理之后,会进行重定向到某个主页或欢迎页面。此时,仔细的开发者会发现,在登录完成之后,我们浏览器中URL的HOST部分发生的改变,该地址变成了具体WebSite服务的地址了。这就是在这一节,我们将分析和解决的重定向问题!
出现该问题的根源是Spring Cloud Zuul没有正确的处理HTTP请求头信息中的Host导致。在Brixton版本中,Spring Cloud Zuul的PreDecorationFilter过滤器实现时完全没有考虑这一问题,它更多的定位于REST API的网关。所以如果要在Brixton版本中增加这一特性就相对较为复杂,不过好在Camden版本之后,Spring Cloud Netflix 1.2.x版本的Zuul增强了该功能,我们只需要通过配置属性zuul.add-host-header=true就能让原本有问题的重定向操作得到正确的处理。关于更多Host头信息的处理,读者可以参考本文之前的分析思路,可以通过查看PreDecorationFilter过滤器的源码来详细更多实现细节。
【本文为清一色专栏作者“翟永超”的原创稿件,转载请通过清一色联系作者获取授权】
©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经