第一章:Go HTTP客户端Get请求基础与核心架构
Go语言标准库中的net/http包提供了简洁而强大的HTTP客户端能力,其中http.Get()是最基础的发起GET请求的方式。它本质上是http.DefaultClient.Get()的便捷封装,背后依赖于http.Client、http.Transport和http.Request三者协同工作——Client负责请求调度与响应管理,Transport实现底层连接复用、TLS握手与超时控制,Request则承载URL、Header、上下文等元数据。
创建并发送最简GET请求
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// 发起GET请求,返回*http.Response和error
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
panic(err) // 实际项目中应使用更精细的错误处理
}
defer resp.Body.Close() // 必须关闭响应体以释放底层TCP连接
// 读取响应正文
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %s\n", resp.Status)
fmt.Printf("Body: %s\n", string(body))
}
该代码直接调用http.Get,内部自动构造http.Request、使用http.DefaultClient执行,并复用默认http.Transport(支持连接池与Keep-Alive)。
默认HTTP客户端的关键配置项
| 配置字段 | 默认值 | 说明 |
|---|---|---|
Timeout |
0(无限制) | 整个请求生命周期超时(含DNS、连接、传输) |
Transport |
DefaultTransport |
内置传输器,启用HTTP/1.1连接复用与空闲连接池 |
CheckRedirect |
DefaultCheckRedirect |
控制重定向策略,默认最多10次 |
自定义客户端以增强可控性
生产环境应避免直接使用http.Get,推荐显式构造http.Client:
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
},
}
resp, err := client.Get("https://httpbin.org/get") // 同样返回*http.Response
此方式可精确控制超时、连接复用与TLS行为,是构建高可靠HTTP客户端的起点。
第二章:连接池与复用机制深度配置
2.1 Transport.MaxIdleConns:全局空闲连接上限的理论边界与压测调优实践
MaxIdleConns 控制 HTTP 连接池中所有主机共享的空闲连接总数上限,是连接复用效率与内存开销的关键平衡点。
默认行为与理论瓶颈
- Go 1.19+ 默认值为
100,非零但保守; - 超过该值的空闲连接将被立即关闭,不进入复用队列;
- 理论边界由
MaxIdleConns ≤ GOMAXPROCS × 并发请求峰值 × 安全冗余系数决定。
压测调优关键观察指标
| 指标 | 正常区间 | 过载信号 |
|---|---|---|
http.Transport.IdleConnMetrics.IdleCount() |
≥80% MaxIdleConns | 持续 |
| 新建连接占比(tcp_connect) | >25% |
典型配置与分析
tr := &http.Transport{
MaxIdleConns: 400, // 全局空闲连接硬上限
MaxIdleConnsPerHost: 100, // 防止单主机独占资源(推荐设为 MaxIdleConns/4)
IdleConnTimeout: 90 * time.Second,
}
逻辑说明:设
400是为支撑 200 QPS、平均 RT 300ms 的服务——按QPS × RT × 2估算活跃连接需求(≈120),预留 3× 冗余保障突发流量;若MaxIdleConnsPerHost未同步调高,将导致跨主机连接争抢失效。
连接复用决策流程
graph TD
A[发起请求] --> B{连接池有可用空闲连接?}
B -- 是 --> C[复用现有连接]
B -- 否 --> D{当前空闲总数 < MaxIdleConns?}
D -- 是 --> E[新建连接并加入池]
D -- 否 --> F[新建连接,不入池,用后即关]
2.2 Transport.MaxIdleConnsPerHost:单主机连接复用策略与微服务场景下的竞争规避实践
在高并发微服务调用中,http.Transport 的 MaxIdleConnsPerHost 控制每个目标主机(含端口)最多缓存的空闲连接数。默认值为2,极易成为下游服务的连接瓶颈。
连接复用与竞争根源
当多个 goroutine 并发请求同一服务(如 auth-service:8080),低 MaxIdleConnsPerHost 会强制新建 TCP 连接,触发 TIME_WAIT 积压与端口耗尽。
典型配置示例
tr := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 50, // 关键:为单主机预留充足空闲连接
IdleConnTimeout: 30 * time.Second,
}
逻辑分析:
MaxIdleConnsPerHost=50允许对auth-service:8080最多复用 50 条空闲连接;配合IdleConnTimeout防止长时闲置连接占用资源;MaxIdleConns是全局上限,需 ≥MaxIdleConnsPerHost × 主机数。
微服务调优建议
- 服务发现集群(如 3 个 auth 实例):按
host:port独立计数,无需额外放大 - 对接限流网关(如 Kong):建议设为 30–100,避免连接争抢触发熔断
| 场景 | 推荐值 | 风险提示 |
|---|---|---|
| 内部高频 RPC 调用 | 50–100 | 过高可能掩盖服务端压力 |
| 跨 AZ 低频管理接口 | 10 | 节省连接资源 |
| Serverless 函数调用 | 5 | 生命周期短,复用价值低 |
graph TD
A[Client Goroutines] -->|并发请求| B(auth-service:8080)
B --> C{MaxIdleConnsPerHost=2?}
C -->|Yes| D[频繁新建连接 → TIME_WAIT ↑]
C -->|No| E[复用空闲连接 → 延迟↓/稳定性↑]
2.3 Transport.IdleConnTimeout:空闲连接保活时长与TLS握手开销的权衡分析与实测验证
HTTP/2 复用连接依赖 IdleConnTimeout 控制连接复用窗口,过短导致频繁重建连接与 TLS 握手,过长则积压无效连接占用资源。
TLS 握手成本实测对比(单次平均耗时)
| 网络环境 | TLS 1.2(毫秒) | TLS 1.3(毫秒) | 连接复用节省率 |
|---|---|---|---|
| 内网 | 8.2 | 3.1 | ~92% |
| 公网(RTT 45ms) | 67.5 | 22.3 | ~86% |
Go HTTP Client 关键配置示例
transport := &http.Transport{
IdleConnTimeout: 30 * time.Second, // ⚠️ 默认90s常过高
TLSHandshakeTimeout: 10 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
}
IdleConnTimeout=30s 在高并发短周期调用场景下可降低 37% 的 TLS 握手频次(基于 10k QPS 压测),但需配合服务端 keep-alive timeout 对齐,否则触发 RST。
连接生命周期决策逻辑
graph TD
A[连接空闲] --> B{空闲时长 ≥ IdleConnTimeout?}
B -->|是| C[关闭连接]
B -->|否| D[保持复用]
C --> E[下次请求触发新TLS握手]
2.4 Transport.TLSHandshakeTimeout:HTTPS建立阶段超时控制与证书链异常的故障注入验证
TLSHandshakeTimeout 控制客户端发起 HTTPS 请求时,等待 TLS 握手完成的最大时长。超时过短易误判正常延迟,过长则阻塞请求队列。
故障注入场景设计
- 模拟中间 CA 证书缺失导致握手卡在
CertificateVerify - 注入自签名根证书信任链断裂
- 强制服务端响应延迟 >
TLSHandshakeTimeout
超时配置示例
transport := &http.Transport{
TLSHandshakeTimeout: 5 * time.Second, // 关键:默认30s,生产建议设为3–8s
}
5 * time.Second 平衡了弱网容忍(如移动网络RTT≈800ms)与快速失败需求;若设为 1s,在证书链校验耗时波动时(如OCSP Stapling响应延迟)将高频触发 net/http: TLS handshake timeout。
| 场景 | 握手耗时 | 是否触发超时 | 常见错误日志 |
|---|---|---|---|
| 正常链(全缓存) | 120ms | 否 | — |
| 缺失中间CA | >6s | 是 | x509: certificate signed by unknown authority |
| OCSP Stapling超时 | 4.8s | 否(但临界) | tls: handshake did not complete |
graph TD
A[Client Initiate HTTPS] --> B{Start TLSHandshakeTimeout Timer}
B --> C[Send ClientHello]
C --> D[Wait ServerHello/Cert/Verify]
D --> E{Timer Expired?}
E -- Yes --> F[Cancel Handshake → net/http: TLS handshake timeout]
E -- No --> G[Complete or Fail with x509 error]
2.5 Transport.ExpectContinueTimeout:100-continue协商机制启用条件与大文件上传的响应延迟优化实践
HTTP/1.1 的 100-continue 机制允许客户端在发送大请求体前,先发送含 Expect: 100-continue 的请求头,等待服务端明确许可(HTTP 100 Continue)后再传输主体,避免无效上传。
启用前提
- 客户端显式设置
Expect: 100-continue - 请求方法为
POST/PUT且含Content-Length或Transfer-Encoding: chunked - 服务端未禁用该行为(如 Nginx 默认开启,但需确保
large_client_header_buffers足够)
关键参数影响
| 参数 | 默认值 | 说明 |
|---|---|---|
ExpectContinueTimeout |
350ms (.NET Core) | 客户端等待 100 Continue 的最大时长 |
ContinueTimeout |
1s (Go net/http) | 服务端生成 100 Continue 的内部超时 |
var handler = new HttpClientHandler
{
ExpectContinueTimeout = TimeSpan.FromMilliseconds(100) // ⬅️ 缩短等待,防阻塞
};
逻辑分析:将超时从默认 350ms 降至 100ms,可使客户端在服务端响应迟缓时快速 fallback 到直接上传,避免大文件卡在预检阶段。适用于高延迟或弱网环境。
graph TD
A[Client sends HEAD + Expect] --> B{Server replies 100?}
B -- Yes --> C[Client streams body]
B -- No/Timeout --> D[Client streams body immediately]
第三章:TLS与安全传输层定制化配置
3.1 Transport.TLSClientConfig:自定义根证书、双向认证与SNI主机名匹配的生产级配置实践
在高安全要求场景中,http.Transport 的 TLSClientConfig 是控制 TLS 连接行为的核心枢纽。
根证书定制与信任链加固
通过 RootCAs 字段加载私有 CA 证书,绕过系统默认信任库,确保仅信任组织内签发的证书:
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM([]byte(customCAPEM)) // 必须为 PEM 编码的 DER 格式根证书
transport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
ServerName: "api.example.com", // 强制 SNI 主机名
},
}
ServerName 同时触发 SNI 扩展发送与证书 DNSNames 匹配校验,防止中间人劫持。
双向 TLS(mTLS)配置要点
需同时提供客户端证书与私钥,并启用 VerifyPeerCertificate 实现动态策略:
| 字段 | 用途 | 生产建议 |
|---|---|---|
Certificates |
客户端身份凭证 | 使用 PKCS#8 私钥 + PEM 链式证书 |
InsecureSkipVerify |
禁用服务端证书验证 | ❌ 永远设为 false |
VerifyPeerCertificate |
自定义对端证书校验逻辑 | ✅ 推荐用于 OCSP 状态检查或 SAN 白名单 |
SNI 与证书匹配流程
graph TD
A[发起 HTTPS 请求] --> B[Transport 设置 ServerName]
B --> C[生成 SNI 扩展并发送]
C --> D[服务端返回匹配域名的证书]
D --> E[客户端校验 DNSNames/IPAddresses]
E --> F[校验通过则建立连接]
3.2 Transport.ForceAttemptHTTP2:HTTP/2强制启用前提与ALPN协商失败的降级诊断流程
ForceAttemptHTTP2 是 Go net/http Transport 的关键开关,但其生效需满足双重前提:
- TLS 配置中显式启用
NextProtos = []string{"h2", "http/1.1"} - 底层连接必须完成 ALPN 协商 —— 若服务端不支持 h2,ALPN 返回空或
"http/1.1",则强制失败。
ALPN 协商失败的典型路径
tr := &http.Transport{
ForceAttemptHTTP2: true,
TLSClientConfig: &tls.Config{
NextProtos: []string{"h2"}, // ❌ 缺失 http/1.1 导致无降级能力
},
}
此配置下,若服务端 ALPN 不返回
"h2",TLS 握手直接终止(tls: no application protocol),不会回退 HTTP/1.1。正确做法是保留兼容协议:[]string{"h2", "http/1.1"}。
降级诊断检查表
| 检查项 | 说明 |
|---|---|
NextProtos 是否含 "h2" 且含后备协议 |
缺失后备项将导致硬失败 |
| 服务端 ALPN 响应是否可捕获 | 可通过 tls.Conn.ConnectionState().NegotiatedProtocol 验证 |
graph TD
A[ForceAttemptHTTP2=true] --> B{TLS NextProtos 包含 h2?}
B -->|否| C[panic: no h2 in NextProtos]
B -->|是| D[发起 TLS 握手]
D --> E{ALPN 返回 h2?}
E -->|是| F[使用 HTTP/2]
E -->|否| G[尝试回退 HTTP/1.1<br>→ 仅当 NextProtos 含 http/1.1]
3.3 Transport.TLSNextProto:协议协商钩子与gRPC-Web等混合协议场景的拦截式适配实践
Transport.TLSNextProto 是 Go http.Server 中用于在 TLS 握手后、HTTP 流量分发前动态选择协议处理器的关键钩子,天然适配 HTTP/2、h2c、gRPC-Web 等多协议共存场景。
协议协商时机与控制权移交
- 在
TLS handshake completed → ALPN 协商完成 → 请求头未解析前触发 - 返回
http.Handler实例,完全接管后续请求处理逻辑 - 可基于
ALPN protocol name(如"h2"、"h2c"、"grpc-web")路由至不同协议栈
gRPC-Web 透明桥接示例
server := &http.Server{
Addr: ":8080",
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler) {
"h2": grpcServer.ServeHTTP, // 原生 gRPC over HTTP/2
"grpc-web": func(s *http.Server, c *tls.Conn, h http.Handler) {
// 将 gRPC-Web JSON/protobuf 请求转换为 gRPC 内部格式
proxy := grpcweb.WrapHandler(grpcServer)
proxy.ServeHTTP(&responseWriter{c}, &request{c})
},
},
}
此处
grpcweb.WrapHandler将Content-Type: application/grpc-web+proto请求解包并转发至 gRPC Server;responseWriter和request需桥接tls.Conn与http.ResponseWriter/Request接口,实现零拷贝流式透传。
协议路由决策矩阵
| ALPN 协议名 | 目标 Handler | 是否需消息转换 |
|---|---|---|
h2 |
grpcServer.ServeHTTP |
否 |
grpc-web |
grpcweb.WrapHandler |
是(JSON ↔ proto) |
http/1.1 |
http.DefaultServeMux |
否(降级为 REST) |
graph TD
A[TLS Handshake] --> B[ALPN Negotiation]
B --> C{ALPN Protocol?}
C -->|h2| D[gRPC Handler]
C -->|grpc-web| E[grpc-web Wrapper]
C -->|http/1.1| F[REST Handler]
第四章:超时控制与请求生命周期精细化管理
4.1 Client.Timeout:整请求生命周期超时的语义陷阱与Context.WithTimeout的协同使用规范
http.Client.Timeout 表示整个请求从开始到响应体读取完成的总耗时上限,但易被误认为仅控制连接或首字节等待。它不中断已建立连接上的流式读取(如大文件下载),且无法与 context.Context 取消信号联动。
语义冲突场景
Client.Timeout = 5s+io.Copy读取 10MB 响应 → 超时后 goroutine 泄漏- 同时设置
Client.Timeout和context.WithTimeout→ 可能触发双重取消,但行为不可预测
推荐协同模式
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
// 显式禁用 Client 级超时,交由 Context 统一管控
client := &http.Client{
Transport: http.DefaultTransport,
// Timeout: 0 // ← 必须设为 0 或未设置
}
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := client.Do(req) // 超时由 ctx 控制,支持中断读取
✅
context.WithTimeout可中断 DNS 解析、TLS 握手、连接建立、header 读取及 body 流读取;
❌Client.Timeout仅终止阻塞在RoundTrip内部状态机,对resp.Body.Read()无效。
| 维度 | Client.Timeout |
context.WithTimeout |
|---|---|---|
| 控制粒度 | 整个 Do() 生命周期 |
全链路(含 DNS/TLS/Read) |
| 可中断流读取? | 否 | 是(需 http.Request 绑定) |
与 CancelFunc 协同 |
不支持 | 原生支持 |
4.2 Transport.ResponseHeaderTimeout:Header接收阶段超时与CDN/网关头部延迟的可观测性埋点实践
ResponseHeaderTimeout 控制客户端等待响应首部(Status Line + Headers)完成的最大时长,是诊断 CDN 缓存穿透、WAF 插入延迟、边缘网关重写耗时的关键观测窗口。
埋点注入示例(Go HTTP Client)
transport := &http.Transport{
ResponseHeaderTimeout: 3 * time.Second,
// 注入可观测性钩子
RoundTrip: otelhttp.NewTransport(http.DefaultTransport).RoundTrip,
}
该配置使任何超过 3s 才返回 HTTP/1.1 200 OK 及后续 headers 的请求被标记为 header_timeout 异常;配合 OpenTelemetry,自动捕获 http.response.header_delay_ms 自定义指标。
常见延迟来源归因表
| 组件 | 典型延迟表现 | 推荐埋点字段 |
|---|---|---|
| CDN 缓存未命中 | 首字节前 Header 滞留 800ms+ | cdn.cache_status, cdn.edge_rt |
| API 网关鉴权 | 多级 JWT 解析+策略匹配 | gateway.auth_latency_ms |
| TLS 1.3 0-RTT 重协商 | ServerHello 后 Header 延迟 | tls.early_data_rejected |
调用链路关键节点
graph TD
A[Client] -->|Start timer| B[DNS+TCP+TLS]
B --> C[Send Request]
C --> D[Wait for Status+Headers]
D -->|Timeout?| E[Fire header_timeout event]
D -->|Success| F[Proceed to body read]
4.3 Transport.ReadHeaderTimeout:Header读取超时与Keep-Alive连接复用率的关联性压测分析
ReadHeaderTimeout 控制服务器在收到请求首行后,等待完整 HTTP 头部到达的最大时长。该值过短会中断慢客户端(如弱网移动端)的合法请求;过长则阻塞连接池中空闲连接,降低 Keep-Alive 复用率。
压测关键观察维度
- 并发连接数 vs.
ActiveConn持久连接存活率 ReadHeaderTimeout=2s与5s下的http_connections_reused_total指标差异- TLS 握手延迟叠加头部解析耗时对超时触发的影响
Go HTTP Server 配置示例
server := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: 3 * time.Second, // ⚠️ 此值需 ≤ 客户端最大头部生成延迟
IdleTimeout: 30 * time.Second,
Handler: handler,
}
逻辑分析:ReadHeaderTimeout 独立于 ReadTimeout,仅约束“首行→\r\n\r\n”区间;若客户端分片发送 header(如某些 IoT 设备),3s 可能触发 http: read header timeout,强制关闭连接,导致复用率下降 18%~32%(见下表)。
| ReadHeaderTimeout | Avg. Keep-Alive Reuse Rate | 5xx Header Timeout Rate |
|---|---|---|
| 1s | 41.2% | 12.7% |
| 3s | 76.5% | 0.9% |
| 10s | 78.3% | 0.1% |
超时判定流程
graph TD
A[收到请求首行] --> B{等待完整 header?}
B -->|Yes| C[启动 ReadHeaderTimeout 计时器]
B -->|No| D[立即返回 400 Bad Request]
C --> E{计时器超时?}
E -->|Yes| F[关闭连接,不计入 keep-alive]
E -->|No| G[解析 header,进入 body 读取阶段]
4.4 Transport.WriteTimeout:请求体写入超时与流式上传场景下TCP窗口阻塞的抓包定位实践
在流式上传(如分块视频上传、大文件PUT)中,Transport.WriteTimeout 并非控制整个请求耗时,而是限定单次底层 Write() 调用阻塞上限——当 TCP发送缓冲区满且对端接收窗口为0时,Write() 会阻塞直至超时。
抓包定位关键特征
- Wireshark 中观察到连续
ACK包携带win=0(零窗口通告) - 后续出现
TCP ZeroWindowProbe探测包 - 应用层
WriteTimeout触发前,send()系统调用已阻塞超时值
典型超时配置示例
tr := &http.Transport{
WriteTimeout: 30 * time.Second, // ⚠️ 注意:仅作用于单次Write,非总上传时间
}
此设置无法缓解接收端处理缓慢导致的窗口关闭;需配合服务端
SO_RCVBUF调优与应用层流控(如限速写入+背压通知)。
TCP窗口阻塞诊断流程
graph TD
A[客户端持续Write] --> B{TCP发送缓冲区满?}
B -->|是| C[等待对端ACK+新窗口]
C --> D{对端通告win=0?}
D -->|是| E[触发ZeroWindowProbe]
D -->|否| F[正常发送]
E --> G[WriteTimeout计时器到期→io.ErrDeadlineExceeded]
| 现象 | 根本原因 | 建议动作 |
|---|---|---|
WriteTimeout 频繁触发 |
服务端读取慢/未及时Read() |
检查服务端IO模型与缓冲区大小 |
抓包见大量win=0 |
接收端应用层积压或GC停顿 | 监控服务端goroutine数与GC频率 |
第五章:总结与高并发HTTP客户端工程化演进路径
工程化演进的四个典型阶段
在某千万级日活电商中台项目中,HTTP客户端经历了从裸调用到平台级治理的完整闭环:初期使用new OkHttpClient()硬编码配置,导致超时策略混乱、连接池泄漏频发;第二阶段引入Spring Boot Starter封装,统一注入RestTemplate Bean并绑定@ConfigurationProperties;第三阶段基于OkHttp拦截器链构建可观测性基础设施——添加TraceIdInterceptor、MetricsInterceptor与RetryableExceptionInterceptor;最终落地自研HttpInvoker SDK,支持动态路由、熔断降级与灰度流量染色。该演进非线性叠加,各阶段共存于不同业务域。
关键指标驱动的配置治理
下表为压测环境(4核8G容器 × 12节点)下不同配置组合的吞吐量对比:
| 连接池最大空闲数 | 每路由最大连接数 | 默认超时(ms) | QPS(95%延迟≤200ms) |
|---|---|---|---|
| 5 | 5 | 3000 | 1,842 |
| 20 | 20 | 5000 | 3,917 |
| 50 | 50 | 2000 | 5,261 |
| 100 | 100 | 1500 | 5,304(连接竞争上升) |
实测表明:当单机QPS突破4000后,需启用连接预热机制(connectionPool.preWarm(10, "https://api.example.com")),否则首请求延迟突增300ms+。
生产故障归因分析
2023年Q4一次大规模超时事件根因如下:
OkHttpClient实例被多个Spring Bean共享,Dispatcher.maxRequestsPerHost未隔离;- 重试逻辑误将
IOException与HttpException混同处理,对503响应执行3次指数退避; - 日志中
"Failed to parse JSON"高频出现,实际是Gzip解压失败(服务端返回Content-Encoding: gzip但客户端未注册GzipInterceptor)。
修复后上线灰度版本,通过-Dokhttp3.internal.platform=AndroidPlatform强制禁用TLS 1.3(规避某安卓内核SSL握手缺陷),72小时内故障率下降98.6%。
架构决策树
graph TD
A[请求发起] --> B{是否跨域调用?}
B -->|是| C[走Service Mesh Sidecar]
B -->|否| D{QPS是否>2000?}
D -->|是| E[启用连接池分片:按域名哈希分配独立ConnectionPool]
D -->|否| F[复用全局OkHttpClient]
E --> G[配置per-host maxIdleConnections=30]
F --> H[启用ConnectionPool.shared]
灰度发布验证清单
- ✅ 使用
X-Env: staging头标识流量,验证下游服务能否正确识别; - ✅ 对比
/actuator/httpclient/metrics端点中pool.active.connection.count在灰度组与基线组的分布差异; - ✅ 抓包确认TLS握手耗时稳定在120±15ms(OpenSSL 3.0.7实测值);
- ✅ 注入
curl -v --connect-timeout 1 --max-time 3 https://api.example.com/health模拟弱网场景,验证快速失败机制。
安全加固实践
在金融类子系统中,强制要求所有HTTPS调用启用证书钉扎(Certificate Pinning):
val certificatePinner = CertificatePinner.Builder()
.add("api.bank.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.bank.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
同时配合NetworkSecurityConfig在Android端禁用明文HTTP,避免中间人劫持风险。
