Sample (NetClientMIDlet.java)



15.4 Sample Code (NetClientMIDlet.java)

The following application, NetClientMIDlet, demonstrates how HTTP can be used to log in to a server, send data, and receive a response.

graphics/15inf01.gif

The MIDlet begins by displaying the instructional screen shown in the top figure. If the user selects OK, the MIDlet sends a POST request that contains a user identifier and password for a basic authentication scheme. It also sends the message Esse quam videri ("To be rather than to seem.")

graphics/15inf02.gif

After sending the message, the MIDlet reads the returned values. A screen full of the values are shown in the next figure: a string identifying itself (such as "NetServer Servlet" as shown in the bottom figure), followed by a date string, the original POST message body, the message body reversed, and a complete list of headers read from the POST request. (The figure only shows the first header.)

The example consists of three classes. The NetClientMIDlet class extends the MIDlet class. It implements the MIDlet life cycle methods and has the device display the screens shown in the figures. The ConnectionManager class opens the HTTP connection to a server, sends the information, and reads the response. The BasicAuth class transforms the user identifier and password into a base64 encoded string.

NetClientMIDlet.java

This class creates the MIDlet's user interface and manages its states. The constructor creates the MIDlet's screens. The startApp method displays the main screen, which is an instructional screen, the first time it is called. If the user selects OK on the main screen, the run method is called. The run method creates a ConnectionManager instance and sets the connection manager's msg field to "Esse quam videri", its user field to book, and its password field to bkpasswd. The run method then has the connection manager handle the request and displays the results that the connection manager returns.

// Package and import declarations
package examples.netclient;
import java.lang.*;
import java.io.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

// Example use of the HTTP networking interface. It opens an
// HTTP POST connection to a server and uses it to authenticate
// with an HTTP Basic Authentication scheme and send a string
// to the server. This MIDlet then reads and displays the response.
public class NetClientMIDlet extends MIDlet
    implements CommandListener, Runnable {

    private Display display;    // handle to the display
    private TextBox mainScr;    // main screen
    private Command cmdOK;      // OK command
    private Command cmdExit;    // EXIT command
    private Form progress;      // Progress Form

    // Creates the MIDlet, its main screen, its progress screen,
    // and its commands. It also caches the MIDlet's display.
    public NetClientMIDlet() {
        display = Display.getDisplay(this);
        mainScr = new TextBox("NetClient",
                    "Press OK to connect to network service",
                    1024, TextField.UNEDITABLE);
        cmdOK = new Command("OK", Command.SCREEN, 1);
        cmdExit = new Command("Exit", Command.EXIT, 1);
        mainScr.addCommand(cmdOK);
        mainScr.addCommand(cmdExit);
        mainScr.setCommandListener(this);
        display.setCurrent(mainScr);

        progress = new Form("Progress");
        Gauge g = new Gauge("Waiting...", false, Gauge.INDEFINITE,
                            Gauge.CONTINUOUS_RUNNING );
        progress.append(g);
        progress.addCommand(cmdExit);
        progress.setCommandListener(this);
    }

    // The system calls this method to start the MIDlet.
    protected void startApp() {}

    // The system calls this method to pause the MIDlet.
    // Does nothing: there are no global resources to release.
    protected void pauseApp() {}

    // The system calls this method to exit the MIDlet.
    // Does nothing: there are no global resources to release.
    protected void destroyApp(boolean unconditional) {}

    // Gets and displays data from the server
    public void run() {
        // Create and set parameters to communicate with the server
        ConnectionManager h = new ConnectionManager();
        h.setMsg("Esse quam videri");
        h.setUser("book");
        h.setPassword("bkpasswd");

        // Communicate with the server
        byte[] data = h.Process();
        // Display the result of the communication
        if (data == null || data.length == 0) {
            // Tell user no data was returned
            Alert alert = new Alert("Alert",
                                    "No information available",
                                    null, AlertType.ERROR);
            display.setCurrent(alert, mainScr);
        } else {
            // Create data screen
            TextBox dataScr = new TextBox("Data Screen", "",
                                          1, TextField.UNEDITABLE);
            dataScr.addCommand(cmdOK);
            dataScr.addCommand(cmdExit);
            dataScr.setCommandListener(this);

            dataScr.setMaxSize(data.length);
            dataScr.setString(new String(data));
            display.setCurrent(dataScr);
        }
    }

    // Respond to user commands, invoke the next task
   public void commandAction(Command c, Displayable d) {
        if (c == cmdOK) {
            if (d == mainScr) {
                // Communicate with the server without blocking UI
                display.setCurrent(progress);
                new Thread(this).start();
            } else {
                // Display the main screen
                display.setCurrent(mainScr);
            }
    } else if (c == cmdExit) {
            // Exit the MIDlet
            destroyApp(false);
            notifyDestroyed();
        }
    }
}

ConnectionManager.java

The ConnectionManager class manages the HTTP POST connection. The run method of the NetClientMIDlet class creates an instance of the class, and then calls the process method of the new instance.

The constructor sets the strings for the User-Agent and Accept-Language headers. The process method opens the HTTP connection by calling the open method. The example hard codes the URL of the server in the variable baseurl; the variable refers to a simple Java servlet that accepts HTTP POST connections.

After it opens the connection, the process method sets the HTTP request type to HttpConnection.POST to allow the MIDlet to write data to the server. It then sets the HTTP header fields to the values shown in Figure.

HTTP header values

Header

Value

User-Agent

The string Profile/MIDP-1.0 Configuration/CLDC-1.0

Accept-Language

The value of the system property microedition.locale or the string en-US if the property is not set

Content-Length

The length of the field msg

Content-Type

The string text/plain

Authorization

The string Basic followed by a space, followed by the base64 encoding of user:password

As the final step in preparing the request, the process method opens the connection's output stream, writes a message, and closes the output stream. At that point the network connection is made, the stored headers are flushed, and the msg field is written to the server.

The connection manager calls the getResponseCode method to see whether the connection was successful. Its subsequent actions depend on the status code. There are three response cases to handle:

  • HttpConnection.HTTP_OK: The connection was opened and the data was written. In this case, the connection manager opens an InputStream and reads the response data from the server.

  • HttpConnection.HTTP_MOVED_TMP, HttpConnection.HTTP_MOVED_PERM, or HttpConnection.HTTP_TEMP_REDIRECT: The server is trying to redirect the request. In this case, the connection manager reads the location header and uses the new URL.

  • The connection was not opened for an unexpected reason; in this case, the connection manager throws an IOException.

package examples.netclient;
import java.lang.*;
import java.util.*;
import java.io.*;
import javax.microedition.io.*;

// Establishes an HTTP POST connection to the server defined by
// baseurl, sends headers and a message, and reads the response.
// It sets the following HTTP headers:
// User-Agent:      CLDC and MIDP version strings
// Accept-Language: microedition.locale or "en-US" if that is null
// Content-Length:  the length of msg
// Content-Type:    text/plain
// Authorization:   "Basic" + the base 64 encoding of user:password
class ConnectionManager {
    private HttpConnection con;
    private InputStream is;
    private OutputStream os;
    private final String baseurl =
        "http://127.0.0.1:8080/Book/netserver";
    private String ua;
    private String locale;
    private String msg;
    private String user;
    private String password;

    // Creates the connection manager and creates the values for the
    // User-Agent and Accept-Language headers.
    ConnectionManager() {
        StringBuffer sb = new StringBuffer(60);
        sb.append("Configuration/");
        sb.append(
            System.getProperty("microedition.configuration"));

        // For each profile, append it to the user agent string
        String prof = System.getProperty("microedition.profiles");
        int i = 0, j = 0;
        while ((j = prof.indexOf(' ', i)) != -1) {
            sb.append(" Profile/");
            sb.append(prof.substring(i, j));
            i = j + 1;
        }
        sb.append(" Profile/");
        sb.append(prof.substring(i));
        ua = sb.toString();

        // Initialize the current locale
        locale = System.getProperty("microedition.locale");
        if (locale == null) {
            locale = "en-US";
        }
    }

    // Processes an HTTP connection request and returns the result
    // in a form that can be displayed for the end user.
    byte[] Process() {
        byte[] data = null;

        try {
            // Open connection, send data, and check response status
            open();

            // Read the response (open would throw an IOException
            // if there were nothing to read.)
            int n = (int)con.getLength();
            if (n > 0) {
                int bytesread = 0;
                data = new byte[n];
                for (int offset = 0; offset < n;
                     offset += bytesread) {
                     bytesread = s.read(data, offset, n-bytesread);
                }
            }
        } catch (IOException ioe) {
            // Close the connection and any open streams
        } finally {
            try {
                if (con != null) {
                    con.close();
                 }
                if (os != null) {
                    os.close();
                 }
                if (is != null) {
                    s.close();
                 }
             } catch (IOException ioe) {
                 return data;
             }
        }
    }

    // Open a connection to the server
    private void open() throws IOException {
        int status = -1;
        String url = baseurl;
        String auth = null;
        is = null;
        os = null;
        con = null;
        // Loop until a connection is made (in case of redirects)
        while (con == null) {
            // Set up the connection values (headers, and so on)
            con = (HttpConnection)Connector.open(url);
            con.setRequestMethod(HttpConnection.POST);
            con.setRequestProperty("User-Agent", ua);
            con.setRequestProperty("Accept-Language", locale);
            con.setRequestProperty("Content-Type", "text/plain");
            con.setRequestProperty("Accept", "text/plain");
            if (user != null && password != null) {
                con.setRequestProperty("Authorization",
                    "Basic " + BasicAuth.encode(user, password));
            }
            // Write the message to the output stream. Opening
            // the output stream might flush the headers
            os = con.openOutputStream();
            os.write(msg.getBytes());
            os.close();
            os = null;

            // check status code (if the headers and message
            // were not flushed before, they will be sent now.)
            status = con.getResponseCode();
            switch (status) {
                case HttpConnection.HTTP_OK:
                // Success! Calling method will read the response
                break;
                 case HttpConnection.HTTP_TEMP_REDIRECT:
                 case HttpConnection.HTTP_MOVED_TEMP:
                 case HttpConnection.HTTP_MOVED_PERM:
                     // Redirect: get the new location
                     url = con.getHeaderField("location");
                     con.close();
                     con = null;
                     break;
                 default:
                     // Error: throw an I/O exception
                     con.close();
                     throw new IOException("Response status not OK:"
                         + status);
            }
        }
        // Open input stream so the caller can read the response
        is = con.openInputStream();
    }

    // Access methods for the fields of the class

    // Set the message to send to the server
    void setMsg(String s) {
        msg = s;
    }
    // Set the user name to use to authenticate to server
    void setUser(String s) {
            user = s;
    }
    // Set the password to use to authenticate to server
    void setPassword(String s) {
        password = s;
    }
}

BasicAuth.java

The BasicAuth class is a utility class; it cannot be instantiated. It has a static method, encode, that creates and returns a string that holds the base64 encoding of its two string arguments. Note that the encode method provides only a subset of the functionality required by RFC2045 [reference 3]. It does not handle strings longer than 76 characters, line breaks, and so forth.

package examples.netclient;

// Encodes a user name and password in the base64 format that
// HTTP Basic Authorization requires. This is a utility class: it
// provides a static method and cannot be instantiated.
class BasicAuth {
    // make sure no one can instantiate this class
    private BasicAuth() {}

    // Conversion table
    private static byte[] cvtTable = {
        (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E',
        (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J',
        (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O',
        (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T',
        (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y',
        (byte)'Z',
        (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e',
        (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j',
        (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o',
        (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t',
        (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y',
        (byte)'z',
        (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4',
        (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9',
        (byte)'+', (byte)'/'
    };
    // Encodes a name-password pair appropriate to
    // use in an HTTP header for Basic Authentication.
    //    name     the user's name
    //    passwd   the user's password
    //    returns  String    the base64 encoded name:password
    static String encode(String name, String passwd) {
        byte input[] = (name + ":" + passwd).getBytes();
        byte[] output = new byte[((input.length / 3) + 1) * 4];
        int ridx = 0;
        int chunk = 0;

    // Loop through input with 3-byte stride. For each chunk of
    // 3-bytes, create a 24-bit value, then extract four 6-bit
    // indices. Use these indices to extract the base64 encoding
    // for this 6-bit character
        for (int i = 0; i < input.length; i += 3) {
            int left = input.length - i;
            // We have at least three bytes of data left
            if (left > 2) {
                chunk = (input[i] << 16) |
                        (input[i + 1] << 8) | input[i + 2];
                output[ridx++] = cvtTable[(chunk&0xFC0000)>>18];
                output[ridx++] = cvtTable[(chunk&0x3F000) >>12];
                output[ridx++] = cvtTable[(chunk&0xFC0)   >> 6];
                output[ridx++] = cvtTable[(chunk&0x3F)];
            } else if (left == 2) {
                // Down to 2 bytes; pad with 1 '='
                chunk = (input[i] << 16) | (input[i + 1] << 8);
                output[ridx++] = cvtTable[(chunk&0xFC0000)>>18];
                output[ridx++] = cvtTable[(chunk&0x3F000) >>12];
                output[ridx++] = cvtTable[(chunk&0xFC0)   >> 6];
                output[ridx++] = '=';
            } else {
                // Down to 1 byte; pad with 2 '='
                chunk = input[i] << 16;
                output[ridx++] = cvtTable[(chunk&0xFC0000)>>18];
                output[ridx++] = cvtTable[(chunk&0x3F000) >>12];
                output[ridx++] = '=';
                output[ridx++] = '=';
           }
       }
       return new String(output);
   }
}