i am using okhttp (not retrofit) to make all my request to the api. It's working perfectly but i am having some trouble to make dynamique header. here is what i did :
private fun buildRequest(url: String, methode: Method = Method.GET, filters: Map<String, String> = mapOf(): Request? {
var builder = FormBody.Builder()
builder.add("body", "test")
val request = Request.Builder()
.url(url)
.addHeader("API-Key", apikey)
.post(formBody)
.build()
}
Problem is that i got a map filter that represent some header i need to add, but those are not hard coded but they are dynamique, it can be for exemple :
var filters = mapOf("limit" to "10", "sort" to "date")
But i can't do a static addHeader for the filters because i don't know if i am going to have some or not or which are they going to be.
My idea was to use newBuilder like this :
if (!filters.isNullOrEmpty()){
filters.forEach{
request.newBuilder()
request.addHeader(it.key, it.value)
.build()
}
Problem is that this doesn't add my new header to the call. I am I missing something on how to use newBuilder ? or is there a way to manage dynamique header with okhttp ?
You can use toHeaders() extension function of map to convert your map object to Headers.
All key-value pairs of the map can be easily added to headers like this
val request = Request.Builder()
.url(url)
.headers(filters.toHeaders())
Note - Using headers() would replace all previously added headers of
the request builder.
You can also add headers to request builder later, on using newBuilder()
request.newBuilder()
.headers(filters.toHeaders())
Related
I couldn't find a way to add an header only in case it doesn't exist.
What I actually need is to add the default header
"Content-Type": "application/json"
but only in case the header doesn't exist already.
One way to do it would be to have a different Http client or a different interceptor for when the default is necessary but I was expecting to be able to check whether the header is already there and add it only in case it is not.
This is definitely possible, but it also depends where you do it.
val i = Interceptor {
val request = if (it.request().header("A") != null) it.request() else it.request()
.newBuilder()
.header("A", "xxx")
.build()
val response = it.proceed(request)
if (response.header("A") != null) response else response
.newBuilder()
.header("A", "xxx")
.build()
}
But Content-Type is special because it is usually carried on the RequestBody, or ResponseBody. The BridgeInterceptor sits between the application interceptors and network interceptors.
See https://github.com/square/okhttp/blob/5c62ed796d05682c969b2636d3419b5bc214eb11/okhttp/src/jvmMain/kotlin/okhttp3/internal/http/BridgeInterceptor.kt#L43-L46
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
and
https://github.com/square/okhttp/blob/5c62ed796d05682c969b2636d3419b5bc214eb11/okhttp/src/jvmMain/kotlin/okhttp3/internal/http/BridgeInterceptor.kt#L102-L103
Is there a way to add a header to a Retrofit object after it has been created ?
I create a Retrofit object using the Retrofit Builder and then at a later point need to add a certain header to it. The reason for adding it here is that this particular header needs to be added with all requests and its value is dynamic. I would like to avoid having to add this header to every network call separately. Here is how I create it:
Retrofit.Builder builder = new Retrofit.Builder()
.client(client)
.baseUrl(String.format(baseUrl, environmentExtension) + "/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
builder.build();
I would like to add the header to this existing object.
To add header to all the requests, you can intercept calls using Interceptor and tweak the request to add header. This has to be done while building OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(chain -> chain.proceed(chain.request().newBuilder()
.addHeader("key","value").build()));
OkHttpClient client = builder.build();//use this client in retrofit
The best way could be creating an Interceptor and adding it through the OkHttpClient's builder. You can achieve it as following;
class HeaderInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Key", "Value")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
val httpClient = OkHttpClient.Builder().apply {
connectTimeout(60, SECONDS)
readTimeout(60, SECONDS)
writeTimeout(60, SECONDS)
interceptors().add(headerInterceptor)
}.build()
You can introduce above interceptor to Retrofit's object as following;
Retrofit.Builder().apply {
baseUrl(BuildConfig.BASE_URL)
addConverterFactory(Json.asConverterFactory(MEDIA_TYPE_DEFAULT.toMediaType()))
client(httpClient)
}.build()
I can not understand the retrofit interceptors ,
private val OkHttpClient by lazy {
okhttp3.OkHttpClient.Builder()
.addInterceptor {
onOnIntercept(it)
}
.addInterceptor(LoggingInterceptor())
.addInterceptor(getInterceptor404())
.callTimeout(10, TimeUnit.MILLISECONDS)
// .addInterceptor(TimeoutInterceptor())
.build()
}
and what do these lines do, and If I have, multiple does the speed down?
val response: Response = chain.proceed(chain.request())
return chain.proceed(chain.request())
In Android sometimes you need to add a couple of parameters, like headers, to make a successful request, this is normal behavior from all the Android Apps when you are using Retrofit, you can do it in multiple ways
For example, you can add parameters directly to your request interface using the annotation Headers and putting a plain String, like this:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Path("someParam") someParam: String?): Observable<LoginResponseViewModel>
Another solution is to send the Headers as a parameter to your interface function, using an annotation Header and sending a parameter, this gives you the possibility to have a custom parameter that you can manage from every request:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Header(UUID.randomUUID().toString()) authToken: String?, #Path("someParam") someParam: String?): Observable<LoginResponseViewModel>**
Interceptor
A couple of people using Dagger probably will go for an Interceptor, you can have two types of interceptor:
The first one is using an interceptor directly in your Singleton, this will not give you versatility, but it will solve your problem faster, in this example, you can go for the chain object, get the request of the Retrofit call, get a new Builder and then add the Headers.
#Provides
#Singleton
fun getUnsafeOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
}
Yes, you can use multiple interceptors. When you do a request calling interface method using retrofit, your request go to the interceptor and then continue. In the interceptor you can rewrite or retry request. For example, you could add the access token in all request and refresh the token if is necessary, add the headers, another bodies, etc. When you received a response from api, the interceptor intercept the response too. But please, read the documentation to understand how it works. Have a nice coding!
I want to add Basic Authentication header to my request done with OkHttp3.
Here is my code:
// Adding Authentication Header
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(username, password);
return response.request().newBuilder().header("Authorization", credential).build();
}
});
//
client.connectTimeout(10, TimeUnit.SECONDS);
client.writeTimeout(10, TimeUnit.SECONDS);
client.readTimeout(10, TimeUnit.MINUTES);
RequestBody body = RequestBody.create(null, new byte[]{});
if( json != null) {
body = RequestBody.create(MediaType.parse(
"application/json"),
json.toString()
);
}
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
client.build().newCall(request).enqueue(callback);
Unfortunately no Authentication header is added and I really can't find the mistake.
Authenticator is primarily intended for "reactive" authentication, i.e. it is not called automatically and every time. The main scenario for its automatic invocation is a 401 "Unauthorized" server response.
You should probably use a regular Interceptor instead for your case, just register it using your client builder like this:
client.addInterceptor(
object : Interceptor { chain ->
val request = chain.request().newBuilder()
.addHeader(...)
.build()
return chain.proceed(request)
}
)
In order to do that, you need to use Interceptors offered by OkHttp Builder. Interceptors, as the name suggests, can intercept and requests sent through the client or any response received by the client before it passes them to your application.
In your case, you need to add this custom interceptor by intercepting the request, and attaching a new header to it before it leaves your client.
Adding a custom header to every request
return OkHttpClient.Builder()
.addInterceptor { chain ->
var request = chain.request()
var url = request.url()
request = request.newBuilder()
.removeHeader("needAuthToken")
.addHeader("Content-Type", "application/json")
// Feel free to add any other headers
.url(url)
.build()
chain.proceed(request)
}
Feel free to wrap this in any control flow conditions in case attachment of these headers matter on a predicate.
For the server I'm using, we have a subdomain and a directory both tied together. With Retrofit, you need to specify the baseURL and it doesn't seem to allow directories. Is there a way I can implement this?
Example:
https://dev.myserver.com/myserver_dev/api/user/login...
https://qa.myserver.com/myserver_qa/api/user/login...
I've tried:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://dev.myserver.com/myserver_dev")
.build();
...
#POST("user/login" )
Call<NewUser> login( #Query( "email" ) String email, #Query( "password" ) String password );
but it always give me a 404 saying "Not Found". It only seems to work if I do:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://dev.myserver.com")
.build();
...
#POST("server_dev/user/login" )
Call<NewUser> login( #Query( "email" ) String email, #Query( "password" ) String password );
Mainly I would just like to be able to quickly switch servers without having to manually edit two different fields. (One for baseUrl and one for the directory.)
Thanks for your time. :)
It appears retrofit ignores the path component of the baseUrl unless it ends in a trailing slash.
Try --
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://dev.myserver.com/myserver_dev/")
.build();
By the way, there is an issue filed on this already -- https://github.com/square/retrofit/issues/1049
I suggest you to use Dynamic Urls or Passing a Full Url
# Example 3 — completely different url
base url: http://futurestud.io/api/
endpoint: https://api.futurestud.io/
Result: https://api.futurestud.io/
# Example 4 — Keep the base url’s scheme
base url: https://futurestud.io/api/
endpoint: //api.futurestud.io/
Result: https://api.futurestud.io/
# Example 5 — Keep the base url’s scheme
base url: http://futurestud.io/api/
endpoint: //api.github.com
Result: http://api.github.com
In your case:
String SCHEME = "https:";
String SERVER_URL= "//{subdomain}.myserver.com/myserver_dev/";
String BASE_URL= SCHEME + BASE_URL;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
#POST(SERVER_URL + "user/login")
Observable<NewUser> createNewUser(#Path("subdomain") String subdomain, #QueryMap HashMap<String, String> params);
Call:
HashMap<String, String> params = new HashMap<>();
params.put("email",email);
params.put("pass",pass);
Observable<NewUser> observable = mApiService.createNewUser("dev",params);