【问题标题】:Retrofit: Remove Content-Length header改造:删除 Content-Length 标头
【发布时间】:2021-06-14 05:10:47
【问题描述】:

我正在使用 Retrofit(版本 2.9.0)向 API 服务器发出请求。默认情况下,此库将 Content-Length 标头添加到请求中。不幸的是,出于“安全”目的,服务器拒绝了此标头-我对此没有影响。是否可以使用 Retrofit 从请求中删除此标头?还是我必须创建自己的 REST API 客户端?

Request.BuilderremoveHeader("Content-Length") 函数不起作用,看起来库在请求构建后添加了它。我可以调用addHeader("Content-Length", "any-value") 更改此标头的值,但这不是我需要的。

override fun intercept(chain: Interceptor.Chain): Response {
        val builder = chain.request().newBuilder().apply {
            removeHeader("Content-Length") // Doesn't works
        }
        
        return chain.proceed(builder.build())
    }

#编辑

对于下面的人。我已经通过日志拦截器确认了这一点,因此无需在这里编写无用的代码。上面的功能来自... Interceptor ... 但是如果您愿意,那可以提供帮助:

HeaderInterceptor

class HeaderInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val builder = chain.request().newBuilder().apply {
            removeHeader("Content-Length") // Doesn't works
        }

        return chain.proceed(builder.build())
    }
}

建筑改造客户端

    @Singleton
    @Provides
    fun providesUserApi(headerInterceptor: HeaderInterceptor): UsersApi {
        val loggingInterceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

        val client = OkHttpClient.Builder().apply {
            addInterceptor(tokenInterceptor)
            addInterceptor(headerInterceptor)
        }.build()

        return Retrofit.Builder()
            .baseUrl(Constants.BASE_USERS_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client).build()
            .create(UsersApi::class.java)
    }

API 接口

interface UsersApi {

    @POST("/api/auth/login")
    suspend fun login(@Body user: LoginRequest): Response<LoginResponse>
}

LoginRequest 是简单的 POJO 数据类,其中包含登录/密码字段,LoginResponse 是服务器返回的数据。

Logcat

I/okhttp.OkHttpClient: --> POST https://this_is_api_server.com/api/auth/login
I/okhttp.OkHttpClient: Content-Length: 60
I/okhttp.OkHttpClient: {"device_type":"ANDROID","email":"admin","password":"admin"}
I/okhttp.OkHttpClient: --> END POST (60-byte body)
I/okhttp.OkHttpClient: <-- HTTP FAILED: java.net.SocketException: socket failed: EPERM (Operation not permitted)

来自客户端的事件日志

我用 XXX 屏蔽了域名和 IP 地址。

I/okhttp.OkHttpClient: [2 ms] callStart: Request{method=POST, url=https://securedapiserver.com/api/auth/v1/loginByEmail, tags={class retrofit2.Invocation=package.name.api.UsersApi.login() [LoginRequest(device_type=ANDROID, email=test, password=test)]}}
I/okhttp.OkHttpClient: --> POST https://securedapiserver.com/api/auth/v1/loginByEmail
I/okhttp.OkHttpClient: Content-Length: 58
I/okhttp.OkHttpClient: {"device_type":"ANDROID","email":"test","password":"test"}
I/okhttp.OkHttpClient: --> END POST (58-byte body)
I/okhttp.OkHttpClient: [98 ms] proxySelectStart: https://securedapiserver.com/
I/okhttp.OkHttpClient: [103 ms] proxySelectEnd: [DIRECT]
I/okhttp.OkHttpClient: [105 ms] dnsStart: securedapiserver.com
I/okhttp.OkHttpClient: [158 ms] dnsEnd: [securedapiserver.com/XXX.XXX.XXX.XXX, securedapiserver.com/XXX.XXX.XXX.XXX, securedapiserver.com/XXX.XXX.XXX.XXX, securedapiserver.com/XXX.XXX.XXX.XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX, securedapiserver.com/XXX:XXX:XXX:XXX]
I/okhttp.OkHttpClient: [175 ms] connectStart: securedapiserver.com/XXX.XXX.XXX.XXX:443 DIRECT
I/okhttp.OkHttpClient: [176 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [178 ms] connectStart: securedapiserver.com/XXX.XXX.XXX.XXX:443 DIRECT
I/okhttp.OkHttpClient: [179 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [180 ms] connectStart: securedapiserver.com/XXX.XXX.XXX.XXX:443 DIRECT
I/okhttp.OkHttpClient: [181 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [182 ms] connectStart: securedapiserver.com/XXX.XXX.XXX.XXX:443 DIRECT
I/okhttp.OkHttpClient: [183 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [184 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [184 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [186 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [187 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [188 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [189 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [191 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [194 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [196 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [197 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [198 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [199 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [201 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [201 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [202 ms] connectStart: securedapiserver.com/XXX:XXX:XXX:XXX:443 DIRECT
I/okhttp.OkHttpClient: [203 ms] connectFailed: null java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/System.out: [java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted), java.net.SocketException: socket failed: EPERM (Operation not permitted)]
I/okhttp.OkHttpClient: <-- HTTP FAILED: java.net.SocketException: socket failed: EPERM (Operation not permitted)
I/okhttp.OkHttpClient: [212 ms] callFailed: java.net.SocketException: socket failed: EPERM (Operation not permitted)
E/NetworkCall: socket failed: EPERM (Operation not permitted)

** 编辑 2021-03-19 **

感谢@Yuri Schimke,问题得以解决。我不得不完全删除模拟器并创建一个新的。 http 栈好像有问题?

【问题讨论】:

  • 您的改造要求是什么?我没有看到 Retrofit 在哪里添加该标题。 OkHttp 可能会添加该标头,特别是如果您的请求使用 Multipart 之类的内容。
  • @CommonsWare 请查看我更新的帖子
  • 尝试将headerInterceptor 改为a network interceptor
  • @CommonsWare 不起作用。更糟糕的是,因为使用网络拦截器,我无法更改 Content-Length 标头的值。

标签: android retrofit okhttp


【解决方案1】:

java.net.SocketException: socket failed: EPERM (Operation not permitted) 看起来像是客户端错误。 Android 操作系统阻止您的应用发送网络请求。您应该检查您是否已授予您的应用程序的 INTERNET 权限。您可能有一个堆栈跟踪异常,您可以将其包含在内,以确认这一点,而没有看到我们通常在这里猜测。

要确认标头,请查看添加 OkHttp 网络拦截器并在需要时进行更改 https://square.github.io/okhttp/interceptors/

或者只是从使用 Logging 拦截器来确认您的怀疑开始https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
  .addInterceptor(logging)
  .build();

要确认这是客户端套接字错误,还是来自服务器的有效 400 响应,您应该使用这种方法 https://stackoverflow.com/a/66398516/1542667 来记录连接事件。

[0 ms] callStart: Request{method=GET, url=https://httpbin.org/get}
[11 ms] proxySelectStart: https://httpbin.org/
[12 ms] proxySelectEnd: [DIRECT]
[12 ms] dnsStart: httpbin.org
[55 ms] dnsEnd: [httpbin.org/54.147.165.197, httpbin.org/34.231.30.52, httpbin.org/54.91.118.50, httpbin.org/18.214.80.1, httpbin.org/54.166.163.67, httpbin.org/34.199.75.4]
[62 ms] connectStart: httpbin.org/54.147.165.197:443 DIRECT
[176 ms] secureConnectStart
[747 ms] secureConnectEnd: Handshake{tlsVersion=TLS_1_2 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 peerCertificates=[CN=httpbin.org, CN=Amazon, OU=Server CA 1B, O=Amazon, C=US, CN=Amazon Root CA 1, O=Amazon, C=US] localCertificates=[]}
[765 ms] connectEnd: h2
[767 ms] connectionAcquired: Connection{httpbin.org:443, proxy=DIRECT hostAddress=httpbin.org/54.147.165.197:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=h2}
[775 ms] requestHeadersStart
[783 ms] requestHeadersEnd
[993 ms] responseHeadersStart
[994 ms] responseHeadersEnd: Response{protocol=h2, code=200, message=, url=https://httpbin.org/get}
[999 ms] responseBodyStart
[999 ms] responseBodyEnd: byteCount=267
[999 ms] connectionReleased
[1000 ms] callEnd

【讨论】:

  • 当然。您似乎对编辑“无用代码”感到沮丧,但请考虑人们回答的努力以及您在问题中提供的示例是完整的还是留下了很多假设。
  • 也许你是对的我有点沮丧,因为我已经处理了几天 - 抱歉。但你错了,这不是客户端错误。我问 API 所有者,他说出于“安全目的”(正如我在第一篇文章中提到的),他们检查标题,如果它包含 Content-Length,他们简单地返回 400 错误。我不知道他们为什么决定这样做,但我对此没有影响。如果无法删除 Content-Length 标头,我似乎必须编写自己的客户端...
  • 但是您已经从上面提供的示例中完全实现了这一点。来自服务器的 400 错误将照此返回。这是连接远程服务器之前的套接字错误。
  • 我在上面添加了一些步骤来记录连接事件。希望您不必编写自己的客户端,如果 OkHttp 不能满足您的需求,还有许多替代客户端可用。
  • 感谢您的回复。我在第一篇文章中添加了日志。我还用邮递员检查了这一点。我发送没有 Content-Length 标头的请求,然后我收到 200 响应内容,然后我添加了 Content-Length 标头,它产生了 400 错误。
猜你喜欢
  • 2015-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-25
  • 2016-11-05
  • 2012-06-12
相关资源
最近更新 更多