安全调用内部微服务需设base_uri指向服务发现地址、显式配置timeout和max_retries、封装认证头、校验响应状态码并注册为独立服务。

直接用 HttpClient::create() 调内部微服务,大概率会出问题——超时、认证失败、重定向失控、甚至绕过网关安全策略。安全调用的前提不是“能发出去”,而是“在服务边界内可控地发出去”。
必须设 base_uri 且指向服务发现地址
硬编码 http://localhost:8001 或 http://user-service(没带命名空间)是常见错误。Kubernetes 或 Consul 环境下,实际地址是 http://user-service.namespace.svc.cluster.local 这类 DNS 名;本地开发可用 symfony.local 域名配合 /etc/hosts 模拟。
- 不设
base_uri:每个请求都得拼完整 URL,容易漏写协议或端口,也难统一治理 - 设成
http://user-service(无域名后缀):在 Kubernetes 集群内解析失败,DNS 查不到 - 生产环境禁止用
127.0.0.1或localhost:容器间网络不走 loopback,必然连不上
timeout 和 max_retries 必须显式配置
默认 timeout 是 null(无限等待),max_retries 是 0(不重试)。内部服务短暂抖动时,客户端会卡死或立即失败,既影响用户体验,又掩盖真实故障点。
-
timeout建议设为3.0~5.0(秒),比下游服务自身超时短 1~2 秒,留出处理余量 -
max_retries设为2或3,配合指数退避(retry_delay默认已启用) - 不要依赖
retry_failed全局开关:它只重试连接失败,不重试 5xx 响应;业务级重试应由客户端逻辑控制
认证头必须封装进客户端,不能每次手动加
内部服务通常要求 Authorization: Bearer xxx 或 Authorization: Basic xxx。如果在每个 $client->request() 调用里重复写 'headers' => ['Authorization' => ...],极易遗漏或写错。
- 用
withOptions()创建专用客户端实例:$userClient = $client->withOptions(['auth_bearer' => $token]) - Token 来源必须可信:从 Vault、K8s Secret 注入,或通过
SecurityTokenStorageInterface获取当前上下文 token(仅限可信内部调用) - 禁止把 token 存在配置文件或环境变量明文里:哪怕只在 dev 环境也不行
响应处理必须校验状态码,不能只看 getStatusCode()
$response->getStatusCode() === 200 不等于“调用成功”。内部 API 可能返回 200 OK 但 body 里是 {"error": "rate_limited"},也可能返回 401 Unauthorized 却没抛异常——因为 HttpClient 默认不基于状态码抛异常。
- 调用后立刻用
$response->getHeaders()检查X-Request-ID是否存在,用于链路追踪对齐 - 用
$response->toArray()解析 JSON 前,先判断$response->getInfo('http_code')是否在 2xx 范围 - 对
401、429、503等明确语义的响应,应抛出自定义异常(如UserSyncUnavailableException),而非吞掉或转成通用TransportException
最常被忽略的一点:所有内部 HTTP 客户端都该注册为独立服务,并打上 app.internal_client 这类 tag,方便后续做统一监控、熔断或替换底层实现。别让 HttpClientInterface 实例散落在控制器或服务里——那已经不是配置问题,是架构失控的开始。











