I'm using retrofit2 to post credentials in a multipart/form-data. The goal is to receive a session cookie.
The solution works as expected running in Android 8, but can't seem to work on older versions such as 5 - 6. I get no errors, but the server doesn't return any cookie.
The code looks something like this
protected Void doInBackground(Void... params) {
if (service == null) {
boxRestService = getService(Constants.URL);
}
MultipartBody mPart = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(Constants.FORMS_USERNAME, credentials.first)
.addFormDataPart(Constants.FORMS_PASSWORD, credentials.second).build();
Call<ResponseBody> currentCall = service.getFormAuthCookie(mPart);
try {
currentCall.execute();
} catch (Exception e) {
Log.d("GET_AUTH_BACKGROUND", e.getMessage());
}
return null;
}
Furthermore, the call looks as such:
#POST("/check/login")
Call<ResponseBody> getFormAuthCookie(#Body MultipartBody body);
In both platforms, retrofit produces the same (correct) post request:
--> POST https://website.dom/check/login http/1.1
Content-Type: multipart/form-data; boundary=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Content-Length: 258
--aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Content-Disposition: form-data; name="sph_username"
Username
--aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Content-Disposition: form-data; name="sph_password"
Password
--aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa--
--> END POST (258-byte body)
Since I need the cookie for the next request, this request is performed synchronously in a background thread.
For the cookie part, that I don't believe it's an issue, I'm using a JavaNetCookieJar.
The request also works fine using Advanced Rest Client.
I'm sure I'm missing something very obvious.
Are those methods usable in older android versions?
Thank you very much.
-nls
So, turns out the server was redirecting the requests without my knowledge. I found this by accident while experimenting with the HTTP client options.
The solution, for this (very, very) specifit cenario, is to disable the following of redirects.
Thus, I changed the retrofit builder method to do something as follows:
OkHttpClient clt = new OkHttpClient().Builder()
builder.followRedirects(false) // <-- Important!
...
.build();
Retrofit r = new Retrofit.Builder()
.client(clt)
...
.build();
Still don't know why it worked in newer versions of android.
Hopefully this helps anyone.
Cheers
-nls
Related
I have seen other threads for this issue but unable to get any proper answer.
#POST("task/GetAllTasks")
Call<MyTask> getMyTasks(#Header("Authorization") String token, #Query("EmployeeId") String emp);
This is how I am calling, at first I thought it is due to GET request data limitation because GET imposes data limits and then I changed request from GET to POST but issue still persists.
ApiUtils.getTaskService().getMyTasks(apiToken, employeeId).enqueue(new Callback<MyTask>() {
#Override
public void onResponse(Call<MyTask> call, Response<MyTask> response) {
// ... Successful code goes here
}
#Override
public void onFailure(Call<MyTask> call, Throwable t) {
//.. This block of code executing now :(
}
}
Always onFailure is being called. I have tested this same request on Postman and it is returning data. Content-Length is content-length →45720
It does work on small amount of data as I have tested it on Dev database which has smaller amount of data but on Live environment it is continuously causing problem.
Please suggest a solution or should I leave Retrofit and move to native Android library for this?
EDIT: Can we increase request timeout in Retrofit, if yes then how?
Try to increase your timeout:
OkHttpClient client = new OkHttpClient().newBuilder()
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS).build();
And set it to your retrofit:
new Retrofit.Builder().baseUrl("xxx").client(client).build().create(xxx.class);
Use #Part to send big string data it can easily send your data to server side
For ex.
You need to send some string that name is "abc" then in the retrofit code
#Part("abc") //your string variable
Its works for me and I save that data to mediumtext in mysql
and the code is $_REQUEST['abc'];
This is how my request looks like:
ApiService apiService = retrofit.create(ApiService.class);
Observable<Response<UserUpdateResponse>> response = apiService.updateUser(Utils.getHeader(), object);
response.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccessUpdate,
this::onErr,
this::hideDialogLoading);
It's supposed to return 'code':'205' 'msg':'successfully update'. But when server response any code 201,202 (anything not 200) it will go to error.
Here is the Error.
java.net.ProtocolException: HTTP 205 had non-zero Content-Length: 121
So how do I prevent it from error, or how do I get error body? Thank you!.
HTTP response codes have a predefined definition and some have requirements that they must fullfill to be considered a valid HTTP payload. You cannot redefine what these codes mean for your application and expect well-implemented clients to accept it.
Looking specifically at HTTP 205 - Reset Content, which has the following requirement:
Since the 205 status code implies that no additional content will be provided, a server MUST NOT generate a payload in a 205 response.
Generally applications will just return HTTP 200 for all requests and include application-specific error codes in the payload. What you're doing does not make much sense.
So technically, I can get response 2xx. The problem was that server response body in response code 205 that suppose to be null (https://www.rfc-editor.org/rfc/rfc7231#section-6.3.6). So after set body null on server, android side works fine.
Is there a proper explanation on how to add caching and ETAG/If-None-Match support to Retrofit+OkHttp?
I'm struggling to add Etag support on 2 projects, and at first I suspected that there might be an issue with HTTP headers, another project has everything set correctly and caching still doesn't work as expected.
Following are my attempts to make it work. Results show that caching seems to be working within the same instance of the application, but as soon as I restart - everything loads long again.
Also, in my logs I didn't see If-None-Match being added to a request, so I assume that server isn't aware of ETag and still recalculates the response completely.
Here are some code samples:
public class RetrofitHttpClient extends UrlConnectionClient
{
private OkUrlFactory generateDefaultOkUrlFactory()
{
OkHttpClient client = new com.squareup.okhttp.OkHttpClient();
try
{
Cache responseCache = new Cache(baseContext.getCacheDir(), SIZE_OF_CACHE);
client.setCache(responseCache);
}
catch (Exception e)
{
Logger.log(this, e, "Unable to set http cache");
}
client.setConnectTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
client.setReadTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS);
return new OkUrlFactory(client);
}
private final OkUrlFactory factory;
public RetrofitHttpClient()
{
factory = generateDefaultOkUrlFactory();
}
#Override
protected HttpURLConnection openConnection(retrofit.client.Request request) throws IOException
{
return factory.open(new URL(request.getUrl()));
}
}
Rest adapter is then created with FULL log level and a custom tag:
restAdapter = new RestAdapter.Builder()
.setClient(new RetrofitHttpClient())
.setEndpoint(Config.BASE_URL)
.setRequestInterceptor(new SignatureSetter())
.setConverter(new JacksonConverter(JsonHelper.getObjectMapper()))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("=NETWORK="))
.build();
I have a long request on the first screen of the app for testing.
When I open the app - it takes 7 seconds to complete the request. If I pause and resume the app - same request takes 250ms, clearly hitting the cache. If I close the app completely and restart - it again takes 7 seconds.
UPDATE:
As was suggested, I have used a custom Retrofit build and attached a LoggingInterceptor. Here's what I'm getting.
Received response for *** in 449,3ms
Date: Wed, 07 Jan 2015 09:02:23 GMT
Server: Apache
X-Powered-By: PHP/5.4.31
Access-Control-Allow-Credentials: true
Pragma:
Cache-Control: public, max-age=3600
X-Frame-Options: SAMEORIGIN
Etag: "hLxLRYztkinJAB453nRV7ncBSuU=-gzip"
Last-Modified: Wed, 24 Dec 2014 13:09:04 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1420621288104
OkHttp-Received-Millis: 1420621288554
Sending request **** on Connection{****:80, proxy=DIRECT# hostAddress=**** cipherSuite=none protocol=http/1.1}
Accept: application/json;
Host: ****
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.2.0
Response is equal to described above
As you can see, no If-None-Match header is present in the next request.
I see this question keeps getting attention and as soon as there is no real answer I can pick from - I'm am providing my investigation on the topic and closing the thread for now.
The end result of investigation and some discussions in the retrofit and okhttp threads on GitHub was that there was supposedly an issue in OkHttp that could prevent If-None-Match tag being set for the outgoing requests.
The issue was supposed to be fixed in OkHttp 2.3, and I'm using 'supposed' here because I didn't yet test if it really works. The testing was difficult because I was using Retrofit, and Retrofit itself had to be updated to use the new version of OkHttp and add some new Interceptors support to be able to debug all headers that are set by OkHttp.
Related thread is here: https://github.com/square/okhttp/issues/831
I'm not sure if Retrofit was updated after that. Hopefully it was, so there is a good chance that issue is already fixed and Etag should properly work - just make sure you have latest versions of Retrofit and OkHttp.
I will try to test everything myself once I have time.
Using OkHttp interceptors will help you to diagnose the headers coming in & out of your application. The interceptors doc gives a code example of an interceptor that logs request & response headers on the network. You can use this as-is.
class LoggingInterceptor implements Interceptor {
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
To hook it up to Retrofit, you'll need to get a pre-release snapshot of Retrofit. As of January, 2015, the currently-shipping versions of Retrofit don't participate in OkHttp's interceptors. There will be a release shortly that does, but it's not ready yet.
I was having a similar problem: OkHttp not hitting cache ever, even when the server was sending same ETAG.
My issue was SIZE_OF_CACHE. I was defining a very small size.
Try to increase it (something like 10 * 1024 * 1024 works for me)
Also you can explore /data/data//files/cache to see if there is actually something stored there
I used Retrofit in order to make HTTP requests and JSON parsing and I loved the way to turn on debug logs. Logs allow to see body requests, URL... which is very useful. As Retrofit use OkHttp, I'm wondering if OkHttp also have a way to enable logs for each requests made.
Using an Interceptor, you can define the following class:
class LoggingInterceptor implements Interceptor {
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d("OkHttp", String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
Log.d("OkHttp", String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
And add it:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
The interceptors feature is currently in review, but you can build your own version of okHttp with the feature by applying the code changes in the pull request.
You can implement the functionality you want with something like this
// Create an interceptor which catches requests and logs the info you want
RequestInterceptor logRequests= new RequestInterceptor() {
public Request execute(Request request) {
Log.i("REQUEST INFO", request.toString());
return request; // return the request unaltered
}
};
OkHttpClient client = new OkHttpClient();
List<RequestInterceptor> requestInterceptors = client.requestInterceptors();
requestInterceptros.add(logRequests);
A test is included within the pull request if you want to see more.
I'm going to have to warn you ahead of time about using this. There may be changes to the interceptor API since it has yet to be merged in. Don't use it with production code, but it's innocuous enough for personal testing.
None yet. But there's an interceptors feature under development that should make it easy.
There's an official solution from Square (employee) now.
You can try:
https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
for okhttp3
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> Log.d(YourClass.class.getSimpleName(), "OkHttp: " + message));
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClient.getHttpClient().interceptors().add(logging);
you can enble logging and integerate with Timber to log only in debug.
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
Timber.tag("OkHttp: ");
Timber.i(message);
}
}).setLevel(HttpLoggingInterceptor.Level.BODY);
client = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build();
I add some info regarding OkHttp3, because it supports logging out of the shelf.
First, be sure to have both these dependencies, the OkHttp3 main package and the specific package containing the logger implementations. Here I use 3.14.6.
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.6</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>3.14.6</version>
</dependency>
Then set up your OkHttp client properly.
...
import okhttp3.logging.HttpLoggingInterceptor;
...
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> logger.info(message));
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.build();
The main idea is that you have to explain to the HttpLoggingInterceptor how to log, so in the above example the message is just routed to an Slf4j logger at INFO level.
For better ui and debugging of OkHttp Network calls you can use libraries like GANDER
Others features include :
Apps using Gander will display a notification showing a summary of ongoing HTTP activity. Tapping on the notification launches the full Gander UI. Apps can optionally suppress the notification, and launch the Gander UI directly from within their own interface. HTTP interactions and their contents can be exported via a share intent.
Search HTTP Activity and also request and response
The main Gander activity is launched in its own task, allowing it to be displayed alongside the host app UI using Android 7.x multi-window support.
Gander Provides following variants
Persistence : Saves logs to disk and TTL can be controlled
In Memory Database : Logs will be in memory as long as the app lifecycle.
No Op : This does nothing. So if users want Gander only in debug builds they can releaseCompile NoOp without dealing with variants, if(Build.DEBUG) ..etc
You can add logs by using:
public final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor())
.cache(new Cache(cacheDir, cacheSize))
.build();
Which will use default config:
Logger DEFAULT = message -> Platform.get().log(INFO, message, null);
I have an issue with caching using OkHttpClient 2.0. It seems that the Response is ignoring the Cache-Control header completely. This is how I am setting up the client and the cache.
OkHttpClient client = new OkHttpClient();
cache = new Cache(new File(Session.getInstance().getContext().getCacheDir(),"http"), 10 * 1024 * 1024);
client.setCache(cache);
client.setCookieHandler(CookieHandler.getDefault());
client.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
client.setReadTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS);
I believe that the cache directory is created correctly. This is what I see in journal in the /cache/http directory of my application.
libcore.io.DiskLruCache
1
201105
2
This is how I am creating the Request.
Request mRequest = new Request.Builder().url(mUrl).get().build();
Getting the response :
Response response = client.newCall(mRequest).execute();
When using curl, the headers as as follows.
< HTTP/1.1 200 OK
< Date: Fri, 27 Jun 2014 19:39:40 GMT
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Cache-Control: no-transform, max-age=1800
< Content-Type: application/json
< Transfer-Encoding: chunked
The OKHttp response headers are as follows.
Connection:Keep-Alive
Content-Type:application/json
Date:Fri, 27 Jun 2014 18:58:30 GMT
Keep-Alive:timeout=5, max=100
OkHttp-Received-Millis:1403895511337
OkHttp-Selected-Protocol:http/1.1
OkHttp-Sent-Millis:1403895511140
Server:Apache-Coyote/1.1
Transfer-Encoding:chunked
The responses never get cached and the call client.getCache().getHitCount() always gives 0. Can someone please suggest what changes might be required here to make the cache work? Thanks.
Okay, the problem was all my get and post requests were using the Authorization Bearer xxxx header and http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.8 states that these requests can't be cached. The solution was to use s-maxage on the server instead of just max age according to this :
When a shared cache (see section 13.7) receives a request
containing an Authorization field, it MUST NOT return the
corresponding response as a reply to any other request, unless one
of the following specific exceptions holds:
If the response includes the "s-maxage" cache-control
directive, the cache MAY use that response in replying to a
subsequent request.
Are you reading the entire response body? OkHttp won't cache unless you consume the entire response.
I realize you solved your specific problem, but the symptom you describe has another cause.
When using the okhttp-urlconnection, caching doesn't kick in by default, unless we do this:
connection.setUseCaches(true)
(It should be on by default, but some library I was using was setting it to off)