I am using retrofit2 in kotlin, and I need to get the content that is a json and this encrypted, I know that to convert json just use the JacksonConverterFactory (until this part was working well) but an encryption was added before that and I do not know how To handle this, do I need to create a converter of my own? Does anyone have a read to tell me?
My current call for retrofit
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()
And i already have my fucntion (working) to decrypt:
CryptAES.decrypt(value))
This can be done by creating an decrypt interceptor:
class DecryptInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain
.run { proceed(request()) }
.let { response ->
return#let if (response.isSuccessful) {
val body = response.body()!!
val contentType = body.contentType()
val charset = contentType?.charset() ?: Charset.defaultCharset()
val buffer = body.source().apply { request(Long.MAX_VALUE) }.buffer()
val bodyContent = buffer.clone().readString(charset)
response.newBuilder()
.body(ResponseBody.create(contentType, bodyContent.let(::decryptBody)))
.build()
} else response
}
private fun decryptBody(content: String): String {
//decryption
return content
}
}
setup:
val httpClient = OkHttpClient().newBuilder()
httpClient.addInterceptor(DecryptInterceptor())
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()
Related
I'm working on a solution that needs to make recurring calls to an api every 10 seconds. However, I need to dynamically change the URL pointing to another service. That is, the new loop that will start will make the call to this new url base. I am using Koin as a DI. Here is an example of my code:
This is my dataModule koin
single<Retrofit>() {
Retrofit.Builder()
.client(httpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(OLD_BASE)
.build()
}
single<ApiService>() {
get<Retrofit>().create(ApiService::class.java)
}
{ single<OkHttpClient>(named(WITH_AUTH)) {
OkHttpClient.Builder()
.callTimeout(30, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(get<HttpLoggingInterceptor>(named(DATA_INTERCEPTOR)))
.addInterceptor(get<AuthInterceptor>(named(AUTH_INTERCEPTOR)))
.authenticator(get<AccessTokenAuthenticator>(named(AUTH_AUTHENTICATOR)))
.build()
}
single(named(DATA_INTERCEPTOR)) {
HttpLoggingInterceptor().apply {
level =
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.BASIC
}
}
}
single<AuthInterceptor>(named(AUTH_INTERCEPTOR)) {
AuthInterceptor(
get(), get()
)
}
And this is my interceptor :
class AuthInterceptor(
private val tokenRepository: TokenRepository,
private val envRepository: EnvRepository
) : Interceptor {
#Volatile
private var host: HttpUrl? = null
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val token = tokenRepository.getToken(TokenRepository.AUTH_TOKEN).blockingGet()
//val authenticationRequest = request(originalRequest, token)
host = envRepository.getEnvBaseUrl().toHttpUrlOrNull()
host?.let {
val newUrl = chain.request().url.newBuilder()
.scheme(it.scheme)
.host(it.toUrl().toURI().host)
.port(it.port)
.build()
request = chain.request().newBuilder()
.url(newUrl)
.build()
}
val authRequest = request(request, token) ?: request
return chain.proceed(authRequest)
}
private fun request(originalRequest: Request?, token: String?): Request? {
return if (!token.isNullOrEmpty()) {
originalRequest?.newBuilder()?.addHeader("Authorization", "Bearer $token")?.build()
} else {
originalRequest
}
}
}
The problem is that my interceptor works well, but each time before calling the new URL it also calls the old one. And I have no idea how to prevent it from calling the old URL in the loop. SO I have something like this in my debuger htts:
call old url
call olrd url
call new url
call new url
call old url
call old url
call new url
call new url
I hope I have been clear
Thanks,
in my application am using a Retrofit 2.9.0, my issue is the user can change completely the URL from the app menu, in this case is not working when i changed the URL only if i restart the app.
this my instance of Retrofit :
object ApiService {
var token: String = ""
#JvmName("setToken1")
fun setToken(tk: String) {
token = tk
}
private val globalInterceptor = GlobalErrorInterceptor()
private val loginInterceptor = LoginErrorInterceptor()
private val okHttpClient =
OkHttpClient.Builder().addInterceptor(globalInterceptor).build()
private val okHttpClientLogin =
OkHttpClient.Builder().addInterceptor(loginInterceptor).build()
var gson = GsonBuilder()
.setLenient()
.create()
/**This instance for the others requests */
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
val API: WebServicesApi by lazy {
retrofit.create(WebServicesApi::class.java)
}
/**This instance for the login to get the Token */
private val retrofitLogin by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClientLogin)
.build()
}
val APILogin: WebServicesApi by lazy {
retrofitLogin.create(WebServicesApi::class.java)
}
}
You can dynamically change retrofit URL by doing something like this. First change retrofit from val to var.
private fun changeBaseUrl(url: String) {
// change the base url only if new url is different than old url
if (retrofit.baseUrl().toString() != url) {
retrofit = retrofit.newBuilder().baseUrl(url).build()
}
}
Please note you might have to change this method and call it according to your flow. The main point to note here is the use of .newBuilder().baseUrl(url).build().
I have a problem. I have a json. I'm trying to parse symbols like ' and \u00e7. Symbol \u00e7 successfully parses, but symbol ' remains unchanged. Here's my retrofit builder.
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(RatersApi.BASE_URL)
.client(get())
.build()
.create(RatersApi::class.java)
and ok http builder which called from get() function
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor()
.apply { level = HttpLoggingInterceptor.Level.BODY }
)
.addInterceptor(HeaderInterceptor())
.build()
ANSWER
Ok. I didn't find correct solution, so i wrote my own interceptor which transform strings from html. Just inject this into your okhttp builder Here it is:
class HtmlStringInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val contentType = response.body?.contentType()
val bodyString = if (android.os.Build.VERSION.SDK_INT >= 24) {
Html.fromHtml(response.body?.string(), Html.FROM_HTML_MODE_COMPACT).toString()
} else {
Html.fromHtml(response.body?.string()).toString()
}
val body = bodyString.toResponseBody(contentType)
return response.newBuilder().body(body).build()
}
}
The cause of this issue is already in your title: ' is a HTML code and not Unicode.
So the JSON parsing is correct and you need additional processing to handle HTML content. For example if you want to show it in a TextView you can use the following:
// extension function to handle different api levels
fun TextView.setHtml(htmlContent: String) {
if (android.os.Build.VERSION.SDK_INT >= 24) {
this.text = Html.fromHtml(htmlContent, Html.FROM_HTML_MODE_COMPACT)
} else {
#Suppress("DEPRECATION")
this.text = Html.fromHtml(htmlContent)
}
}
// set view content from JSON
text_view.setHtml("test: '")
Inside my Android kotlin app i'm calling some apis by using retrofit2 like
#FormUrlEncoded
#POST("something/some")
fun callMyApi(
#Field("myField") myField: String
): Deferred<MyResponseClass>
Now i need to add some common post params to all my api request (and keep the specific ones for each call, in this case i need to keep "myField"), so i'm using an interceptor:
val requestInterceptor = Interceptor { chain ->
val newRequest = chain.request()
.newBuilder()
.post(
FormBody.Builder()
.add("common1Key", "common1")
.add("common2Key", "common2")
.add("common3Key", "common3")
.build()
)
.build()
return#Interceptor chain.proceed(newRequest)
}
But this implementation fails because the interceptor seems to overwrite myField.
How can i fix it?
We can create Interceptor by using two or more common query parameter.
val requestInterceptor = Interceptor { chain ->
val url = chain.request()
.url()
.newBuilder()
.addQueryParameter("common1key", "common1")
.addQueryParameter("common2key", "common2")
.addQueryParameter("common3key", "common3")
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return#Interceptor chain.proceed(request)
}
I have added Interceptor for post form body.
interface PostWebApiService {
#POST("posts")
#FormUrlEncoded
fun savePost(
#Field("title") title: String
): Deferred<Post>
companion object {
operator fun invoke(): PostWebApiService {
val requestInterceptor = Interceptor { chain ->
var request = chain.request()
val requestBuilder = request.newBuilder()
val formBody = FormBody.Builder()
.add("body", "Body")
.add("userId", "12")
.build()
var postBodyString = bodyToString(request.body())
val concat = if (postBodyString.isNotEmpty()) "&" else ""
postBodyString = postBodyString + concat + bodyToString(formBody)
request = requestBuilder.post(
RequestBody.create(
MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
postBodyString
)
)
.build()
return#Interceptor chain.proceed(request)
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://jsonplaceholder.typicode.com/")
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PostWebApiService::class.java)
}
fun bodyToString(request: RequestBody?): String {
try {
var buffer = Buffer()
request?.writeTo(buffer)
return buffer.readUtf8()
} catch (e: IOException) {
return "error"
}
}
}
}
Here is my code , I have added CacheHeaderInterceptor but one of the requests
for some cases needs to do force call from network instead of retrieving cache response
but as I have added CacheHeaderInterceptor it never called after first call.
but I need to have check and based on that check fetch from network or retrieve cache response
#Singleton
#Provides
fun httpClient(context: Context, #Named(“UserPreferences”) preferences: SharedPreferences): OkHttpClient {
val appCacheDir = context.cacheDir
val httpCacheDir = File(appCacheDir, HTTP_CACHE_DIRNAME)
if (!httpCacheDir.exists()) {
httpCacheDir.mkdirs()
}
val builder = OkHttpClient.Builder()
val authInterceptor = LegacyAuthInterceptor(preferences, userAuthRelay)
builder.addNetworkInterceptor(authInterceptor)
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(StethoInterceptor())
}
builder.addNetworkInterceptor(CacheHeaderInterceptor(isStoreUpdatedRelay))
return builder
.cache(Cache(httpCacheDir, MAX_HTTP_CACHE_SIZE))
.connectTimeout(30, SECONDS)
.writeTimeout(30, SECONDS)
.readTimeout(30, SECONDS)
.retryOnConnectionFailure(true)
.build()
}
class CacheHeaderInterceptor(private val isUpdatedRelay: BehaviorRelay<Boolean>) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
chain.request().headers().get(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY)
?: return chain.proceed(chain.request())
val maxAge = chain.request().headers().values(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY).firstOrNull()?.toLongOrNull()
val modifiedRequest = chain.request().newBuilder().removeHeader(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY).build()
val originalResponse = chain.proceed(modifiedRequest)
return when {
isUpdatedRelay.value -> {
val modifiedResponse = originalResponse.newBuilder()
.addHeader("Cache-Control", "no-cache")
.build()
isStoreUpdatedRelay.accept(false)
modifiedResponse
}
maxAge != null -> {
// Add Cache-Control to the response.
val modifiedResponse = originalResponse.newBuilder()
.removeHeader("Cache-Control")
.removeHeader("Pragma")
.addHeader("Cache-Control", "max-age=$maxAge")
.build()
modifiedResponse
}
else -> // Missing max-age, proceed with original response.
originalResponse
}
}
}
I found the solution for my question
adding addInterceptor()
There is addNetworkInterceptor() and addInterceptor().