Under-Utils
stable 1.0.2 1.0.3-SNAPSHOT
library

HTTP

under-utils-http

HTTP 便捷调用与 OpenAPI 客户端治理,包括 token、签名、重试和错误解码。

依赖

under-utils-http Maven 坐标
<dependency>
    <groupId>io.github.yexianglun-d</groupId>
    <artifactId>under-utils-http</artifactId>
    <version>1.0.2</version>
</dependency>

适用场景

第三方 OpenAPI 调用、幂等 header 和业务错误治理。

能力边界

文档来源 under-utils-http/README.md
API 入口 查看 HTTP 相关 API
设计约束 保留失败语义、配置 key 和 public API 兼容性说明,不以营销描述替代真实边界。

README 同步内容

来源:under-utils-http/README.md,构建时自动读取并渲染。

基于 OkHttp 的 HTTP 调用和 OpenAPI 客户端治理模块。

模块分为两层:

  • HttpRequestHttpResponseHttpUtils:直接 HTTP 调用。
  • OpenApiClient:封装开放平台常见治理能力,例如 token 注入、签名、trace header、幂等 key、业务错误解码和重试决策。
  • RefreshingAccessTokenProvider:封装 access token 本地缓存、提前刷新和并发收敛。

依赖

<dependency>
    <groupId>io.github.yexianglun-d</groupId>
    <artifactId>under-utils-http</artifactId>
    <version>1.0.2</version>
</dependency>

直接 HTTP 调用

HttpResponse response = HttpRequest.builder()
        .url("https://api.example.com/users")
        .method(HttpMethod.GET)
        .timeout(5000)
        .retry(1)
        .build()
        .execute();

if (response.isSuccess()) {
    String body = response.asString();
}

常用方法可以直接从方法级快捷 builder 开始,并由 builder 直接执行:

HttpResponse response = HttpRequest.get("https://api.example.com/users")
        .header("Authorization", "Bearer " + token)
        .param("page", "1")
        .timeout(Duration.ofSeconds(5))
        .execute();

POST JSON:

HttpResponse response = HttpRequest.post("https://api.example.com/orders")
        .header("Content-Type", "application/json")
        .body(command)
        .execute();

便捷 API:

String body = HttpUtils.get("https://api.example.com/users");
String created = HttpUtils.postJson("https://api.example.com/orders", command);

全局默认配置:

HttpConfig config = HttpConfig.builder()
        .connectTimeoutDuration(Duration.ofSeconds(5))
        .readTimeoutDuration(Duration.ofSeconds(10))
        .maxRetries(1)
        .retryIntervalDuration(Duration.ofMillis(500))
        .addDefaultHeader("User-Agent", "my-service/1.0")
        .build();

HttpUtils.setDefaultConfig(config);

已有配置可以通过 toBuilder() 复制后调整,避免重复声明所有字段。

OpenAPI 客户端

当每次调用都需要统一 token、签名、幂等和业务错误处理时,使用 DefaultOpenApiClient

OpenApiClient client = new DefaultOpenApiClient(
        OpenApiClientOptions.builder()
                .connectTimeoutDuration(Duration.ofSeconds(3))
                .readTimeoutDuration(Duration.ofSeconds(8))
                .maxRetries(1)
                .retryIntervalDuration(Duration.ofMillis(300))
                .build(),
        request -> tokenService.getAccessToken(),
        request -> {
            request.header("X-Sign", signer.sign(request));
            request.query("timestamp", String.valueOf(System.currentTimeMillis()));
        },
        ApiErrorDecoder.httpStatus()
);

OpenApiResponse<OrderResult> response = client.execute(
        OpenApiRequest.builder()
                .url("https://open.example.com/orders")
                .method(HttpMethod.POST)
                .operationName("create-order")
                .traceId(traceId)
                .idempotencyKey(requestNo)
                .body(command)
                .build(),
        OrderResult.class
);

token 有有效期时,可以使用 RefreshingAccessTokenProvider

AccessTokenProvider tokenProvider = new RefreshingAccessTokenProvider(
        request -> {
            TokenResponse token = tokenGateway.refreshToken();
            return RefreshingAccessTokenProvider.AccessToken.of(
                    token.accessToken(),
                    token.expiresAt()
            );
        },
        Duration.ofSeconds(30)
);

refreshAhead 表示到期前多久开始刷新。该实现只负责当前 JVM 内的 token 缓存;多实例共享、分布式锁、持久化和上游刷新失败兜底应由业务项目在 TokenFetcher 中处理。

OpenApiResponse 会区分 HTTP 响应和治理层结果:

  • success 是经过 HTTP 状态和业务错误解码后的结果。
  • statusCode 是 HTTP 状态码;没有拿到响应时为 0
  • errorCodeerrorMessageretryable 来自配置的 ApiErrorDecoder

业务错误解码

很多开放平台会用 HTTP 200 返回业务错误码。可以通过 ApiErrorDecoder 把规则从业务方法中移出。

ApiErrorDecoder decoder = (request, httpResponse) -> {
    if (!httpResponse.isSuccess()) {
        return ApiErrorDecoder.httpStatus().decode(request, httpResponse);
    }

    Map<String, Object> body = JsonUtils.fromJson(
            httpResponse.asString(),
            new TypeReference<Map<String, Object>>() {
            }
    );
    String code = String.valueOf(body.get("code"));
    if ("OK".equals(code)) {
        return ApiErrorDecodeResult.success();
    }
    return ApiErrorDecodeResult.failure(
            code,
            String.valueOf(body.get("message")),
            "BUSY".equals(code)
    );
};

只有解码结果标记为 retryable,并且还有重试预算时,客户端才会重试。 同步客户端默认不会在重试前 sleep,即使设置了 retryInterval 也只会立即重试,避免占用 Web 请求线程。 如确需当前线程等待,可显式设置 blockingRetryDelayEnabled(true);生产级退避、熔断和异步调度建议放在应用侧客户端治理层。

异常

直接 HTTP API 会针对网络和超时失败抛出模块异常:

try {
    String body = HttpUtils.get("https://api.example.com/users");
} catch (HttpTimeoutException e) {
    // timeout
} catch (HttpNetworkException e) {
    // network failure
} catch (HttpException e) {
    // other HTTP module failure
}

注意事项

  • 生产环境不要关闭 SSL 校验。
  • 非幂等请求应关闭或谨慎配置重试。
  • 超时时间应按上游接口特点配置,全局默认只作为基线。
  • 当 token、签名、幂等、trace 和业务错误处理在多个 service 方法中重复出现时,优先使用 OpenApiClient