Web browsers are the universal clients for web applications. They
retrieve documents for display and serve as a user interface, primarily
through the use of HTML, JavaScript, and linked documents. In this
section, we‘ll show how to write client-side Java code that uses HTTP
through the URL
class to work with
web applications directly using GET
and POST
operations to
retrieve and send data. Later in this chapter, we’ll begin a discussion of
web services, which marry HTTP with XML to enable cross-platform
application-to-application communications using web standards.
There are many reasons an application might want to communicate via HTTP. For example, compatibility with another browser-based application might be important, or you might need to gain access to a server through a firewall where direct socket connections (and RMI) are problematic. HTTP is the lingua franca of the Net, and despite its limitations (or more likely because of its simplicity), it has rapidly become one of the most widely supported protocols in the world. As for using Java on the client side, all the other reasons you would write a client-side GUI or non-GUI application (as opposed to a pure web/HTML-based application) also present themselves. A client-side GUI can perform sophisticated presentation and validation while, with the techniques presented here, still using web-enabled services over the network.
The primary task we discuss here is sending data to the server,
specifically HTML form-encoded data. In a web browser, the name/value
pairs of HTML form fields are encoded in a special format and sent to the
server using one of two methods. The first method, using the HTTP GET
command, encodes the user’s input into the
URL and requests the corresponding document. The server recognizes that
the first part of the URL refers to a program and invokes it, passing
along the information encoded in the URL as a parameter. The second method
uses the HTTP POST
command to ask the
server to accept the encoded data and pass it to a web application as a
stream. In Java, we can create a URL that refers to a server-side program
and request or send it data using the GET
and POST
methods. (In Chapter 15, we’ll see how to build
web applications that implement the other side of this
conversation.)
Using the GET
method of
encoding data in a URL is pretty easy. All we have to do is create a URL
pointing to a server program and use a simple convention to tack on the
encoded name/value pairs that make up our data. For example, the
following code snippet opens a URL to an old-school CGI program called
login.cgi on the server myhost
and passes it two name/value pairs. It then prints whatever text the CGI
sends back:
URL
url
=
new
URL
(
// this string should be URL-encoded
"http://myhost/cgi-bin/login.cgi?Name=Pat&Password=foobar"
);
BufferedReader
bin
=
new
BufferedReader
(
new
InputStreamReader
(
url
.
openStream
()
));
String
line
;
while
(
(
line
=
bin
.
readLine
())
!=
null
)
{
System
.
out
.
println
(
line
);
}
To form the URL with parameters, we start with the base URL of
login.cgi; we add a question mark (?
), which marks the beginning of the parameter
data, followed by the first name/value pair. We can add as many pairs as
we want, separated by ampersand (&
) characters. The rest of our code simply
opens the stream and reads back the response from the server. Remember
that creating a URL doesn’t actually open the connection. In this case,
the URL connection was made implicitly when we called openStream()
. Although we are assuming here
that our server sends back text, it could send anything.
It’s important to point out that we have skipped a step here. This
example works because our name/value pairs happen to be simple text. If
any “nonprintable” or special characters (including ?
or &
) are in the pairs, they must be encoded
first. The java.net.
URL
Encoder
class provides a utility for encoding the
data. We’ll show how to use it in the next example.
Another important thing is that although this small example sends a password field, you should never send sensitive data using this simplistic approach. The data in this example is sent in clear text across the network (it is not encrypted). And in this case, the password field would appear anywhere the URL is printed as well (e.g., server logs and bookmarks). We’ll talk about secure web communications later in this chapter and when we discuss writing web applications using servlets in Chapter 15.
Here’s a small application that acts like an HTML form. It
gathers data from two text fields—name
and password
—and posts the data to a specified URL
using the HTTP POST
method. This
Swing-based client application works with a server-side web-based
application, just like a web browser.
Here’s the code:
//file: Post.java
import
java.net.*
;
import
java.io.*
;
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
Post
extends
JPanel
implements
ActionListener
{
JTextField
nameField
,
passwordField
;
String
postURL
;
GridBagConstraints
constraints
=
new
GridBagConstraints
(
);
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
Post
(
String
postURL
)
{
this
.
postURL
=
postURL
;
setBorder
(
BorderFactory
.
createEmptyBorder
(
5
,
10
,
5
,
5
));
JButton
postButton
=
new
JButton
(
"Post"
);
postButton
.
addActionListener
(
this
);
setLayout
(
new
GridBagLayout
(
)
);
constraints
.
fill
=
GridBagConstraints
.
HORIZONTAL
;
addGB
(
new
JLabel
(
"Name "
,
JLabel
.
TRAILING
),
0
,
0
);
addGB
(
nameField
=
new
JTextField
(
20
),
1
,
0
);
addGB
(
new
JLabel
(
"Password "
,
JLabel
.
TRAILING
),
0
,
1
);
addGB
(
passwordField
=
new
JPasswordField
(
20
),
1
,
1
);
constraints
.
fill
=
GridBagConstraints
.
NONE
;
constraints
.
gridwidth
=
2
;
constraints
.
anchor
=
GridBagConstraints
.
EAST
;
addGB
(
postButton
,
1
,
2
);
}
public
void
actionPerformed
(
ActionEvent
e
)
{
postData
(
);
}
protected
void
postData
(
)
{
StringBuffer
sb
=
new
StringBuffer
(
);
sb
.
append
(
URLEncoder
.
encode
(
"Name"
)
+
"="
);
sb
.
append
(
URLEncoder
.
encode
(
nameField
.
getText
(
))
);
sb
.
append
(
"&"
+
URLEncoder
.
encode
(
"Password"
)
+
"="
);
sb
.
append
(
URLEncoder
.
encode
(
passwordField
.
getText
(
))
);
String
formData
=
sb
.
toString
(
);
try
{
URL
url
=
new
URL
(
postURL
);
HttpURLConnection
urlcon
=
(
HttpURLConnection
)
url
.
openConnection
(
);
urlcon
.
setRequestMethod
(
"POST"
);
urlcon
.
setRequestProperty
(
"Content-type"
,
"application/x-www-form-urlencoded"
);
urlcon
.
setDoOutput
(
true
);
urlcon
.
setDoInput
(
true
);
PrintWriter
pout
=
new
PrintWriter
(
new
OutputStreamWriter
(
urlcon
.
getOutputStream
(
),
"8859_1"
),
true
);
pout
.
(
formData
);
pout
.
flush
(
);
// read results...
if
(
urlcon
.
getResponseCode
(
)
!=
HttpURLConnection
.
HTTP_OK
)
System
.
out
.
println
(
"Posted ok!"
);
else
{
System
.
out
.
println
(
"Bad post..."
);
return
;
}
//InputStream in = urlcon.getInputStream( );
// ...
}
catch
(
MalformedURLException
e
)
{
System
.
out
.
println
(
e
);
// bad postURL
}
catch
(
IOException
e2
)
{
System
.
out
.
println
(
e2
);
// I/O error
}
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"SimplePost"
);
frame
.
add
(
new
Post
(
args
[
0
]
),
"Center"
);
frame
.
pack
(
);
frame
.
setVisible
(
true
);
}
}
When you run this application, you must specify the URL of the server program on the command line. For example:
%
java
Post
http:
//
www.myserver.example/cgi-bin/login.cgi
The beginning of the application creates the form; there’s nothing
here that won’t be obvious after you’ve read Chapters 16 through
18,
which cover the AWT and Swing GUI toolkits. All the magic happens in the
protected postData()
method. First,
we create a StringBuffer
and load it
with name/value pairs, separated by ampersands. (We don’t need the
initial question mark when we’re using the POST
method because we’re not appending to a
URL string.) Each pair is first encoded using the static URLEncoder.encode()
method. We run the name fields through the encoder as well as the value
fields, even though we know that in this case they contain no special
characters.
Next, we set up the connection to the server program. In our
previous example, we weren’t required to do anything special to send the
data because the request was made by the simple act of opening the URL
on the server. Here, we have to carry some of the weight of talking to
the remote web server. Fortunately, the HttpURLConnection
object does most of the work
for us; we just have to tell it that we want to do a POST
to the URL and the type of data we are
sending. We ask for the URLConnection
object that is using the URL’s openConnection()
method. We know that we are
using the HTTP protocol so we should be able to cast it to an HttpURLConnection
type, which has the support
we need. Because HTTP is one of the guaranteed protocols, we can safely
make this assumption.
We then use setRequestMethod()
to
tell the connection we want to do a POST
operation. We also use setRequestProperty()
to
set the Content-Type
field of our
HTTP request to the appropriate type—in this case, the proper MIME type
for encoded form data. (This is necessary to tell the server what kind
of data we’re sending.) Finally, we use the setDoOutput()
and
setDoInput()
methods to
tell the connection that we want to both send and receive stream data.
The URL connection infers from this combination that we are going to do
a POST
operation and expects a
response. Next, we get an output stream from the connection with
getOutputStream()
and create a
PrintWriter
so that we can easily
write our encoded data.
After we post the data, our application calls getResponseCode()
to
see whether the HTTP response code from the server indicates that the
POST
was successful. Other response
codes (defined as constants in HttpURLConnection
) indicate various failures.
At the end of our example, we indicate where we could have read back the
text of the response. For this application, we’ll assume that simply
knowing that the post was successful is sufficient.
Although form-encoded data (as indicated by the MIME type we
specified for the Content-Type
field) is
the most common, other types of communications are possible. We could
have used the input and output streams to exchange arbitrary data types
with the server program. The POST
operation could send any kind of data; the server application simply has
to know how to handle it. One final note: if you are writing an
application that needs to decode form data, you can use the java.net.URLDecoder
to undo the operation of
the URLEncoder
. If you use the
Servlet API, this happens automatically, as you’ll see in Chapter 15.
Other information from the request is available from the
HttpURLConnection
as well. We could
use getContentType()
and
getContentEncoding()
to
determine the MIME type and encoding of the response. We could also
interrogate the HTTP response headers by using getHeaderField()
. (HTTP
response headers are metadata name/value pairs carried with the
response.) Convenience methods can fetch integer and date-formatted header fields, getHeaderFieldInt()
and
getHeaderFieldDate()
,
which return an int
and a long
type, respectively. The content length
and last modification date are provided through getContentLength()
and
getLastModified()
.
The previous examples sent a field called Password
to the server. However, standard HTTP
doesn’t provide encryption to hide our data. Fortunately, adding
security for GET
and POST
operations like this is easy (trivial in
fact, for the client-side developer). Where available, you simply have
to use a secure form of the HTTP protocol—HTTPS:
https:
//www.myserver.example/cgi-bin/login.cgi
HTTPS is a version of the standard HTTP protocol run over
Secure Sockets Layer (SSL), which uses public-key encryption techniques
to encrypt the browser-to-server communications. Most web browsers and
servers currently come with built-in support for HTTPS (or raw SSL
sockets). Therefore, if your web server supports HTTPS and has it
configured, you can use a browser to send and receive secure data simply
by specifying the https
protocol in your
URLs. There is much more to learn about SSL and related aspects of
security such as authenticating whom you are actually talking to, but as
far as basic data encryption goes, this is all you have to do. It is not
something your code has to deal with directly. The Java JRE standard
edition ships with SSL and HTTPS support, and beginning with Java 5.0,
all Java implementations must support HTTPS as well as HTTP for URL
connections. We’ll discuss writing secure web applications in more
detail in Chapter 15.
Earlier, we discussed URLs and distinguished them from the concept of URNs. Whereas a URL points to a specific location on the Net and specifies a protocol or scheme for accessing its contents, a URN is simply a globally unique name. A URL is analogous to giving someone your phone number. But a URN is more like giving them your social security number. Your phone number may change, but your social security number is supposed to uniquely identify you forever.
While it’s possible that some mechanism might be able to look at a given URN and tie it to a location (a URL), it is not necessarily so. URNs are intended only to be permanent, unique, abstract identifiers for an item, whereas a URL is a mechanism you can use to get in touch with a resource right now. You can use a phone number to contact me today, but you can use my social security number to uniquely identify me anytime.
An example of a URN is http://www.w3.org/1999/XSL/Transform, which is the identifier for a version of the Extensible Stylesheet Language, standardized by the W3C. Now, it also happens that this is a URL (you can go to that address and find information about the standard), but that is for convenience only. This URN’s primary mission is to uniquely label the version of the programming language in a way that never changes.
Collectively, URLs and URNs are called Uniform Resource Identifiers or URIs. A URI is simply a URL or URN. So, URLs and URNs are kinds of URIs. The reason for this abstraction is that URLs and URNs, by definition, have some things in common. All URIs are supposed to be human-readable and “transcribable” (it should be possible to write them on the back of a napkin). They always have a hierarchical structure, and they are always unique. Both URLs and URNs also share some common syntax, which is described by RFC 2396.
The java.net.URI
class
formalizes these distinctions. The difference between the URI and URL
classes is that the URI class does not try to parse the contents of the
identifier and apply any “meaning.” Whereas the URL class immediately
attempts to parse the scheme portion of the URL and locate a protocol
handler, the URI class doesn’t interpret its content. It serves only to
allow us to work with the identifier as structured text, according to
the general rules of URI syntax. With the URI class, you can construct
the string, resolve relative paths, and perform equality or comparison
operations, but no hostname or protocol resolution is done.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.