I have an Android app that sends commands to a robot via HTTP. (The robot server is written in Python using the BaseHttpServer class.) I'm setting up my connection in the app correctly as far as I can tell, but most requests fail to return and if they do there is a very long delay. Here is the initialization code:
private void setupHttpStuff() {
HttpParams params = new BasicHttpParams();
ConnManagerParams.setMaxTotalConnections(params, 10);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 1504));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
this.HttpClient = new DefaultHttpClient(cm, params);
}
The GET request takes place in an AsyncTask and the code looks like:
HttpContext localContext = new BasicHttpContext();
HttpGet httpGet = new HttpGet(url);
mClient.execute(httpGet, localContext);
Where mClient is a variable shared from the Activity.
By using Log.d messages I've determined that the first two requests work fine but then all subsequent requests (in separate AsyncTasks) hang and never return from execute. I've also tried to manually create a Socket and send a HttpRequest:
Socket socket = new Socket("192.168.1.45", 1504);
HttpParams params = new BasicHttpParams();
DefaultHttpClientConnection conn = new DefaultHttpClientConnection();
conn.bind(socket, params);
HttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader(); // Hangs here
conn.receiveResponseEntity(response);
socket.close();
But this hangs on the conn.receiveResponseHeader() line and the server never sees the request.
The Python server works fine with requests from a browser, just not with my Android app. I've tried this single-threaded as well in the main UI but the same effect occurs.
EDIT
Here is the Python code that causes the problems:
class MyHand(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
qv = parse_qs(get_qs(urlparse(self.path)))
if("method" not in qv):
self.send_nack()
elif(qv["method"][0] == "drive"):
createbot.Drive(int(qv["velocity"][0]), int(qv["radius"][0]))
self.send_ack()
else:
self.send_nack()
def send_ack(self, content_type='text/html'):
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
def send_nack(self):
self.send_response(500)
self.send_header('Content-type', 'text/html')
self.end_headers()
This may be related to keep-alive. Android sends Connection: Keep-Alive and Python sends back a Connection: close. I've looked at the HTTP headers that are being sent from Firefox and HttpClient and there is no difference (other than Firefox sending some additional Accept headers). Somehow Android is expecting the Python server to send something other than HTTP/1.1 200 OK\nContent-type: text/html and therefore keeping the socket open. I ended up writing my own basic HTTP server and client.
Python code:
class SingleTCPHandler(SocketServer.BaseRequestHandler):
def handle(self):
data = self.request.recv(1024)
parts = data.rsplit(" ")
if (parts[0] != "GET"):
self.request.close()
return
url = parts[1].split("?")[1]
qv = parse_qs(url)
...
Android:
Socket socket = null;
PrintWriter output = null;
try {
socket = new Socket(this.ipAddress, this.portNo);
output = new PrintWriter(new BufferedWriter
(new OutputStreamWriter(socket.getOutputStream())), true);
output.println("GET " + url);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
if (output != null)
output.close();
}
Related
When my activity loads, I am connecting to a web service. As and when I get the response from service, I again call then service and so on.
#Override
protected void onCreate(Bundle savedInstanceState) {
….
callWebMethod();
}
// Called on getting response
#Override
public void run(String value) {
….
callWebMethod();
}
This is how I am connecting to service
HttpGet request = new HttpGet(url + combinedParams);
HttpClient client = new DefaultHttpClient(httpParameters);
HttpResponse httpResponse;
httpResponse = client.execute(request);
responseCode = httpResponse.getStatusLine().getStatusCode();
message = httpResponse.getStatusLine().getReasonPhrase();
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
response = convertStreamToString(instream);
response = StringUtils.remove(response, "\n");
response = StringUtils.remove(response, '"');
}
Is it possible that I connect to the service only once at the start, then the connection remains open and application keeps on reading data from service till connection is forcefully closed.
Please let me know if more code is required.
Update: I then tried with ClientConnectionManager but still connection is again and again initialising. Though it is getting data. What I want is that connection remains open, and keeps on reading data from service.
HttpParams httpParameters = new BasicHttpParams();
SharedPreferences preferences = context.getSharedPreferences(
"MyPreferences", Context.MODE_PRIVATE);
int timeoutConnection = Integer.parseInt(preferences.getString(
"timeout", "60")) * 1000;
HttpConnectionParams.setConnectionTimeout(httpParameters,
timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, 2000);
System.setProperty("http.keepAlive", "true");
HttpClient client = new DefaultHttpClient(httpParameters);
ClientConnectionManager mgr = client.getConnectionManager();
client = new DefaultHttpClient(new ThreadSafeClientConnManager(
client.getParams(), mgr.getSchemeRegistry()),
client.getParams());
while (true) {
HttpResponse httpResponse;
try {
httpResponse = client.execute(request);
responseCode = httpResponse.getStatusLine().getStatusCode();
message = httpResponse.getStatusLine().getReasonPhrase();
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
response = convertStreamToString(instream);
response = StringUtils.remove(response, "\n");
response = StringUtils.remove(response, '"');
((Activity) context).runOnUiThread(new Runnable() {
public void run() {
callback.run(response); // This calls activity callback function.
}
});
// Closing the input stream will trigger connection release
// instream.close();
}
} catch (ConnectTimeoutException e) {
….
}
It sounds like what you really need is a socket connection (see here). A socket will stay connected and allow you to stream data back and forth with the socket server until you are finished.
you just need to close the InputStream you get from HttpResponse.getEntity().getContent() after you are done using/reading-it. This will officially indicate the end of your current request.
You can then proceed to execute another request, the same HttpClient connection will be used.
Add a close
InputStream instream = entity.getContent();
response = convertStreamToString(instream);
// close the InputSream
instream.close()
// you can now reuse the same `HttpClient` and execute another request
// using same connection
httpResponse = client.execute(request);
Is it possible that I connect to the service only once at the start,
then the connection remains open...
The web server has a role to play in this. If the server "ends" the HTTP response, there is no further communication going to happen on same HTTP call.
It is possible to keep an HTTP connection open, with help of server. In this case, server never really ends the response but keeps writing data to response stream after some time intervals, so client can keep listening.
The new replacement for the above technique is a duplex socket connection. Both client and server can send and receive messages over a socket. Again, both client and server have to support it properly, and necessary handling for connection drops etc has to be there.
There are android specific client implementations available like https://github.com/nkzawa/socket.io-client.java that take care of most of connection management for you.
I think you could try to use the AsyncTask class to try to keep your thread open and do what you want, like this:
public class ConnectToWebService extends AsyncTask<Void, Void, Boolean> {
#Override
protected Boolean doInBackground(Void... params) { ... }
#Override
protected void onPostExecute(final Boolean success) { ... }
#Override
protected void onCancelled() { ... }
}
Check the API documentation for more information ;)
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.
I'm experiencing some odd behavior in my HTTP requests. I have some users that are saying that this call isn't ever coming back (the spinner marking it's asynchronous call never goes away). I have seen this happen before, but I attributed it to the emulator going through Charles Proxy. I haven't yet seen it on actual phone until now.
I'm not sure what would cause this to happen, which is why I'm posting it here. Here's the call, using Jackson to deserialize the result into a Value Object. The two spots I saw the emulator freeze are httpclient.execute(httpGet); and getObjectMapper().readValue(jp, SyncVO.class);.
While debugging, stepping over the offending statement caused the debugger to never gain control back of stepping. Meanwhile, I see the request go out AND come back from the server through Charles. It's just that the app doesn't seem to get the response and just sits there.
So, here's the code. Thanks for any help!
public SyncVO sync(String userId, long lastUpdate, boolean includeFetch) throws IOException {
SyncVO result = null;
String url = BASE_URL + "users/" + userId + "/sync" + "?" + "fetch=" + includeFetch;
if (lastUpdate > 0) {
url += "&updatedSince=" + lastUpdate;
}
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
httpGet.setHeader("Accept-Encoding", "gzip");
httpGet.setHeader(AUTHORIZATION, BEARER + " " + mOAuthToken);
httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, USER_AGENT_STRING);
httpclient.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
HttpResponse response = httpclient.execute(httpGet);
if (isUnauthorized(response)) {
APPLICATION.needReauthentication();
return null;
}
if (response != null) {
InputStream stream = response.getEntity().getContent();
Header contentEncoding = response.getFirstHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
stream = new GZIPInputStream(stream);
}
InputStreamReader inReader = new InputStreamReader(stream, "UTF-8");
JsonParser jp = mJsonFactory.createJsonParser(inReader);
result = getObjectMapper().readValue(jp, SyncVO.class);
}
return result;
}
private ObjectMapper getObjectMapper() {
return (new ObjectMapper()
.configure(Feature.AUTO_DETECT_FIELDS, true)
.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true));
}
don't forget to consume entities content after each request.
HttpEntity entity = response.getEntity();
try {
if (entity != null)
entity.consumeContent();
} catch (IOException e) {
e.printStackTrace();
}
You should definitely use connection timeout and socket read and be prepared for the worst from the server. Network operations will never be 100% predictable and there is not much your client can do then so make sure you code optimally.
httpParameters = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 5000);
HttpConnectionParams.setSoTimeout(httpParameters, 10000);
You can also cancel a task with asyncTask.cancel(true);
The reason is because you have left stream open. As such, the response is left in limbo. This means your global variable httpClient is also left in limbo, and unable to get a new entity when it re-uses the client.
You should call close() after finishing with the stream.
stream.close();
Network calls take a while and will block the UI thread. Same with your jackson deserialization code. This stuff needs to be put on a separate thread. See AsyncTask for an easy way to do it.
I am developing an application on android, and there is a proxy server in the company preventing it from accessing the internet.
I recall there are Http methods in java that serves the purpose, I searched for them but with no result.
So what I'd like is how to put the server IP-address and port-number, with my login-username and password, and the domain-name.
Note: I have set my emulator to connect to the internet by going to the APNs and it successfully connects to the internet via the browser.
try to use the following code
/**
* A simple example that uses HttpClient to execute an HTTP request
* over a secure connection tunneled through an authenticating proxy.
*/
public class ClientProxyAuthentication {
public static void main(String[] args) throws Exception {
DefaultHttpClient httpclient = new DefaultHttpClient();
try {
httpclient.getCredentialsProvider().setCredentials(
new AuthScope("localhost", 8080),
new UsernamePasswordCredentials("username", "password"));
HttpHost targetHost = new HttpHost("www.verisign.com", 443, "https");
HttpHost proxy = new HttpHost("localhost", 8080);
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
HttpGet httpget = new HttpGet("/");
System.out.println("executing request: " + httpget.getRequestLine());
System.out.println("via proxy: " + proxy);
System.out.println("to target: " + targetHost);
HttpResponse response = httpclient.execute(targetHost, httpget);
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
}
EntityUtils.consume(entity);
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection manager to ensure
// immediate deallocation of all system resources
httpclient.getConnectionManager().shutdown();
}
}
}
I have the following problem:
My application needs to inform a website of a change as fast as possible
This can happen so fast that the previous webrequest hasn't been completely dealt with
The application should always send at least the last webrequest (it doesn't matter if previous ones are lost).
I'm not sure how to do this optimally. My current method is below, but gives me a warning ("Invalid use of SingleClientConnManager: connection still allocated."). I suspect I can reuse this connection, but have no clue how. Using threadSafeConnManager doesn't seem to be the solution, since I only need one connection (I think :) ).
How should I optimize my code for my needs?
The runnable in the code below is in a thread (webThread) and webrequest is a global variable that gets set to a certain url. After setting the variable, webThread.run() is fired.
private Runnable mSyncInternet = new Runnable() {
HttpClient httpClient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
public void run() {
HttpGet httpGet = new HttpGet(webrequest);
try {
HttpResponse response = httpClient.execute(httpGet, localContext);
} catch (ClientProtocolException e) {
sendMessageToUI(MSG_NO_INTERNET, 1);
} catch (IOException e) {
sendMessageToUI(MSG_NO_INTERNET, 1);
}
}
};
Thanks so much in advance!
Just today I found a blog with the solution to the SingleClientConnManager issue:
public static DefaultHttpClient getThreadSafeClient() {
DefaultHttpClient client = new DefaultHttpClient();
ClientConnectionManager mgr = client.getConnectionManager();
HttpParams params = client.getParams();
client = new DefaultHttpClient(new ThreadSafeClientConnManager(params,
mgr.getSchemeRegistry()), params);
return client;
}
I tried it, and it worked perfectly!