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

Azure Cosmos DB HTTP 400错误求助:Retrofit连接失败排查

排查Azure Cosmos DB REST API 400错误的几个关键点

嘿,连续3天卡这个问题确实闹心,我帮你梳理下Cosmos DB REST API返回400(错误请求)的常见坑,结合你用的Retrofit+Gson场景来分析:

一、先盯紧认证头——这是400的重灾区

Cosmos DB的Authorization头生成规则特别严格,差一点都不行,你可以对照这几点检查:

  • 时间戳必须精准:必须是RFC1123格式(比如Fri, 05 Jul 2024 12:34:56 GMT),而且和Cosmos DB服务器时间差不能超过15分钟,否则直接拒。生成的时候一定要用英文Locale,别用中文的,不然月份会变成“七月”这种,签名直接无效。
  • 资源路径/类型不能错:
    • resourceId要对应你操作的资源层级,比如操作容器里的文档,就是dbs/你的数据库ID/colls/你的容器ID;如果是操作数据库本身,就是dbs/你的数据库ID。
    • resourceType要匹配操作类型:比如文档是docs,容器是colls,数据库是dbs。
  • 签名的字符串要完整:签名的原始字符串必须包含HTTP方法(小写)、Content-Type、时间戳、资源URI、resourceType,顺序不能乱,格式要和官方要求一致,少个换行或者空格都会失败。
  • 密钥别搞混:要用主密钥或者只读密钥,复制的时候别漏了末尾的等号,也别把密钥里的特殊字符转义错了。

二、Gson序列化是否符合Cosmos DB要求

Cosmos DB对请求体格式要求很严,Gson的配置容易踩坑:

  • 必填字段不能少:比如创建文档时必须有id字段,而且id不能包含特殊字符(比如/、\、?、#等)。
  • 日期格式要正确:必须是ISO 8601格式(yyyy-MM-dd'T'HH:mm:ss.SSS'Z'),Gson默认的日期序列化可能不符合,建议手动指定:
    Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
            .create();
    
  • 别序列化多余字段:如果你的实体类里有Cosmos DB不认可的字段,可能会被拒,要么用@Expose注解控制,要么用自定义序列化器。

三、Retrofit的配置有没有遗漏

  • 必须加的请求头不能忘:
    • x-ms-version:指定API版本,比如2023-03-15,这个没设置肯定400。
    • Content-Type:必须是application/json,别写错成别的类型。
  • 动态头要用拦截器:x-ms-date和Authorization是动态生成的,不能用静态的@Headers注解,得用OkHttp拦截器来添加,不然每次请求的时间戳都是固定的,很快就过期了。

四、调试技巧:把请求打出来看看

建议你给Retrofit加个日志拦截器,把完整的请求头、请求体都打印出来,和官方示例对比:

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
        .build();

然后把打印出来的请求拿到Postman里手动发一遍,如果Postman能成功,那问题肯定在代码的请求构造上;如果Postman也失败,那就是资源配置或者密钥的问题。

附一个可参考的认证拦截器示例

我给你写个简单的拦截器模板,你可以根据自己的需求调整:

public class CosmosAuthInterceptor implements Interceptor {
    private final String masterKey;
    private final String resourceType;
    private final String resourceId;

    public CosmosAuthInterceptor(String masterKey, String resourceType, String resourceId) {
        this.masterKey = masterKey;
        this.resourceType = resourceType;
        this.resourceId = resourceId;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        String verb = original.method().toLowerCase();
        String date = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US).format(new Date());
        String contentType = original.header("Content-Type") != null ? original.header("Content-Type") : "application/json";
        
        // 构造签名原始字符串,顺序不能乱
        String stringToSign = String.format("%s\n%s\n%s\n%s\n%s\n",
                verb, contentType, date, original.url().encodedPath(), resourceType.toLowerCase());
        
        // 生成签名
        String signature = generateSignature(stringToSign, masterKey);
        String authHeader = String.format("type=master&ver=1.0&sig=%s", signature);

        // 构建新请求
        Request modified = original.newBuilder()
                .header("x-ms-date", date)
                .header("x-ms-version", "2023-03-15")
                .header("Authorization", authHeader)
                .header("Content-Type", contentType)
                .build();

        return chain.proceed(modified);
    }

    private String generateSignature(String stringToSign, String masterKey) {
        try {
            byte[] keyBytes = Base64.decode(masterKey, Base64.DEFAULT);
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(keyBytes, "HmacSHA256"));
            byte[] hash = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            return URLEncoder.encode(Base64.encodeToString(hash, Base64.NO_WRAP), StandardCharsets.UTF_8.name());
        } catch (Exception e) {
            throw new RuntimeException("生成签名失败", e);
        }
    }
}

使用的时候把拦截器加到OkHttpClient里,注意根据你的操作类型传正确的resourceType和resourceId。

你可以先按这些点排查,应该能找到问题所在!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:30:52