.

Optimized HTTP Handling for Your BlackBerry Java Application

September 28, 2008 10:00 by Jorge

In this post I’m going to explore some examples of the approaches to reading HTTP responses in your BlackBerry  Java application and how they can impact the application’s performance.

I realize that the performance of your HTTP-handling routines might not impose critical limitations to your application. But if you are considering ways to optimize these routines, I encourage you not only to take a look at these approaches, but also measure their performance characteristics in the context of your application.

The first approach, which I have successfully used in a commercial application, consists of reading the contents of the HTTP response one character at a time. Although slow, this approach is attractive because it is simple and does not require any HTTP-specific behavior from your code:

StreamConnection c = null;
InputStream s = null;
try {
    c = (StreamConnection)Connector.open(url);
    s = c.openInputStream();
    int ch;
    StringBuffer response = new StringBuffer();
    while ((ch = s.read()) != -1) {
        response.append((char)i);
    }
} finally {
    if (s != null)
        s.close();
    if (c != null)
        c.close();
}

Reading the response data in bulk is a way to gain efficiency. However, this code relies on the underlying connection being able to provide the length of the response:

ContentConnection c = null;
DataInputStream dis = null;
try {
    c = (ContentConnection)Connector.open(url);
    int len = (int)c.getLength();
    dis = c.openDataInputStream();
    if (len > 0) {
        byte[] data = new byte[len];
        dis.readFully(data);
    } else {
        int ch;
        while ((ch = dis.read()) != -1) {
            ...
        }
    }
} finally {
    if (dis != null)
        dis.close();
    if (c != null)
        c.close();
}

A version of the previous approach that I use in my KnowledgeBase application is this:

HttpConnection c = null;
InputStream in = null;
String response = null;
 
try {    
    c = (HttpConnection)Connector.open(url)
    in = c.openInputStream();                         
    int len = (int)c.getLength();
 
    if (len > 0) {
      int actual = 0;
      int bytesread = 0 ;
      byte[] data = new byte[len];
      while ((bytesread != len) && (actual != -1)) {
        actual = in.read(data, bytesread, len - bytesread);
        bytesread += actual;
      }
      response = new String(data);
    } 
}

While the methods above produce a byte array and thus require a parsing or conversion step – omitted from the sample code – in order to deserialize the data, if your application has intimate knowledge of the response’s contents, you can use specialized methods of the DataInputStream class to read and deserialize the different values in the response in one step:

HttpConnection c = null;
DataInputStream is = null;
int rc;
 
try {
    c = (HttpConnection)Connector.open(url);
 
    rc = c.getResponseCode();
    if (rc != HttpConnection.HTTP_OK) {
        throw new IOException("HTTP response code: " + rc);
    }
 
    is = (DataInputStream)c.openInputStream();
 
    String lastName = i.readUTF();
    String firstName = i.readUTF();
    int numberOfOrders = i.readInt();
    boolean isActive= i.readBoolean();
    
    
} catch (ClassCastException e) {
    throw new IllegalArgumentException("Not an HTTP URL");
} finally {
    if (is != null)
        is.close();
    if (c != null)
        c.close();
}

As you can see, the performance gains have a cost in the amount of complexity of your code and the knowledge of the response contents that your application should have. You will find that this is true for the approaches above as well as other approaches that exist. When deciding on one, consider the tradeoffs and always measure your gains before making a decision.

Actions: E-mail | Permalink | Comments (0) | Trackback

End-To-End BlackBerry Application (Part 6)

July 15, 2008 18:01 by Jorge

In this sixth delivery of my end-to-end BlackBerry application walk-through, I will cover the following topics:

  • Creating a .NET http handler to send and receive information from the handheld application
  • Consuming the server-side business logic services from our http handler
  • Making requests from the handheld application and receiving data

While the previous articles of this series dealt with creating the Java application that will be installed on the handheld device, today I will be working on the server-side modules. Let’s take another look at our basic building blocks:

Building-Blocks1

The server-side modules consist of a .NET HTTP handler, a set of class libraries that contain the business logic and data access code, and a SQL Server database that will serve as the articles repository of our Knowledge Base application.

Creating The HTTP Handler

The mission of  the HTTP Handler is to extract the information from the HTTP requests made from the handheld, determine what type of action is required and ask the business logic layer to execute such actions. It will also append the results produced by the business logic to HTTP responses and send them to the handheld application.

In order for the handheld application and the handler to be able to talk to each other, all the commands that the handheld application can possibly send need to be defined within the handler as they were defined in the handheld:

// The commands the handheld can send us.
private const int CMD_SEARCH_ARTICLES = 1;
private const int CMD_GET_ARTICLES_BY_TAG = 2;
private const int CMD_GET_TAG_COUNTS = 3;

The keys identifying  each of the request parameters need to be defined as well:

// The request keys.
private const string KEY_COMMAND = "cmd";
private const string KEY_SEARCH_PHRASE = "sp";
private const string KEY_TAG = "tg";

KEY_SEARCH_PHRASE identifies the request parameter carrying the search phrase when a request to search the existing articles is made. KEY_TAG identifies the parameter carrying the tag name when the request is to retrieve the articles for a given tag.

Determining what to do is a matter of inspecting the request parameter marked with the KEY_COMMAND key:

string commandString = request.Form[KEY_COMMAND];
 
if (!int.TryParse(commandString, out currentCommand)) 
{
    // Bail if we don't receive a numeric command.
    return;
}
 
articlesServer = Shared.GetArticlesServer(context);
 
switch (currentCommand)
{
    case CMD_SEARCH_ARTICLES:
        string searchPhrase = request.Params[KEY_SEARCH_PHRASE];
        responseString = SearchArticles(searchPhrase);
        break;
    case CMD_GET_ARTICLES_BY_TAG:
        string tag = request.Params[KEY_TAG];
        responseString = GetArticlesForTag(tag);
        break;
    case CMD_GET_TAG_COUNTS:
        responseString = GetTagCounts();
        break;
    default:
        return;
}

Consuming The Business Logic Services

I won’t go into the details of the construction of the business logic and data access layers today, since they were covered in my End-to-end ExtJS application series. Something noteworthy, though, is the fact that these layers support storage of the articles in a file or in a SQL Server database. The code download for today’s article defaults to file-based storage, but you can run the included SQL script to install the database and point the business layer to it by changing the web application’s configuration file.

In the HTTP handler, SearchArticles(), GetArticlesForTag() and GetTagCounts() simply forward the requests to the business logic layer:

private string SearchArticles(string searchPhrase)
{
    Debug.Assert(searchPhrase != null);
    if (null == searchPhrase)
    {
        ExceptionPolicy.HandleException(
                new ArgumentNullException("searchPhrase"), 
                Shared.KB_EXCEPTION_PLCY);
    }
 
    string result = string.Empty;
 
    try
    {
        List<IArticle> articles = 
            articlesServer.GetArticlesForSearchPhrase(searchPhrase);
        if (null == articles) return 
                string.Empty;
        result = SerializeArticlesList(articles);
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
 
    return result;
}
private string GetArticlesForTag(string tag)
{
    Debug.Assert(tag != null);
    if (null == tag)
    {
        ExceptionPolicy.HandleException(
            new ArgumentNullException("tag"), 
            Shared.KB_EXCEPTION_PLCY);
    }
 
    string result = string.Empty;
 
    try
    {
 
        List<IArticle> articles = articlesServer.GetArticlesForTag(tag);
        if (null == articles) return
                string.Empty;
        result = SerializeArticlesList(articles);
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
 
    return result;
}
private string GetTagCounts()
{
    StringBuilder sb = new StringBuilder("");
 
    try
    {                
        Dictionary<string, int> tagCounts = articlesServer.GetTagCounts();
 
        if (null != tagCounts && tagCounts.Count > 0)
        {
            foreach (KeyValuePair<string, int> tagStat in tagCounts)
            {
                sb.Append(string.Format("{0}^~^{1}~^~", 
                    tagStat.Key, 
                    tagStat.Value));                       
            }
        }
 
    }
    catch (Exception ex)
    {
        ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
    }
    return sb.ToString();
}

After the business logic results are delivered to the handler I need to serialize the results according the formatting convention I had established when I wrote the handheld code. For example, this is the routine that converts a list of articles into a string that will be sent to the handheld as part of the HTTP response:

private string SerializeArticlesList(List<IArticle> articles)
{
    Debug.Assert(articles != null);
    if (null == articles)
    {
        ExceptionPolicy.HandleException(
            new ArgumentNullException("articles"), 
                Shared.KB_EXCEPTION_PLCY);
    }
 
    StringBuilder sb = new StringBuilder("");
    string body;
 
    foreach (IArticle article in articles)
    {
        // Decode the html characters in the body of the article.
        body = HttpUtility.HtmlDecode(HttpUtility.UrlDecode(article.Body));
        // Strip the html characters (We're using a RichTextField on the handheld,
        // which isn't capable yet of displaying html.
        body = Regex.Replace(body, @"<(.|\n)*?>", string.Empty);
 
        sb.Append(string.Format("{0}^~^{1}^~^{2}^~^{3}^~^", 
            article.Id.ToString(), 
            article.Title, 
            article.DateCreated.ToShortDateString(), 
            article.Author));
        sb.Append(string.Format("{0}^~^{1}~^~", 
            article.Tags, body));
    }
 
    return sb.ToString();
}

Did you notice that in the code above I’m also removing the Html characters from the body of the article? I have to do this because the field I’m using to display the body of the article on the handheld is not capable of rendering Html.

All that’s left now is to send the data to the handheld:

response.ContentType = "text/plain";
int contentLength = response.Output.Encoding.GetByteCount(responseString);
response.AppendHeader("content-length", contentLength.ToString());
response.Write(responseString);
response.End();

Well,  I know the walk through the server-side code went pretty fast. I encourage you to download the code for today’s post and check it out to see all the details. Also, reading the End-to-end ExtJS application post can give you more insight on the business logic and data access layers.

Making Requests From The Handheld Application And Receiving Data

Let’s return to the device-side code and ready it to connect to the HTTP handler. The first thing I will do is stop using the MockHTTPTransport instance and start using KbHTTPTransport. While MockHTTPTransport simply simulates an HTTP request to allow for testing of all the rendering logic on the device, KbHTTPTransport is the real deal. This is how I use it from the Articles Screen or the Tags Screen:

private void sendHttpRequest(String[] keys, String[] values) {
   
   articlesList.set(null);  // Show "No Articles" while we download.