Preventing HttpClient 4 from following redirect - android

I'm connecting to my AppEngine application using the Apache HttpComponents library. In order to authenticate my users, I need to pass an authentication token along to the application's login address (http://myapp.appspot.com/_ah/login?auth=...) and grab a cookie from the header of the response. However, the login page responds with a redirect status code, and I don't know how to stop HttpClient from following the redirect, thus thwarting me from intercepting the cookie.
Fwiw, the actual method I use to send the request is below.
private void execute(HttpClient client, HttpRequestBase method) {
// Set up an error handler
BasicHttpResponse errorResponse = new BasicHttpResponse(
new ProtocolVersion("HTTP_ERROR", 1, 1), 500, "ERROR");
try {
// Call HttpClient execute
client.execute(method, this.responseHandler);
} catch (Exception e) {
errorResponse.setReasonPhrase(e.getMessage());
try {
this.responseHandler.handleResponse(errorResponse);
} catch (Exception ex) {
// log and/or handle
}
}
}
How would I stop the client from following the redirect?
Thanks.
Update:
As per the solution below, I did the following after creating a DefaultHttpClient client (and before passing it to the execute method):
if (!this.followRedirect) {
client.setRedirectHandler(new RedirectHandler() {
public URI getLocationURI(HttpResponse response,
HttpContext context) throws ProtocolException {
return null;
}
public boolean isRedirectRequested(HttpResponse response,
HttpContext context) {
return false;
}
});
}
More verbose than it seems it needs to be, but not as difficult as I thought.

You can do it with the http params:
final HttpParams params = new BasicHttpParams();
HttpClientParams.setRedirecting(params, false);
The method has no javadoc, but if you look at the source you can see it sets:
HANDLE_REDIRECTS
which controls:
Defines whether redirects should be handled automatically

With the Version 4.3.x of HttpClient its directly in the clientBuilder.
so when u build your Client use:
CloseableHttpClient client = clientBuilder.disableRedirectHandling().build();
I know its an old question but I had this problem too and want to share my solution.

Try using a RedirectHandler. That may require extending DefaultHttpClient to return your custom implementation from createRedirectHandler().

The RedirectHandler seem to be deprecated, I managed to disable automatically following the redirect responses by changing the default RedirectionStrategy for the DefaultHttpClient liks this:
httpClient.setRedirectStrategy(new RedirectStrategy() {
#Override
public boolean isRedirected(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException {
return false;
}
#Override
public HttpUriRequest getRedirect(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException {
return null;
}
});
On the downside, this tie us to a specific implementation of the HttpClient but it does the job.

a quick google presented: http://hc.apache.org/httpclient-3.x/redirects.html

Related

How to parse a none standard HTTP response?

I'm having a real hard time figuring out how to parse a none standard HTTP response.
The none standard response contains ICY 200 OK instead of HTTP 200 OK. Here is a sample URL that sends the none standard HTTP response.
http://50.117.121.162:80
Since Android 4.4 HttpURLConnection will no longer work with these none standard responses. I have tried using the HttpClient by Apache but it doesn't work because of the none standard HTTP response. I have then tried following the guide for adding a custom response parser, but Android doesn't seem have all the classes needed to do it.
I'm really struggling to figure out a solution. Possibly modify the none standard response before it is parsed by the HttpClient or the HttpURLConnection could work but I'm not sure if that is even possible...
Any help would be greatly appreciated.
After a lot of research for a small/lite http client library, I ran into this port of the apache httpclient for android. The library provided a complete support for http connections. Then I simply modified the source code, particularly the BasicLineParser to replace ICY with HTTP/1.0.
I had similar problem with KitKat and had a success with using two classes found here for http post. They are incredibly easy to use and you can modify the protocol params easily too.
There is another solution to this issue in Android 4.4 but it requires using Apache HttpClient. This is based on possibility of providing custom response parser into Apache Http engine that can change ICY 200 OK to HTTP/1.0 200 OK. This is based on general idea presented in:
http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/advanced.html
I have successfully used following code.
public class IcyClientConnection extends DefaultClientConnection{
#Override
protected HttpMessageParser createResponseParser(SessionInputBuffer buffer,
HttpResponseFactory responseFactory, HttpParams params) {
return new IcyHttpResponseParser(
buffer,
new BasicLineParser (),
responseFactory,
params);
}
}
public class IcyClientConnectionOperator extends DefaultClientConnectionOperator {
public IcyClientConnectionOperator(SchemeRegistry schemes) {
super(schemes);
}
#Override
public OperatedClientConnection createConnection() {
return new IcyClientConnection();
}
}
public class IcyClientConnManager extends SingleClientConnManager {
public IcyClientConnManager(HttpParams params, SchemeRegistry schreg) {
super(params, schreg);
}
#Override
protected ClientConnectionOperator createConnectionOperator(
SchemeRegistry schreg) {
return new IcyClientConnectionOperator(schreg);
}
}
Now you have to extend parser used by default and add code that will change wrong server replay to correct one. Normally code will block on hasProtocolVersion.
public class IcyHttpResponseParser extends DefaultResponseParser{
private CharArrayBuffer icyLineBuf;
private int icyMaxGarbageLines = 1000;
private final HttpResponseFactory icyResponseFactory;
public IcyHttpResponseParser(SessionInputBuffer buffer, LineParser parser,
HttpResponseFactory responseFactory, HttpParams params) {
super(buffer, parser, responseFactory, params);
this.icyLineBuf = new CharArrayBuffer(128);
icyResponseFactory = responseFactory;
}
#Override
protected HttpMessage parseHead(SessionInputBuffer sessionBuffer)
throws IOException, HttpException {
int count = 0;
ParserCursor cursor = null;
do {
// clear the buffer
this.icyLineBuf.clear();
final int i = sessionBuffer.readLine(this.icyLineBuf);
//look for ICY and change to HTTP to provide compatibility with non standard shoutcast servers
String tmp = icyLineBuf.substring(0, this.icyLineBuf.length());
if(tmp.contains("ICY ")){
tmp = tmp.replace("ICY", "HTTP/1.0");
}
//copy
this.icyLineBuf = new CharArrayBuffer(128);
System.arraycopy(tmp.toCharArray(), 0, icyLineBuf.buffer(), 0, tmp.length());
icyLineBuf.setLength( tmp.length());
if (i == -1 && count == 0) {
// The server just dropped connection on us
throw new NoHttpResponseException("The target server failed to respond");
}
cursor = new ParserCursor(0, this.icyLineBuf.length());
if (lineParser.hasProtocolVersion(this.icyLineBuf, cursor)) {
// Got one
break;
} else if (i == -1 || count >= this.icyMaxGarbageLines) {
// Giving up
throw new ProtocolException("The server failed to respond with a " +
"valid HTTP response");
}
//if (this.log.isDebugEnabled()) {
// this.log.debug("Garbage in response: " + this.lineBuf.toString());
// }
count++;
} while(true);
//create the status line from the status string
final StatusLine statusline = lineParser.parseStatusLine(this.icyLineBuf, cursor);
return this.icyResponseFactory.newHttpResponse(statusline, null);
}
}
Plug in HttpClient:
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
Scheme ftp = new Scheme("ftp", PlainSocketFactory.getSocketFactory(), 21);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(ftp);
HttpClient httpClient = new DefaultHttpClient(new IcyClientConnManager(params, sr), params);
This is still being tested but initial results are promising.
Thanks #Michael M, you can even make it simpler by subclassing the BasicLineParser instead of subclassing the DefaultResponseParser.
I've uploaded the code into a gist
To use it:
IcyGetRequest request = new IcyGetRequest(urlStr);
HttpResponse response = request.get();
int responseCode = response.getStatusLine().getStatusCode();
Create an runnable that creates socket proxy then you will be able to response with HTTP/1.0 instead of ICY , then just connect to this local socket proxy with your player
Here a modification of the solution from Michal M in case you don't like to create lots of subclasses just to configure already available HttpClient classes.
final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
HttpClient httpClient = new DefaultHttpClient() {
#Override
protected ClientConnectionManager createClientConnectionManager() {
return new SingleClientConnManager(getParams(), schemeRegistry) {
#Override
protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
return new DefaultClientConnectionOperator(schreg) {
#Override
public OperatedClientConnection createConnection() {
return new DefaultClientConnection() {
#Override
protected HttpMessageParser createResponseParser(SessionInputBuffer buffer, HttpResponseFactory responseFactory, HttpParams params) {
return new IcyHttpResponseParser(buffer, new BasicLineParser(), responseFactory, params);
}
};
}
};
}
};
}
};
Probably there is a way to get the SchemeRegistry obsoleted if one could get hold somehow from within the DefaultHttpClient class.

Android Http Client Caching

In my android application I am trying to cache the response of Http Client. I am testing this task using facebook graph api and have the following url: https://graph.facebook.com/riz.ahmed.52
For the first time I get the "first_name" and display it. Then I change the First Name of my facebook profile and call the same link again. I am expecting to get the old/cached "first_name" but I get the updated one. The console always shows the "The response came from an upstream server" message when I call the url.
My code for Http Client is as follows:
CacheConfig cacheConfig = new CacheConfig();
cacheConfig.setMaxCacheEntries(1000);
cacheConfig.setMaxObjectSizeBytes(8192);
//HttpClient httpclient = new CachingHttpClient(new DefaultHttpClient(), cacheConfig);
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
// Updated code [START]
httpclient.addResponseInterceptor(new HttpResponseInterceptor() {
public void process(
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
response.removeHeader(response.getFirstHeader("Pragma"));
response.removeHeader(response.getFirstHeader("Expires"));
}
});
// Updated code [END]
HttpGet httpget = new HttpGet(url);
// Execute HTTP Get Request
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity = response.getEntity();
String res = EntityUtils.getContentCharSet(entity);
CacheResponseStatus responseStatus = (CacheResponseStatus) localContext.getAttribute(
CachingHttpClient.CACHE_RESPONSE_STATUS);
switch (responseStatus) {
case CACHE_HIT:
System.out.println("A response was generated from the cache with no requests " +
"sent upstream");
break;
case CACHE_MODULE_RESPONSE:
System.out.println("The response was generated directly by the caching module");
break;
case CACHE_MISS:
System.out.println("The response came from an upstream server");
break;
case VALIDATED:
System.out.println("The response was generated from the cache after validating " +
"the entry with the origin server");
break;
}
I am using Android 2.3.3. Please let me know what I am missing here
The page you are loading specifies a Expires:Sat, 01 Jan 2000 00:00:00 GMT header, i.e. it's always considered stale and must always be re-fetched.
Edit:
Also returns a Pragma: no-cache apparently. Basically, it's telling your HTTP client to never cache this page. You may be able to remove these headers with a HttpResponseInterceptor if you're dead-set on caching the response.
#2 Edit:
Using http-clientcache-4.2.jar is going to be problematic as it is not completely compatible with the version of the HTTP client packaged with the Android SDK - you're going to get NoClassDefFoundErrors and similar nonsense when using it.
However - if you "build-your-own" by downloading the source for clientcache-4.2 and strip out any unfulfilled references (such as refactoring the package name of the commons logging) & killing of all the annotations sprinkled throughout the code (etc.) you can probably get a working version. If you do, this worked:
class MakeCacheable implements HttpResponseInterceptor {
public static MakeCacheable INSTANCE = new MakeCacheable();
public void process(HttpResponse resp, HttpContext ctx) throws HttpException, IOException {
resp.removeHeaders("Expires");
resp.removeHeaders("Pragma");
resp.removeHeaders("Cache-Control");
}
}
Injected into the DefaultHttpClient used by the CachingHttpClient like so:
DefaultHttpClient realClient = new DefaultHttpClient();
realClient.addResponseInterceptor(MakeCacheable.INSTANCE, 0); // This goes first
CachingHttpClient httpClient = new CachingHttpClient(realClient, cacheConfig);
If an entry is cached or not is decided by the ResponseCachingPolicy which unfortunately is a final in the CachingHttpClient, but looking through it will show all the headers that need to go to make an un-cacheable entry cacheable.

How to unit test a class that uses HttpClient in Android using the built-in framework?

I've got a class:
public class WebReader implements IWebReader {
HttpClient client;
public WebReader() {
client = new DefaultHttpClient();
}
public WebReader(HttpClient httpClient) {
client = httpClient;
}
/**
* Reads the web resource at the specified path with the params given.
* #param path Path of the resource to be read.
* #param params Parameters needed to be transferred to the server using POST method.
* #param compression If it's needed to use compression. Default is <b>true</b>.
* #return <p>Returns the string got from the server. If there was an error downloading file,
* an empty string is returned, the information about the error is written to the log file.</p>
*/
public String readWebResource(String path, ArrayList<BasicNameValuePair> params, Boolean compression) {
HttpPost httpPost = new HttpPost(path);
String result = "";
if (compression)
httpPost.addHeader("Accept-Encoding", "gzip");
if (params.size() > 0){
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
}
try {
HttpResponse response = client.execute(httpPost);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
if (entity.getContentEncoding() != null
&& "gzip".equalsIgnoreCase(entity.getContentEncoding()
.getValue()))
result = uncompressInputStream(content);
else
result = convertStreamToString(content);
} else {
Log.e(MyApp.class.toString(), "Failed to download file");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private String uncompressInputStream(InputStream inputStream)
throws IOException {...}
private String convertStreamToString(InputStream is) {...}
}
I cannot find a way to test it using a standard framework. Especially, I need to simulate total internet lost from inside the test.
There are suggestions to manually turn the Internet in the emulator off while performing the test. But it seems to me as not quite a good solution, because the automatic tests should be... automatic.
I added a "client" field to the class trying to mock it from inside the test class. But implementation of the HttpClient interface seems quite complex.
The Robolectric framework allows the developers to test Http connection as far as I know. But I guess there is some way to write such a test without using so big additional framework.
So are there any short and straightforward ways of unit testing classes that use HttpClient? How did you solve this in your projects?
I added a "client" field to the class trying to mock it from inside the test class. But implementation of the HttpClient interface seems quite complex.
I am a little bit confuse about this statement. From the question title, you are asking about unit-testing httpClint, by mocking a FakeHttpClient may help you unit-testing other part of app except httpClient, but doesn't help anything for unit-testing httpClient. What you need is a FakeHttpLayer for unit-testing httpClient (no remote server, network requires, hence unit-testing).
HttpClient Dummy Test:
If you only need examine app behavior in the situation that internet is lost, then a classic Android Instrument Test is sufficient, you can programmatically turn the Internet in the emulator off while performing the test:
public void testWhenInternetOK() {
... ...
webReader.readWebResource();
// expect HTTP 200 response.
... ...
}
public void testWhenInternetLost() {
... ...
wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
wifiManager.setWifiEnabled(false);
webReader.readWebResource();
// expect no HTTP response.
... ...
}
This requires the remote http server is completely setup and in a working state, and whenever you run your test class, a real http communication is made over network and hit on http server.
HttpClient Advanced Test:
If you want to test app behavior more precisely, for instance, you want to test a http call in you app to see if it is handle different http response properly. the Robolectric is the best choice. You can use FakeHttpLayer and mock the http request and response to whatever you like.
public void setup() {
String url = "http://...";
// First http request fired in test, mock a HTTP 200 response (ContentType: application/json)
HttpResponse response1 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 200, null);
BasicHttpEntity entity1 = new BasicHttpEntity();
entity1.setContentType("application/json");
response1.setEntity(entity1);
// Second http request fired in test, mock a HTTP 404 response (ContentType: text/html)
HttpResponse response2 = new DefaultHttpResponseFactory().newHttpResponse(HttpVersion.HTTP_1_1, 404, null);
BasicHttpEntity entity2 = new BasicHttpEntity();
entity2.setContentType("text/html");
response2.setEntity(entity2);
List<HttpResponse> responses = new ArrayList<HttpResponse>();
responses.add(response1);
responses.add(response2);
Robolectric.addHttpResponseRule(new FakeHttpLayer.UriRequestMatcher("POST", url), responses);
}
public void testFoo() {
... ...
webReader.readWebResource(); // <- a call that perform a http post request to url.
// expect HTTP 200 response.
... ...
}
public void testBar() {
... ...
webReader.readWebResource(); // <- a call that perform a http post request to url.
// expect HTTP 404 response.
... ...
}
Some pros of using Robolectric are:
Purely JUnit test, no instrument test so don't need start emulator (or real device) to run the test, increase development speed.
Latest Robolectric support single line of code to enable/disable FakeHttpLayer, where you can set http request to be interpreted by FakeHttpLayer (no real http call over network), or set the http request bypass the FakeHttpLayer(perform real http call over network). Check out this SO question for more details.
If you check out the source of Robolectric, you can see it is quite complex to implement a FakeHtppLayer properly by yourself. I would recommend to use the existing test framework instead of implementing your own API.
Hope this helps.

How to use HTTP cookies in an Android app?

I am trying to maintain a logged-in user session between my Android app and my Drupal website. In my research, it comes down to sending cookie(s) back to Drupal but I am struggling to implement it. How can I make a start on this?
Just in case anyone else got the same issue, I had similar problem and I was able to solve it by the following code:
1- Define CookieManager and CookieStore in your class
CookieManager cookieManager;
CookieStore cookieStore;
2- Add default cookie handler, e.g. in the class constructor or in OnCreate method
cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
3- Use the cookie storage when you do HTTP request
public byte[] openURI(String uri) {
try {
URI uriObj = new URI(uri);
DefaultHttpClient client = new DefaultHttpClient();
// Use the cookieStor with the request
if (cookieStore == null) {
cookieStore = client.getCookieStore();
} else {
client.setCookieStore(cookieStore);
}
HttpGet getRequest = new HttpGet(uriObj);
HttpResponse response = client.execute(getRequest);
// Read the response data
InputStream instream = response.getEntity().getContent();
int contentLength = (int) response.getEntity().getContentLength();
byte[] data = new byte[contentLength];
instream.read(data);
response.getEntity().consumeContent();
return data ;
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I am pretty sure that if you use the HttpClient provided with the Android APIs it should do session management with cookies for you until you close the connection manually.
If I am wrong on this, then you can easily work around this by implementing your own cookie store using the CookieStore interface or the BasicCookieStore class. If all else fails, you can store cookies manually and set cookies in the header each time you make a HTTP request.
I am not sure how this might change for your particular problem though but this should most likely work considering the description of the problem you gave.

Android asynchronus HTTP client problem

I am trying to implement asynchronus http client for Android and I am haveing a trouble with type mismatch:
The method execute(HttpUriRequest) in the type HttpClient is not applicable for the arguments (HttpRequest)
I am doing all based on this tutorial: http://blog.androgames.net/12/retrieving-data-asynchronously/
Have found a type in AsynchronousSender - private HttpRequest request; but I have still problem with above which occurs in:
public void run() {
try {
final HttpResponse response;
synchronized (httpClient) {
response = getClient().execute(request); //<-- here is that problem
}
// process response
wrapper.setResponse(response);
handler.post(wrapper);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Can you suggest anything ?
cheers,
/Marcin
The code snippets on http://blog.androgames.net/12/retrieving-data-asynchronously/ are wrong. To fix it just replace HttpRequest with HttpUriRequest since the method signature is: HttpClient#execute(HttpUriRequest). It shouldn't be any problem since most requests you work with are HttpUriRequest instances.

Categories

Resources