I'm using okhttp library v.3.14.7 with Retrofit and Moshi to make network requests.
I have a custom interceptor for handling errors:
class ErrorInterceptorProvider #Inject constructor(
private val serverErrorConverter: ServerErrorConverter
) : Provider<Interceptor> {
override fun get(): Interceptor {
return Interceptor { chain ->
val response = chain.proceed(chain.request().newBuilder().build())
when (response.code()) {
in 400..599 -> {
throw serverErrorConverter.makeErrorException(response)
}
}
return#Interceptor response
}
}
}
the serverErrorConverter.makeErrorException function has the following body:
fun makeErrorException(response: Response): ServerException {
val httpCode = response.code()
val requestId = response.headers().get(NetworkConstants.REQUEST_ID_HEADER)
val responseContent = getResponseContent(response)
val exceptionContent = ExceptionContent.createDefault(context)
/rest of lines/
}
and the getResponseContent(response) has this implementation:
private fun getResponseContent(response: Response): String {
return try {
response.body()!!.string()
} catch (_: Exception) {
response.message()
}
}
I'm trying to test how my app reacts on the response with HTTP code 500. I'm using Charles as a proxy and I change the response to my request to this one:
HTTP/1.1 500 Internal server error
Date: Mon, 24 Oct 2022 10:37:05 GMT
Connection: keep-alive
After I execute the response, I expected my app to show error immediately. However, my loading is not finishing and after some time in my getResponseContent function I'm catching an exception:
java.net.SocketTimeoutException: timeout
W/System.err: at okio.SocketAsyncTimeout.newTimeoutException(JvmOkio.kt:146)
W/System.err: at okio.AsyncTimeout.access$newTimeoutException(AsyncTimeout.kt:158)
W/System.err: at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:337)
W/System.err: at okio.RealBufferedSource.read(RealBufferedSource.kt:189)
W/System.err: at okhttp3.internal.http1.Http1ExchangeCodec$AbstractSource.read(Http1ExchangeCodec.java:389)
W/System.err: at okhttp3.internal.http1.Http1ExchangeCodec$UnknownLengthSource.read(Http1ExchangeCodec.java:529)
W/System.err: at okhttp3.internal.connection.Exchange$ResponseBodySource.read(Exchange.java:286)
W/System.err: at okio.RealBufferedSource.read(RealBufferedSource.kt:189)
W/System.err: at com.chuckerteam.chucker.internal.support.TeeSource.read(TeeSource.kt:24)
W/System.err: at okio.RealBufferedSource.select(RealBufferedSource.kt:229)
W/System.err: at okhttp3.internal.Util.bomAwareCharset(Util.java:467)
W/System.err: at okhttp3.ResponseBody.string(ResponseBody.java:181)
W/System.err: at ServerErrorConverter.getResponseContent(ServerErrorConverter.kt:156)
The problem is that it takes some time to produce that exception after the response has been received.
I don't understand why calling body()!!.string() method doesn't simple return an empty string?
The only what I've found is that if i add a Content-Length: 0 header to the previous one everything will work fine.
the request code
var myClient: HttpClient = HttpClient(Android) {
// Logging
install(Logging) {
logger = Logger.ANDROID
level = LogLevel.BODY
}
}
when try to request URL
myClient.get("https://www.sample.com/state")
try to run the request and got the following request log
2022-07-05 11:20:58.667 977-1021/? W/System.err: [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient - REQUEST: https://www.sample.com/state
2022-07-05 11:20:58.667 977-1021/? W/System.err: METHOD: HttpMethod(value=GET)
2022-07-05 11:20:58.667 977-1021/? W/System.err: BODY Content-Type: null
2022-07-05 11:20:58.667 977-1021/? W/System.err: BODY START
2022-07-05 11:20:58.667 977-1021/? W/System.err:
2022-07-05 11:20:58.667 977-1021/? W/System.err: BODY END
response log
2022-07-05 11:20:58.924 977-2181/? W/System.err: [DefaultDispatcher-worker-2] INFO io.ktor.client.HttpClient - RESPONSE: 200 OK
2022-07-05 11:20:58.924 977-2181/? W/System.err: METHOD: HttpMethod(value=GET)
2022-07-05 11:20:58.924 977-2181/? W/System.err: FROM: https://www.sample.com/state
2022-07-05 11:20:58.924 977-2181/? W/System.err: BODY Content-Type: application/json; charset=utf-8
2022-07-05 11:20:58.924 977-2181/? W/System.err: BODY START
2022-07-05 11:20:58.924 977-2181/? W/System.err: "idle"
2022-07-05 11:20:58.924 977-2181/? W/System.err: BODY END
In the log, show the request URL https://www.sample.com/state twice.
For security reasons, we don't want to display this URL in the log.
How do I set or operate not to display this URL?
the kotlin version and ktor version
def kotlin_version = '1.6.21'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
def kotlinx_coroutines_version = '1.6.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
def ktor_version = '2.0.2'
implementation "io.ktor:ktor-client-core:$ktor_version"
// HTTP engine: The HTTP client used to perform network requests.
implementation "io.ktor:ktor-client-android:$ktor_version"
// Logging
implementation "io.ktor:ktor-client-logging:$ktor_version"
You can either write your own interceptors and log only desired information (you can use the source code of the Logging plugin as an example) or write a logger that will remove unwanted information from messages. The latter solution is naive and fragile:
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import org.slf4j.LoggerFactory
suspend fun main() {
val myLogger = object : Logger {
private val urlRegex = Regex("""FROM: .+?$""", RegexOption.MULTILINE)
private val delegate = LoggerFactory.getLogger(HttpClient::class.java)!!
override fun log(message: String) {
val strippedMessage = urlRegex.replace(message, "FROM: ***")
delegate.info(strippedMessage)
}
}
val client = HttpClient(OkHttp) {
install(Logging) {
logger = myLogger
level = LogLevel.ALL
}
}
client.get("https://httpbin.org/get")
}
when I start my application I keep getting this error. I don't know why am I getting this error because my Response class has list in constructor. I saw this link but I did not find an answer for my question. I am using json server for dummy RESTful api.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.social.network, PID: 3448
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:226)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:174)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:929)
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:215)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:174)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:929)
I/Process: Sending signal. PID: 3448 SIG: 9
This is my Api interface:
interface Api {
companion object {
const val BASE_URL = "http://be7c232bf30e.ngrok.io "
}
#GET("/posts")
suspend fun searchPhotos(
#Query("_page") page: Int,
#Query("_limit") perPage: Int
): Response
}
This is response data class:
data class Response(
val results: List<Post>
)
Probably you should either return list of Post or list of Response
interface Api {
companion object {
const val BASE_URL = "http://be7c232bf30e.ngrok.io "
}
#GET("/posts")
suspend fun searchPhotos(
#Query("_page") page: Int,
#Query("_limit") perPage: Int
): List<Post> // or List<Response>
}
I have deployed a firebase cloud function (http function) which i could access successfully using postman tool, but when i try to access through client sdk from my android application i always get a permission denied error:
W/System.err: com.google.firebase.functions.FirebaseFunctionsException: PERMISSION_DENIED
04-19 15:18:00.258 3118-3118/com.abc.xyz W/System.err: at com.google.firebase.functions.FirebaseFunctions$2.onResponse(com.google.firebase:firebase-functions##16.3.0:273)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Here is the code i used to access the function
private fun saveInstallationId(key: String, userId : String, installationKey : String) : Task<String> {
val data = hashMapOf(
"key" to key,
"installationId" to installationKey,
"userId" to userId
)
return functions
.getHttpsCallable("setInstallationId")
.call(data)
.continueWith { task ->
val result = task.result?.data as String
result
}
I think i am missing some headers, is it possible to set header parameters using client sdk?
I am using Retrofit and RxAndroid to send GET request to server which is based on Django, and the server response with a JSON data.
The interesting thing i found is, with stetho, the last right brace of Json is lost. So i got this error msg:
W/System.err: java.io.EOFException: End of input at line 1 column 812 path $.user
W/System.err: at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1393)
W/System.err: at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:482)
W/System.err: at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:414)
W/System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:214)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
W/System.err: at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
W/System.err: at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
W/System.err: at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
W/System.err: at rx.Subscriber.setProducer(Subscriber.java:211)
W/System.err: at rx.internal.operators.OnSubscribeMap$MapSubscriber.setProducer(OnSubscribeMap.java:102)
W/System.err: at rx.Subscriber.setProducer(Subscriber.java:205)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
W/System.err: at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:48)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:33)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:48)
W/System.err: at rx.internal.operators.OnSubscribeMap.call(OnSubscribeMap.java:33)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
W/System.err: at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
W/System.err: at rx.Observable.unsafeSubscribe(Observable.java:10142)
W/System.err: at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
W/System.err: at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
W/System.err: at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/System.err: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/System.err: at java.lang.Thread.run(Thread.java:764)
Here is the json data from stetho:
{"id":47,"credit_card":"","order_ordereditem_set":[{"id":36,"product_item":{"id":3,"category":{"id":1,"name":"Fruit"},"added_time":"2018-02-11 15:23:21","upc":"4321","desc":"Mandarin","price":150,"img_url":"http://15.32.134.74:8000/media/images/products/mandarin.jpg","not_sale":false,"is_hot":false,"is_recommended":false},"added_time":"2018-02-12 14:02:11","quantity":1,"order_id":47},{"id":37,"product_item":{"id":2,"category":{"id":1,"name":"Fruit"},"added_time":"2018-02-08 13:29:07","upc":"123456","desc":"Kiwi","price":500,"img_url":"http://15.32.134.74:8000/media/images/products/kiwi.jpg","not_sale":false,"is_hot":true,"is_recommended":false},"added_time":"2018-02-12 14:02:11","quantity":1,"order_id":47}],"added_time":"2018-02-12 14:02:11","payment":"0","total_quantity":2,"total_price":6.5,"user":1
But i checked wireshark, there is no missing of right brace.
I also used postman, but there is no problem.
This issue comes out 30%.
Following is my code details:
private HttpUtil() {
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(getOkHttpClient())
.baseUrl(SERVER_BASE_URL)
.build();
mService = retrofit.create(RestAPIService.class);
}
static public HttpUtil getHttpUtilInstance() {
if (mHttpUtil == null) {
synchronized (HttpUtil.class) {
mHttpUtil = new HttpUtil();
}
}
return mHttpUtil;
}
public RestAPIService getService() {
return mService;
}
private OkHttpClient getOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
if (GlobalConfig.CONFIG_DEBUG)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
else
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
OkHttpClient.Builder httpClientBuilder = new OkHttpClient
.Builder();
httpClientBuilder.addInterceptor(loggingInterceptor)
.addInterceptor(mTokenInterceptor)
.addNetworkInterceptor(new StethoInterceptor());
//httpClientBuilder.addNetworkInterceptor(loggingInterceptor);
return httpClientBuilder.build();
}
private boolean shouldAddToken(String url) {
return !url.contains("api/login");
}
private boolean alreadyHasAuthorizationHeader(Request request) {
return request.header("Authorization") != null;
}
// Interceptor used for inserting token into Header
private Interceptor mTokenInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request;
Request originalRequest = chain.request();
//request = originalRequest.newBuilder().addHeader("Connection", "close").build();
request = originalRequest;
String token = UserDataRepository.getInstance().getToken();
if (token == null || token.length() == 0 || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
if (shouldAddToken(originalRequest.url().toString())) {
request = request.newBuilder()
.header("Authorization", "JWT " + token)
.build();
}
return chain.proceed(request);
}
};
#Headers({"Content-Type: application/json", "Accept: application/json"})
#GET("api/order")
Observable<List<Order>> getOrder();
it's a bit late for the answer, but if someone has the same problem, I'll tell you how I solved it.
I was having the same error, the last character of the response was missing, but when testing the api with postman everything was fine, so I try the same code with other apis and it works.
The problem is the server, I was using laravel server so I installed and configured apache and the problem is solved.