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);    }  }}
相关推荐
郝学胜-神的一滴2 分钟前
完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
java·开发语言·数据结构·c++·python·算法
黄毛火烧雪下19 分钟前
Java 基础笔记:文件、递归与字符编码
java·开发语言·笔记
swordbob41 分钟前
CAP 定理:为什么不能同时实现 C、A、P?
开发语言·后端·spring
疯狂成瘾者42 分钟前
Java 常用工具包 java.util
java·开发语言·windows
枫叶丹442 分钟前
【HarmonyOS 6.0】MDM Kit 新特性:PC/2in1设备无锁屏密码重启自动解锁能力详解
开发语言·华为·harmonyos
ZHW_AI课题组1 小时前
Python 调用百度智能云 API 实现地址识别
开发语言·人工智能·python·机器学习·百度·数据挖掘
88号技师1 小时前
2026年2月一区SCI-交叉传播优化算法Propagation Alongside Crossover-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
A.零点1 小时前
【2个月 C 语言从入门到精通:零基础系统教程】第十二讲:深入了解指针(五)
c语言·开发语言·网络·笔记·visual studio
飞天狗1112 小时前
零基础JavaWeb入门——第五课第一小节:九大内置对象 · 第1个:request(请求对象)
java·开发语言·前端·后端·servlet
z落落2 小时前
C#ToolStrip+StatusStrip 状态栏实时显示系统时间+NotifyIcon系统托盘
开发语言·c#