OkHttp 如何记录请求体

OkHttp how to log request body

我正在使用拦截器,我想记录我正在发出的请求的正文,但我看不到任何执行此操作的方法。

可能吗?

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@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] 

尼古拉的回答对我不起作用。我的猜测是 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() 的文档:

Removes all bytes from this, decodes them as UTF-8, and returns the string.

这应该是你想要的。

我试图评论@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";
        }
}

编辑

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

请注意,此代码使用了 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;
}
}

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

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 "";
}

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

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

这样您就无法为不同的 HTTP 方法精细地配置输出,但它也适用于可能具有 body.

的其他方法

这里是一个显示 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)

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

The logs generated by this interceptor when using the HEADERS or BODY levels have the potential to leak sensitive information such as "Authorization" or "Cookie" headers and the contents of request and response bodies. This data should only be logged in a controlled way or in a non-production environment.

You can redact headers that may contain sensitive information by calling redactHeader().

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

创建一个单独的新 class 并实施拦截器。

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

yourOkHttpClient.addInterceptor(yourInstance)

获取请求体

            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.
    }
}

Kotlin 版本:

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

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

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