Make HttpLoggingInterceptor do not log images - android

I'm trying to get rid of trash in logs, like
*�$ʞx���J/
when i recieve an image
So i tried to override HttpLoggingInterceptor intercept(), to detect is there a Content-Type => image/jpeg header in responce, but HttpLoggingInterceptor is final so i cant extend it :(
Code in RetrofitModule:
OkHttpClient provideOkHttpClient(Context context, Application app, Preferences preferences) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.HEADERS);
Cache cache = new Cache(app.getCacheDir(), cacheSize);
return new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpInterceptor(context, preferences))
.addInterceptor(loggingInterceptor)
.cache(cache)
.build();
}
How can i disable image-logging in my project?

So, since no one have an answer iv'e invent my own bicycle:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
if(!message.contains("�")){
Timber.d(message);
}
}
});
Not really sure if String.contains() cheap enough for use it like this, but goal is reached

You can use OkHttpLogger class to print logs without binary data:
class OkHttpLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
okHttpLog(message)
}
private fun okHttpLog(message: String, level: Int = Log.DEBUG, t: Throwable? = null) {
val maxLogLength = 4000
val tag = "OkHttp"
val encoder = Charset.forName("ISO-8859-1").newEncoder()
var logMessage = message
if (t != null) logMessage = logMessage + '\n'.toString() + Log.getStackTraceString(t)
// Split by line, then ensure each line can fit into Log's maximum length.
var i = 0
val length = logMessage.length
var isBinaryLogDisplayed = false
var isBinaryContentType = false
while (i < length) {
var newline = logMessage.indexOf('\n', i)
newline = if (newline != -1) newline else length
do {
val end = minOf(newline, i + maxLogLength)
val msg = logMessage.substring(i, end).trim()
if (msg.contains("Content-Type") &&
msg.contains("application/octet-stream")) { // use another Content-Type if need
isBinaryContentType = true
}
val isBinaryData = !encoder.canEncode(msg)
// multipart boundary
if (isBinaryLogDisplayed && msg.startsWith("--")) {
isBinaryContentType = false
isBinaryLogDisplayed = false
}
// don't print binary data
if (isBinaryContentType && isBinaryData && !isBinaryLogDisplayed) {
Log.println(level, tag, "<BINARY DATA>")
isBinaryLogDisplayed = true
}
if (!isBinaryLogDisplayed) {
Log.println(level, tag, msg)
}
i = end
} while (i < newline)
i++
}
}
}
To use it pass an instance to HttpLoggingInterceptor's constructor:
val httpLoggingInterceptor = HttpLoggingInterceptor(OkHttpLogger()).apply {
level = HttpLoggingInterceptor.Level.BODY
}

Related

Write to function, is called multi time in request body

I have a progreesBar for uploading with retrofit and I implementation that with some of examples.
my problem is 'WriteTo' function in my custom requestBody class.
This function send progress value for use in my progressBar but this function is called twice. I used debugger and I think some interceptors call WriteTo function.
Let me explain the problem more clearly,When I click Upload button, The number of progress bar reaches one hundred and then it starts again from zero.
Some of the things I did:
I removed HttpLoggingInterceptor.
I used a boolean variable for check that 'writeTo' don't post anything the first time
I don't have any extra interceptors
Also I read this topics:
Retrofit 2 RequestBody writeTo() method called twice
using Retrofit2/okhttp3 upload file,the upload action always performs twice,one fast ,and other slow
Interceptor Problem
My codes:
ProgressRequestBody class
class ProgressRequestBody : RequestBody() {
var mutableLiveData = MutableLiveData<Int>()
lateinit var mFile: File
lateinit var contentType: String
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
override fun contentType(): MediaType? {
return "$contentType/*".toMediaTypeOrNull()
}
#Throws(IOException::class)
override fun contentLength(): Long {
return mFile.length()
}
#Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val fileLength = mFile.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val `in` = FileInputStream(mFile)
var uploaded: Long = 0
`in`.use { `in` ->
var read: Int
while (`in`.read(buffer).also { read = it } != -1) {
val percentage = (100 * uploaded / fileLength).toInt()
mutableLiveData.postValue(percentage)
uploaded += read.toLong()
sink.write(buffer, 0, read)
}
}
}
}
private fun upload(file: File, fileType: FileType) {
val fileBody = ProgressRequestBody()
fileBody.mFile = file
fileBody.contentType = file.name
uploadImageJob = viewModelScope.launch(Dispatchers.IO) {
val body = MultipartBody.Part.createFormData("File", file.name, fileBody)
fileUploadRepo.upload(body).catch {
// ...
}.collect {
when (it) {
// ...
}
}
}
}
In my fragment I use liveData for collect progressBar progress value.

How to declare a reactive variable that updates itself when interpolated values change

I'm building some developer options for our team in order to make testing easier. Backend url selection is part of these requested options. The backend url has the following definition in our APIClient class:
val BASE_API_URL = "${scheme}://${serverType}.domain.com/api/v${apiVersion}/mobileapp/"
Now, I have defined the serverType component (which is the component that devs should be able to change via the dev options) using the following definition:
val serverType by PreferenceGeneric<String>(
defaultValue = DEFAULT_SERVER_TYPE,
jsonAdapter = Moshi.Builder().build().adapter(String::class.javaObjectType),
securePrefsManager = SecurePrefsManager(context),
key = PrefsKeys.MAIN_BACKEND_URL_KEY
)
where PreferenceGeneric is defined as follows:
class PreferenceGeneric<T>(
private val key: String,
private val defaultValue: T?,
private val onChanged: ((value: T) -> Unit)? = null,
private val jsonAdapter: JsonAdapter<T>,
private val securePrefsManager: SecurePrefsManager<T>
) : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return securePrefsManager.encryptedPrefsGetValue(key, defaultValue, jsonAdapter)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
if (value == null) return
securePrefsManager.encryptedPrefsPutValue(key, value, jsonAdapter)
onChanged?.let {
it(value)
}
}
}
When I change the value of the PrefsKeys.MAIN_BACKEND_URL_KEY preference from our dev options screen, the serverType variable is updated correctly in the APIClient class, but the BASE_API_URL's value isn't. I thought that interpolated strings will rebuild themselves when a part's value changes. Is there a way that I can make this variable reactive to its components so that when any of them change value, the value of BASE_API_URL is recalculated?
APIClient class:
class APIClient(private val context: Context, private val config: ClientConfiguration) {
private val scheme = "https"
val serverType by PreferenceGeneric<String>(
defaultValue = DEFAULT_SERVER_TYPE,
jsonAdapter = Moshi.Builder().build().adapter(String::class.javaObjectType),
securePrefsManager = SecurePrefsManager(context),
key = PrefsKeys.MAIN_BACKEND_URL_KEY
)
private val apiVersion = 1
//todo:sp set to private after testing
val BASE_API_URL =
"${scheme}://${serverType}.domain.com/api/v${apiVersion}/mobileapp/"
private lateinit var httpClient: OkHttpClient
lateinit var retrofit: Retrofit
var appVersion by PreferenceGeneric<Int>(
defaultValue = DEFAULT_APP_VERSION,
jsonAdapter = Moshi.Builder().build().adapter(Int::class.javaObjectType),
securePrefsManager = SecurePrefsManager(context),
key = PrefsKeys.APP_VERSION_KEY
)
init {
setUpOkHttpClient()
setUpRetrofitClient()
}
private fun setUpRetrofitClient() {
retrofit = Retrofit.Builder()
.baseUrl(BASE_API_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(httpClient)
.build()
}
private fun setUpOkHttpClient() {
val httpClientBuilder = OkHttpClient.Builder()
httpClientBuilder.readTimeout(5, TimeUnit.SECONDS)
httpClientBuilder.connectTimeout(5, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
httpClientBuilder.addInterceptor(logging)
}
if (config.hasCaching) {
val cacheSize = (5 * 1024 * 1024).toLong()
val cache = context.cacheDir?.let { Cache(it, cacheSize) }
httpClientBuilder.cache(cache)
.addInterceptor { chain ->
var request = chain.request()
request = if (context.hasNetwork())
request.newBuilder().header("Cache-Control", "public, max-age=" + 5).build()
else
request.newBuilder().header(
"Cache-Control",
"public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7
).build()
chain.proceed(request)
}
}
httpClientBuilder.addInterceptor { chain ->
var request = chain.request()
request =
request.newBuilder().header("x-app-version", appVersion.toString()).build()
chain.proceed(request)
}
if (config.hasOauth) {
httpClientBuilder.addInterceptor(AccessTokenInterceptor(context))
}
httpClient = httpClientBuilder.build()
}
}
We're using Koin for D.I. and this is how it's defined in our data layer's module:
single { params -> APIClient(androidContext(), config = params.get()) }
And for checking the backend url's value in the developer options screen, I retrieve the instance like so:
val apiClient: APIClient = get(parameters = { parametersOf(UserAuthClientConfig()) })
and access it using : apiClient.BASE_API_URL

How to fix "java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException" using protobuf and retrofit

I am using protobuf and retrofit to get data from api
here is my apiResponse(test on Postman and it works)
{
"AAA": {
"protoTestString": "protoTestString_val",
"protoTestInt": 99,
"protoTestBool": true
}
}
ProtoTest.proto file
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.test.prototest";
message ProtoTest {
AAA aaa = 1;
}
message AAA {
string protoTestString = 1;
int32 protoTestInt = 2;
bool protoTestBool = 3;
}
api util
interface ProtoTestModel {
#GET("/genToken")
fun genToken(): Call<ProtoTest>
}
object ProtoTestService {
fun genToken(): Call<ProtoTest> {
return mProtoTestServiceModel.genToken()
}
val mProtoTestServiceModel: ProtoTestModel by lazy {
val client = OkHttpClient.Builder()
.callTimeout(60L, TimeUnit.SECONDS)
.connectTimeout(60L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.writeTimeout(60L, TimeUnit.SECONDS)
.addInterceptor(LoggingInterceptor())
.build()
val retrofit = Retrofit.Builder()
.client(client)
.baseUrl("http://10.99.4.66:1111")
.addConverterFactory(ProtoConverterFactory.create())
.build()
retrofit.create(ProtoTestModel::class.java)
}
}
and how I call
val apiCall = ProtoTestService.genToken()
apiCall.enqueue(object: Callback<ProtoTest> {
override fun onResponse(call: Call<ProtoTest>, response: Response<ProtoTest>) {
val body = response.body()!!
Log.d("", " body="+body )
Log.d("", " protoTestString=" + body.aaa.protoTestString)
}
override fun onFailure(call: Call<ProtoTest>, t: Throwable) {
Log.d("", " fail="+t )
}
})
but it's always get into onFailure and i print error says
java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException: While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length.
How to fix it. Any help will be appreciate.

How to check token expiration at interceptors android retrofit?

I would like to handle token expiration by myself and send request for new tokens. I have such condition:
sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60
This condition checks when my token will become expired and I have to send a new request from interceptor. I saw this question also. I have created such interceptor:
class RefreshTokens(cont: Context) : Interceptor{
val context = cont
override fun intercept(chain: Interceptor.Chain): Response {
var tokenIsUpToDate = false
val sp = context.getSharedPreferences("app_data", 0)
if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
Singleton.apiService(context).getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", ""))).enqueue(object : Callback<ResNewTokens>, retrofit2.Callback<ResNewTokens> {
override fun onResponse(call: Call<ResNewTokens>, response: retrofit2.Response<ResNewTokens>) {
if (response.isSuccessful) {
tokenIsUpToDate = true
}
}
override fun onFailure(call: Call<ResNewTokens>, t: Throwable) {
}
})
return if (tokenIsUpToDate) {
chain.proceed(chain.request())
} else {
chain.proceed(chain.request())
}
} else {
val response = chain.proceed(chain.request())
when (response.code) {
401->{
chain.request().url
response.request.newBuilder()
.header("Authorization", "Bearer " + context.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
}
500 -> {
Toast.makeText(context, context.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
}
}
return response
}
}
}
I can't imagine how to add return condition to my code. I know about Authentificator but when I use it I send one more request which response gives me 401 error for token updating. When I use Authentificator I send such requests:
Request with old access_token -> 401 error
Request for the new tokens -> 200 OK
Request with new access_token -> 200 OK
So I would like to remove 1 request which will give error and send request for a new tokens. But I have to problems:
I don't know how to fix my interceptor for solving this task
I don't know how to repeat request which I was going to make like in Authentificator
Maybe someone knows how to solve my problem?
Yes that is too much simple do not take is difficult, I also have same issue but i solve like this
So When the token is expred so the Retrofit give the
Error Code = 401
So you need to save the data of user Using sharedPref the userEmail or userName as well as userPassword So
When the user get token exipre message or error code 401 then you need to call a method to login the user again to show anything to the user using useremail and userpassword and then a fresh token generated then send that generated Token to the server and it will work in this case
I hope that will help
I would like to share my own solution which works good as I see:
class AuthToken(context: Context) : Interceptor {
var cont = context
override fun intercept(chain: Interceptor.Chain): Response {
val sp = cont.getSharedPreferences("app_data", 0)
if (sp!!.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) updateAccessToken(cont)
val initialRequest = if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) {
updateAccessToken(cont)
requestBuilder(chain)
} else {
requestBuilder(chain)
}
val initialResponse = chain.proceed(initialRequest)
return if (initialResponse.code == 401 && !sp.getString("refresh_token", "").isNullOrBlank() && sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
updateAccessToken(cont)
initialResponse.close()
val authorizedRequest = initialRequest
.newBuilder()
.addHeader("Content-type:", "application/json")
.addHeader("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
chain.proceed(authorizedRequest)
} else {
val errorBody = initialResponse.message
when {
}
if (initialResponse.code == 500) {
val thread = object : Thread() {
override fun run() {
Looper.prepare()
Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
thread.start()
}
initialResponse
}
}
private fun updateAccessToken(context: Context) {
val sp = context.getSharedPreferences("app_data", 0)
synchronized(this) {
val tokensCall = accessTokenApi()
.getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", "")!!))
.execute()
if (tokensCall.isSuccessful) {
val responseBody = tokensCall.body()
val editor = sp.edit()
val localTime = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH).parse(tokensCall.headers()["Date"]!!)
Singleton.setServerTime(localTime!!.time / 1000, context)
editor.putString("access_token", Objects.requireNonNull<ResNewTokens>(responseBody).access_token).apply()
editor.putString("refresh_token", Objects.requireNonNull<ResNewTokens>(responseBody).refresh_token).apply()
editor.putLong("expires_in", responseBody!!.expires_in!!).apply()
} else {
when (tokensCall.code()) {
500 -> {
val thread = object : Thread() {
override fun run() {
Looper.prepare()
Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
thread.start()
}
401 -> {
Singleton.logOut(context)
}
}
}
}
}
private fun requestBuilder(chain: Interceptor.Chain): Request {
return chain.request()
.newBuilder()
.header("Content-type:", "application/json")
.header("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
}
private fun accessTokenApi(): APIService {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val dispatcher = Dispatcher()
dispatcher.maxRequests = 1
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(100, TimeUnit.SECONDS)
.dispatcher(dispatcher)
.readTimeout(100, TimeUnit.SECONDS).build()
client.dispatcher.cancelAll()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(APIService::class.java)
}
}
In general as I see I send request for token refreshing before send request with expired access_token. Maybe someone will have some suggestions or improvements for my solution :)

Change retrofit okhttp client at runtime

fun getClient(token: String, userId: Long, language: String = "en", maxTry: Int = 2): Retrofit {
val okHttpClient = OkHttpClient.Builder()
okHttpClient.readTimeout(30, TimeUnit.SECONDS)
okHttpClient.connectTimeout(30, TimeUnit.SECONDS)
okHttpClient.writeTimeout(90, TimeUnit.SECONDS)
var tryCount = 0
okHttpClient.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("secToken", token)
.addHeader("userId", userId.toString()).build()
var response = chain.proceed(request)
while (!response.isSuccessful && tryCount < maxTry) {
Log.d("intercept", "Request is not successful - $tryCount")
tryCount++
response = chain.proceed(request)
}
response
}
val builder = GsonBuilder()
builder.registerTypeAdapter(TransModel::class.java, NotificationTypeAdapter(language))
val gson = builder.create()
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
} else {
}
// .client(getHttpClientForFile())
return retrofit!!
}
Above code is to get singleton retrofit client for every request in App.
What I need to do is what to do in else part of retrofit == null.
Here language is initialized only once. while initializing retrofit, but for second request I don't have idea to change language and maxTry count for request.
I want to change language, and maxTry at runTime. For every request there must different maxTry count and may language also.
Edit:
As per suggestion of #EarlOfEgo my else part is
retrofit!!.newBuilder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
but It is not changing language.
Edit 2:
TransModel
class TransModel {
var en: String = ""
var gu: String = ""
var hi: String = ""
constructor()
fun get(language: String): String? {
return when (language) {
LanguageUtil.languageEn -> {
en
}
LanguageUtil.languageGu -> {
gu
}
LanguageUtil.languageHi -> {
hi
}
else -> {
null
}
}
}
constructor(language: String, value: String) {
when (language) {
LanguageUtil.languageEn -> {
en = value
}
LanguageUtil.languageGu -> {
gu = value
}
LanguageUtil.languageHi -> {
hi = value
}
}
}
fun getValueByLanguage(language: String): String? {
return when (language) {
LanguageUtil.languageEn -> {
en
}
LanguageUtil.languageGu -> {
gu
}
LanguageUtil.languageHi -> {
hi
}
else -> {
null
}
}
}
fun updateIt(title: TransModel, currentLanguage: String) {
when (currentLanguage) {
LanguageUtil.languageEn -> {
gu = title.gu
hi = title.hi
}
LanguageUtil.languageGu -> {
en = title.en
hi = title.hi
}
LanguageUtil.languageHi -> {
gu = title.gu
en = title.en
}
}
}
}
and my NotificationTypeAdapter
class NotificationTypeAdapter(val language: String) : TypeAdapter<TransModel>() {
override fun write(out: JsonWriter?, value: TransModel?) {
if (out == null || value == null) return
out.beginObject()
out.name("title")
out.value(value.getValueByLanguage(language))
out.endObject()
}
override fun read(reader: JsonReader?): TransModel? {
if (reader == null) return null
val jsonParser = JsonParser()
val je = jsonParser.parse(reader)
val trans = TransModel(language, (je.asString))
return trans
}
}
You can use the Retrofit method newBuilder to get a new builder and there set a different OkHttpClient with different attributes. Put something like this into your else case:
retrofit.newBuilder()
.client(anotherOkHttpClientWithOtherAttributes.build())
.build()
You can add Credentials manual as Header To your request.
Example:
#GET("user")
Call<UserDetails> getUserDetails(#Header("Authorization") String credentials)
Credentials.basic("ausername","apassword");
You can change BASE_URL at runtime by passing it as a parameter with the use of #Url annotation.
#FormUrlEncoded
#POST()
fun testService(#Url baseUrl: String,
#Field("exampleParam") exampleParam: String): Observable<String>
Please try this and let me know for any query.
Since I didn't found proper answer I am using new retrofit client for every call.
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient.build())
.build()
If anyone have more proper answer, Please suggest me some.

Categories

Resources