Interceptors That Actually Help: Request Logging and Automatic Bearer-Token Injection
Interceptors That Actually Help: Request Logging and Automatic Bearer-Token Injection
真正有用的拦截器:请求日志记录与自动 Bearer Token 注入
Cross-cutting HTTP concerns — logging, authentication headers, request IDs, metrics — should never be hand-edited into every request. JQuickCurl rides on OkHttp’s mature interceptor pipeline, and because interceptors are wired into the global configuration, a single registration upgrades every curl command in your application. In this post: What an interceptor sees and can do. The built-in JLoggingInterceptor. Writing a request/response logging interceptor. Auto-injecting a Bearer token from your secret store.
HTTP 的横切关注点(如日志记录、认证头、请求 ID、指标监控等)绝不应该手动添加到每一个请求中。JQuickCurl 基于 OkHttp 成熟的拦截器管道构建,由于拦截器被挂载到全局配置中,只需注册一次,即可升级应用中的所有 curl 命令。本文将涵盖:拦截器的作用与能力、内置的 JLoggingInterceptor、编写自定义请求/响应日志拦截器,以及从密钥存储中自动注入 Bearer Token。
The Interceptor Model in One Paragraph
一句话理解拦截器模型
An OkHttp Interceptor wraps a call: it receives the outgoing Request, may modify it, calls chain.proceed(...), and then sees the returned Response. Registering interceptors happens on the global config, so both annotation-mode and XML-mode commands pass through them.
OkHttp 拦截器包装了一个调用:它接收外发的 Request,可以对其进行修改,调用 chain.proceed(...),然后获取返回的 Response。拦截器的注册是在全局配置中完成的,因此无论是注解模式还是 XML 模式的命令都会经过它们。
Interceptor myInterceptor = chain -> {
Request request = chain.request(); // 外发请求
Response response = chain.proceed(request); // 执行并获取响应
return response;
};
JQuickCurlConfig.getInstance().addInterceptor(myInterceptor);
addNetworkInterceptor(...) registers at the network layer (after redirects/retries) when you need to observe the real wire traffic.
当你需要观察真实的底层网络流量时,可以使用 addNetworkInterceptor(...) 在网络层(重定向/重试之后)进行注册。
Built-In Logging: JLoggingInterceptor
内置日志记录:JLoggingInterceptor
Out of the box, the config already registers a JLoggingInterceptor at level ALL — it measures each call and reports the elapsed time (and failures) through its console logger. If you want a quieter default, the same interceptor can be constructed with an explicit level:
开箱即用,配置中已经注册了一个级别为 ALL 的 JLoggingInterceptor —— 它会测量每次调用,并通过控制台记录器报告耗时(及失败情况)。如果你希望默认输出更简洁,可以在构造该拦截器时显式指定级别:
public enum JCurlLevelLog { NONE, BASIC, HEADERS, ALL }
import com.github.paohaijiao.enums.JCurlLevelLog;
import com.github.paohaijiao.interceptor.JLoggingInterceptor;
import com.github.paohaijiao.config.JQuickCurlConfig;
JQuickCurlConfig.getInstance()
.addInterceptor(new JLoggingInterceptor(JCurlLevelLog.BASIC));
Expect output in the spirit of: the request cost : 132 ms. Choose the noisiest level in development, BASIC in staging, and keep an eye on token redaction (see below).
输出格式类似于:the request cost : 132 ms。建议在开发环境使用最详细的级别,在预发布环境使用 BASIC,并注意对 Token 进行脱敏处理(见下文)。
Writing a Purpose-Built Logging Interceptor
编写自定义日志拦截器
Build one that logs method, URL, status, and duration — and be deliberate about what you don’t log (authorization headers!).
编写一个记录方法、URL、状态码和耗时的拦截器,并谨慎处理不应记录的内容(如 Authorization 请求头!)。
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class CallLogInterceptor implements Interceptor {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CallLogInterceptor.class);
@Override
public Response intercept(Chain chain) throws java.io.IOException {
Request request = chain.request();
long start = System.nanoTime();
Response response = chain.proceed(request);
long tookMs = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(
System.nanoTime() - start);
String bodyPreview = "";
if (response.body() != null) {
bodyPreview = safePreview(response.peekBody(256).string());
}
log.info("[http] {} {} -> {} in {} ms body={}",
request.method(), request.url(), response.code(), tookMs, bodyPreview);
return response;
}
private String safePreview(String s) {
return s == null ? "" : s.replace('\n', ' ').substring(
0, Math.min(120, s.length()));
}
}
Register it next to the built-in one: 将其与内置拦截器一起注册:
JQuickCurlConfig.getInstance().addInterceptor(new CallLogInterceptor());
Automatic Bearer-Token Injection
自动 Bearer Token 注入
Tokens expire, rotate, and come from secret managers — they don’t belong in @JCurlCommand strings. One interceptor can stamp every outgoing request with the current token:
Token 会过期、轮换,且通常来自密钥管理器——它们不应该硬编码在 @JCurlCommand 字符串中。通过一个拦截器,可以为每个外发请求自动添加当前 Token:
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class BearerAuthInterceptor implements Interceptor {
private final java.util.function.Supplier<String> tokenSupplier;
public BearerAuthInterceptor(java.util.function.Supplier<String> tokenSupplier) {
this.tokenSupplier = tokenSupplier;
}
@Override
public Response intercept(Chain chain) throws java.io.IOException {
String token = tokenSupplier.get();
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}
Because the token is fetched per request via a Supplier, rotating tokens are picked up automatically — no restart, no stale header. Wire it with your vault/environment source:
由于 Token 是通过 Supplier 在每次请求时获取的,因此轮换后的 Token 会被自动捕获——无需重启,也不会出现过期的请求头。将其与你的密钥库或环境变量源连接起来:
import com.github.paohaijiao.config.JQuickCurlConfig;
JQuickCurlConfig.getInstance().addInterceptor(
new BearerAuthInterceptor(() -> System.getenv("API_TOKEN")));
Now a plain command stays clean: 现在,简单的命令保持整洁:
@JCurlCommand("curl -X GET 'https://api.example.com/me'")
String me(JQuickCurlReq request); // Authorization stamped by interceptor
The same trick works for API keys, X-Correlation-Id, tenant headers, and basic-auth pairs (Post 15).
同样的技巧也适用于 API Key、X-Correlation-Id、租户请求头以及 Basic-Auth 认证对(详见第 15 篇博文)。
Runnable Demo
可运行的演示
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.config.JQuickCurlConfig;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public interface InspectApi {
@JCurlCommand("curl -X GET 'https://httpbin.org/headers'")
String headers(JQuickCurlReq request);
}
class InterceptorDemo {
public static void main(String[] args) {
JQuickCurlConfig.getInstance().addInterceptor(chain -> {
okhttp3.Request r = chain.request().newBuilder()
.addHeader("Authorization", "Bearer demo-token-123")
.build();
return chain.proceed(r);
});
String echo = JCurlInvoker.createProxy(InspectApi.class)
.headers(new JQuickCurlReq());
System.out.println(echo); // "Authorization": "Bearer demo-token-123"
}
}
Ordering and Pitfalls
顺序与陷阱
- Interceptors run in registration order (application interceptors before network interceptors). 拦截器按注册顺序运行(应用拦截器先于网络拦截器)。
- Headers you add in an interceptor are applied on top of whatever the curl string specified. 你在拦截器中添加的请求头会叠加在 curl 字符串指定的请求头之上。
- Never log or stash raw Authorization values — hash or redact in logs. 切勿记录或存储原始的 Authorization 值——请在日志中进行哈希或脱敏处理。
- Keep interceptors fast; they run on the request thread. 保持拦截器高效;它们运行在请求线程中。
Summary
总结
Interceptors are the clean seam for concerns that cut across every HTTP call. JQuickCurl comes with timing-oriented logging out of the box and…
拦截器是处理跨越所有 HTTP 调用关注点的最佳切入点。JQuickCurl 开箱即用,提供了基于耗时的日志记录功能,并且……