资讯动态

Envoy Mobile 入门指南:从环境搭建、AAR/Framework 构建到 EngineBuilder 配置实战

发布时间:2026/9/14 20:50:02 来源:尧图企业网站定制
Envoy Mobile 入门指南从环境搭建、AAR/Framework 构建到 EngineBuilder 配置实战【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy本篇指南以仓库中 mobile/docs/root/start/start.rst 及其关联文档构建指南、Hello World 示例、公开 API 说明为核心系统讲解如何在 iOS 与 Android 平台上构建、集成并运行 Envoy Mobile基于 Envoy 的云原生高性能边缘/中间/服务代理的移动端库。阅读本文后你将掌握完整的本地构建流程.aar与.framework产物生成、四语言 Hello World 示例的运行方法以及通过EngineBuilder配置引擎DNS、超时、HTTP/3、压缩、xDS 等的全部公开 API 细节。一、快速上手三大步骤概览Envoy Mobile 的入门路径由 start.rst 组织的三部分内容构成也是本文的骨架API 总览mobile/docs/root/api/api.rst掌握Engine/StreamClient/EngineBuilder等核心类型与基于功能的 API 分组启动引擎、HTTP、gRPC、Stats构建mobile/docs/root/start/building/building.rst搭建 Bazel 构建环境并产出 Androidenvoy.aar与 iOSEnvoy.xcframework产物Hello World 示例mobile/docs/root/start/examples/hello_world.rst在 Java、Kotlin、Objective-C、Swift 四种语言下运行示例 App。二、构建环境准备2.1 获取源码构建 Envoy Mobile 之前系统必须先具备构建 Envoy 的能力。由于 Envoy Mobile 以 git 子模块方式依赖 Envoy 源码克隆仓库时必须使用--recursive以确保子模块被一并初始化git clone https://github.com/envoyproxy/envoy-mobile.git --recursive如果最初未以递归方式克隆可手动初始化 Envoy 子模块git submodule update --init2.2 Bazel 要求Envoy Mobile 使用 mobile/.bazelversion构建指南通过:repo:.bazelversion引用即仓库根下的.bazelversion 文件中指定的 Bazel 版本进行编译。官方推荐使用bazelisk来自动管理并切换正确的 Bazel 版本避免本机 Bazel 版本与项目要求不一致。2.3 Java 要求需要Java 8确保java位于PATH中且JAVA_HOME已指向对应的 JDK 8 SDK。2.4 Android 要求Android SDKplatform 30、build-tools 35.0.0与 NDKr29API level 23会由 Bazel 通过hermetic toolchains封闭式工具链自动拉取。因此本地构建无需安装 Android SDK/NDK也无需设置ANDROID_HOME/ANDROID_NDK_HOME环境变量。这大大降低了本地构建的配置成本从 mobile/BUILD 中的android_dist等构建目标定义也可以印证其构建逻辑集中在 Bazel 层。2.5 iOS 要求Xcode 14.1iOS 13.0 或更高版本更细粒度的要求记录在 mobile/.bazelrc 与 CI 脚本中ci/、mobile/ci/目录可作为排查构建问题的依据。三、构建产物Android AAR 与 iOS Framework3.1 Android AAR.aarEnvoy Mobile 可编译为.aar文件供 Android 应用使用。该命令定义在仓库根 mobile/BUILD 的android_dist目标中可直接在本地执行bazel build android_dist --configandroid --fat_apk_cpuarch1,arch2构建完成后.aar文件位于bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy.aar其中--fat_apk_cpu接受架构列表可用值为x86、armeabi-v7a、arm64-v8a。也可以直接使用 Envoy Mobile releases 页面的预构建产物或通过 Maven 获取见下文。发布构建供生产使用注意事项除用于开发调试的构建外发布产物务必加上--configrelease-android并明确声明目标架构bazel build android_dist --configrelease-android --fat_apk_cpux86,armeabi-v7a,arm64-v8aMobile BUILD 文件 中的envoy_mobile_androidBazel 规则给出了该产物在实际工程中的引用示例。可运行示例见 Hello World 示例。3.2 iOS 静态 FrameworkEnvoy Mobile 支持编译为.framework供 iOS 应用使用对应 mobile/BUILD 中的ios_dist目标bazel build ios_dist --configios构建完成后会生成一个ios_framework.zip压缩后的 framework 产物具体输出路径由 Bazel 决定通常在bazel-bin下。同样也可使用 releases 页面提供的预构建产物Envoy.xcframework.zip或通过 Swift Package Manager见下文引入。发布构建注意事项发布产物需加入--configrelease-ios并声明目标 CPU 架构bazel build ios_dist --configrelease-ios --ios_multi_cpusi386,x86_64,armv7,arm643.3 通过 Maven 获取 Android 产物Envoy Mobile 的 Android 产物同时发布到了 Maven 中央仓库坐标io.envoyproxy.envoymobile:envoy可在工程中直接以依赖方式引入适合不希望本地编译envoy.aar的团队。3.4 通过 Swift Package Manager 引入 iOS 产物使用 SwiftPM 时可在Package.swift中通过.binaryTarget引入预构建的 Envoy Mobile framework.binaryTarget( name: Envoy, url: https://github.com/envoyproxy/envoy-mobile/releases/download/version/Envoy.xcframework.zip, checksum: ... )仓库中还提供了完整的 SwiftPM 集成示例工程位于 mobile/examples/swift/swiftpm其中Packages/Sources/NetworkClient/NetworkClient.swift展示了如何封装网络客户端。注意url中的version与checksum需替换为实际发布版本与对应校验值。四、构建带私有扩展的 Envoy Mobile与 Envoy 一致Envoy Mobile 提供 Bazel 目标允许库作为 git 子模块嵌入到消费方工程中从而支持创建私有扩展如自定义 filter。4.1 扩展注册机制顶层envoy_build_config目录使 Envoy Mobile 能够复用 Envoy 既有的选择性扩展系统selective extensions system参见 bazel/README.md。重要提醒由于静态链接static linking的原因Envoy Mobile必须在extension_registry.cc/h文件中强制注册force registration扩展。例如安装 XffIpDetection 扩展时需要在扩展注册表中显式注册否则该扩展不会生效。这一点与动态链接的常规 Envoy 构建有明显差异。4.2 覆盖内置扩展如需覆盖 Envoy Mobile 内置的扩展集先创建一个envoy_build_config目录然后通过命令行或user.bazelrc覆盖生成的仓库build --override_repositoryenvoy_build_config_extenvoy_build_config/somewhere/on/filesystem/envoy_build_config注意envoy_build_config仓库是由模块扩展module extension生成的因此必须使用如上所示的规范化mangled名称引用而不能用其表面名称。五、本地部署发布到本地 Maven 仓库5.1 Android 本地部署执行以下两步即可将 Envoy Mobile 的 aar 部署到本地 Maven 仓库# 1. 构建 Envoy Mobile。--fat_apk_cpu 接受架构列表[x86|armeabi-v7a|arm64-v8a] bazel build android_dist --configandroid --fat_apk_cpux86 # 2. 发布到本地 maven ci/sonatype_nexus_upload.py --local --files bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy.aar bazel-bin/library/kotlin/io/envoyproxy/envoymobile/envoy-pom.xml部署的版本号将为LOCAL-SNAPSHOT产物可在本地 Maven 目录中找到~/.m2/repository/io/envoyproxy/envoymobile/envoy/LOCAL-SNAPSHOT/5.2 iOS 本地部署iOS 侧对应的本地部署流程在构建指南中标记为 TODO关联 issue #980即当前版本尚未提供 iOS 的本地 Maven/SwiftPM 发布脚本需要时请使用预构建的Envoy.xcframework.zip。六、Hello World四个平台的示例运行hello_world.rst 描述的示例工程会启动 Envoy Mobile并以1 秒定时器周期性地发起网络请求将返回结果集合展示在列表中。各语言工程源码位于 mobile/examples 目录下其中 Kotlin 示例mobile/examples/kotlin/hello_world还额外演示了DemoFilter.kt、AsyncDemoFilter.kt、BufferDemoFilter.kt等平台 filter 的用法。6.1 Javamobile/examples/java/hello_world先构建 Android AAR 产物对应:ref:android_aar 小节确保有一个正在运行的 Android 模拟器使用以下 Bazel 规则运行示例 Appbazel mobile-install //examples/java/hello_world:hello_envoy --fat_apk_cpuarch1,arch2安装完成后模拟器上会出现名为Hello Envoy的 App打开它即可看到请求持续流动。6.2 Kotlinmobile/examples/kotlin/hello_world同样先构建 AAR 产物并启动模拟器然后执行bazel mobile-install //examples/kotlin/hello_world:hello_envoy_kt --fat_apk_cpuarch1,arch2模拟器上安装的 App 名为Hello Envoy Kotlin。6.3 Objective-Cmobile/examples/objective-c/hello_world先构建 iOS Framework 产物对应:ref:ios_framework 小节执行bazel run //examples/objective-c/hello_world:app --configios该命令会启动一个模拟器并打开示例 App随后即可看到请求开始流动。6.4 Swiftmobile/examples/swift/hello_world流程与 Objective-C 一致bazel run //examples/swift/hello_world:app --configios七、核心 APIEngine 与 StreamClient7.1 启动引擎StreamClient启动一个 Envoy Mobile 实例是通过EngineBuilder构建出Engine实例完成的请求则由Engine提供的StreamClient执行。获取StreamClient的方式在Engine实例上调用streamClient()方法。获得 stream client 后应将其保存并在后续复用用于发起网络请求/流。Kotlin 示例val streamClient AndroidEngineBuilder(getApplication()) .setLogLevel(LogLevel.WARN) ... .build() .streamClient()Swift 示例let streamClient try EngineBuilder() .setLogLevel(.warn) ... .build() .streamClient()从源码看Engine.kt 定义的Engine接口除streamClient()外还提供pulseClient()记录时间序列指标、terminate()终止引擎、dumpStats()导出全部活动统计可能阻塞一段时间、drainConnectionsBySocketTag(tag)按 socket tag 排空连接、resetConnectivityState()刷新 DNS 并排空连接等能力对应 Kotlin 实现位于 EngineImpl.kt。7.2 EngineBuilder统一配置入口EngineBuilder用于在最终调用.build()创建引擎之前配置Engine实例。iOS 与 Android 两侧的可用 builder 方法几乎一一对应以下是全部公开配置项方法名、作用、默认值与双端示例均来自 starting_envoy.rst默认值以 EngineBuilder.kt 与 EngineBuilder.swift 中的实现为准。连接与超时方法作用默认值addConnectTimeoutSeconds设置到 Envoy Mobile 集群中主机的新建连接超时Kotlin 10s / Swift 30saddStreamIdleTimeoutSeconds流在无 headers/data 事件时的空闲超时15saddPerTryIdleTimeoutSeconds每次重试含首次尝试的空闲超时15saddH2ConnectionKeepaliveIdleIntervalMilliseconds空闲 h2 连接在新建流时的 ping 间隔1ms等效于每条新流都 ping设为 100000000ms 可禁用addH2ConnectionKeepaliveTimeoutSecondsh2 ping 超时10ssetMaxConnectionsPerHost单个主机的最大连接数7// Kotlin builder.addConnectTimeoutSeconds(30L) builder.addStreamIdleTimeoutSeconds(5L) builder.addPerTryIdleTimeoutSeconds(5L) // Swift builder.addConnectTimeoutSeconds(30) builder.addStreamIdleTimeoutSeconds(5) builder.addPerTryIdleTimeoutSeconds(5)其中addStreamIdleTimeoutSeconds对应 Envoy HTTP Connection Manager 的stream_idle_timeout字段addPerTryIdleTimeoutSeconds对应 RouteRetryPolicy的per_try_timeout语义均与 Envoy 侧配置概念一致。DNS 相关方法作用默认值addDNSRefreshSeconds强制刷新 DNS 的间隔60saddDNSFailureRefreshSeconds(base, max)DNS 失败状态下的刷新速率base/max 区间base 2s / max 10saddDNSQueryTimeoutSecondsDNS 查询超时5saddDNSMinRefreshSecondsDNS 最小刷新间隔解析成功后遵循 TTL 但不低于该值60saddDNSPreresolveHostnames引擎启动时预解析的主机名列表空setDnsNumRetries解析器放弃前的重试次数不设置则无限重试直到成功或查询超时无限enableDrainPostDnsRefresh软 DNS 刷新Engine API 或系统网络状态更新触发后排空连接不中断已有连接/请求但后续新请求会建立新连接falseenableDNSCache启用 DNS 缓存需配合名为reserved.platform_store的 KeyValueStorefalse// Kotlin builder.addDNSFailureRefreshSeconds(2, 5) builder.addDNSRefreshSeconds(60L) builder.addDNSQueryTimeoutSeconds(60L) builder.addDNSPreresolveHostnames(listOf(lyft.com, google.com)) // Swift builder.addDNSFailureRefreshSeconds(base: 2, max: 5) builder.addDNSRefreshSeconds(60) builder.addDNSQueryTimeoutSeconds(60) builder.addDNSPreresolveHostnames([lyft.com, google.com])关于addDNSFailureRefreshSeconds文档特别强调失败态刷新速率通常应比正常态更激进因为 Envoy Mobile 需要依靠它快速恢复并继续发起请求。DNS 缓存启用后结果写入 KeyValueStore 的间隔默认 1s可通过saveInterval参数显式调整最多存储100 条记录// Kotlin builder.enableDNSCache(true, saveInterval 60) // Swift builder.enableDNSCache(true, saveInterval: 60)日志与回调方法作用setLogLevel设置底层 Envoy 引擎的日志级别setOnEngineRunning引擎异步启动完成、开始运行后的回调纯观测用途setLogger引擎输出日志时的回调setEventTracker引擎发出事件时的回调事件以MapString, String传入addStringAccessor(name, accessor)注册字符串访问器供 Envoy 从平台运行时读取任意字符串// Kotlin builder.setLogLevel(LogLevel.WARN) builder.setOnEngineRunning { /* do something */ } builder.setLogger { level, message - /* log it */ } builder.setEventTracker { /* 事件以 MapString, String 传入 */ } builder.addStringAccessor(demo-accessor, { PlatformString }) // Swift builder.setLogLevel(.warn) builder.setOnEngineRunning { /* do something */ } builder.setLogger { level, msg in NSLog(Envoy log: \(msg)) } builder.setEventTracker { event in NSLog(Envoy log: \(event)) } builder.addStringAccessor(name: demo-accessor, accessor: { return PlatformString })setOnEngineRunning的设计动机值得注意Envoy初始化时其构造函数会在内部配置完成之前返回因此该回调用于观测引擎是否已完成设置、可以开始派发请求在设置完成前发出的请求会被自动排队此回调通常仅用于可观测性。应用信息与压缩方法作用默认值addAppVersion指定使用 Envoy Mobile 的 App 版本unspecifiedaddAppId指定使用 Envoy Mobile 的 App 标识unspecifiedenableGzipDecompression是否启用响应透明 Gzip 解压trueenableBrotliDecompression是否启用响应透明 Brotli 解压false// Kotlin builder.addAppVersion(v1.2.3) builder.addAppId(com.mydomain.myapp) builder.enableGzipDecompression(false) builder.enableBrotliDecompression(true) // Swift builder.addAppVersion(v1.2.3) builder.addAppId(com.mydomain.myapp) builder.enableGzipDecompression(false) builder.enableBrotliDecompression(true)两个解压开关均使用对应 Envoy 解压器 proto 中的默认配置gzip decompressor / brotli decompressor 的默认值。原生 Filter方法作用addNativeFilter(name, typedConfig)向 Envoy Mobile filter 链添加一个 C filter// Kotlin builder.addNativeFilter( envoy.filters.http.buffer, [type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer] { max_request_bytes: { value: 5242880 } ) // Swift builder.addNativeFilter( envoy.filters.http.buffer, [type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer] { max_request_bytes: { value: 5242880 } )使用要点文档中的attention提示仅当指定的 C filter已链接进 Envoy Mobile 构建时才生效C/Android 测试场景下调用addNativeFilter并把库加入envoy_build_config/extensions_build_config.bzl即可iOS 由于激进的垃圾回收机制以及上游 CI 为了暴露 bug还必须在envoy_build_config/extension_registry.cc中forceRegister该 filter两个平台默认使用 proto 语法配置若以--defineenvoy_yamlenabled构建则同时支持 YAML。网络与平台能力方法作用默认值/可用性enableHttp3是否启用 HTTP/3true仅当构建包含 HTTP/3 时可用addQuicHint(host, port)声明已知支持 QUIC 的主机端口对可多次调用追加仅在 HTTP/3 启用时有效addQuicCanonicalSuffix(suffix)声明已知支持 QUIC 的规范化域名后缀配合 QUIC hint 使用空enableEarlyData是否启用 0-RTT early datatrueenableSocketTagging是否支持 Android socket taggingfalseiOS 不可用enableInterfaceBinding是否允许 socket 按网络状况绑定到特定接口falseenableProxying是否遵循系统代理设置false仅 AndroidenablePlatformCertificatesValidation是否使用平台提供的证书校验接口false当前仅 Android 支持setUpstreamTlsSni覆盖上游 TLS 的 SNI空不覆盖setTrustChainVerification设置信任链校验策略可静默 TLS 证书校验用于测试VERIFY_TRUST_CHAINforceIPv6将 IPv4 地址重映射到 IPv6 空间并强制使用 IPv6Kotlin 无此 API恒启用Swift 默认关闭// Kotlin builder.enableHttp3(true) builder.addQuicHint(www.example.com, 443) builder.addQuicCanonicalSuffix(.example.com) builder.enableSocketTagging(true) builder.enableInterfaceBinding(true) builder.enableProxying(true) builder.enablePlatformCertificatesValidation(true) // Swift builder.enableHttp3(true) builder.addQuicHint(www.example.com, 443) builder.addQuicCanonicalSuffix(.example.com) builder.enableInterfaceBinding(true) builder.forceIPv6(true)HTTP/3 的升级机制当启用 HTTP/3 时客户端先通过 HTTP/2 与服务器通信收到响应中的alt-svc之后后续流量将切换到 HTTP/3。addQuicHint的作用是让首次请求就直接使用 HTTP/3从而跳过 HTTP/2 → HTTP/3 的切换过程addQuicCanonicalSuffix是 QUIC hint 的扩展若将.abc.com加入 canonical suffix并将foo.abc.com加入 QUIC hint则所有对*.abc.com的请求都会被视作 QUIC 就绪。forceIPv6属于实验性选项文档明确提示应谨慎启用。KeyValueStore方法作用addKeyValueStore(name, store)注册平台的 KeyValueStore 实现供库内部使用未来主要用于 HTTP 与端点属性缓存// Kotlin自定义实现 builder.addKeyValueStore(io.envoyproxy.envoymobile.MyKeyValueStore, MyKeyValueStoreImpl()) // Kotlin使用内置的 SharedPreferences 实现Android val preferences context.getSharedPreferences( io.envoyproxy.envoymobile.MyPreferences, Context.MODE_PRIVATE ) builder.addKeyValueStore(io.envoyproxy.envoymobile.MyKeyValueStore, SharedPreferencesStore(preferences)) // SwiftComing soon暂无对应 API其中SharedPreferencesStore的 Android 实现位于 mobile/library/kotlin/io/envoyproxy/envoymobile/android/SharedPreferencesStore.kt而KeyValueStore接口定义在 mobile/library/kotlin/io/envoyproxy/envoymobile/KeyValueStore.kt。注意启用 DNS 缓存时必须注册名为reserved.platform_store的 KeyValueStore。运行时开关与 xDS方法作用addRuntimeGuard(name, value)向 Envoy 配置添加运行时开关键值对key 使用短形式feature而非全限定envoy.reloadable_features.feature。若指定未知开关debug 模式下 Envoy 会启动失败setXds(xdsBuilder)配置 Bootstrap 使用 xDS 从管理服务器拉取动态配置要求管理服务器支持 ADS 协议目前仅支持 State-of-the-World 协议不支持 Delta客户端通过 gRPC 通信setNodeId设置node.id字段setNodeLocality设置node.locality字段region/zone/subZone// Kotlin builder.addRuntimeGuard(feature, true) val xdsBuilder XdsBuilder(address my_xds_server.com, port 443) .addRuntimeDiscoveryService(my_rtds_resource) .addClusterDiscoveryService() builder.setXds(xdsBuilder) builder.setNodeId(nodeId my_test_node) builder.setNodeLocality(region us-west-1, zone some_zone, subZone some_sub_zone) // Swift builder.addRuntimeGuard(feature, true) var xdsBuilder XdsBuilder(address: my_xds_server.com, port: 443) .addRuntimeDiscoveryService(my_rtds_resource) .addClusterDiscoveryService() builder.setXds(xdsBuilder) builder.setNodeID(my_test_node) builder.setNodeLocality(region: us-west-1, zone: some_zone, subZone: some_sub_zone) // C XdsBuilder xds_builder(/*address*/my_xds_server.com, /*port*/443); xds_builder.addRuntimeDiscoveryService(my_rtds_resource).addClusterDiscoveryService(); builder.setXds(std::move(xds_builder)); builder.setNodeId(my_test_node); builder.setNodeLocality(us-west-1, some_zone, some_sub_zone);XdsBuilder提供按需配置各类发现服务的能力例如addRuntimeDiscoveryService()配置 Runtime Discovery ServiceRTDSaddClusterDiscoveryService()配置 Cluster Discovery ServiceCDS。node.id/node.locality对应 Envoy 的config.core.v3.Node消息字段用于标识客户端节点及其位置。7.3 高级配置自定义 YAML多数场景下EngineBuilder提供的函数已覆盖基本配置需求。但某些情况下直接提供一个附加了自定义项的 Envoy 配置 YAML 文件会更有用。此时可以在初始化 builder 时传入 YAML 文件内容// Kotlin val streamClient AndroidEngineBuilder(baseContext, Yaml(yamlFileString)) .setLogLevel(LogLevel.WARN) ... .build() .streamClient() // Swift let streamClient try EngineBuilder(yaml: yamlFileString) .setLogLevel(.warn) ... .build() .streamClient()重要提醒使用自定义 YAML 配置可能引发运行时 bug 或崩溃原因有二其一配置字符串直到运行时才会被求值无法在编译期发现错误其二并非所有 Envoy 核心配置项都被 Envoy Mobile 支持。因此除非必要应优先使用 builder 的标准配置项。7.4 发起请求拿到StreamClient实例后即可开始发起请求相关能力分两类HTTP 请求与流参见 mobile/docs/root/api/http.rstgRPC 流参见 mobile/docs/root/api/grpc.rst。对应的 Kotlin 实现分别位于 mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt、StreamClient.kt 与 mobile/library/kotlin/io/envoyproxy/envoymobile/grpc 目录GRPCClient.kt、GRPCStreamPrototype.kt等统计/指标能力见 mobile/docs/root/api/stats.rst。八、常见问题与最佳实践小结克隆务必--recursiveEnvoy 子模块未初始化会导致构建失败可用git submodule update --init补救。Bazel 版本交给 bazelisk 管理避免本机 Bazel 版本漂移导致构建异常。Android/iOS 无需本地 SDK 安装hermetic toolchains 会自动拉取所需 SDK/NDKAndroid platform 30、build-tools 35.0.0、NDK r29iOS 则需本机 Xcode 14.1 与 iOS 13.0 目标。发布构建务必带 release 配置Android 使用--configrelease-androidiOS 使用--configrelease-ios并显式声明目标架构--fat_apk_cpu/--ios_multi_cpus。静态链接决定扩展必须强制注册iOS 与上游 CI 场景下原生 filter 需在extension_registry.cc中 forceRegister并通过envoy_build_config--override_repository覆盖内置扩展集。Builder 配置有合理默认值如流/单次尝试空闲超时默认 15s、Gzip 解压默认开启、HTTP/3 默认开启含 0-RTT early data。按需调整 DNS 失败刷新速率建议激进与 QUIC hints 可显著改善弱网与首包体验。自定义 YAML 属高风险操作配置运行时才求值且并非所有核心配置项受支持优先使用公开 builder API。通过本文的构建流程与 API 清单开发者可以快速在 iOS/Android 工程中集成 Envoy Mobile并用统一的两端 API 完成超时控制、DNS 优化、HTTP/3、透明压缩、私有扩展与 xDS 动态配置等企业级移动网络能力。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价