改造:删除 Content-Length header

Retrofit: Remove Content-Length header

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

removeHeader("Content-Length") Request.Builder 的功能不起作用,看起来库在请求构建后添加了它。我可以更改此 header 调用 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())
    }

#编辑

对于下面的人。我已经用logging interceptor确认过了,所以没必要在这里写无用的代码。上面的功能来自... 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 数据 class,其中包含 login/password 字段,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 堆栈似乎有问题?

java.net.SocketException: socket failed: EPERM (Operation not permitted) 看起来像是客户端错误。 Android OS 阻止您的应用程序发送网络请求。您应该检查您是否已向您的应用程序授予 INTERNET 权限。你可能有一个 stacktrace 的异常,你可以包含它来证实这一点,而没有看到我们通常在这里猜测。

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

或者只是开始查看使用日志拦截器来确认您的怀疑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://whosebug.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