在 Envoy 的 HTTP 连接管理器(HCM)中,HTTP 过滤器链是一组可配置的过滤器,用于处理 HTTP 请求和响应。

HTTP 过滤器的结构与原理

Envoy Proxy 中的 HTTP 过滤器类似于网络层过滤器栈,但它们在 HTTP 级别操作。HTTP 过滤器允许对 HTTP 消息(如请求和响应)进行操作,而不必了解底层的物理协议(如 HTTP/1.1、HTTP/2)或多路复用功能。

HTTP 过滤器的类型

HTTP 过滤器根据其处理的内容分为三种类型:

  1. Decoder 过滤器:在连接管理器解码请求流的部分(如请求头、请求体和 trailers)时调用。

  2. Encoder 过滤器:在连接管理器即将编码响应流的部分(如响应头、响应体和 trailers)时调用。

  3. Decoder/Encoder 过滤器:在连接管理器解码请求流和编码响应流时都会调用。

HTTP 过滤器可以在请求的下游(客户端到服务端)和上游(服务端到客户端)都进行操作。下游过滤器与给定的监听器相关联,对每个下游请求进行流处理;上游过滤器与给定的集群相关联,对每个上游请求进行流处理。

HTTP 过滤器的工作原理

  • 过滤器调用顺序

    • Decoder 过滤器按照配置顺序执行。
    • Encoder 过滤器按照相反的顺序执行。

    例如,假设配置了三个 Decoder/Encoder 过滤器:ABC。当处理请求时,Decoder 过滤器的执行顺序为 A -> B -> C;而在处理响应时,Encoder 过滤器的执行顺序为 C -> B -> A

  • 条件过滤器配置

    • 可以根据传入请求的不同条件来调整使用的过滤器配置,例如通过 composite filter 来匹配使用的过滤器配置。
  • 过滤器路由修改

    • 在处理下游 HTTP 过滤器链时,当 decodeHeaders() 被某个过滤器调用时,连接管理器会执行路由解析,并设置指向上游集群的缓存路由。
    • 下游 HTTP 过滤器可以使用 setRoute 回调机制直接修改缓存的路由。
  • 特定路由配置

    • 可以为特定路由、虚拟主机或路由配置提供特定的过滤器配置。这些配置根据过滤器名称进行查找,并对每个过滤器使用的配置进行调整。
  • 基于路由的过滤器链

    • 支持为不同路由设置不同的过滤器链,可以为特定路由禁用或启用某些过滤器。

下图展示了 HTTP 过滤器的结构和工作原理。

image
HTTP 过滤器的结构和工作原理
  • HTTP Connection Manager (HCM):管理 HTTP 连接的组件。
  • HTTP Filter Chain:包含多个过滤器的链,按顺序处理请求和响应。
  • Decoder Filter A/B:处理解码请求流(如请求头和请求体)的过滤器。
  • Decoder/Encoder Filter C:既处理解码请求流又处理编码响应流的过滤器。
  • Route Resolution:路由解析步骤,确定将请求转发到哪个上游集群。
  • Upstream Cluster:请求的目标上游服务。
  • Encoder Filter A/B:处理编码响应流(如响应头和响应体)的过滤器。
  • Client Response:最终的客户端响应。

注意事项

在使用 http_filters 时,有下列事项需要特别注意:

  • http_filters 的顺序非常重要。过滤器的顺序决定了它们在处理请求和响应时的执行顺序。
  • router 过滤器通常应该是 HTTP 过滤器链中的最后一个。这意味着所有的请求和响应在经过其他过滤器后,才会被路由到上游服务。

HTTP 过滤器示例

为了更好地理解 HTTP 过滤器的工作方式,下面我们通过一个实际的配置示例来演示如何在 Envoy 中使用 HTTP 过滤器。

注意:为了保持示例的简单,我们不使用 Lua 或 Wasm 扩展,而是使用 Envoy 内置的过滤器来实现。

配置示例

以下是 Envoy 的部分配置,展示了如何使用内置的 header_mutation 过滤器和访问日志来实现上述功能:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
              log_format:
                text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(:PATH)% %PROTOCOL%\" %RESPONSE_CODE% \"%RESPONSE_FLAGS%\"\n"
          http_filters:
          - name: envoy.filters.http.header_mutation
            typed_config:
              '@type': type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation
              mutations:
                request_mutations:
                - append:
                    header:
                      key: my-request-header
                      value: my-request-value
                response_mutations:
                - append:
                    header:
                      key: my-response-header
                      value: my-response-value
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite_literal: www.envoyproxy.io
                  cluster: service_envoyproxy_io
  clusters:
  - name: service_envoyproxy_io
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_envoyproxy_io
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.envoyproxy.io
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: www.envoyproxy.io
admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

解释

  • envoy.filters.http.header_mutation:这是 Envoy 内置的一个过滤器,用于在请求或响应中添加、修改或删除 HTTP 头部。

  • mutations:指定对请求头部进行修改。

    • request_mutations/response_mutations:设置要在请求/响应中添加或修改的头部列表。
    • header:定义要设置的头部的键和值,这里添加了 my-request-header:my-request-value
  • 访问日志:在 http_connection_manager 中配置了 access_log,用于记录请求和响应的日志信息。

  • log_format:指定日志的格式,这里配置了日志包含响应状态码 %RESPONSE_CODE%

  • 过滤器执行顺序

    • 请求阶段执行顺序header_mutationrouter

      • 当请求到达时,header_mutation 过滤器首先执行,添加自定义头部。

      • 然后,router 过滤器将请求路由到上游服务 service_envoyproxy_io

    • 响应阶段执行顺序routerheader_mutation

    • 在响应返回时,router 过滤器首先处理响应。

    • 然后,header_mutation 过滤器可以对响应头部进行修改。

配置细节

  • listeners:定义了 Envoy 监听的地址和端口,这里是 0.0.0.0:10000,表示监听所有接口的 10000 端口。

  • filter_chains:包含了网络过滤器链,这里使用了 http_connection_manager 来管理 HTTP 连接。

  • http_filters:定义了 HTTP 过滤器链。

    • header_mutation:用于修改请求头部,添加自定义头部。

    • router:负责将请求路由到上游服务,并在响应返回后继续执行后续的编码过滤器。

  • route_config:配置了路由信息,将所有请求(前缀为 /)路由到名为 service_envoyproxy_io 的上游集群。

  • clusters:定义了上游集群 service_envoyproxy_io,使用逻辑 DNS 解析,将请求转发到 www.envoyproxy.io

总结

通过这个示例,我们演示了如何使用 Envoy 的内置过滤器来在请求到达后端服务之前添加自定义的 HTTP 头部,并在响应返回客户端之前记录响应的状态码。这个配置展示了以下关键点:

  • HTTP 过滤器的类型和执行顺序:理解解码过滤器和编码过滤器的执行顺序对于正确配置过滤器链非常重要。

  • 内置过滤器的使用:利用 header_mutation 过滤器可以方便地修改请求和响应头部,而无需编写任何脚本。

  • 访问日志的配置:通过 access_log,我们可以在响应阶段记录关键信息,如响应状态码,方便调试和监控。