Envoy Lua 过滤器功能实现分析

1. 功能概述

Envoy 的 Lua 过滤器是一个非常强大的功能,它允许开发人员使用 Lua 脚本编写高度定制化的 HTTP 请求和响应处理逻辑。它提供了与 Envoy 核心功能的深度集成,包括请求/响应修改、路由决策、异步请求处理、加密操作等。

该功能的主要特点包括:

  1. 高灵活性:允许使用 Lua 脚本编写自定义逻辑,满足复杂的业务需求

  2. 快速开发:使用熟悉的 Lua 语言快速编写和部署自定义逻辑

  3. 性能优化:与 Envoy 架构深度集成,提供高效的执行环境

  4. 简化架构:减少了需要编写和维护的自定义代码

  5. 与其他 Envoy 功能的深度集成:包括 JWT 认证、速率限制、路由决策等

通过合理配置和使用 Lua 过滤器,开发人员可以在不修改 Envoy 核心代码的情况下,快速实现复杂的业务需求,提高系统的可维护性和可扩展性。

2. 解决的问题

2.1 高灵活性

  • 允许开发人员使用 Lua 脚本编写自定义逻辑,满足复杂的业务需求

  • 提供了与 Envoy 内部系统的深度集成,包括请求/响应修改、路由决策、异步请求处理等

  • 支持在请求和响应阶段都执行自定义逻辑

2.2 快速开发

  • 允许使用熟悉的 Lua 语言快速编写和部署自定义逻辑

  • 提供了强大的调试和日志记录功能,帮助开发人员快速定位和修复问题

  • 支持热更新功能,可以在不重启服务的情况下修改和部署新的 Lua 脚本

2.3 性能优化

  • 与 Envoy 架构深度集成,确保高效的执行

  • 支持异步操作,避免阻塞主事件循环

  • 提供了轻量级的执行环境,适合高并发场景

2.4 简化架构

  • 减少了需要编写和维护的自定义代码

  • 直接在 Envoy 层面处理复杂的业务逻辑

  • 提供了与其他 Envoy 功能的集成支持

3. 架构设计

3.1 核心组件架构

3.2 类图

3.3 基础配置

css 复制代码
http_filters:- name: envoy.filters.http.lua  typed_config:    "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua    inline_code: |      function envoy_on_request(request_handle)        request_handle:logInfo("Received request from " .. request_handle:headers():get(":authority"))      end
      function envoy_on_response(response_handle)        response_handle:logInfo("Received response with status " .. response_handle:headers():get(":status"))      end
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match: { prefix: "/" }      route: { cluster: backend_cluster }
static_resources:  clusters:  - name: backend_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: backend_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: backend.example.com                port_value: 80

3.4 高级配置

swift 复制代码
http_filters:- name: envoy.filters.http.lua  typed_config:    "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua    default_source_code:      filename: /etc/envoy/lua_scripts/default.lua    source_codes:      api_scripts:        filename: /etc/envoy/lua_scripts/api.lua      static_scripts:        filename: /etc/envoy/lua_scripts/static.lua
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match:        prefix: "/api"      route: { cluster: api_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "api_scripts"    - match:        prefix: "/static"      route: { cluster: static_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "static_scripts"    - match:        prefix: "/"      route: { cluster: default_cluster }
static_resources:  clusters:  - name: api_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: api_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: api.example.com                port_value: 80  - name: static_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: static_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: static.example.com                port_value: 80  - name: default_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: default_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: default.example.com                port_value: 80

3.5 禁用 Lua 过滤器配置

python 复制代码
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match:        prefix: "/healthz"      route: { cluster: health_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          disabled: true    - match:        prefix: "/"      route: { cluster: default_cluster }
static_resources:  clusters:  - name: health_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: health_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: health.example.com                port_value: 80  - name: default_cluster    connect_timeout: 0.25s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    load_assignment:      cluster_name: default_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address:                address: default.example.com                port_value: 80

4. 工作流程分析

4.1 过滤器执行流程

4.2 异步 HTTP 调用流程

5. 代码实现ER图

6. 最佳实践

6.1 与其他过滤器配合使用

sql 复制代码
http_filters:- name: envoy.filters.http.jwt_authn  typed_config:    "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication    # JWT认证配置- name: envoy.filters.http.lua  typed_config:    "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua    inline_code: |      function envoy_on_request(request_handle)        -- 自定义逻辑      end
      function envoy_on_response(response_handle)        -- 自定义逻辑      end- name: envoy.filters.http.router

6.2 性能优化

css 复制代码
http_filters:- name: envoy.filters.http.lua  typed_config:    "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua    default_source_code:      filename: /etc/envoy/lua_scripts/optimized.lua
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match:        prefix: "/api"      route: { cluster: api_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "api_scripts"    - match:        prefix: "/"      route: { cluster: default_cluster }

6.3 复杂路由配置

css 复制代码
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match:        prefix: "/api/v1"      route: { cluster: api_v1_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "api_v1_scripts"    - match:        prefix: "/api/v2"      route: { cluster: api_v2_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "api_v2_scripts"    - match:        prefix: "/static"      route: { cluster: static_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          name: "static_scripts"    - match:        prefix: "/"      route: { cluster: default_cluster }

6.4 禁用特定路径的 Lua 过滤器

css 复制代码
route_config:  name: local_route  virtual_hosts:  - name: backend    domains: ["*"]    routes:    - match:        prefix: "/healthz"      route: { cluster: health_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          disabled: true    - match:        prefix: "/metrics"      route: { cluster: metrics_cluster }      per_filter_config:        envoy.filters.http.lua:          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute          disabled: true    - match:        prefix: "/"      route: { cluster: default_cluster }

7. 代码实现细节

7.1 过滤器初始化

css 复制代码
Filter::Filter(FilterConfigConstSharedPtr config, TimeSource& time_source)    : config_(config), time_source_(time_source) {}

7.2 请求处理

php 复制代码
Http::FilterHeadersStatusFilter::doHeaders(StreamHandleRef& handle, Filters::Common::Lua::CoroutinePtr& coroutine,                  FilterCallbacks& callbacks, int function_ref, PerLuaCodeSetup* setup,                  Http::RequestOrResponseHeaderMap& headers, bool end_stream) {  if (function_ref == LUA_REFNIL) {    return Http::FilterHeadersStatus::Continue;  }  ASSERT(setup);  coroutine = setup->createCoroutine();
  handle.reset(StreamHandleWrapper::create(coroutine->luaState(), *coroutine, headers, end_stream,                                           *this, callbacks, time_source_),               true);
  Http::FilterHeadersStatus status = Http::FilterHeadersStatus::Continue;  try {    status = handle.get()->start(function_ref);    handle.markDead();  } catch (const Filters::Common::Lua::LuaException& e) {    scriptError(e);  }
  return status;}

7.3 响应处理

php 复制代码
Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) {  PerLuaCodeSetup* setup = getPerLuaCodeSetup(config_.get(), decoder_callbacks_.callbacks_);  const int function_ref = setup ? setup->responseFunctionRef() : LUA_REFNIL;  return doHeaders(response_stream_wrapper_, response_coroutine_, encoder_callbacks_, function_ref,                   setup, headers, end_stream);}

7.4 异步 HTTP 调用

php 复制代码
int StreamHandleWrapper::doSynchronousHttpCall(lua_State* state, Tracing::Span& span) {  http_request_ = makeHttpCall(state, filter_, span, *this);  if (http_request_ != nullptr) {    state_ = State::HttpCall;    return lua_yield(state, 0);  } else {    // Immediate failure case. The return arguments are already on the stack.    ASSERT(lua_gettop(state) >= 2);    return 2;  }}
void StreamHandleWrapper::onSuccess(const Http::AsyncClient::Request&,                                    Http::ResponseMessagePtr&& response) {  ASSERT(state_ == State::HttpCall || state_ == State::Running);  ENVOY_LOG(debug, "async HTTP response complete");  http_request_ = nullptr;
  // We need to build a table with the headers as return param 1. The body will be return param 2.  lua_newtable(coroutine_.luaState());  response->headers().iterate([lua_State = coroutine_.luaState()](                                  const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate {    lua_pushlstring(lua_State, header.key().getStringView().data(),                    header.key().getStringView().size());    lua_pushlstring(lua_State, header.value().getStringView().data(),                    header.value().getStringView().size());    lua_settable(lua_State, -3);    return Http::HeaderMap::Iterate::Continue;  });
  // TODO(mattklein123): Avoid double copy here.  if (response->body().length() > 0) {    lua_pushlstring(coroutine_.luaState(), response->bodyAsString().data(),                    response->body().length());  } else {    lua_pushnil(coroutine_.luaState());  }
  // In the immediate failure case, we are just going to immediately return to the script. We  // have already pushed the return arguments onto the stack.  if (state_ == State::HttpCall) {    state_ = State::Running;    markLive();
    try {      coroutine_.resume(2, yield_callback_);      markDead();    } catch (const Filters::Common::Lua::LuaException& e) {      filter_.scriptError(e);    }
    if (state_ == State::Running) {      headers_continued_ = true;      callbacks_.continueIteration();    }  }}

7.5 响应生成

ruby 复制代码
void Filter::DecoderCallbacks::respond(Http::ResponseHeaderMapPtr&& headers, Buffer::Instance* body,                                       lua_State*) {  if (Runtime::runtimeFeatureEnabled(          "envoy.reloadable_features.lua_respond_with_send_local_reply")) {    uint64_t status = Http::Utility::getResponseStatus(*headers);    auto modify_headers = [&headers](Http::ResponseHeaderMap& response_headers) {      headers->iterate(          [&response_headers](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate {            response_headers.addCopy(Http::LowerCaseString(header.key().getStringView()),                                     header.value().getStringView());            return Http::HeaderMap::Iterate::Continue;          });    };    callbacks_->sendLocalReply(static_cast<Envoy::Http::Code>(status), body ? body->toString() : "",                               modify_headers, absl::nullopt,                               HttpResponseCodeDetails::get().LuaResponse);  } else {    callbacks_->encodeHeaders(std::move(headers), body == nullptr,                              HttpResponseCodeDetails::get().LuaResponse);    if (body && !parent_.destroyed_) {      callbacks_->encodeData(*body, true);    }  }}
相关推荐
XMYX-019 小时前
39 - Go 信号捕获与处理:优雅退出、进程控制
开发语言·golang
是星辰吖~19 小时前
C++_vector_调用及模拟实现
开发语言·c++
薛定e的猫咪19 小时前
从 DSM 到多智能体仿真:复杂产品变更传播研究路线图
开发语言·人工智能·笔记·学习·php
say_fall19 小时前
从零开始学x86汇编_16位指令系统完全指南
开发语言·汇编·计算机组成·微机原理
粉嘟小飞妹儿19 小时前
Java Switch与Break用法详解
java·开发语言
艾莉丝努力练剑19 小时前
【QT】常用控件(三)Qt布局管理器(网格/表单/间隔器)
java·linux·运维·服务器·开发语言·网络·qt
川冰ICE19 小时前
JavaScript入门⑩|BOM与浏览器对象,localStorage_位置_历史记录
开发语言·javascript·ecmascript
折哥的程序人生 · 物流技术专研19 小时前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·后端·设计模式·面试·架构
ch.ju19 小时前
Java程序设计(第3版)第四章——构造方法
java·开发语言