If you’re programming in the Java language and want to talk to a Web server, there are several libraries you can choose from. HttpURLConnection is one popular choice, and for Android programming, the engineering team has now officially suggested that you use it where possible.
Since there are irritating orthographical and Web-Architecture issues with the name “HttpURLConnection”, let’s just say HUC. HUC is reasonably well documented, if by “reasonably well” you mean “omits any discussion of the relationship between method calls and underlying HTTP traffic”. Let me fill that in. Who knows, maybe some JavaDocs maintainer somewhere will feel inspired to address this.
Let’s look at a snippet of the simplest possible HUC invocation, to dereference a URI and fetch the data. It would look something like this:
void get(URL url) {
// this does no network IO
HttpURLConnection conn = url.openConnection();
InputStream in;
int http_status;
try {
// this opens a connection, then sends GET & headers
in = conn.getInputStream();
// can't get status before getInputStream. If you try, you'll
// get a nasty exception.
http_status = conn.getResponseCode();
// better check it first
if (http_status / 100 != 2) {
// redirects, server errors, lions and tigers and bears! Oh my!
}
} catch (IOException e) {
// Something horrible happened, as in a network error, or you
// foolishly called getResponseCode() before HUC was ready.
// Essentially no methods of on "conn" now work, so don't go
// looking for help there.
}
try {
// now you can try to consume the data
try_reading(in);
} catch (IOException e) {
// Network-IO lions and tigers and bears! Oh my!
} finally {
// Do like Mom said and practice good hygiene.
conn.disconnect();
}
}
Now, suppose you want to do a POST instead of a GET.
void post(URL url, byte[] payload) {
// this does no network IO.
HttpURLConnection conn = uri.openConnection();
// tells HUC that you're going to POST; still no IO.
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(payload.length); // still no IO
InputStream in;
OutputStream out;
int http_status;
try {
// this opens a connection, then sends POST & headers.
out = conn.getOutputStream();
// At this point, the client may already have received a 4xx
// or 5xx error, but don’t you dare call getResponseCode()
// or HUC will hit you with an exception.
} catch (IOException e) {
// some horrible networking error, don't try any methods on "conn".
}
try {
// now we can send the body
out.write(payload);
// NOW you can look at the status.
http_status = conn.getResponseCode();
if (http_status / 100 != 2) {
// Dear me, dear me
}
} catch (IOException e) {
// Network-IO lions and tigers and bears! Oh my!
}
// presumably you’re interested in the response body
try {
// Unlike the identical call in the previous example, this
// provokes no network IO.
in = conn.getInputStream();
try_reading(in);
} catch (IOException e) {
// Network-IO lions and tigers and bears! Oh my!
} finally {
conn.disconnect(); // Let's practice good hygiene
}
}
If you want a look at real working code, check out getOrPost()
in
AppEngineClient.java.
I’m this literal-minded guy who likes HTTP; I’d have been happier if the methods lined up a little closer with the actual client/server conversation. But hey, it seems to work.
By the way, I used vogar to run the same little test program on a JVM and on my phone’s Dalvik. HUC’s behavior seems identical; and I recommend vogar, very handy.
Comment feed for ongoing:
From: Justin Lee (Jan 18 2012, at 15:26)
I've actually started using WebClient from HtmlUnit for some uses. Pretty easy to use.
[link]
From: Eugene Koontz (Jan 18 2012, at 15:39)
Instead of :
(http_status / 100 != 2)
Why not write something simpler like:
(http_status - 199) == 1)
?
[link]
From: Kyle Butt (Jan 18 2012, at 17:40)
Because there are more 200 status codes than just 200, including 201 created, 204 no content, etc.
[link]
From: Pierre Phaneuf (Jan 18 2012, at 18:13)
Eugene: Because the division by 100 is integer, it catches anything from 200 to 299, not just 200 (which is what your code is equivalent to).
[link]
From: Eugene Koontz (Jan 18 2012, at 20:59)
Kyle and Pierre,
I see, thank you. Very clever!
[link]
From: Bruno (Jan 19 2012, at 00:46)
I'm familiar with HttpURLConnection from Java, not Android, but I suppose it's the same. Some immediate remarks on your post:
- It seems you have not looked at the documentation of the superclass URLConnection:
http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URLConnection.html
The important bit is this: "URLConnection objects go through two phases: first they are created, then they are connected. After being created, and before being connected, various options can be specified (e.g., doInput and UseCaches). After connecting, it is an error to try to set them. Operations that depend on being connected, like getContentLength, will implicitly perform the connection, if necessary."
I find it very instructive to explicitly call URLConnection.connect() to mark the state change, even if getOutputStream() will start the connection for you. After connecting, the response code should be available.
- In your code, you need to cast the result URL.openConnection() to HttpURLConnection (since the URL can also be non-HTTP), this won't compile.
- You write Java code with the style of a C program. I would consider it better style to declare your variables at the point where you initialize them, use try/finally,...
- Something more awful about HttpURLConnection is that it stores the Authenticator in a global variable.
- Basic on limited experience, I would say that libneon is a quite nice example of HTTP client API design.
[link]
From: Beat Bolli (Jan 19 2012, at 04:18)
You might want to fix the uri.openConnection() call in post(). It should be url.openConnection().
[link]
From: Avi Flax (Jan 19 2012, at 06:53)
That’s a terrible API. A good API doesn’t require so much out-of-band documentation for understanding basic flow; it should be apparent from class and method names.
I personally much prefer Restlet’s client APIs (there are 2: one medium-level and one high-level). But even Apache HttpClient is better.
Here’s an example of using Restlet’s higher-level API:
<code>
ClientResource postResource = new ClientResource("http://www.tbray.org/ongoing/When/201x/2012/01/17/HttpURLConnection");
Form commentForm = new Form();
form.add("name", "Avi Flax");
form.add("url", "http://aviflax.com");
form.add("comment", "That’s a terrible API.");
try {
postResource.post(commentForm);
// it worked!
if (postResource.getStatus().equals(Status.SUCCESS_CREATED))
logger.log(Level.INFO, "new comment resource successfully created with URI {0}", postResource.getResponse().getLocationRef());
} catch (ResourceException e) {
// it didn’t work :(
logger.log(Level.WARNING, MessageFormat.format("comment could not be posted to {0}: {1}", postResource.getReference(), postResource.getStatus()), e);
} finally {
/* if using the Apache HttpClient Restlet extension as the transport layer, need to exhaust
the representation to release the underlying connection back into the connection pool */
if (postResource.getResponseEntity() != null)
postResource.getResponseEntity().exhaust();
}
</code>
[link]
From: Mike Hayes (Jan 19 2012, at 08:13)
Also, its gets complicated if you're trying to make use of persistent connections, on the JDK at least.
Closing the stream, or even disconnecting the connection may not actually close the physical connection. And you have to do extra work when errors occur to make sure the connectionis reusable.
This might become an issue if you're calling your get() or post() in a loop.
http://docs.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html
[link]
From: Joel Hockey (Jan 19 2012, at 14:25)
I haven't used HUC on android, but on the standard desktop Java, your example code will not work for reading the content of a 400 / 500 response.
To handle this, you will need to catch the IOException that is thrown on 'conn.getInputStream()', and assign 'in' to 'conn.getErrorStream()', check that 'in' is not null, and then do the reading.
[link]
From: nik (Jan 19 2012, at 18:29)
Using apache's httpclient library ( http://hc.apache.org/httpcomponents-client-ga/index.html ) is much more pleasant. Looks like there's dalvik support as well: http://developer.android.com/reference/org/apache/http/client/package-summary.html
[link]
From: Patrick Mueller (Jan 23 2012, at 13:09)
HUC, huh? Back in the day, for a fairly large and non-trivial Java app I was working on, we evaluated three client http libraries. Ended up with Apache's HttpClient; HUC was the bottom of the list, in terms of capabilities and experience using it. Maybe things have changed - this is going on 5 years or more since I used it.
Also, seems weird to say "HttpURLConnection ...is where we will be spending our energy going forward", as the API is fixed (it's a java.net class). What happens when the point is reached where you want to extend the API?
[link]