Basic Network Programming in J2ME MIDP
Date: Aug 27, 2001
Article is provided courtesy of Sams.
Network programming plays an important role in the development of wireless applications that take advantage of the connectivity wireless devices have to offer. This sample chapter discusses the important concepts involved in network programming with J2ME MIDP-such as the difference between network programming with J2SE and with J2ME, and the concept of the Generic Connection framework. It also examines several sample MIDlet applications using different types of network communications available in the Generic Connection framework, namely sockets, datagrams, and HTTP communication.
Overview
The biggest advantages of a wireless device are its connectivity and accessibility. Wireless devices keep people connected to the outside world all the time and from virtually any place. The functionality of these wireless devices has changed significantly in the last couple of years, as increased wireless network coverage area, higher data transfer bandwidth, and improved wireless technology have become available. Cell phones are no longer just for conversation purposes; increasingly, they are becoming "mobile information devices" that allow people to access enterprise data and business/personal information in a timely fashion.
Network programming plays an important role in developing wireless applications that take advantage of the connectivity these devices have to offer. This chapter is intended to help you understand and learn the important concepts in network programming with J2ME MIDP. The first part of the chapter explains the main difference between network programming with J2SE and with J2ME.
Next, the concept of the Generic Connection framework is introduced and explained. The latter part of the chapter examines several sample MIDlet applications using different types of network communications available in the Generic Connection framework: namely, sockets (in the section "Wireless Network Programming Using Sockets"), datagrams ("Wireless Network Programming Using Datagrams"), and HTTP communication ("Wireless Network Programming Using HttpConnection").
Network Programming with J2SE Versus J2ME
For those who have developed network applications using Java 2 Standard Edition, network programming is fairly simple and straightforward; J2SE provides network libraries that are rich in functionality. Approximately 60 classes are available in the java.io package to support file input and output, and approximately 20 classes are available in the java.net package to support networking.
However, most of the classes in these two packages are designed to support traditional computer systems with enough CPU power, sufficient memory, and sufficient disk storage. The total static size of these class files is approximately 200 kilobytes. These packages are too big to fit in the typical wireless device, which has very limited computing power and a total memory and storage budget of a few hundred kilobytes.
Size is not the only issue when dealing with wireless devices. Java 2 Micro Edition needs to support a variety of mobile devices. The networking and file I/O capability varies significantly from one wireless device to another, so the requirements for networking and file I/O libraries are very different. For example, some wireless carriers use packet-switched networks, whereas others use circuit-switched networks. The difference between the two networks requires two different communication abstractions in Java libraries: datagram-based communication for packet-switched networks and socket-based communication for circuit-switched networks. Vendors that support datagram-based communication may not be interested in supporting socket-based connections and vice versa.
NOTE
A circuit-switched network creates telecommunication connections by setting up an end-to-end circuit. The circuit remains open for the duration of the communication, and a fixed share of network resources is tied up; no one else can use those resources until the connection is closed. The main advantage of a circuit-switched network is that performance guarantees can be offered.
A packet-switched network creates telecommunication connections by breaking up the information to be sent into packets of bytes, sending them along a network with other information streams, and reassembling the original information flow at the other end. The main advantage of a packet-switched network is that it makes very efficient use of fixed capacity. The disadvantage is that the quality of service of an information channel cannot be guaranteed.
The file I/O for wireless devices falls into a similar situation. These file accesses are highly device specific and require different implementations. Due to strict memory limitations, the vendors who support one type of file I/O mechanism generally do not want to support another.
The networking in J2ME has to be very flexible to support a variety of devices and has to be very device specific at the same time. To meet these challenges, the Generic Connection framework is first introduced in the CLDC. The idea of the Generic Connection framework is to define the abstractions of the networking and file I/O as generally as possible to support a broad range of handheld devices, and leave the actual implementations of these abstractions to individual device manufacturers. These abstractions are defined as Java interfaces. The device manufacturers choose which one to implement in their MIDP implementations or PDAP implementations based on the actual device capabilities.
The Generic Connection Framework
The Generic Connection framework is introduced in J2ME's CLDC to reflect the requirements of small-footprint networking and file I/O for a broad range of mobile devices.
To meet the small-footprint requirement, the Generic Connection framework generalizes the functionality of J2SE's network and file I/O classes from J2SE's java.io and jave.net packages. It is a precise functional subset of J2SE classes, but much smaller in size. These J2ME classes and interfaces are all included in a single package, javax.microedition.io.
To meet the extendibility and flexibility requirement, the Generic Connection framework uses a set of related abstractions for different forms of communications, represented by seven connection interfaces: Connection, ContentConnection, DatagramConnection, InputConnection, OutputConnection, StreamConnection, and StreamConnectionNotifier.
The Generic Connection framework supports the following basic forms of communications. All the connections are created by one common method, Connector.open():
HTTP:
Connector.open("http://www.webyu.com");
Sockets:
Connector.open("socket://localhost:80");
Datagrams:
Connector.open("datagram://http://www.webyu.com:9000");
Serial Port:
Connector.open("comm:0;baudrate=9600");
File:
Connector.open("file:/foo.dat");This flexible design makes adding a new form of communication much easier without causing major structural changes to the class libraries.
Figure 9.1 shows the hierarchical relationships between these connection interfaces.
Connection Interfaces
As shown in Figure 9.1, Connection is the base interface, the root of the connection interface hierarchy. All the other connection interfaces derive from Connection. StreamConnection derives from InputConnection and OutputConnection. It defines both the input and output capabilities for a stream connection. ContentConnection derives from StreamConnection. It adds three methods for MIME data handling to the input and output methods in StreamConnection. Finally, HttpConnection derives from ContentConnection.
The HttpConnection is not part of the Generic Connection framework. Instead, it is defined in the MIDP specification that is targeted at cellular phones and two-way pagers. HttpConnection contains methods and constants specifically to support the HTTP 1.1 protocol. The HttpConnection interface must be implemented by all MIDP implementations, which means the HttpConnection will be a concrete class in the actual MIDP implementations. The http communication capability is expected to be available on all MIDP devices.
Figure 9.1 The connection interface hierarchy.
Why are these connections defined as interfaces instead of concrete classes? Because the Generic Connection framework specifies only the basic framework of how these connection interfaces should be implemented. The actual implementations are left to individual device manufacturers' profile implementations (MIDP or PDAP). As discussed in the previous section, individual device manufacturers may be interested in implementing only a subset of these connection interfaces based on the capabilities of their devices. The documentation of each manufacturer's J2ME MIDP SDK should indicate which connection interfaces are implemented and which are not.
Creating Network Connections
So far we have talked about the different kinds of connections. You may wonder how the connections are created and how to use them.
The Connector class is the core of the Generic Connection framework, because all connections are created by the static open() method in the Connector class. Different types of communication are created from the same method. The connection type can be file I/O, serial port communication, datagram connection, or an HTTP connection, depending on the string parameter passed to the method. Such a design makes the J2ME implementation more extensible and flexible in supporting new devices and product.
The method's signature is as follows:
Connection open(String connect_string)
The connect_string has a format of {protocol}:[{target}][{params}], which is similar to the commonly used URL format, such as http://www.somewhere.com. It consists of three parts: protocol, target, and params.
protocol dictates what type of connection will be created by the open() method. There are several possible values for protocol, as listed in Table 9.1.
Table 9.1 Protocol Values
|
Value |
Connection Usage |
|
file |
File I/O |
|
comm |
Serial port communication |
|
socket |
TCP/IP socket communication |
|
datagram |
Datagram communication |
|
http |
Accessing Web servers |
target can be a hostname, a network port number, a file name, or a communication port number.
params is optional. It specifies the additional information needed to complete the connect string.
The following examples demonstrate how to use the open() method to create different types of communication based on different protocols:
HTTP communication:
Connection hc = Connector.open("http://www.webyu.com")
Socket communication:
Connection sc = Connector.open("socket://localhost:9000")
Datagram communication:
Connection dc = Connector.open("datagram://http://www.webyu.com:9000")
Serial port communication:
Connection cc = Connector.open("comm:0;baudrate=9000")
File I/O:
Connection fc = Connector.open("file:/foo.dat")NOTE
Actual support for these protocols varies from vendor to vendor. Of all the J2ME MIDP implementations we have evaluated (Sun's J2ME Wireless Toolkit, MotoSDK from Motorola, and RIMSDK from Research in Motion), MotoSDK supports all three networking protocols that we are concerned with in this chapter: socket (TCP/IP), datagram (UDP), and http (HTTP). For this reason, all the sample codes in this chapter are compiled and run under MotoSDK (release 0.7).
A ConnectionNotFoundException will be thrown if your MIDlet program tries to create a connection based on a protocol that is not supported by your device manufacturer's MIDP implementation.
Even though we said that Sun's J2ME Wireless Toolkit only supports the http communication, there is actually an undocumented feature that will let you run socket or datagram programs under the Toolkit if you set the environment variable ENABLE_CLDC_PROTOCOLS=INTUITIVE_TOOLKIT. Because it is an undocumented feature that is not officially supported by Sun, we chose not to use Sun's J2ME Wireless Toolkit to run our samples in this chapter.
The Methods in the Connector Class
This section takes a close look at the Connector class. The Connector class is the only concrete class in Generic Connection framework in CLDC. It contains seven static methods:
static Connection open(String connectString)
This method creates and opens a new Connection based on the connectString.
static Connection open(String connectString, int mode)
This method creates and opens a new Connection based on the connectString. The additional mode parameter specifies the access mode for the connection. There are three access modes: Connector.READ, Connector.READ_WRITE, and Connector.WRITE. If mode is not specified, the default value is Connector.READ_WRITE. The validity of the actual setting is protocol dependent. If the access mode is not allowed for a protocol, an IllegalArgumentException will be thrown.
static Connection open(String connectString, int mode, boolean timeouts)
This method creates and opens a new Connection based on the connectString. The additional timeouts parameter is a Boolean flag that dictates whether the method will throw a timeout exception InterruptedIOException. The default timeouts value is false, which indicates that no exception will be thrown.
static DataInputStream openDataInputStream(String connectString)
This method creates and opens a new DataInputStream based on the connectString.
static DataOutputStream openDataOutputStream(String connectString)
This method creates and opens a DataOutputStream from the connectString.
static InputStream openInputStream(String connectString)
This method creates and opens a new InputStream from the connectString.
static OutputStream openOutputStream(String connectString)This method creates and opens a new OutputStream from the connectString.
The last four I/O stream-creation methods combine creating the connection and opening the input/output stream into one step. For example, the following statement
DataInputStream dis = Connector.openDataInputStream(http://www.webyu.com);
is the equivalent of the following two statements:
InputConnection ic = (InputConnection)
Connector.open("http://www.webyu.com", Connector.READ, false);
DataInputStream dis = ic.openDataInputStream();
An IllegalArgumentException will be thrown if a malformed connectString is received. A ConnectionNotFoundException will be thrown if the protocol specified in connectString is not supported. An IOException will be thrown for other types of I/O errors.
Listing 9.1 contains an example of how an http connection is created and how a DataInputStream is opened on top of the connection.
Listing 9.1 Listing1.txt
/**
* This sample code block demonstrates how to open an
* http connection, how to establish an InputStream from
* this http connection, and how to free them up after use.
**/
// include the networking class libraries
import javax.microedition.io.*;
// include the I/O class libraries
import java.io.*;
// more code here ...
// define the connect string with protocol: http
// and hostname: 64.28.105.110
String connectString = "http://64.28.105.110";
InputConnection hc = null;
DataInputStream dis = null;
// IOException must be caught when Connector.open() is called
try {
// an http connection is established with read access.
// The returned object is cast into an InputConnection object.
hc = (InputConnection)
Connector.open(connectString, Connector.READ, false);
// an InputStream is created on top of the InputConnection
// object for read operations.
dis = hc.openDataInputStream();
// perform read operations here ...
} catch (IOException e) {
System.err.println("IOException:" + e);
} finally {
// free up the I/O stream after use
try { if (dis != null ) dis.close(); }
catch (IOException ignored) {}
// free up the connection after use
try { if ( hc != null ) hc.close(); }
catch (IOException ignored) {}
}
// more code here ...
Connection Interfaces
This section takes a look at all the connection interfaces defined in the javax.microedition.io package including the seven connections from the Generic Connection framework in CLDC and the HttpConnection in MIDP:
Connection ContentConnection DatagramConnection InputConnection OutputConnection StreamConnection StreamConnectionNotifier HttpConnection
Connection
The Connection interface has one method:
void close()
This method closes the connection.
InputConnection
The InputConnection interface has two methods:
DataInputStream openDataInputStream()
This method opens a data input stream from the connection.
InputStream openInputStream()
This method opens an input stream from the connection.
OutputConnection
The OutputConnection interface has two methods:
DataOutputStream openDataOutputStream()
This method opens a data output stream from the connection.
OutputStream openOutputStream()
This method opens an output stream from the connection.
DatagramConnection
The DatagramConnection interface is used to create a datagram for a UDP communication. More details regarding this interface are discussed in the section "Wireless Network Programming Using Datagrams." This interface has eight methods:
int getMaximumLength()
This method returns the maximum length that is allowed for a datagram packet.
int getNominalLength()
This method returns the nominal length for datagram packets.
Datagram newDatagram(byte[] buf, int size)
This method creates a new Datagram object. buf is the placeholder for the data packet, and size is the length of the buffer to be allocated for the Datagram object.
Datagram newDatagram(byte[] buf, int size, String addr)
This method creates a new Datagram object. The additional parameter addr specifies the destination of this datagram message. It is in the format {protocol}://[{host}]:{port}.
Datagram newDatagram(int size)
This method creates a new Datagram object with an automatically allocated buffer with length size.
Datagram newDatagram(int size, String addr)
This method creates a new Datagram object. addr specifies the destination of this datagram message.
void receive(Datagram dgram)
This method receives a Datagram object dgram from the remote host.
void send(Datagram dgram)
This method sends a Datagram object dgram to the remote host.
StreamConnection
The StreamConnection interface offers both send and receive capabilities for socket-based communication. All four of its methods are inherited from InputConnection and OutputConnection:
DataInputStream openDataInputStream() InputStream openInputStream() DataOutputStream openDataOutputStream() OutputStream openOutputStream()
StreamConnectionNotifier
The StreamConnectionNotifier interface has one method:
StreamConnection acceptAndOpen()
This method returns a StreamConnection that represents a server-side socket connection to communicate with a client.
ContentConnection
The ContentConnection interface extends from StreamConnection and adds three methods to determine an HTTP stream's character encoding, MIME type, and size:
String getEncoding()
This method returns the value of the content-encoding in the HTTP header of an HTTP stream.
long getLength()
This method returns the value of the content-length in the HTTP header of an HTTP stream.
String getType()
This method returns the value of the content-type in the HTTP header of an HTTP stream.
The methods inherited from StreamConnection are as follows:
DataInputStream openDataInputStream() InputStream openInputStream() DataOutputStream openDataOutputStream() OutputStream openOutputStream()
HttpConnection
The HttpConnection extends from ContentConnection. It adds the following methods to support the HTTP 1.1 protocol:
long getDate() long getExpiration() String getFile() String getHeaderField(int index) String getHeaderField(String name) long getHeaderFieldDate(String name, long def) int getHeaderFieldInt(String name, int def) String getHeaderFieldKey(int n) String getHost() long getLastModified() int getPort() String getProtocol() String getQuery() String getRef() String getRequestMethod() String getRequestProperty(String key) int getResponseCode() String getResponseMessage() String getURL() void setRequestMethod(String method) void setRequestProperty(String key, String value)
The HttpConnection interface is mandatory for all MIDP vendor implementations. However, the underlying support mechanisms could be different from vendor to vendor. Some vendors may support the HTTP stack on top of non-IPbased protocols such as WSP transport or TL/PDC-P, and other vendors may use HTTP over TCP/IP. To support the HTTP protocol on MIDP devices, non-IP networks may have to install gateways in order to convert HTTP requests from the wireless network format to a TCP/IP format to be able to access the Internet.
Figure 9.2 illustrates the possible implementations of HttpConnection, based on different wireless network infrastructures.
The section "Wireless Network Programming Using HttpConnection" discusses HttpConnection in detail.
Figure 9.2 Implementations of HttpConnection on different underlying transports.
Wireless Network Programming Using Sockets
A socket is one end-point of a two-way communication link between programs running on the network. A socket connection is the most basic low-level reliable communication mechanism between a wireless device and a remote server or between two wireless devices. The socket communication capability provided with some of the mobile devices in J2ME enables a variety of client/server applications. Using the socket connection to SMTP and POP3 servers, an e-mail client on wireless devices can send or receive regular Internet e-mail messages.
Socket use gives J2ME developers the flexibility to develop all kinds of network applications for wireless devices. However, not every wireless manufacturer supports socket communication in MIDP devices, which means that wireless applications developed using sockets could be limited to certain wireless devices and are less likely to be portable across different types of wireless networks.
To use a socket, the sender and receiver that are communicating must first establish a connection between their sockets. One will be listening for a request for a connection, and the other will be asking for a connection. Once two sockets have been connected, they may be used for transmitting data in either direction.
To receive data from the remote server, InputConnection has to be established and an InputStream must be obtained from the connection. To send data to the remote server, OutputConnection has to be established and an OutputStream must be obtained from the connection. In J2ME, three types of connections are defined to handle input/output streams: InputConnection, OutputConnection, and StreamConnection. As the names indicate, InputConnection defines the capabilities for input streams to receive data and OutputConnection defines the capabilities for output streams to send data. StreamConnection defines the capabilities for both input and output streams. When to use which connection depends on whether the data needs to be sent, received, or both.
Network programming using sockets is very straightforward in J2ME. The process works as follows:
A socket connection is opened with a remote server or another wireless device using Connector.open().
InputStream or OutputStream is created from the socket connection for sending or receiving data packets.
Data can be sent to and received from the remote server via the socket connection by performing read or write operations on the InputStream or OutputStream object.
The socket connection and input or output streams must be closed before exiting the program.
The example in Listing 9.2 demonstrates how a socket connection is created and how a DataOutputStream is opened on top of the connection.
Listing 9.2 Listing2.txt
/**
* This sample code block demonstrates how to open a
* socket connection, how to establish a DataOutputStream
* from this socket connection, and how to free them up
* after use.
**/
// include the networking class libraries
import javax.microedition.io.*;
// include the I/O class libraries
import java.io.*;
// more code here ...
// define the connect string with protocol: socket,
// hostname: 64.28.105.110, and port number: 80
String connectString = "socket://64.28.105.110:80";
OutputConnection sc = null;
DataOutputStream dos = null;
// IOException must be caught when Connector.open() is called.
try {
// a socket connection is established with the remote server.
sc = (OutputConnection) Connector.open(socketUrlString);
// an OutputStream is created on top of the OutputConnection
// object for write operations.
dos = sc.openDataOutputStream();
// perform write operations that send data to the remote server ...
} catch (IOException e) {
System.err.println("IOException caught:" + e)
} finally {
// free up the I/O stream after use
try { if (dos != null ) dos.close(); }
catch (IOException ignored) {}
// free up the socket connection after use
try { if ( sc != null ) sc.close(); }
catch (IOException ignored) {}
}
// more code here ...
In this example, a socket connection is established with remote server 64.28.105.110 on port 80. Port 80 is the well-known port for HTTP service. Note that we are using the socket connection to communicate with the remote server; therefore, the protocol is specified as socket. The DataOutputStream is then created on the top of the connection for sending requests to the remote server.
Sample Program
The sample program in Listing 9.3 creates a Web client session to request a page from a Web server on the Internet. Figure 9.3 illustrates the program flow of a Web client implemented using socket connections.
Figure 9.3 The program flow of a Web client implemented with sockets.
Listing 9.3 SocketExample.java
/**
* The following MIDlet application creates socket connection with
* a remote Web server at port 80, and then sends an HTTP request
* to retrieve the Web page "index.html" via the connection.
*/
// include MIDlet class libraries
import javax.microedition.midlet.*;
// include networking class libraries
import javax.microedition.io.*;
// include GUI class libraries
import javax.microedition.lcdui.*;
// include I/O class libraries
import java.io.*;
public class SocketExample extends MIDlet {
// StreamConnection allows bidirectional communication
private StreamConnection streamConnection = null;
// use OutputStream to send requests
private OutputStream outputStream = null;
private DataOutputStream dataOutputStream = null;
// use InputStream to receive responses from Web server
private InputStream inputStream = null;
private DataInputStream dataInputStream = null;
// specify the connect string
private String connectString = "socket://64.28.105.110:80";
// use a StrignBuffer to store the retrieved page contents
private StringBuffer results;
// define GUI components
private Display myDisplay = null;
private Form resultScreen;
private StringItem resultField;
public SocketExample() {
// initializing GUI display
results = new StringBuffer();
myDisplay = Display.getDisplay(this);
resultScreen = new Form("Page Content:");
}
public void startApp() {
try {
// establish a socket connection with remote server
streamConnection =
(StreamConnection) Connector.open(connectString);
// create DataOuputStream on top of the socket connection
outputStream = streamConnection.openOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
// send the HTTP request
dataOutputStream.writeChars("GET /index.html \n");
dataOutputStream.flush();
// create DataInputStream on top of the socket connection
inputStream = streamConnection.openInputStream();
dataInputStream = new DataInputStream(inputStream);
// retrieve the contents of the requested page from Web server
int inputChar;
while ( (inputChar = dataInputStream.read()) != -1) {
results.append((char) inputChar);
}
// display the page contents on the phone screen
resultField = new StringItem(null, results.toString());
resultScreen.append(resultField);
myDisplay.setCurrent(resultScreen);
} catch (IOException e) {
System.err.println("Exception caught:" + e);
} finally {
// free up I/O streams and close the socket connection
try {
if (dataInputStream != null)
dataInputStream.close();
} catch (Exception ignored) {}
try {
if (dataOutputStream != null)
dataOutputStream.close();
} catch (Exception ignored) {}
try {
if (outputStream != null)
outputStream.close();
} catch (Exception ignored) {}
try {
if (inputStream != null)
inputStream.close();
} catch (Exception ignored) {}
try {
if (streamConnection != null)
streamConnection.close();
} catch (Exception ignored) {}
}
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
The program first opens a socket connection with the Web server http://www.webyu.com at port 80. It sends an HTTP request to the Web server using the DataOutputStream established from the connection. It receives the requested content from the Web server using the DataInputStream opened from the connection. After the Web page content is completely received, the content is displayed on the emulator.
Because the program needs to perform bidirectional communication between a cell phone and a Web server, StreamConnection is used to support both the send and receive operations.
Figure 9.4 illustrates a screen shot of the Web page content displayed on the Motorola iDEN 3000 Emulator.
Figure 9.4 A screenshot of SocketExample.java.
So far, we have talked about how to develop wireless applications using sockets. The previous example used a socket connection to communicate with a Web server to retrieve a Web document. The next section examines how to develop J2ME applications using datagram connections to communicate with a remote server or another wireless device. This datagram communication is based on the UDP protocol.
Wireless Network Programming Using Datagrams
A datagram is an independent, self-contained message sent over the network; the datagram's arrival, arrival time, and content are not guaranteed. It is a packet-based communication mechanism. Unlike stream-based communication, packet-based communication is connectionless, which means that no dedicated open connection exists between the sender and the receiver.
Packet-switched wireless networks are more likely to support this type of communication on MIDP devices. Datagrams may not be supported by circuit-switched wireless networks, which means that wireless applications developed with datagrams might be limited to certain devices and are less likely to be portable across different networks.
UDP
Datagram communication is based on UDP. The sender builds a datagram packet with destination information (an Internet address and a port number) and sends it out. Lower-level network layers do not perform any sequencing, error checking, or acknowledgement of packets. So, there is no guarantee that a data packet will arrive at its destination. The server might never receive your initial datagrammoreover, if it does, its response might never reach your wireless device. Because UDP is a not a guaranteed-delivery protocol, it is not suitable for applications such as FTP that require reliable transmission of data. However, it is useful in the following cases:
When raw speed of the communication is more critical than transmitting every bit correctly. For example, in a real-time wireless audio/video application on a cell phone, lost data packets simply appear as static. Static is much more tolerable than awkward pauses (when socket data transmission is used) in the audio stream.
When information needs to be transmitted on a frequent basis. In this case, losing a communication packet now and then doesn't affect the service significantly.
When socket communication is not supported at all, which is most likely the case in a packet-switched wireless network.
Using Datagrams
Here are the typical steps for using datagram communication in MIDlet applications:
Establish a datagram connection.
Construct a send datagram object with a message body and a destination address.
Send the datagram message out through the established datagram connection.
Construct a receive datagram object with a pre-allocated buffer.
Wait to receive the message through the established connection using the allocated datagram buffer.
Free up the datagram connection after use.
The following are rules of thumb for choosing a datagram size:
Never exceed the maximum allowable packet size. The maximum allowable packet size can be obtained by using the method GetMaximumLength() in the DatagramConnection interface (discussed earlier in the section "The Generic Connection Framework"). This number varies from vendor to vendor.
If the wireless network is very reliable and most of the data transmitted will arrive at the destination, use a bigger packet size. The bigger the packet size, the more efficient the data transfer, because the datagram header causes significant overhead when the packet size is too small.
If the wireless network is not very reliable, packets will probably be dropped during transmission. Use a smaller packet size so that they are unlikely to be corrupted in transit.
DatagramConnection and Datagram Classes
J2ME network programming with datagrams is very similar to J2SE. Two classes are defined in J2ME to support datagram communication: DatagramConnection and Datagram.
DatagramConnection is one of the connection interfaces in the Generic Network framework. It defines methods to support network communication based on the UDP protocol. (These methods were discussed in the section "The Generic Connection Framework" earlier in this chapter.)
Datagram provides a placeholder for a datagram message. A Datagram object can then be sent or received through a DatagramConnection.
The Datagram class extends from the DataInput and DataOutput classes in the java.io package. These classes provide methods for the necessary read and write operations to the binary data stored in the datagram's buffer.
Datagram also defines several UDP-specific methods in addition to the methods inherited from DataInput and DataOutput. These methods are as follows:
String getAddress()
This method returns the destination address in a datagram message.
byte[] getData()
This method returns the data buffer for a datagram message.
int getLength()
This method returns the length of the data buffer.
int getOffset()
This method returns the offset position in the data buffer.
void reset()
This method resets the read/write pointer to the beginning of the data structure.
void setAddress(Datagram ref)
This method gets the destination address from ref and assigns it to the current Datagram object.
void setAddress(String addr)
This method sets the destination address using addr. The address string is in the format {protocol}://[{host}]:{port}.
void setData(byte[] buffer, int offset, int len)
This method sets the data buffer, offset, and length for the Datagram object.
void setLength(int len)
This method sets a new length for its data buffer.
The following methods in the Datagram class are inherited from DataInput for reading binary data from its data buffer:
boolean readBoolean() byte readByte() char readChar() void readFully(byte[] b) void readFully(byte[] b, int off, int len) int readInt() long readLong() short readShort() int readUnsignedByte() int readUnsignedShort() String readUTF() int skipBytes(int n)
The following methods in the Datagram class are inherited from DataOutput for writing binary data to the datagram's data buffer:
void write(byte[] b) void write(byte[] b, int off, int len) void write(int b) void writeBoolean(boolean v) void writeByte(int v) void writeChar(int v) void writeChars(String s) void writeInt(int v) void writeLong(long v) void writeShort(int v) void writeUTF(String str)
Datagram Connections
To use datagram communication in a J2ME application, a datagram connection has to be opened first. Here is how to do that:
DatagramConnection dc = (DatagramConnection)
Connector.open("datagram://localhost:9000");
Like other types of connections, a datagram connection is created with the open method in Connector. The connect string is in this format:
datagram://[{host}]:{port}
In the connect string, the port field is required; it specifies the target port with a host. The host field is optional; it specifies the target host. If the host field is missing in the connection string, the connection is created in "server" mode. Otherwise, the connection is created in "client" mode.
For example, here is how a "server" mode datagram connection is created:
DatagramConnection dc = (DatagramConnection)
Connector.open("datagram://:9000");
This is the equivalent of the following:
DatagramConnection dc = (DatagramConnection)
Connector.open("datagram://localhost:9000");
A "server" mode connection means that the connection can be used both for sending and receiving datagrams via the same port. In the previous example, the program can receive and send datagrams on port 9000.
A "client" mode datagram connection is created with host specified in the connect string:
DatagramConnection dc = (DatagramConnection)
Connector.open("datagram://64.28.105.110:9000");
A "client" mode connection can be used only for sending datagram messages. The datagram to be sent must have the destination host and port; in this case, the host is 64.28.105.110 and the target port is 9000. When a datagram message is sent with a client mode connection, the reply-to port is always allocated dynamically.
Once a DatagramConnection is established, datagram messages can be sent and received using the send and receive methods.
The example in Listing 9.4 shows how to use datagrams to communicate with a remote server:
Listing 9.4 Listing4.txt
/**
* This sample code block demonstrates how a "server" mode
* DatagramConnection is created, and how datagram
* messages are sent and received via the connection.
* For demostration purpose, we assume that the remote server
* listens to port 9000 for incoming datagrams and responses
* back with a datagram message once the incoming datagram is
* received.. Once the message is received.
*/
import javax.microedition.io.*;
import java.io.*;
import java.lang.*;
// more code here ...
// the destination address of the datagram message to be sent.
String destAddr = "datagram://64.28.105.110:9000";
// the message string to be sent
String messageString = "REQUEST INFO";
// the DatagramConnection to be used for exchanging message with remote server
DatagramConnection datagramConnection = null;
try {
// create a "server" mode DatagramConnection
datagramConnection =
(DatagramConnection) Connector.open("datagram://:9000");
// get the length of the datagram message
int length = messageString.length();
byte[] messageBytes = new byte[length];
// store the message string into a byte array
System.arraycopy(messageString.getBytes(), 0, messageBytes, 0, length);
// construct a Datagram object to be sent with the message byte array,
// length of the byte array, and the destination address
Datagram sendDatagram =
datagramConnection.newDatagram(messageBytes, length, destAddr);
// send the Datagram object to its destination
datagramConnection.send(sendDatagram);
// create a Datagram object as a place holder for receiving message
receiveDatagram = datagramConnection.newDatagram(
datagramConnection.getMaximumLength());
// wait for Datagram sent back from remote server
datagramConnection.receive(receiveDatagram);
// do something with the received Datagram ...
} catch (IOException e) {
System.err.println("IOException Caught:" + e);
} finally {
// free up open connection
try { if (dc != null) dc.close();
} catch (Exception ignored) {}
}
// more code here ...
Sample Program
The following sample application demonstrates datagram communication between two cell phones. It consists of two programs: DatagramClient.java and DatagramServer.java. They are running on separate emulators. DatagramClient initiates a message, sends it out to DatagramServer using port 9000, and uses the same port to receive the response back. DatagramServer receives the message sent from the DatagramClient at port 9001, reverses the message string, and sends the reversed message back to the client. In this example, the client and server programs are running on separate emulators on the same machine. But, this process could just as easily occur across the Internet.
Figure 9.5 illustrates the program flow between two J2ME programs communicating with each other using datagrams.
Figure 9.5 The program flow of the datagram sample application.
The code of the client program can be found in Listing 9.5, and the code of the server program can be found in Listing 9.6.
Listing 9.5 DatagramClient.java
/**
* The following MIDlet application is a datagram client program
* that exchanges datagram with another MIDlet application acting
* as a datagram server program.
*/
// include MIDlet class libraries
import javax.microedition.midlet.*;
// include networking class libraries
import javax.microedition.io.*;
// include GUI class libraries
import javax.microedition.lcdui.*;
// include I/O class libraries
import java.io.*;
public class DatagramClient extends MIDlet
implements CommandListener {
// define the GUI components for entering the message text to be sent
private Form mainScreen;
private TextField sendingField;
private Display myDisplay = null;
private DatagramClientHandler client;
// define the GUI components for displaying the returned message
private Form resultScreen;
private StringItem resultField;
private String resultString;
// the "send" button on the mainScreen
Command sendCommand = new Command("SEND", Command.OK, 1);
public DatagramClient(){
// initialize the GUI components
myDisplay = Display.getDisplay(this);
mainScreen = new Form("Datagram Client");
sendingField = new TextField(
"Enter your message", null, 30, TextField.ANY);
mainScreen.append(sendingField);
mainScreen.addCommand(sendCommand);
mainScreen.setCommandListener(this);
}
public void startApp() {
myDisplay.setCurrent(mainScreen);
client = new DatagramClientHandler();
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == sendCommand) {
// get the message text from user input
String sendMessage = sendingField.getString();
// send and receive datagram messages
try {
resultString = client.send_receive(sendMessage);
} catch (IOException e) {
System.out.println("Failed in send_receive():" + e);
}
// display the returned message
resultScreen = new Form("Message Confirmed:");
resultField = new StringItem(null, resultString);
resultScreen.append(resultField);
resultScreen.setCommandListener(this);
myDisplay.setCurrent(resultScreen);
}
}
class DatagramClientHandler extends Object {
private DatagramConnection dc;
// Datagram object to be sent
private Datagram sendDatagram;
// Datagram object to be received
private Datagram receiveDatagram;
public DatagramClientHandler() {
try {
// establish a DatagramConnection at port 9000
dc = (DatagramConnection)
Connector.open("datagram://" + ":9000");
/* Since the datagram server program runs on the same machine
* where the client program runs on, and the server program
* listens to port 9001, the destination address of datagram
* to be sent is set to "localhost:9001". If the server
* program runs on a different machine, "localhost" in the
* connect string needs to be replaced with that machine's ip
* address.
*/
sendDatagram = dc.newDatagram(
dc.getMaximumLength(), "datagram://localhost:9001");
// initialize the Datagram object to be received
receiveDatagram = dc.newDatagram(dc.getMaximumLength());
} catch (IOException e) {
System.out.println(e.toString());
}
}
public String send_receive(String msg) throws IOException {
int length = msg.length();
byte[] message = new byte[length];
// copy the send message text into a byte array
System.arraycopy(msg.getBytes(), 0, message, 0, length);
sendDatagram.setData(message, 0, length);
sendDatagram.setLength(length);
// use retval to store the received message text
String retval = "";
try {
// send the message to server program
dc.send(sendDatagram);
// wait and receive message from the server
dc.receive(receiveDatagram);
// put the received message in a byte array
byte[] data = receiveDatagram.getData();
// transform the byte array to a string
retval = new String(data, 0, receiveDatagram.getLength());
} finally{
if (dc != null) dc.close();
}
// return the received message text to the calling program
return retval;
}
}
}
Listing 9.6 DatagramServer.java
/**
* The following MIDlet application is a datagram server program
* that waits and receives datagram message from a client program,
* reverses the message text, then sends the reversed text back to
* the datagram client program.
*/
// include MIDlet class libraries
import javax.microedition.midlet.*;
// include networking class libraries
import javax.microedition.io.*;
// include GUI class libraries
import javax.microedition.lcdui.*;
// include I/O class libraries
import java.io.*;
public class DatagramServer extends MIDlet{
// define the GUI components for displaying the received message
private Display myDisplay = null;
private Form mainScreen;
private StringItem resultField;
// text string for storing the received message
private String resultString;
public DatagramServer() {
// initialize the GUI components
myDisplay = Display.getDisplay(this);
mainScreen = new Form("Message Received");
resultField = new StringItem(null, null);
}
public void startApp() {
myDisplay.setCurrent(mainScreen);
// perform the receive, reverse and send back tasks
DatagramServerHandler server = new DatagramServerHandler();
try {
resultString = server.receive_reverse_send();
} catch (IOException e) {
System.out.println("Failed in receive_reverse_send():" + e);
}
// display the received message text
resultField.setText(resultString);
mainScreen.append(resultField);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
class DatagramServerHandler extends Object {
// the server program listens to port 9001
private static final String defaultPortNumber="9001";
private String msg;
private DatagramConnection dc;
// define the Datagram objects for messages
private Datagram sendDatagram;
private Datagram receiveDatagram;
public DatagramServerHandler() {
try {
// Create a "server" mode connection on the default port
dc = (DatagramConnection)Connector.open(
"datagram://:" + defaultPortNumber);
// construct a Datagram object for receiving message
receiveDatagram = dc.newDatagram(dc.getMaximumLength());
// construct a Datagram object for sending message
sendDatagram = dc.newDatagram(dc.getMaximumLength());
} catch (Exception e) {
System.out.println("Failed to initialize Connector");
}
}
public String receive_reverse_send() throws IOException {
String receiveString = "";
try{
// wait to receive datagram message
dc.receive(receiveDatagram);
// extract data from the Datagram object receiveDatagram
byte[] receiveData = receiveDatagram.getData();
int receiveLength = receiveDatagram.getLength();
// store message text in receiveString
receiveString = (new String(receiveData)).trim();
// reverse the string
StringBuffer reversedString =
(new StringBuffer(receiveString)).reverse();
// getting the reply-to address from the Datagram object
String address = receiveDatagram.getAddress();
// construct the sendDatagram with the reversed text
// and the reply-to address.
int sendLength = reversedString.length();
byte[] sendData = new byte[sendLength];
System.arraycopy(reversedString.toString().getBytes(),
0, sendData, 0, sendLength);
sendDatagram.setData(sendData, 0, sendLength);
sendDatagram.setAddress(address);
// send the reversed string back to client program
dc.send(sendDatagram);
} finally {
if (dc != null) dc.close();
}
return receiveString;
}
}
}
Figure 9.6 shows the client program before the message test message is sent to the server program. Figure 9.7 shows the server program after the message test message is received.
Figure 9.6 Before test message is sent to the server.
Figure 9.7 After test message is received by the server.
Figure 9.8 shows the client program after the reversed message string is received from the server program. The string test message is now egassem tset.
Figure 9.8 The reversed message egassem tset is received from the server.
Wireless Network Programming Using HttpConnection
The section "Wireless Network Programming Using Sockets," showed an example of using socket connections to communicate with a Web server using the HTTP protocol. The same thing can be done more easily with the HttpConnection, which is more closely tied to the HTTP protocol for communicating with Web servers. It defines several HTTP-specific methods to make HTTP-based network programming simpler and more straightforward. For example, HttpConnection provides methods that allow developers to obtain HTTP header information much easier.
Using HttpConnection as the network communication in your application offers several major advantages:
Not every MIDP device supports socket and datagram communication. However, all MIDP devices support HTTP communication.
Socket and datagram communications are very network dependent. Some networks may implement only one type of communication and not the other. This limitation makes your wireless application less portable.
The mandatory support of the HTTP protocol in MIDP devices gives wireless application a high-level, standard, network-independent protocol to work with. Therefore, J2ME wireless applications developed using HttpConnection are very portable across different wireless networks.
Different types of data can be encapsulated into HTTP requests easily, especially if developers use XML in their applications. Chapter 10, "Using XML in Wireless Applications," discusses in more detail how to use XML in wireless application development. HTTP communication makes it easier to deal with issues such as network security and firewalls. Because the HTTP's well-known port 80 is the least likely port blocked by firewalls.
Methods in HttpConnection
The HttpConnection interface supports a subset of the HTTP 1.1 protocol. Here are the methods defined in HttpConnection:
long getDate()
This method returns the value of the date field in the HTTP header. The result is the number of milliseconds since January 1, 1970, GMT.
long getExpiration()
This method returns the value of the expires field in the HTTP header. The result is the number of milliseconds since January 1, 1970, GMT. It returns 0 if the value is unknown.
String getFile()
This method returns the file portion of the URL of this HttpConnection. It returns null if there is no file.
String getHeaderField(int index)
This method returns the String value of a header field by index. It returns null if index is out of range. Because the HTTP headers returned by different Web servers are different, it is recommended that you check to see if the value is null before applying any operation on it. You have to know the sequence of the header fields to use this method.
String getHeaderField(String name)
This method returns the String value of a named header field. It returns null if the field is missing or malformed.
long getHeaderFieldDate(String name, long def)
This method returns the long value of a named header field. The value is parsed as a date. The result is the number of milliseconds since January 1, 1970, GMT. The default value def is returned if the field is missing or malformed.
int getHeaderFieldInt(String name, int def)
This method returns the int value of a named header field. The default value def is returned if the field is missing or malformed.
String getHeaderFieldKey(int index)
This method returns the name of a header field by index. It returns null if index is out of range. You have to know the sequence of the header fields to use this method.
String getHost()
This method returns the host information of the URL string.
long getLastModified()
This method returns the value of the last-modified field in the HTTP header. The result is the number of milliseconds since January 1, 1970, GMT. It returns 0 if the value is unknown.
int getPort()
This method returns the port number of the URL string. It returns 80 by default if there was no port number in the string passed to Connector.open().
String getProtocol()
This method returns the protocol name of the URL string, such as http or https.
String getQuery()
This method returns the query portion of the URL string. In the HTTP protocol, a query component of a URL is defined as the text after the last question mark (?) character in the URL. For instance, the query portion of the URL string http://64.28.105.110/servlets/webyu/Chapter9Servlet?request=gettimestamp is request=gettimestamp.
String getRef()
This method returns the reference portion of the URL string. In the HTTP protocol, a reference component of a URL is defined as the text after the crosshatch character (#) in the URL. For instance, the reference portion of the URL string http://64.28.105.110/index.html#top is top.
String getRequestMethod()
This method returns the current request method of the HttpConnection. The possible values are GET, HEAD, and POST.
String getRequestProperty(String key)
This method returns the value of the general request property by the key property.
int getResponseCode()
This method returns the HTTP response status code. For instance
HTTP/1.1 200 OK HTTP/1.1 401 Unauthorized
The method returns the integers 200 and 401, respectively, from the above responses.
String getResponseMessage()
This method returns the HTTP response message from a Web server. For instance, given the responses HTTP/1.1 200 OK and HTTP/1.1 401 Unauthorized, the method returns the strings OK and Unauthorized, respectively.
String getURL()
This method returns the URL string of this HttpConnection.
void setRequestMethod(String method)
This method sets the request method for this HttpConnection. The possible values are GET, POST, and HEAD. If not specified, the default HTTP request method is GET.
void setRequestProperty(String key, String value)
This method sets the general request property for this HttpConnection. For instance
setRequestProperty( "User-Agent", "Mozilla/5.001 (windows; U; NT4.0; en-us) Gecko/25250101");
sets a value for the request property "User-Agent" of an HttpConnection. If a property with the key already exists, the method overwrites its value with the new value.
The following methods are inherited from the ContentConnection interface:
String getEncoding() long getLength() String getType() DataInputStream openDataInputStream() InputStream openInputStream() DataOutputStream openDataOutputStream() OutputStream openOutputStream()
HttpConnection States
There are two possible states for an HTTP connection: Setup and Connected.
In the Setup state, the connection has not been made to the server. In the Connected state, the connection has been made, request parameters have been sent, and the response is expected in the Connected state.
The transition from the Setup state to the Connected state is caused by any method that requires data to be sent to or received from the server. The following methods cause the transition to the Connected state from a Setup state:
openInputStream() openDataInputStream() openOutputStream() openDataOutputStream() oetLength() oetType() oetEncoding() getDate() getExpiration() getLastModified() getHeaderField() getHeaderFieldKey() getResponseCode() getResponseMessage() getHeaderFieldInt() getHeaderFieldDate()
The following methods may be invoked only in the Setup state:
setRequestMethod() setRequestProperty()
The following methods may be invoked in any state:
close() getRequestMethod() getRequestProperty() getURL() getProtocol() getHost() getFile() getRef() getPort() getQuery()
HttpConnection Request Methods
HttpConnection allows three types of requests to be sent to a Web server: GET, HEAD, and POST.
The GET method is used by programs to obtain the contents of a Web document from the specified URL. The Web server responses consist of HTTP header information about the Web document, MIME type information about the content data, and the actual content data.
The HEAD method is used by programs to obtain information about a Web document instead of retrieving the contents of the Web document. When the Web server receives a HEAD request, only the HTTP header data (without the content data) is returned.
The POST method is often used by programs to send the form information to the URL of a CGI program. Both POST and GET can be used to send data to a CGI program; the difference is that the POST method sends data via a stream while the GET method sends data via environment variables embedded in the query string.
The following examples explain how to use these request methods with HttpConnection.
Using the GET Request Method with HttpConnection
The default request method used by HttpConnection is GET. This type of request carries all the information as part of the URL string. In the following code example, a GET request is sent to server 64.28.105.110:
http://64.28.105.110/servlets/webyu/Chapter9Servlet?request=gettimestamp
The Java servlet Chapter9Servlet will accept the request, get the current local time, and send it back to the client.
In a GET request, the query string is embedded as part of the URL string. For instance, if getQuery() is called on this HTTP connection, the value request=gettimestamp will be returned. When the GET request is sent to the Java servlet, the previous environment variable request and its value are also passed along to the server program.
Listing 9.7 is a sample program that demonstrates how to send a GET request to a Web server using HttpConnection.
Listing 9.7 HttpGET.java
/**
* The following MIDlet application demonstrates how to establish a
* HttpConnection and uses it to send a GET request to Web server.
*/
// include MIDlet class libraries
import javax.microedition.midlet.*;
// include networking class libraries
import javax.microedition.io.*;
// include GUI class libraries
import javax.microedition.lcdui.*;
// include I/O class libraries
import java.io.*;
public class HttpGET extends MIDlet implements CommandListener {
// A default URL is used. User can change it from the GUI.
private static String defaultURL =
"http://64.28.105.110/servlets/webyu/Chapter9Servlet?request=gettimestamp";
// GUI components for entering a Web URL.
private Display myDisplay = null;
private Form mainScreen;
private TextField requestField;
// GUI components for displaying server responses.
private Form resultScreen;
private StringItem resultField;
// the "send" button used on mainScreen
Command sendCommand = new Command("SEND", Command.OK, 1);
// the "back" button used on resultScreen
Command backCommand = new Command("BACK", Command.OK, 1);
public HttpGET(){
// initialize the GUI components
myDisplay = Display.getDisplay(this);
mainScreen = new Form("Type in a URL:");
requestField =
new TextField(null, defaultURL,
100, TextField.URL);
mainScreen.append(requestField);
mainScreen.addCommand(sendCommand);
mainScreen.setCommandListener(this);
}
public void startApp() {
myDisplay.setCurrent(mainScreen);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
// when user clicks on the "send" button on mainScreen
if (c == sendCommand) {
// retrieve the Web url that user entered
String urlstring = requestField.getString();
// send a GET request to Web server
String resultstring = "";
try {
resultstring = sendGetRequest(urlstring);
} catch (IOException e) {
resultstring = "ERROR";
}
// display the page content retrieved from Web server
resultScreen = new Form("GET Result:");
resultField =
new StringItem(null, resultstring);
resultScreen.append(resultField);
resultScreen.addCommand(backCommand);
resultScreen.setCommandListener(this);
myDisplay.setCurrent(resultScreen);
} else if (c == backCommand) {
// do it all over again
requestField.setString(defaultURL);
myDisplay.setCurrent(mainScreen);
}
}
// send a GET request to Web server
public String sendGetRequest(String urlstring) throws IOException {
HttpConnection hc = null;
DataInputStream dis = null;
String message = "";
try {
// open up an HttpConnection with the Web server
// the default request method is GET.
hc = (HttpConnection) Connector.open(urlstring);
// obtain a DataInputStream from the HttpConnection
dis = new DataInputStream(hc.openInputStream());
// retrieve the contents of the requested page from Web server
int ch;
while ((ch = dis.read()) != -1) {
message = message + (char) ch;
}
} finally {
if (hc != null) hc.close();
if (dis != null) dis.close();
}
return message;
}
}
Figure 9.9 shows a screenshot of the HttpGET program before the GET request is sent out to a Web server.
Figure 9.10 shows a screenshot of the page content retrieved from a Web URL.
Figure 9.9 Type in the Web URL.
Figure 9.10 The content of a Web document.
Using the HEAD Request Method with HttpConnection
HTTP servers provide a substantial amount of information in the HTTP headers that precede each response. For instance, here's a typical HTTP header returned by an Apache Web server running on Sun Solaris:
HTTP 1.1 200 OK Data: Mon, 18 Oct 1999 20:06:48 GMT Server: Apache/1.3.4 (Unix) PHP/3.0.6 Last-Modified: Mon, 18 Oct 1999 Accept-Ranges: bytes Content-Length: 35259 Content-Type: text/html
In most cases, an HTTP header includes the content type of the page requested, the content length and the character set in which the content is encoded, the date and time of the response, the last modified time of the page requested, and the expiration date for caching purposes. The following are some of the most common header fields in an HTTP header:
Content-type Content-length Content-encoding Date Last-modified Expires
When a HEAD request is sent to a Web server, only the header information will be returned. This type of request is typically used to determine if a cache entry can be reused or if it should be replaced with newer information based on the property values retrieved from the header fields.
The sample program in Listing 9.8 sends a HEAD request to Web server 64.28.105.110 and retrieves all the HTTP header information. The setRequestMethod(HttpConnection.HEAD) method is used to specify that this request is a HEAD request. The getHeaderField() method is used to retrieve the field values and the getHeaderFieldKey() method is used to retrieve the field names.
Listing 9.8 HttpHEAD.java
/**
* The following MIDlet application demonstrates how to establish
* an HttpConnection and use it to send a HEAD request
* to a Web server.
*/
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class HttpHEAD extends MIDlet
implements CommandListener {
// A default URL is used. User can change it from the GUI.
private static String defaultURL = "http://64.28.105.110";
// GUI components for entering a Web URL.
private Display myDisplay = null;
private Form mainScreen;
private TextField requestField;
// GUI components for displaying server responses.
private Form resultScreen;
private StringItem resultField;
// the "send" button used on mainScreen
Command sendCommand = new Command("SEND", Command.OK, 1);
// the "back" button used on resultScreen
Command backCommand = new Command("BACK", Command.OK, 1);
public HttpHEAD(){
// initialize the GUI components
myDisplay = Display.getDisplay(this);
mainScreen = new Form("Type in a URL:");
requestField = new TextField(
null, defaultURL, 50, TextField.URL);
mainScreen.append(requestField);
mainScreen.addCommand(sendCommand);
mainScreen.setCommandListener(this);
}
public void startApp() {
myDisplay.setCurrent(mainScreen);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
// when user clicks on the "send" button
if (c == sendCommand) {
// retrieve the Web URL that user entered
String urlstring = requestField.getString();
// send a HEAD request to Web server
String resultstring = "";
try{
resultstring = sendHeadRequest(urlstring);
} catch (IOException e) {
resultstring = "ERROR";
}
// display the header information retrieved from Web server
resultScreen = new Form("HEAD Result:");
resultField = new StringItem(null, resultstring);
resultScreen.append(resultField);
resultScreen.addCommand(backCommand);
resultScreen.setCommandListener(this);
myDisplay.setCurrent(resultScreen);
} else if (c == backCommand) {
// do it all over again
requestField.setString(defaultURL);
myDisplay.setCurrent(mainScreen);
}
}
// send a HEAD request to Web server
public String sendHeadRequest(String urlstring) throws IOException {
HttpConnection hc = null;
InputStream is = null;
String message = "";
try {
// open up an HttpConnection with the Web server
hc = (HttpConnection) Connector.open(urlstring);
// set request method to HEAD
hc.setRequestMethod(HttpConnection.HEAD);
// obtain an InputStream from the HttpConnection
is = hc.openInputStream();
// retrieve the value pairs of HTTP header information
int i = 1;
String key = "";
String value = "";
while ((value = hc.getHeaderField(i)) != null) {
key = hc.getHeaderFieldKey(i++);
message = message + key + ":" + value + "\n";
}
} finally {
if (hc != null) hc.close();
if (is != null) is.close();
}
return message;
}
}
Figure 9.11 shows a screenshot of all the header information retrieved from a Web server.
Several additional methods are available in HttpConnection for retrieving header information: getLength, getType, getEncoding, getResponseCode, getResponseMessage, getHeaderFieldInt, and getHeaderFieldDate.
Using the POST Request Method with HttpConnection
To send an HTTP request using the POST method, both InputStream and OutputStream have to be obtained from the HttpConnection. InputStream will be used to retrieve the responses from the Web server. OutputStream will be used to send the data separately via a stream (in our examples, the data to be sent is request=gettimestamp).
The following MIDlet application is very similar to HttpGET.java, except that the request being sent is a POST request. The Web URL is
http://64.28.105.110/servlets/webyu/Chapter9Servlet
Figure 9.11 Header information retrieved by the program HttpHEAD.java.
The Java servlet Chapter9Servlet will accept this request, get the local current time, and send it back to the client. Notice that in this POST request, the data to be sent request=gettimestamp is no longer part of the URL. It will be sent to Web server separately once the HTTP connection is established.
Listing 9.9 demonstrates how to send a POST request to a Web server using HttpConnection.
Listing 9.9 HttpPOST.java
/**
* This MIDlet application demonstrates how to establish
* an HttpConnection and use it to send a POST request
* to a Web server.
*/
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class HttpPOST extends MIDlet
implements CommandListener {
// A default URL is used. User can change it from the GUI.
private static String defaultURL =
"http://64.28.105.110/servlets/webyu/Chapter9Servlet";
// GUI component for entering a Web URL
private Display myDisplay = null;
private Form mainScreen;
private TextField requestField;
// GUI component for displaying server responses.
private Form resultScreen;
private StringItem resultField;
// the "send" button used on mainScreen
Command sendCommand = new Command("SEND", Command.OK, 1);
// the "back" button used on resultScreen
Command backCommand = new Command("BACK", Command.OK, 1);
public HttpPOST(){
// initialize the GUI components
myDisplay = Display.getDisplay(this);
mainScreen = new Form("Type in a URL:");
requestField =
new TextField(null, defaultURL, 100, TextField.URL);
mainScreen.append(requestField);
mainScreen.addCommand(sendCommand);
mainScreen.setCommandListener(this);
}
public void startApp() {
myDisplay.setCurrent(mainScreen);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
// help Garbage Collector
Display myDisplay = null;
mainScreen = null;
requestField = null;
resultScreen = null;
resultField = null;
}
public void commandAction(Command c, Displayable s) {
// when user clicks on the "send" button
if (c == sendCommand) {
// retrieve the Web URL that user entered
String urlstring = requestField.getString();
// send a POST request to Web server
String resultstring = "";
try {
resultstring = sendPostRequest(urlstring);
} catch (IOException e) {
resultstring = "ERROR";
}
// display the message received from Web server
resultScreen = new Form("POST Result:");
resultField = new StringItem(null, resultstring);
resultScreen.append(resultField);
resultScreen.addCommand(backCommand);
resultScreen.setCommandListener(this);
myDisplay.setCurrent(resultScreen);
} else if (c == backCommand) {
// do it all over again
requestField.setString(defaultURL);
myDisplay.setCurrent(mainScreen);
}
}
// send a POST request to Web server
public String sendPostRequest(String urlstring) throws IOException {
HttpConnection hc = null;
DataInputStream dis = null;
DataOutputStream dos = null;
String message = "";
// the request body
String requeststring = "request=gettimestamp";
try {
// an HttpConnection with both read and write access
hc = (HttpConnection)
Connector.open(urlstring, Connector.READ_WRITE);
// set the request method to POST
hc.setRequestMethod(HttpConnection.POST);
// obtain DataOutputStream for sending the request string
dos = hc.openDataOutputStream();
byte[] request_body = requeststring.getBytes();
// send request string to Web server
for (int i = 0; i < request_body.length; i++) {
dos.writeByte(request_body[i]);
}
// flush it out
dos.flush();
// obtain DataInputStream for receiving server responses
dis = new DataInputStream(hc.openInputStream());
// retrieve the responses from Web server
int ch;
while ((ch = dis.read()) != -1) {
message = message + (char) ch;
}
} finally {
// free up i/o streams and http connection
if (hc != null) hc.close();
if (dis != null) dis.close();
if (dos != null) dos.close();
}
return message;
}
}
When the sample program HttpPOST.java runs successfully, it should generate the same result as shown in Figure 9.9.
Server-Side Handling of GET and POST Requests
The requests sent by both HttpGET.java and HttpPOST.java are handled by a single Java servlet Chapter9Servlet on http://www.webyu.com. The responses to these requests are identical even though they are handled differently inside the servlet.
Network programming with J2ME typically involves both client and server programs. Due to the limited resources available on wireless, most of J2ME clients are thin clients. This means that complex business logic and heavy computation will be left to the servers. Server-side programming is as important as client-side programming, if not more so. It is beneficial to understand how HTTP's GET and POST requests are handled on the Web server side.
Listing 9.10 is the Java servlet that handles the HTTP requests sent by HttpGET.java and HttpPOST.java.
Listing 9.10 Chapter9Servlet.java
/**
* Chapter9Servlet is a Java servlet running at
* http://www.webyu.com/servlets/webyu/Chatper9Servlet.
* It responds to both GET and POST requests. The request
* message must be "request=gettimestamp". The response
* message is a current timestamp.
*/
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class Chapter9Servlet extends HttpServlet{
public void init (ServletConfig config)
throws ServletException {
super.init(config);
}
// doGet will be called when a GET request is received.
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// get field value in the query string
String value = request.getParameter("request");
// turn auto flush on
PrintWriter out = new PrintWriter(
response.getOutputStream(), true);
// call getResult to get the current timestamp
String message = getResult(value);
// send an HTML response back to the client
response.setContentType("text/html");
response.setContentLength(message.length());
out.println(message);
}
// doPost will be called when a POST request is received.
public void doPost (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String name = "";
String value = "";
// parse out the value pair from the POST body
// the expected string is "request=gettimestamp"
try {
BufferedReader br = request.getReader();
String line;
String requeststring = "";
while (( line = br.readLine()) != null) {
requeststring = requeststring + line;
}
StringTokenizer sTokenizer =
new StringTokenizer(requeststring, "=");
if (sTokenizer.hasMoreTokens())
name = (String) sTokenizer.nextToken();
if (sTokenizer.hasMoreTokens())
value = (String) sTokenizer.nextToken();
} catch (Exception e) {
System.err.println(e);
}
// turn auto flush on
PrintWriter out =
new PrintWriter(response.getOutputStream(), true);
String message = getResult(value);
response.setContentType("text/html");
response.setContentLength(message.length());
out.println(message);
}
//get current timestamp and put it into HTML format
public String getResult(String method) {
String message = "";
// if the query string value is "gettimestamp"
//then current local timestamp is returned
if ( method.equals("gettimestamp") ) {
TimeZone timezone = TimeZone.getDefault();
Calendar calendar = Calendar.getInstance(timezone);
String local_time = calendar.getTime().toString();
message = message + "<html><head><title>" +
local_time + "</title></head>\n";
message = message +
"<body>Web Server's Local Time is <br>\n";
message = message + local_time + "</body></html>\n";
} else {
// otherwise, an error message is returned
message = message +
"<html><head><title>Error</title></head>\n";
message = message +
"<body>Unrecoganized Method Name</body></html>\n";
}
return message;
}
}
In Chapter9Servlet.java, the doGet() method is called when GET requests are received, and the doPost() method is called when POST requests are received. In the doGet() method, the environment variable request is retrieved by calling the getParameter("request") method. While in the doPost() method, the data chunk request=timestamp is retrieved via a BufferedReader. Compiling and running this server program requires Java servlet and Web server knowledge and is beyond the scope of this book. For more information about Java servlets, please visit Sun's Java Web site at http://www.javasoft.com.
Summary
This chapter discussed the basics of network programming in J2ME using the CLDC and MIDP networking class libraries, explained the concept of the Generic Connection framework and its connection interfaces, and explained the mandatory class HttpConnection in MIDP. It then used sample MIDlet applications to demonstrate how to use socket, datagram, and http communications with your wireless applications.