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
Related
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())
I'm working on my android project and I'm making a request function for uploading file that makes request as given below,
httpClient.put(uri) {
body = MultiPartFormDataContent(formData {
append("file", fileContent, Headers.build {
append(HttpHeaders.ContentType, fileMimeType)
append(HttpHeaders.ContentDisposition, ContentDisposition.File.withParameter(ContentDisposition.Parameters.FileName, fileOriginalName))
})
})
}
So as you can see this request has a ContentType header, but I already declared ContentType header in my Ktor HttpClient,
install(DefaultRequest) {
url {
protocol = URLProtocol.HTTP
host = baseURL
}
headers {
append(HttpHeaders.ContentType, ContentType.Application.Json)
append(HttpHeaders.Authorization, accessToken)
append(USER, user)
}
}
So my question is which ContentType is my request going to take? if it can't take the ContentType that I specified in my request function then how can I make it accept ContentType different than default one?
If you try to set the Content-Type header in the DefaultRequest feature then you will get io.ktor.http.UnsafeHeaderException: Header(s) [Content-Type] are controlled by the engine and cannot be set explicitly.
So if you remove the line append(HttpHeaders.ContentType,ContentType.Application.Json) then the Content-Type of a request will be multipart/form-data and the Content-Type for the file body part ‒ value of fileMimeType variable.
So I was fiddling around the same issue found out from some examples that
If you are using Multipart, you need to add Content in body
Header shouldnot be added at all in the request, as this will now automatically be handled by Ktor engine. Since, you already added header in the body.
Here is an example
return helper.getAuthClient(isMultiPartRequest = true).put {
url {
takeFrom(ApiConstants.BASE_URL)
encodedPath = ApiConstants.UPLOAD_DOCUMENT
body = MultiPartFormDataContent (
formData {
append(
key = "file",
value = request.file,
headers = io.ktor.http.Headers.build {
append(HttpHeaders.ContentDisposition, "filename=${request.fileName}")
}
)
append("fileName", request.fileName)
}
)
}
}
Here is what I do for headers.
private fun addCommonHeaders(headers: HeadersBuilder, isMultiPartRequest: Boolean = false) {
headers.append(Headers.API_KEY, ApiConstants.API_KEY)
headers.append(Headers.CORR_ID, randomUUID())
headers.append(Headers.API_VERSION, Headers.Values.VERSION_V3)
headers.append(Headers.PLATFORM, getUserPlatform())
if(!isMultiPartRequest) {
headers.append(Headers.CONTENT_TYPE, Headers.Values.CONTENT_JSON)
}// this is where I decide, for Multipart, don't add any Content-Type headers.
}
points to note:
key = the request key where you will send the byteArray
add more
parameters, if your API contract demands, by further adding
append() in succession
Note : getAuthClient() is simple custom function that provides the instance of HttpClient so ignore that.
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)
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.
I have an API which returns 200, 202, 4xx based on different scenarios. When I get a 202, I am supposed to make the same API until I get a 200 or 4xx. I tried using doOnErrorNext, onError, onNext. I was not able to crack the problem
Observable<MyPOJO> makeAPI();
Observable<MyPOJO> makeAPIImpl(){
makeAPI().doOnErrorNext(/*how to I access the error code here*/);
makeAPI().doOnNext(/*given that 202 is a success code, I expected it to come here, but it goes to Error because of JSON Mapping*/);
}
doOnErrorNext -> I was able to make the API call again but then it would happen for all the error scenarios which I dont want
I have checked multiple answers regarding this, but nobody has solved this combination specifically and am unable to incorporate other answers to my use case.
I would suggest you use OkHttp and use an interceptor to retry your request, something along these lines (this is from one of my apps in Kotlin, but it should give you the idea):
inner class ExpiredSessionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if (response.code() == 202) {
val newRequest = request.newBuilder().build()
return chain.proceed(newRequest)
} else {
return response;
}
}
}
then
val httpClientBuilder = OkHttpClient.Builder()
httpClientBuilder.addInterceptor(ExpiredSessionInterceptor())
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(SERVER_ENDPOINT_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(httpClientBuilder.build())
.build()