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")
}
Related
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.
When I would like to use #Get I get 401 HTTP error which means I am not authorized.
Before that I make POST on /authentication and POST on /authorization and it works I am successfully logged in.
The weird thing for me is I get two tokens, can someone explain why?
class AuthInterceptor(context: Context) : Interceptor {
private val sessionManager = SessionManager(context)
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
// If token has been saved, add it to the request
sessionManager.fetchAuthToken()?.let {
requestBuilder.addHeader("Authorization", "Bearer $it")
Log.d("authorization", "$it")
// on authorization I get two tokens, after click Login first appears, 2 seconds later next one
}
return chain.proceed(requestBuilder.build())
}
}
Api
// I think I do not need add here #Headers if I use method in previous class requestBuilder.addHeader~
interface Api {
#GET("device")
suspend fun getDetails(#Query("id") id: String): CameraResponse
}
authApi
interface AuthApi {
#POST("authenticate")
fun login(#Body request: User): Call<AuthResponse>
#POST("authorize")
fun authorize(#Body request: AuthResponse): Call<AuthorizeResponse>
}
error from stacktrace
W/System.err: retrofit2.HttpException: HTTP 401
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err: at java.lang.Thread.run(Thread.java:923)
My documentation
curl -X GET https://myApiDocumentation/device -d "id=[MY_ID]" -H "Authentication: [API_KEY]" --cookie "auth_key=[AUTH_KEY]" -G
If you need more code from program, here is my post which is fixed and another problem occurs...
Why it gets HTTP 404 error with #GET retrofit?
I want to do an async-await to my Retrofit request.
#GET("{companyId}/{filename}")
#Streaming
suspend fun getFilePart(
#Path(value = "companyId") companyId: Int,
#Path(value = "filename") filename: String,
#QueryMap queryMap: Map<String, String>
): Deferred<ResponseBody>
and when i call it from a CoroutineScope i have
val deferredList = pendingFiles.map {
async(Dispatchers.IO) {
try {
// runs in parallel in background thread
// Consider i have the data parameters....
apiManager.mApiService(base).getFilePart(it.companyId, fileName, urlQueryMap)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
So the request returns 200 which means that it is successful, and i see the Logging with the File part data. But i get the following exception
W/System.err: java.lang.RuntimeException: Unable to invoke no-args constructor for com.google.firebase.inject.Deferred<okhttp3.ResponseBody>. Registering an InstanceCreator with Gson for this type may fix this problem.
W/System.err: at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:228)
W/System.err: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
W/System.err: at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
W/System.err: at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
W/System.err: at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
W/System.err: at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:520)
W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err: at java.lang.Thread.run(Thread.java:920)
W/System.err: Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: com.google.firebase.inject.Deferred
W/System.err: at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117)
W/System.err: at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49)
W/System.err: at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:225)
W/System.err: ... 9 more
If your function is suspend, it shouldn't return Deferred. Just declare it this way:
#GET("{companyId}/{filename}")
#Streaming
suspend fun getFilePart(
#Path(value = "companyId") companyId: Int,
#Path(value = "filename") filename: String,
#QueryMap queryMap: Map<String, String>
): ResponseBody // no Deferred here
Functions with suspend keyword are used like regular functions (that's the beauty of coroutines), so they return a regular type. You actually start asynchronous code when you use coroutine builders like you do with async - this is the one that returns Deferred<T> and is not suspending.
I had successfully queried a list of Github issues from a flutter repository using Github Graphql API and Apollo and was able to fetch them to my application. Strangely, I woke up this morning with a HTTP 401 Error, I am unable to understand where the error is coming from and how to catch and correct it.
2021-03-29 07:51:53.590 2532-3799/smartherd.githubissuetracker E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-3
Process: smartherd.githubissuetracker, PID: 2532
com.apollographql.apollo.exception.ApolloHttpException: HTTP 401
at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor.parse(ApolloParseInterceptor.java:108)
at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor$1.onResponse(ApolloParseInterceptor.java:53)
at com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor$executeHttpCall$1.onResponse(ApolloServerInterceptor.kt:110)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:203)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
The Request
val token = "MyToKenHere"
val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain: Interceptor.Chain ->
val original: Request = chain.request()
val builder: Request.Builder =
original.newBuilder().method(original.method(), original.body())
builder.header("Authorization", "bearer $token")
chain.proceed(builder.build())
}
.build()
val apolloClient: ApolloClient = ApolloClient.builder()
.serverUrl("https://api.github.com/graphql")
.okHttpClient(okHttpClient)
.build()
val results = apolloClient.query(LoadgitQuery()).await()
val theget = results.data?.organization?.repository?.issues?.nodes
arrayList_details.clear()
theget?.forEachIndexed { index, value ->
}
As #Rajasekaran M suggested, I checked and turned out I had to create a new token.
It turns out that sometimes you may (mistakenly) publish sensitive info like GitHub token to some repo. I know, it is a silly mistake but sometimes it happens for all of us.
And from what I noticed, as soon as GitHub sees that you published the token (not sure how it is checked though) it becomes invalid.
val apolloClient= ApolloClient.Builder()
.serverUrl("YourDamin")
.addHttpHeader("Accept","application/json")
.addHttpHeader(AUTHORIZATION, token!!)
.build()
lifecycleScope.launchWhenResumed {
try {
val response = apolloClient.query(UserQuery()).execute()
if (response.hasErrors()) {
} else {
// response==>
}
}catch (e:com.apollographql.apollo3.exception.ApolloHttpException){
try {
if (e.statusCode == 401) {
// true
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
use this code for apollo3
I doing POST to my server. The result is 204 / Success code, the body for the result is empty. java.util.NoSuchElementException is raised right after the result from server. My guess is that is because the respond contains no body (Void), but I am very unsure
My interface
#POST("register/")
fun register(#Body phone: PhoneBody): Single<Void>
PhoneBody
data class PhoneBody(val phone: String)
My query
disposable += registrationRepository.register(phone)
.addSchedulers()
.doOnSubscribe { viewState.showLoading() }
.doAfterTerminate { viewState.hideLoading() }
.subscribe({it ->
Log.d("PAPDEB", "$it Personal Acc Presenter")
}, this::handleError)
it never goes to in-subscribe lambda, it goes right into this::handle which is
if (t is ApiException) {
viewState.showError(t.message)
} else if (t is IOException) {
viewState.showError("No internet connection")
}
if (BuildConfig.DEBUG) {
t.printStackTrace()
}
}
D/OkHttp: --> POST https://.../register/
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 24
D/OkHttp: {"phone":"+79090000000"}
D/OkHttp: --> END POST (24-byte body)
D/OkHttp: <-- 204 https://.../register/ (386ms)
D/OkHttp: server: nginx/1.13.12
D/OkHttp: date: Tue, 25 Jun 2019 20:52:49 GMT
D/OkHttp: vary: Accept, Accept-Language, Cookie, Origin
D/OkHttp: allow: POST, OPTIONS
D/OkHttp: x-frame-options: SAMEORIGIN
D/OkHttp: content-language: ru
D/OkHttp: strict-transport-security: max-age=31536000
D/OkHttp: <-- END HTTP
W/System.err: java.util.NoSuchElementException
W/System.err: at io.reactivex.internal.operators.observable.ObservableSingleSingle$SingleElementObserver.onComplete(ObservableSingleSingle.java:111)
W/System.err: at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onComplete(BodyObservable.java:66)
W/System.err: at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:48)
W/System.err: at io.reactivex.Observable.subscribe(Observable.java:12246)
W/System.err: at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
W/System.err: at io.reactivex.Observable.subscribe(Observable.java:12246)
W/System.err: at io.reactivex.internal.operators.observable.ObservableSingleSingle.subscribeActual(ObservableSingleSingle.java:35)
W/System.err: at io.reactivex.Single.subscribe(Single.java:3575)
W/System.err: at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
W/System.err: at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
W/System.err: at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
W/System.err: at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
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)
I would be very thankful to hear any ideas of what is wrong and how to get to lambda
As #CommonsWare said in the comment to the question the problem was that I used Single (from logic of ctrl+c ctrl+V -> change return model) I changed it to Completable and now it works!
Refer to https://github.com/square/retrofit/issues/2867
Using Completable is one of the way out but if the response will sometime carry data you wanted to keep it using Single<T> you can add the Interceptor to your okhttpclient. All credits goes to jgavazzisp and LouisCAD.
Interceptor
class EmptyBodyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.isSuccessful.not() || response.code().let { it != 204 && it != 205 }) {
return response
}
if ((response.body()?.contentLength() ?: -1) > 0) {
return response.newBuilder().code(200).build()
}
val emptyBody = ResponseBody.create(MediaType.get("application/json"), "{}")
return response
.newBuilder()
.code(200)
.body(emptyBody)
.build()
}
}
use on OkHttpClient
private val okHttpClient: OkHttpClient by lazy { OkHttpClient().newBuilder()
.addInterceptor(EmptyBodyInterceptor()).build() }
private var retrofit : Retrofit = Retrofit.Builder().client(okHttpClient).build()