Sunday, June 17, 2012

HTTP POST from a J2ME midlet

Although there are several examples and tutorials about how to connect to an HTTP server from a J2ME Midlet, I found that they all seem to concentrate a lot on the details of a GET and the POST is always assumed as an "exercise to the reader."

Now, although it is indeed rather straightforward to work out how to send POST requests to a server, I found (the hard way) that there are certain "minor details" that are best not forgotten.

I have thus decided to post here a brief example of how to send a request to a server, using a form-encoded body, so that either a JSP or a servlet can retriever the parameters, but one can benefit from the many advantages of POSTing, as oppposed to GETting.

The source code follows, I believe it is reasonably self-explanatory and comments should guide even the most novice reader through, but please do let me know if you feel that this is still too cryptic:



private void OpenConnection(String server) 
throws java.io.IOException {
// TODO: the actual "resource" part of the URL (/test in this case)
// should be either passed as a parameter or obtained at application
// level (maybe even set up at runtime).
// To test this code, I have written the simple Servlet below, however
// this would also work with a JSP page that echoes back the contents
// in text/plain format for the midlet to display.
//
  String url = "http://"+server+"/test"; 
  byte[] data = null;
  InputStream istrm = null;

  HttpConnection http = (HttpConnection)Connector.open(url);
  http.setRequestMethod(HttpConnection.POST);

  // This allows a JSP page to process the parameters correctly
  // This format (or content type) is the same as when forms are
  // submitted from a web page.
  http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

  // You may want to indicate to the server side (eg servlet) that 
  // this request is coming from a specific application.  This would
  // allow generation of appropriate format for the content.
  http.setRequestProperty("User-Agent", "HttpMidlet/0.2");

  // You can send any custom property as part of the HTTP header
  // This would matter for the "infrastructure" code, as opposed
  // to "body parameters" that would matter to the application
  // (eg, user=marco)
  http.setRequestProperty("Custom-Property", 
      "MyCustomProperty/1.0; AnotherProperty/debug_0.1");

  // You MUST create the body of the post BEFORE actually opening
  // the stream, otherwise you won't be able to set the length of
  // the request (see next step).
  // In this example, I am sending coordinates to a mapping server
  String msg = "x="+location.getX()+"&y="+location.getY();

  // THIS is important! without it a JSP won't process the POST data
  // it would also appear that CASE MATTERS (using "Content-Length" -note 
  // the capital 'L'- caused my servlet to return -1 from the 
  //    HttpServletRequest.getContentLenght() method
  http.setRequestProperty("Content-length", ""+msg.getBytes().length);

  // After this point, any call to http.setRequestProperty() will
  // cause an IOException
  OutputStream out = http.openOutputStream();
  out.write(msg.getBytes());
  out.flush();

  if (http.getResponseCode() == HttpConnection.HTTP_OK) {
    int len = (int)http.getLength();
    istrm = http.openInputStream();
    if (istrm == null) {
      log("Cannot open stream - aborting");
      throw new IOException("Cannot open HTTP InputStream, aborting");
    }
    if (len != -1) {
      data = new byte[len];
      int bytesRead = istrm.read(data);
      addProgressMsg("Read "+bytesRead+" bytes");
    } else {
    ByteArrayOutputStream bo = new ByteArrayOutputStream();
      int ch;
      int count = 0;

      // This is obviously not particularly efficient
      // You may want to use a byte[] buffer to read bytes in chunks
      while ((ch = istrm.read()) != -1) {
        bo.write(ch);
        count++;
      }
      data = bo.toByteArray();
      bo.close();
      addProgressMsg("Read "+count+" bytes");
    }
    response = new String(data);
    addProgressMsg("finished");
  } else {
    log("Response: "+http.getResponseCode()+", "+http.getResponseMessage());
    response = null;
    addProgressMsg("failed: "+http.getResponseMessage());
  }
  // This is critical, unless you close the HTTP connection, the application
  // will either be consuming needlessly resources or, even worse, sending
  // 'keep-alive' data, causing your user to foot unwanted bills!
  http.close();
}


To test this code, all that is required is a simple Serlvet that runs, for example, in an Apache Tomcat JSP container:
public class ConnectivityDiag extends HttpServlet {

  private static final String REVISION = "0.0.04";
  private static Logger log;

  public ConnectivityDiag() {
    if (log == null) {
      log = Logger.getLogger(getClass().getName());
      BasicConfigurator.configure();
    }
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    doProcessRequest(req, res);
  } 
 
public void doPost(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    doProcessRequest(req, res);
  }

  public void doProcessRequest(HttpServletRequest req, HttpServletResponse res) {
    BufferedReader reader = null;
    Writer writer = null;

    try {
      // Note this call seems to be case-sensitive
      // if the midlet does not set exactly "Content-length"
      // as the property, -1 will be returned
      int len = req.getContentLength();
      String contentType = req.getContentType();
      log.debug("Len: " + len);
      log.debug("Type: "+contentType);

      int bytesRead = 0;
      String remoteHost = req.getRemoteHost();
      String userAgent = req.getHeader("user-agent");
      log.info("Accessed at " + (new Date()) + " from " + remoteHost
          + ", using " + userAgent);

      // This simply echoes the parameters back to the client.
      // A JSP would use these in a  tag
      // and the ${param} macro.
      ArrayList strings = new ArrayList();
      Enumeration e = req.getParameterNames();
      while (e.hasMoreElements()) {
        String param = (String)e.nextElement();
        String value = (String)req.getParameter(param);
        strings.add(param+" = "+value);
      }
      res.setContentType("text/plain");

      // This is a custom property, that the remote client 
      // could query
      res.setHeader("Sent-at", new Date().toString());

      writer = res.getWriter();
      writer.write("Diagnostic servlet - Rev. "+REVISION);
      writer.write("\nBytes received: " + len);
      writer.write("\nFrom: " + remoteHost);
      writer.write("\nUsing: " + userAgent);
      writer.write("\n=== Parameters ===\n");
      for(String s:strings) {
        writer.write(s);
        writer.write('\n');
      }
      writer.write("=== Parameters end here ===\n");
      writer.flush();

    } catch (IOException e) {
      log.error(e.getMessage());
    } finally {
      try {
        if (reader != null)
          reader.close();
        if (writer != null)
          writer.close();
      } catch (IOException ex) {
        log.error(ex.getMessage());
      }
    }
  }
}
 
 
Link: http://codetrips.blogspot.com/2007/04/http-post-from-j2me-midlet.html 

No comments:

Post a Comment