OkHttp如何记录请求正文


75

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

可能吗 ?

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] 

Answers:


147

尼古拉的答案对我不起作用。我的猜测是执行方式已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,然后返回字符串。

这应该是您想要的。


1
@MathieudeBrito尝试response.body().string()
aga 2015年

1
@aga谢谢,但这不会复制响应正文,因此当我尝试稍后(在拦截器之后)读取正文时,它告诉我responsebody为null且已被读取。
Mathieu de Brito 2015年

4
您为此使用哪个“缓冲区”?writeTo()仅对java.nio.Buffer未实现的BufferedSink对象执行。
Graeme

5
@Graeme使用okio.Buffer
P1nGu1n

2
^希望看到此类示例中提及的正确导入语句
Timo

19

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

它只是返回一组随机字符串。不工作
Pravesh

您以任何方式序列化了吗?
Unu

不,我还没有序列化它。。。您可以编辑您的答案以准确方式打印请求正文的所有内容吗?
Pravesh

对不起,我不记得它从内存中打印出来的内容,在处理该项目时会添加它。
Unu

11

编辑

因为我看到仍然有人对此帖子感兴趣,所以这里是我的日志拦截器的最终版本(直到下一个改进)。我希望这可以节省你们一些时间。

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

1
如果您可以将导入内容放在顶部,那将非常有帮助,因为OKHttp有多个版本,我正在猜测并检查以确定您在此处拥有哪些。谢谢
PGMacDesign 2016年

做完了,希望对您有所帮助;)
Mathieu de Brito

的确如此,非常感谢您对Mathieu的澄清。
PGMacDesign '16

5

在当前版本的OkHttp中,您可以使用HTTP日志拦截器并将级别设置为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)

如您所见,这还将打印出标题,并且如文档所述,您应该小心:

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

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

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

如何从请求对象中仅获取[{“ op”:“ add”,“ path”:“ / path”,“ value”:true}]?
阿舒托什

2

处理带有或不带有主体的请求的版本:

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

0

创建一个单独的新类并实现Intercepter。

  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.
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.