I am using Retrofit/Robospice to make api calls in an app I've built, with a RetrofitGsonSpiceService. All responses are converted into POJOs using a GSON converter, however there is some information I need to retrieve from the response header. I cannot find any means to get the headers (I can only get the headers if the request is unsuccessful because the raw response is sent in the error object!) how can I intercept the response to grab the headers before it is converted?
It took me a few minutes to figure out exactly what #mato was suggesting in his answer. Here's a concrete example of how to replace the OkClient that comes with Retrofit in order to intercept the response headers.
public class InterceptingOkClient extends OkClient
{
public InterceptingOkClient()
{
}
public InterceptingOkClient(OkHttpClient client)
{
super(client);
}
#Override
public Response execute(Request request) throws IOException
{
Response response = super.execute(request);
for (Header header : response.getHeaders())
{
// do something with header
}
return response;
}
}
You then pass an instance of your custom client to the RestAdapter.Builder:
RestAdapter restAdapter = new RestAdapter.Builder()
.setClient(new InterceptingOkClient())
....
.build();
RoboSpice was designed in a way it doesn't know anything about the HTTP client you end up using in your app. That being said, you should get the response headers from the HTTP client. As Retrofit may use Apache, OkHttp or the default Android HTTP client, you should take a look and see which client you are currently using. Take into account that Retrofit chooses the HTTP client based on certain things (please refer to the Retrofit documentation, or dig into the code, you will find it), unless you manually specify it.
Retrofit defines an interface for clients called Client. If you take a look at the source code, you will see that three classes implement this interface: ApacheClient, OkClient and UrlConnectionClient. Depending on which of them you want to use, extend from one of those, and try to hook into the code that is executed when a response comes back, so that you can get the headers from it.
Once you do that, you have to set your custom Client to Retrofit.
Related
I have an authorization api that returns null body but with access token in headers.
I am able to read the okhttp3.Headers object and also get header names as Set using
Headers headers = response.headers(); // response object of type Response<T>
Set<String> headerNames = headers.names();
But in the code the headers object does not show the custom header (access_token) returned as response. However, in postman i can see the custom header as shown below:
access_token -> { "Token":"adklasldalksdalkdask",
"Provider":"ABC" }
I am using interceptors to get the header as shown:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Can someone suggest how to read the access token as part of the custom header in auth response?
First print the entire response, body, code, message, header(by logging or something else) and try to find a clue from there.
I would recommend you to read the API docs and see the type of request it is asking for.
Use Postman to check which one of the following is working:
1.form-data
2.x-www-form-Urlencoded
3.raw
4.binary
And then accordingly set the annotations in the method declarations in the interface.
eg: in my case it was taking x-www-form-Urlencoded so I had to mention it using
#FormUrlEncoded
#Headers("Content-Type: application/x-www-form-urlencoded")
in the method declaration.
I call a Rest API of salesforce by post method:
url = "https://test-dev-ed.my.salesforce.com/services/apexrest/AccountUsers/"
client = OkHttpClient()
val jsonIn = FormBody.Builder()
.add("email",URLEncoder.encode("dt1#gmail.com", "UTF-8"))
.add("password", URLEncoder.encode("1","UTF-8"))
.build()
request = Request.Builder()
.post(jsonIn)
.header("Authorization", "Bearer "+accesstoken)
.addHeader("Content-Type","application/x-www-form-urlencoded")
.url(url)
.build()
response = client.newCall(request).execute()
This is rest api:
#HttpPost
global static ID createUser(String email, String password) {
AccountUser__c us=new AccountUser__c();
us.Email__c=email;
us.Password__c=password;
us.Status__c=0;
insert us;
return us.Id;
}
But result return is error:
[{"errorCode":"UNSUPPORTED_MEDIA_TYPE","message":"Content-Type header specified in HTTP request is not supported: application/x-www-form-urlencoded"}]
I had try change application/json to application/x-www-form-urlencoded , but still can't resolve.
I try call a Get method, it is ok.
Why Post method occur error [Content-Type header specified in HTTP request is not supported]?
I would like to suggest a better resolution. Retrofit Library
Even though it is not mandatory to use Retrofit, these are few eye catchy aspects which makes it reliable and handy in similar use case of yours.
Why to use Retrofit?
Type-safe REST Adapter that makes common networking tasks easy
For POST operations, retrofit helps in assembling what needed to be submitted. Eg:- Generating URL encoded form.
Takes care of URL manipulation, requesting, loading, caching, threading, synchronization, sync/async calls
Helps to generate URL using type-aware generated code tied to specific REST API
Parsing JSON using GSON
Retrofit is an API adapter wrapped over OkHttp
The problem that you are facing can be resolved using retrofit like this.
public interface APIConfiguration{
#Headers({"Accept:application/json",
"Content-Type:application/x-www-form-urlencoded"})
#FormUrlEncoded
#POST("user/registration")
Observable<DataPojo> registrationAPI(#FieldMap(encoded = true) Map<String, String> params);
}
That's it, with few annotation the library takes care of Form URL
Encoding and related dependencies.
As it is inappropriate to start from corresponding Retrofit dependencies and sample code, you can go through Reference One and Reference Two for more details.
As per my understanding just checkout the difference the content type header "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.
The content "multipart/form-data" follows the rules of all multipart MIME data streams.
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
Also try your http request by setting your content type header as multipart/formdata.
I've been trying to implement an interceptor ( OkHttp 3.2 & Retrofit 2 ) for editing the JSON response before is returned as response. The server we request data returns different data dependes on success or error and that makes difficult to map the objects.
I was trying to do it by adding the interceptor to Retrofit as a NetworkInterceptor, however the string returned had no format.
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
try {
final String responseString = new String(response.body().bytes() );
LOGD("OkHttp-NET-Interceptor", "Response: " + responseString);
String newResponseString = editResponse( responseString );
LOGD("OkHttp-NET-Interceptor", "Response edited: " + newResponseString);
return response.newBuilder()
.body(ResponseBody.create(response.body().contentType(), newResponseString))
.build();
}catch (Exception ex){
return response;
}
}
responseString had a string without any understandable format.
After changing to the normal interceptor, the string had format a it was able to convert to JSONObject.
Could tell me someone which are the differences between the responses?
why this line new String(response.body().bytes() ); return different content?
The differences are in the names. NetworkInterceptor hooks in at the network level and is an ideal place to put retry logic and anything that doesn't rely on the actual content of the response.
If what you do depends on the contents of the response (like in your case), using a ApplicationInterceptor is more useful, as it gives you the response after it's been processed by any other moving parts you may have such as a JSON deserializer. Otherwise you would have to implement the JSON deserializing yourself inside the NetworkInterceptor which doesn't make much sense considering it's done for you by Retrofit.
Clarification
Square have this useful diagram on their wiki that shows where each type of interceptor sits
Thus, the reason you receive a readable string in the ApplicationInterceptor is because Square are trying to de-couple the purposes of the two interceptor types. They don't think you should be making any application dependent decisions in the NetworkInterceptor, and so they don't provide an easy way for you to access the response string. It is possible to get ahold of, but like I said, they don't want you to make decisions that depend on the content of the response - rather, they want you to make decisions based or the network state, or headers etc.
The ApplicationInterceptor is where they want you to make decisions dependent upon the contents of the response, so they provide easier methods to access the content of the response so that you can make informed decisions to retry, or as they detail in their wiki, rewrite responses (which I believe is what you're trying to do).
According to #square:
Each interceptor chain has relative merits.
Application interceptors
Don’t need to worry about intermediate responses like redirects and retries.
Are always invoked once, even if the HTTP response is served from the cache.
Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
Permitted to short-circuit and not call Chain.proceed().
Permitted to retry and make multiple calls to Chain.proceed().
Can adjust Call timeouts using withConnectTimeout, withReadTimeout, withWriteTimeout.
Network Interceptors
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection that carries the request.
I have four different apis and each one of them has its own headers.
I tried using the interceptor mechanism for filling the headers, instead of giving each call the same headers but since OkHttp interceptor runs for each and every request, i cannot make an interceptor per api.
I didn't find any other mechanism in Retrofit2 that would allow me to avoid writing the same headers for each and every request.
What would be a good way to give bunch of calls same headers?
I had the same kind of problem. I needed to add Cache Headers to the responses received on the basis of the API.
To solve this problem, I used an interceptor that can evaluate the URL of the response and depending on that add/remove the Cache Headers.
For your problem the interceptor would be something like this,
public class HeaderInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url();
// Add headers depending upon the url
// Proceed the request
}
}
Add this interceptor to your OkHttp client, and it will add particular headers to a set of calls.
Hope it helps.
In my API server returns HTTP 400 response code if request does not pass validation, and provides detailed message, that should be parsed as the response.
For example:
public class RegistrationResponse {
private String emailError; // Detailed message. Null if no error occured
}
But Robospice (Retrofit + OkHttp) fires onRequestFailure() with message "retrofit.RetrofitError: 400 BAD REQUEST" in this case and, of course, does not parse anything.
How should I make it parse the response in case if response code is not 2XX?
You should declare Retrofit methods that return HTTP Response objects and check the raw object in your loadDataFromNetwork() for the status you need. This way, however, you will skip the out-of-the-box functionality of parsing responses and will have to do that manually.
Therefore, you should also find a way to reuse the Converter passed to your RestAdapter in the RetrofitSpiceService. Overriding the RetrofitSpiceService#createConverter() method is probably the simplest way to achieve this.