Add value before query params via interceptor - android

I have an id, which I should add to each url's path, which I did via interceptor.
val newRequest = chain.request().newBuilder()
newRequest.url(chain.request().url.toString() + "/" + id)
return chain.proceed(newRequest.build())
Problems occur with the first endpoint with query parameters. So with this code I got
MY_BASE_URL?query_param_1=value1&query_param_2=value2/ID
but it should be
MY_BASE_URL/ID?query_param_1=value1&query_param_2=value2
Any good idea how to solve this?
P.S.
There is a hacky solution, but would like to have something better.
newRequest.url(chain.request().url.toString().replace("?", "/" + userId.toString() + "?", true))

HttpUrl has several methods for obtaining the specific parts of the URL, such as host, path and query. You could do something like
val url = chain.request().url
val newUrl = url.scheme()+"://"+url.host()+url.encodedPath()+"/"+userId.toString()
with url.query() appended to the end if there is one. Consider also adding url.port() and url.encodedFragment() if needed.

Try turning the HttpUrl into a Builder which you can manipulate.
val newUrl = chain.request().url
.newBuilder()
.addPathSegment("ID")
.build()
val newRequest = chain.request()
.newBuilder()
.url(newUrl)
.build()
chain.proceed(newRequest)

Related

Kotlin okhttp dynamic header

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())

OKHttp Add header only if it doesn't already exist

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

OKHttpClient Response - bad request

I get a response - Bad Request when making an api request using OKHttpClient. Can someone please help? Response Code - 400
https://sendgrid.com/docs/API_Reference/api_v3.html
val policy = StrictMode.ThreadPolicy.Builder()
.permitAll().build()
StrictMode.setThreadPolicy(policy)
val client = OkHttpClient()
val body: RequestBody = RequestBody.create(
"application/json".toMediaTypeOrNull(),
"{\"list_ids\":[\"a7aab3b0-\"],\"contacts\":[{\"email\": \"" + userEmail + "\"}]}"
)
val request: Request = Request.Builder()
.url("https://api.sendgrid.com/v3/marketing/contacts")
.put(body)
.addHeader("authorization", "Bearer SG.7LPq")
.addHeader("content-type", "application/json")
.build()
val response = client.newCall(request).execute()
It looks like your keys don't match the case being used in the API docs, they are capitalised. Also have you tried checking that toMediaTypeOrNull() is able to format that as JSON? It's difficult to tell how it will handle those slashes but they shouldn't be needed.

OkHttp add Basic Authentication Header

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.

Add Header Parameter in Retrofit

I'm trying to call an API which requires me to pass in an API key.
My Service call using HttpURLConnection is working perfectly.
url = new URL("https://developers.zomato.com/api/v2.1/search?entity_id=3&entity_type=city&q=" + params[0]);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("user-key","9900a9720d31dfd5fdb4352700c");
if (urlConnection.getResponseCode() != 200) {
Toast.makeText(con, "url connection response not 200 | " + urlConnection.getResponseCode(), Toast.LENGTH_SHORT).show();
Log.d("jamian", "url connection response not 200 | " + urlConnection.getResponseCode());
throw new RuntimeException("Failed : HTTP error code : " + urlConnection.getResponseCode());
}
However, I'm not sure how this works with Retrofit as my call in going into Failure at all times.
Here's the code I'm using for the same service call
#GET("search")
Call<String> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query,#Header("Accept") String accept, #Header("user-key") String userkey);
and I'm using this to call it
Call<String> call = endpoint.getRestaurantsBySearch("3","city","mumbai","application/json","9900a9720d31dfd5fdb4352700c");
All these calls are going into the OnFailure Method in RetroFit.
If I send it without the HeaderParameters it goes into Success with a 403 because I obviously need to pass the api key somewhere but I cant figure out how.
#GET("search")
Call<String> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query);
The error I'm getting in OnFailure is
java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $
Try this type header for Retrofit 1.9 and 2.0. For the JSON content type.
#Headers({"Accept: application/json"})
#POST("user/classes")
Call<playlist> addToPlaylist(#Body PlaylistParm parm);
You can add many more headers, i.e,
#Headers({
"Accept: application/json",
"User-Agent: Your-App-Name",
"Cache-Control: max-age=640000"
})
Dynamically add to headers:
#POST("user/classes")
Call<ResponseModel> addToPlaylist(#Header("Content-Type") String content_type, #Body RequestModel req);
Call your method, i.e.,
mAPI.addToPlayList("application/json", playListParam);
Or
Want to pass every time, then create an HttpClient object with the HTTP Interceptor:
OkHttpClient httpClient = new OkHttpClient();
httpClient.networkInterceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request.Builder requestBuilder = chain.request().newBuilder();
requestBuilder.header("Content-Type", "application/json");
return chain.proceed(requestBuilder.build());
}
});
Then add to a Retrofit object
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(httpClient).build();
If you are using Kotlin, remove the { }. Else it will not work.
You can use the below
#Headers("user-key: 9900a9720d31dfd5fdb4352700c")
#GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query);
and
Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes");
The above is based in the zomato api which is documented at
https://developers.zomato.com/documentation#!/restaurant/search
Thing to note is the end point change api/v2.1/search and the Header #Headers("user-key: 9900a9720d31dfd5fdb4352700c").
Also check your base url .baseUrl("https://developers.zomato.com/")
Also i tried the above with a api key i generated and it works
and my query was cafes as suggested the zomato documentation.
Note : I hope you have the below
.addConverterFactory(ScalarsConverterFactory.create()) // for string conversion
.build();
and the below in build.gradle file
compile group: 'com.squareup.retrofit2', name: 'converter-scalars', version: '2.2.0'
Edit:
You can also pass header with dynamic value as below
#GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query,#Header("user-key") String userkey);
And
Call<String> call = endpoint.getRestaurantsBySearch("3","city","cafes","9900a9720d31dfd5fdb4352700c");
After trying a couple of times i figured out the answer.
The error
java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $
was coming due the failure of parsing the json.
In the method call I was passing a String instead of a POJO class.
#Headers("user-key: 9900a9720d31dfd5fdb4352700c")
#GET("api/v2.1/search")
Call<String> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query);
I should have passed instead of Call<String> the type of Call<Data>
Data being the Pojo class
something like this
#Headers("user-key: 9900a9720d31dfd5fdb4352700c")
#GET("api/v2.1/search")
Call<Data> getRestaurantsBySearch(#Query("entity_id") String entity_id, #Query("entity_type") String entity_type, #Query("q") String query);
As far as i can see you are passing the data in a wrong way.
Your method getRestaurantsBySearch is accepting the last two parameter as header field i.e accept and user-key. But while calling the method you are passing headers first.
Pass the data as you have declared it in method signature of getRestaurantsBySearch
Please take a look at the response. It clearly shows that the api key you provided is wrong. At first you get the correct api key. Then call the request it will work
.
Let me also comment a little bit (actually a lot) about adding headers in Kotlin focusing on Dependency Injection.
The best approach would be to provide both OkHttpClient and HttpLoggingInterceptor on the same di method making use of the handy Kotlin Scoping function in this case also and apply.
We will be needing these Retrofit (2.9) and OkHttpClient dependencies - this example uses Kotlin DSL but should be more or less the same in Groovy. Of-course you will be needing other dependencies like Hilt if you are using Dependency Injection.
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.7")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.7")
Next stop is to create the #Provide function which returns OkHttpClient.
#Provides
#Singleton
fun provideOkHttpClient():OkHttpClient { ...}
Background theory about Interceptors is very vital; to use an interceptor, you need to create a class that implements the Interceptor interface and override the intercept() method.
intercept() receives an Interceptor.Chain object - which represents the current request and allows you to proceed with the request by calling the proceed() method, or cancel the request by throwing an exception. intercept() override function returns a Response object which is exactly what chain.proceed(request) returns.
class MyInterceptor : Interceptor {
//throw an exception to cancel request
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
.newBuilder() // returns Request.Builder
.addHeader("Header_1", "value_1")
.build()
//proceed with the request
return chain.proceed(request)
}
}
Thanks to Kotlin Anonymous Function Syntax and Builder Pattern we can skip the above theory steps and start to build OkHttpClient which has the addInterceptor() function.
fun provideOkHttpClient(): OkHttpClient {
//build client
return OkHttpClient.Builder()
//create anonymous interceptor in the lambda and override intercept
// passing in Interceptor.Chain parameter
.addInterceptor { chain ->
//return response
chain.proceed(
//create request
chain.request()
.newBuilder()
//add headers to the request builder
.also {
it.addHeader("Header_1", "value_1")
it.addHeader("Header_2", "value_2")
}
.build()
)
}
.also { okHttpClient ->.... }
In the above code addInterceptor() opens up a lambda where we anonymously override intercept() passing in a chain parameter.
We use chain.proceed(request) to return a Response. It is when constructing the request to pass to chain.proceed() that we modify the actual request to add the headers.
You can also proceed to build up on the OkHttpClient to add timeouts etc.
.also { okHttpClient ->
okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
okHttpClient.addInterceptor(httpLoggingInterceptor)
}
}
.build()
This is the final code.
#Provides
#Singleton
fun provideOkHttpClient(): OkHttpClient {
//build client
return OkHttpClient.Builder()
//create anonymous interceptor in the lambda and override intercept
// passing in Interceptor.Chain parameter
.addInterceptor { chain ->
//return response
chain.proceed(
//create request
chain.request()
.newBuilder()
//add headers to the request builder
.also {
it.addHeader("Header_1", "value_1")
it.addHeader("Header_2", "value_2")
}.build()
)
}
//add timeouts, logging
.also { okHttpClient ->
okHttpClient.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
okHttpClient.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
//log if in debugging phase
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
okHttpClient.addInterceptor(httpLoggingInterceptor)
}
}
.build()
}
This marks my StackOverflow's longest ever post, I'm sorry guys.

Categories

Resources