OkHttp如何记录请求正文

Posted

技术标签:

【中文标题】OkHttp如何记录请求正文【英文标题】:OkHttp how to log request body 【发布时间】:2015-04-26 03:28:44 【问题描述】:

我正在使用拦截器,我想记录我正在发出的请求的正文,但我看不到任何这样做的方法。

有可能吗?

public class LoggingInterceptor implements Interceptor 
    @Override
    public Response intercept(Chain chain) throws IOException 
        Request request = chain.request();

        long t1 = System.nanoTime();
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();

        double time = (t2 - t1) / 1e6d;

        if (request.method().equals("GET")) 
            Logs.info(String.format("GET " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), response.code(), response.headers(), response.body().charStream()));
         else if (request.method().equals("POST")) 
            Logs.info(String.format("POST " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body(), response.code(), response.headers(), response.body().charStream()));
         else if (request.method().equals("PUT")) 
            Logs.info(String.format("PUT " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body().toString(), response.code(), response.headers(), response.body().charStream()));
         else if (request.method().equals("DELETE")) 
            Logs.info(String.format("DELETE " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITHOUT_BODY, request.url(), time, request.headers(), response.code(), response.headers()));
        

        return response;
    

结果:

POST  [some url] in 88,7ms
    ZoneName: touraine
    Source: android
    body: retrofit.client.OkClient$1@1df53f05 <-request.body().toString() gives me this, but I would like the content string
    Response: 500
    Date: Tue, 24 Feb 2015 10:14:22 GMT
    body: [some content] 

【问题讨论】:

【参考方案1】:

尼古拉的回答对我不起作用。我的猜测是 ByteString#toString() 的实现发生了变化。这个解决方案对我有用:

private static String bodyToString(final Request request)

    try 
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        copy.body().writeTo(buffer);
        return buffer.readUtf8();
     catch (final IOException e) 
        return "did not work";
    

来自readUtf8()的文档:

从中删除所有字节,将它们解码为 UTF-8,并返回字符串。

这应该是你想要的。

【讨论】:

@MathieudeBrito 试试response.body().string() @aga 谢谢,但这不会复制响应正文,因此当我稍后(在拦截器之后)尝试读取正文时,它告诉我 responsebody 为 null 并且已经被读取。 您为此使用了哪个“缓冲区”? writeTo() 仅对 java.nio.Buffer 未实现的 BufferedSink 对象执行。 @Graeme 使用 okio.Buffer ^ 希望看到此类示例中提到的正确导入语句【参考方案2】:

我试图评论@msung 的正确答案,但我的声誉还不够高。

这是我在将 RequestBody 设为完整请求之前所做的修改。它就像一个魅力。谢谢

private static String bodyToString(final RequestBody request)
        try 
            final RequestBody copy = request;
            final Buffer buffer = new Buffer();
            copy.writeTo(buffer);
            return buffer.readUtf8();
         
        catch (final IOException e) 
            return "did not work";
        

【讨论】:

它只是返回一组随机字符串。不工作 你有没有以任何方式序列化它? 不,我还没有对其进行序列化...您能否编辑您的答案以准确的方式完成所有工作以打印请求正文 对不起,我不记得它从内存中打印了什么,我会在我处理那个项目时添加它。 为什么我们需要创建副本?【参考方案3】:

编辑

因为我看到还有一些人对这篇文章感兴趣,所以这里是我的日志拦截器的最终版本(直到下一次改进)。我希望它能节省你们一些人的时间。

请注意,此代码使用OkHttp 2.2.0(和Retrofit 1.9.0

import com.squareup.okhttp.*;
import okio.Buffer;
import java.io.IOException;

public class LoggingInterceptor implements Interceptor 

private static final String F_BREAK = " %n";
private static final String F_URL = " %s";
private static final String F_TIME = " in %.1fms";
private static final String F_HEADERS = "%s";
private static final String F_RESPONSE = F_BREAK + "Response: %d";
private static final String F_BODY = "body: %s";

private static final String F_BREAKER = F_BREAK + "-------------------------------------------" + F_BREAK;
private static final String F_REQUEST_WITHOUT_BODY = F_URL + F_TIME + F_BREAK + F_HEADERS;
private static final String F_RESPONSE_WITHOUT_BODY = F_RESPONSE + F_BREAK + F_HEADERS + F_BREAKER;
private static final String F_REQUEST_WITH_BODY = F_URL + F_TIME + F_BREAK + F_HEADERS + F_BODY + F_BREAK;
private static final String F_RESPONSE_WITH_BODY = F_RESPONSE + F_BREAK + F_HEADERS + F_BODY + F_BREAK + F_BREAKER;

@Override
public Response intercept(Chain chain) throws IOException 
    Request request = chain.request();

    long t1 = System.nanoTime();
    Response response = chain.proceed(request);
    long t2 = System.nanoTime();

    MediaType contentType = null;
    String bodyString = null;
    if (response.body() != null) 
        contentType = response.body().contentType();
        bodyString = response.body().string();
    

    double time = (t2 - t1) / 1e6d;

    if (request.method().equals("GET")) 
        System.out.println(String.format("GET " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), response.code(), response.headers(), stringifyResponseBody(bodyString)));
     else if (request.method().equals("POST")) 
        System.out.println(String.format("POST " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), stringifyRequestBody(request), response.code(), response.headers(), stringifyResponseBody(bodyString)));
     else if (request.method().equals("PUT")) 
        System.out.println(String.format("PUT " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body().toString(), response.code(), response.headers(), stringifyResponseBody(bodyString)));
     else if (request.method().equals("DELETE")) 
        System.out.println(String.format("DELETE " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITHOUT_BODY, request.url(), time, request.headers(), response.code(), response.headers()));
    

    if (response.body() != null) 
        ResponseBody body = ResponseBody.create(contentType, bodyString);
        return response.newBuilder().body(body).build();
     else 
        return response;
    



private static String stringifyRequestBody(Request request) 
    try 
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        copy.body().writeTo(buffer);
        return buffer.readUtf8();
     catch (final IOException e) 
        return "did not work";
    


public String stringifyResponseBody(String responseBody) 
    return responseBody;


【讨论】:

如果您能够在顶部发布导入,这将非常有帮助,因为 OKHttp 有多个版本,我正在猜测并检查您这里有哪些版本。谢谢 完成,希望对您有所帮助;) 确实如此,非常感谢 Mathieu 的澄清。【参考方案4】:

使用当前版本的 OkHttp,您可以使用HTTP Logging Interceptor 并将级别设置为BODY

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BODY);

这样,您无法为不同的 HTTP 方法精细地配置输出,但它也适用于可能有正文的其他方法。

这里是一个显示PATCH 请求输出的示例(经过最少编辑):

--> PATCH https://hostname/api/something/123456 HTTP/1.1
Content-Type: application/json-patch+json; charset=utf-8
Content-Length: 49
Authorization: Basic YWRtaW46c2VjcmV0Cg==
Accept: application/json

[  "op": "add", "path": "/path", "value": true ]
--> END PATCH (xx-byte body)

如您所见,这也会打印出标题,并且正如文档所述,您真的应该小心:

此拦截器在使用HEADERSBODY 级别时生成的日志可能会泄露敏感信息,例如“授权”或“Cookie”标头以及请求和响应正文的内容。此数据只能以受控方式或在非生产环境中记录。

您可以通过调用redactHeader() 来编辑可能包含敏感信息的标题。

logging.redactHeader("Authorization");
logging.redactHeader("Cookie");

【讨论】:

如何从请求对象中只获取 [ "op": "add", "path": "/path", "value": true ]?【参考方案5】:

处理带有或不带有正文的请求的版本:

private String stringifyRequestBody(Request request) 
  if (request.body() != null) 
      try 
          final Request copy = request.newBuilder().build();
          final Buffer buffer = new Buffer();
          copy.body().writeTo(buffer);
          return buffer.readUtf8();
       catch (final IOException e) 
          Log.w(TAG, "Failed to stringify request body: " + e.getMessage());
      
  
  return "";

【讨论】:

【参考方案6】:

Kotlin 版本:

val buf = okio.Buffer()
requestBody.writeTo(buf)
Log.d("AppXMLPostReq", "reqBody = $buf.readUtf8()")

【讨论】:

【参考方案7】:

创建一个单独的新类并实现拦截器。

  override fun intercept(chain: Interceptor.Chain): Response 
        val request: Request = chain.request()
        var logInfo = ""
        val requestBody=loggerUtil.getRequestBody
         return response
    

yourOkHttpClient.addInterceptor(yourInstance)

GetRequestBody

            var requestContent = ""
            val requestBody = request.body
            val buffer = Buffer()
            if (requestBody != null) 
                requestBody.writeTo(buffer)
            

            val contentType = requestBody?.contentType()
            val charset: Charset =
                contentType?.charset(StandardCharsets.UTF_8) ?:StandardCharsets.UTF_8

            if (buffer.isProbablyUtf8()) 
                requestContent = buffer.readString(charset)
            

判断缓冲区数据是否为UT8格式的扩展

fun Buffer.isProbablyUtf8(): Boolean 
    try 
        val prefix = Buffer()
        val byteCount = size.coerceAtMost(64)
        copyTo(prefix, 0, byteCount)
        for (i in 0 until 16) 
            if (prefix.exhausted()) 
                break
            
            val codePoint = prefix.readUtf8CodePoint()
            if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) 
                return false
            
        
        return true
     catch (_: EOFException) 
        return false // Truncated UTF-8 sequence.
    

【讨论】:

【参考方案8】:

我们应该在缓冲区对象上调用 .close() 吗?还是使用try-with-resources 语句?

private String getBodyAsString(Request request) throws IOException 
        try(var buffer = new Buffer()) 
            request.body().writeTo(buffer);
            return buffer.readUtf8();
        
    

【讨论】:

以上是关于OkHttp如何记录请求正文的主要内容,如果未能解决你的问题,请参考以下文章

OKHTTP GET 和 POST 请求返回空正文消息

OkHttp 2.0 响应(POST 请求)正文字符串为空字符串

在 Android 的 OKhttp 中通过 POST 请求发送 JSON 正文

当使用 okhttp 发送 post 请求时,服务器接收到的请求正文为空。可能是啥问题?

如何使用保留请求正文和响应正文的 servlet 过滤器记录请求和响应?

如何在 Spring Webflux Java 中记录请求正文