聊聊spring cloud gateway的ForwardedHeadersFilter

本文主要研究一下spring cloud gateway的ForwardedHeadersFilter

GatewayAutoConfiguration

spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
    //......
    @Bean
    @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
    public ForwardedHeadersFilter forwardedHeadersFilter() {
        return new ForwardedHeadersFilter();
    }
    //......
}

ForwardedHeadersFilter

spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java

public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {

    public static final String FORWARDED_HEADER = "Forwarded";

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

    @Override
    public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders original = input;
        HttpHeaders updated = new HttpHeaders();

        // copy all headers except Forwarded
        original.entrySet().stream()
                .filter(entry -> !entry.getKey().toLowerCase().equalsIgnoreCase(FORWARDED_HEADER))
                .forEach(entry -> updated.addAll(entry.getKey(), entry.getValue()));

        List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));

        for (Forwarded f : forwardeds) {
            updated.add(FORWARDED_HEADER, f.toString());
        }

        //TODO: add new forwarded
        URI uri = request.getURI();
        String host = original.getFirst(HttpHeaders.HOST);
        Forwarded forwarded = new Forwarded()
                .put("host", host)
                .put("proto", uri.getScheme());

        InetSocketAddress remoteAddress = request.getRemoteAddress();
        if (remoteAddress != null) {
            String forValue = remoteAddress.getAddress().getHostAddress();
            int port = remoteAddress.getPort();
            if (port >= 0) {
                forValue = forValue + ":" + port;
            }
            forwarded.put("for", forValue);
        }
        // TODO: support by?

        updated.add(FORWARDED_HEADER, forwarded.toHeaderValue());

        return updated;
    }

    /* for testing */ static List<Forwarded> parse(List<String> values) {
        ArrayList<Forwarded> forwardeds = new ArrayList<>();
        if (CollectionUtils.isEmpty(values)) {
            return forwardeds;
        }
        for (String value : values) {
            Forwarded forwarded = parse(value);
            forwardeds.add(forwarded);
        }
        return forwardeds;
    }

    /* for testing */ static Forwarded parse(String value) {
        String[] pairs = StringUtils.tokenizeToStringArray(value, ";");

        LinkedCaseInsensitiveMap<String> result = splitIntoCaseInsensitiveMap(pairs);
        if (result == null) return null;

        Forwarded forwarded = new Forwarded(result);

        return forwarded;
    }

    @Nullable
    /* for testing */ static LinkedCaseInsensitiveMap<String> splitIntoCaseInsensitiveMap(String[] pairs) {
        if (ObjectUtils.isEmpty(pairs)) {
            return null;
        }

        LinkedCaseInsensitiveMap<String> result = new LinkedCaseInsensitiveMap<>();
        for (String element : pairs) {
            String[] splittedElement = StringUtils.split(element, "=");
            if (splittedElement == null) {
                continue;
            }
            result.put(splittedElement[0].trim(), splittedElement[1].trim());
        }
        return result;
    }
}

这个filter首先拷贝了请求的header,然后将请求中的Forwarded提取出来,解析成一个个Forwarded对象,添加到新的HttpHeaders中。除此之外,还补充了一个转发信息的Forwarded(host,proto,for)

Forwarded

语法

Forwarded: by=<identifier>; for=<identifier>; host=<host>; proto=<http|https>
  • by=该请求进入到代理服务器的接口。
  • for=发起请求的客户端以及代理链中的一系列的代理服务器。
  • host=代理接收到的 Host首部的信息。
  • proto=表示发起请求时采用的何种协议(通常是 “http” 或者 “https”)。

实例

Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43
Forwarded: proto=http;host="localhost:10000";for="0:0:0:0:0:0:0:1:56443"

对象

static class Forwarded {

        private static final char EQUALS = '=';
        private static final char SEMICOLON = ';';

        private final Map<String, String> values;

        public Forwarded() {
            this.values = new HashMap<>();
        }

        public Forwarded(Map<String, String> values) {
            this.values = values;
        }

        public Forwarded put(String key, String value) {
            this.values.put(key, quoteIfNeeded(value));
            return this;
        }

        private String quoteIfNeeded(String s) {
            if (s.contains(":")) { //TODO: broaded quote
                return "\""+s+"\"";
            }
            return s;
        }

        public String get(String key) {
            return this.values.get(key);
        }

        /* for testing */ Map<String, String> getValues() {
            return this.values;
        }

        @Override
        public String toString() {
            return "Forwarded{" +
                    "values=" + this.values +
                    '}';
        }

        public String toHeaderValue() {
            StringBuilder builder = new StringBuilder();
            for (Map.Entry<String, String> entry : this.values.entrySet()) {
                if (builder.length() > 0) {
                    builder.append(SEMICOLON);
                }
                builder.append(entry.getKey())
                        .append(EQUALS)
                        .append(entry.getValue());
            }
            return builder.toString();
        }
    }

小结

RFC 7239(June 2014)提出了一个标准化的Forwarded头部,来携带反向代理的基本信息,用于替代X-Forwarded系列及X-Real-IP等非标准化的头部。而ForwardedHeadersFilter便是提供了Forwarded头部的转发支持,目前经过gateway的请求会带上一个转发信息的Forwarded(host,proto,for)。

doc

  • Forwarded
  • Forwarded HTTP Extension
  • THE FORWARDED HEADER
  • Using the Forwarded header

原文发布于微信公众号 - 码匠的流水账(geek_luandun)

原文发表时间:2018-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏androidBlog

一步步拆解 LeakCanary

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

821
来自专栏移动开发的那些事儿

Android开发之逻辑单元测试

以上createInetSocketAddress方法就是我在编写单元测试的时候单独抽离出来的方法,一方面我需要mock一个InetSocketAddress来...

1001
来自专栏coolblog.xyz技术专栏

Dubbo 源码分析 - 集群容错之 Directory

前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码。这部分源码包含四个部分,分别是服务目录 Directory、服...

732
来自专栏JAVA高级架构

适配器模式(Adapter)

933
来自专栏技术之路

Caliburn.Micro学习笔记(五)----协同IResult

今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows custom code to execute...

2058
来自专栏天天P图攻城狮

深入Android Runtime: 指令优化与Java方法调用

在进行apk热修复、插件化、动态加载的时候,会经常多个jar/dex包含相同的class,如果class结构因为需要升级出现了变化,会隐藏一些很难解释的坑在里面...

4746
来自专栏郭霖

Android图片加载框架最全解析(四),玩转Glide的回调与监听

大家好,今天我们继续学习Glide。 在上一篇文章当中,我带着大家一起深入探究了Glide的缓存机制,我们不光掌握了Glide缓存的使用方法,还通过源码分析对缓...

5326
来自专栏Android点滴积累

SharedPreferences 详解(多进程,存取数组解决方案)

一、SharedPreferences基本概念 文件保存路径:/data/data/<包名>/shared_prefs目录下目录下生成了一个SP.xml文件 S...

3269
来自专栏向治洪

picasso图片缓存框架

picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓...

2898
来自专栏求索之路

Android源码设计模式解析与实战笔记

1.单一职责原则:比如说一个ImageLoader,需要加载图片的缓存图片,此时如果将这两个功能都放在一个类中,就违反了这个原则, 我们需要将不同的功能用类精...

4405

扫码关注云+社区