首页 > 解决方案 > 改造:删除 Content-Length 标头

问题描述

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

removeHeader("Content-Length")的功能Request.Builder不起作用,看起来库在请求构建后添加了它。我可以更改此标头调用的值,但这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...但是如果您愿意,那可以提供帮助:

标头拦截器

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是服务器返回的数据。

日志猫

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堆栈有问题?

标签: androidretrofitokhttp

解决方案


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

要确认标头,请查看添加 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://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

推荐阅读