You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apollo Server安卓客户端Header传递及x-api-key丢失问题求助

Apollo Gateway自定义Header丢失导致401错误排查

我在Apollo Server(Lambda环境)中使用自定义Header参数时遇到问题:通过端口3000的GraphQL Playground调用CONFIGURATIONSERVICE子图时,请求中缺少代码里添加的x-api-key Header,触发401授权错误。以下是我的代码:

import { ApolloServer } from 'apollo-server-lambda'
import { ApolloGateway, IntrospectAndCompose, GraphQLDataSourceProcessOptions, RemoteGraphQLDataSource } from '@apollo/gateway'
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core'
import { GraphQLRequest } from 'apollo-server-types'
import { SignatureV4 } from '@aws-sdk/signature-v4'
import { Sha256 } from '@aws-crypto/sha256-js'
import { OutgoingHttpHeader } from 'http'
import { defaultProvider } from '@aws-sdk/credential-provider-node'
import { HttpRequest } from '@aws-sdk/protocol-http'

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  /**
   * Adds the necessary IAM Authorization headers for AppSync requests
   * @param request The request to Authorize
   * @returns The headers to pass through to the request
   */
  private async getAWSCustomHeaders(request: GraphQLRequest): Promise<{
    [key: string]: OutgoingHttpHeader | undefined
  }> {
    const { http, ...requestWithoutHttp } = request

    if (!http) return {}

    const url = new URL(http.url)

    //check local env
    if(url.host.match(/localhost:20002/)) return {'x-api-key':'da2-fakeApiId123456'}

    // If the graph service is not AppSync, we should not sign these request.
    if (!url.host.match(/appsync-api/)) return {}

    const httpRequest = new HttpRequest({
      hostname: url.hostname,
      path: url.pathname,
      method: http.method,
      headers: {
        Host: url.host,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(requestWithoutHttp),
    })

    const signedRequest = await new SignatureV4({
      region: 'eu-west-1',
      credentials: defaultProvider(),
      service: 'appsync',
      sha256: Sha256,
    }).sign(httpRequest)

    return signedRequest.headers || {}
  }

  /**
   * Customize the request to AppSync
   * @param options The options to send with the request
   */
  public async willSendRequest({ request, context }: GraphQLDataSourceProcessOptions) {
    const customHeaders = await this.getAWSCustomHeaders(request)

    if (customHeaders) {
      Object.keys(customHeaders).forEach((h) => {
        request.http?.headers.set(h, customHeaders[h] as string)
      })
    }

    // context not available when introspecting
    if (context.event) 
      Object.keys(context.event.requestContext.authorizer.lambda).forEach((h) => {
        request.http?.headers.set(h, context.event.requestContext.authorizer.lambda[h] as string)
      })
  }
}

const server = new ApolloServer({
  gateway: new ApolloGateway({
    buildService({ url }) {
      return new AuthenticatedDataSource({ url })
    },
    supergraphSdl: new IntrospectAndCompose({
      subgraphs: [
        { name: 'CONFIGURATIONSERVICE', url: process.env.CONFIGURATION_SERVICE_API_URL }
      ]
    })
  }),
  debug: true,
  context: ({ event, context, express}) => ({
    headers: event.headers,
    functionName: context.functionName,
    event,
    context,
    expressRequest: express.req,
  }),
  introspection: true,
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
})

exports.handler = server.createHandler({
  expressGetMiddlewareOptions: {
    cors: {
      origin: '*',
    }
  }
})

排查方向及修复建议

  • 验证URL匹配逻辑
    确认process.env.CONFIGURATION_SERVICE_API_URL的实际值,检查url.host是否匹配localhost:20002。可以在getAWSCustomHeaders中添加日志打印当前URL的host:

    const url = new URL(http.url)
    console.log('当前子图URL Host:', url.host) // 打印用于验证
    

    如果日志显示host不是localhost:20002,说明环境变量配置有误,或者URL格式问题(比如包含路径前缀)。

  • 检查request.http是否存在
    代码中使用可选链request.http?.headers.set,如果request.http为undefined,Header无法被设置。添加判断和日志:

    public async willSendRequest({ request, context }: GraphQLDataSourceProcessOptions) {
      const customHeaders = await this.getAWSCustomHeaders(request)
    
      if (!request.http) {
        console.warn('请求对象无http属性,无法设置Header')
        return
      }
    
      if (customHeaders) {
        Object.keys(customHeaders).forEach((h) => {
          request.http.headers.set(h, customHeaders[h] as string)
        })
      }
      // 其余逻辑...
    }
    
  • 临时跳过分支验证
    临时修改getAWSCustomHeaders,直接返回x-api-key,测试是否能正常携带:

    private async getAWSCustomHeaders(request: GraphQLRequest): Promise<{
      [key: string]: OutgoingHttpHeader | undefined
    }> {
      // 临时注释原有逻辑,强制返回Header
      return {'x-api-key':'da2-fakeApiId123456'}
      // 原有逻辑...
    }
    

    如果此时请求成功,说明问题出在URL匹配的分支判断,需重新调整匹配规则(比如使用url.hostname或包含端口的完整匹配)。

  • 确认Header大小写一致性
    虽然HTTP Header不区分大小写,但部分服务可能强制要求小写的x-api-key。确保代码中设置的键名为小写,且子图服务能正确解析该Header。

内容的提问来源于stack exchange,提问作者Onur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:40:17