http://www.developer.com/

Back to article

Adding New Transaction Engines to QUALCOMM BREW's IWeb


August 14, 2006

Because BREW is a connected platform, most BREW developers have at least seen IWeb, even if they haven't had the opportunity to use it much. Its interface is both simple and appealing: Set some options, call IWEB_GetResponse with a callback, and a little while later, IWeb invokes your callback with a result and any data retrieved is available on an ISource from which you can read. Posting data is easy, too; just include an ISource in the outbound request containing the data you want to send to a remote server, and IWeb reads the data from the source and sends it to the remote server prior to calling your callback with the result.

But what if you need to create a new transaction interface? Perhaps you want a Web-based application to access local and remote data using the same engine, or perhaps you want to develop an application using an Internet protocol such as HTTP or SMTP not supported by BREW. Do you need to create a wholly new interface wrapping or replacing IWeb? Not at all—you can extend IWeb with your protocol, letting clients of your protocol use the same familiar interface provided by IWeb.

Peering Under IWeb's Hood

Even if you don't intend to extend IWeb, it's interesting to understand how IWeb actually works because it provides a good example of how to structure various interrelated components that solve a complex problem in a general way within Qualcomm BREW. Figure 1 shows the relationship among the various interfaces in the IWeb family.



Click here for a larger image.

As you can see, it isn't actually the IWeb interface that's responsible for handling URL protocols such as HTTP; in fact, the responsibility is handed off to interfaces implementing the IWebEng interface. The IWeb interface searches the system registry for classes registered with the HTYPE_BROWSE base class and specific protocol schemes (instead of MIME types), selecting the class that matches the indicated scheme. Once found, IWeb creates an instance of the class and invokes its Transaction method to perform the necessary request.

For all this to work, the class created by IWeb must:

  • Implement the IWebEng interface.
  • In your Transaction method, cache aside the callback pointer, create the resulting IWebResp instance, and start your transaction asynchronously.
  • When your transaction is complete, populate the IWebResp instance with the result and invoke the resulting callback.

The devil is in the details: You need to create a new IWebEng subclass for your protocol.

Creating a IWebEng Interface

Creating an IWebEng for use with IWeb involves making an extension; if that sounds new or scary, see my previous article in Developer on the topic. The IWebEng interface has four methods:

  • AddRef: To increase the interface's reference count.
  • Release: To decrement the interface's reference count and free the interface when the reference count goes to zero.
  • QueryInterface: To return EBADCLASS for all arguments.
  • Transaction:To perform the network transaction for IWeb.

To take a closer look, observe a trivial protocol that returns the current date and time; the form of its URL will be date:arbitrary-string.

The interface declaration can be written thus:

typedef struct IDateWebEng IDateWebEng;
AEEINTERFACE(IDateWebEng)
{
   INHERIT_IQueryInterface(IDateWebEng);
   int (*Transaction)(IDateWebEng *piwe, IWeb *piw,
                      IWebReq *piwreq, AEECallback *pcb,
                      IWebResp **ppiwresp);
};
#define IDATEWEBENG_AddRef(p) 
   AEEGETPVTBL((p),IDateWebEng)->AddRef((p))
#define IDATEWEBENG_Release(p) 
   AEEGETPVTBL((p),IDateWebEng)->Release((p))
#define IDATEWEBENG_QueryInterface(p,i,ppo) 
   AEEGETPVTBL((p),IDateWebEng)->QueryInterface((p),(i),(ppo))
#define IDATEWEBENG_Transaction(p,w,q,cb,a)
   AEEGETPVTBL((p),IDateWebEng)->Transaction((p),(w),(q),(cb),(a))

Perhaps oddly, I could not find an INHERIT_IWebEng macro the way INHERIT_IQueryInterface is provided, but it's almost as easy to write out the Transaction method declaration anyway, so that doesn't matter much.

Unfortunately, BREW lets you only inherit interfaces (like Java's implements keyword), so when setting out to create a new interface, you have to do everything from scratch, even if a superclass interface has the same behavior. Thus, we're forced to rewrite the entire constructor and inherited methods such as AddRef and Release.

The constructor is self-explanatory, allocating memory for the object, its vtable, and the necessary structures for performing the request. As previous readers of my articles will know, I'm a big fan of preallocationg resources when I can, because it lets me put the bulk of my error handling in just a few functions:

typedef struct SDateWebEng
{
   int nRefs;
   IShell *pis;
   IWeb *piw;
   IWebReq *piwreq;
   IWebResp *piwres;
   ISourceUtil *pisu;
   char *pszResult;
   AEECallback cb;
   AEECallback *pcbUser;
} SDateWebEng;

static int IDATEWEBENG_New( IShell *pIShell, IDateWebEng **ppOut )
{
   SDateWebEng *p;
   AEEVTBL(IDateWebEng) *modFuncs;
   int16 nSize = sizeof( SDateWebEng ) +
       sizeof( AEEVTBL(IDateWebEng) ) ;
   int result = EBADPARM;

   if ( !pIShell || !ppOut ) return result;
   result = ENOMEMORY;
   p = (SDateWebEng *)MALLOC( nSize );
   if ( !p ) return result;
   ->
   modFuncs = (AEEVTBL(IDateWebEng) *)((byte *)p +
      sizeof( SDateWebEng ));
   modFuncs->AddRef         = DateWebEng_AddRef;
   modFuncs->Release        = DateWebEng_Release;
   modFuncs->QueryInterface = DateWebEng_QueryInterface;
   modFuncs->Transaction    = DateWebEng_Transaction;

   INIT_VTBL(p, IModule, *modFuncs);

   p->nRefs  = 1;
   p->pis    = pIShell;
   p->piw    = NULL;
   p->piwreq = NULL;
   result = ISHELL_CreateInstance( p->pis, AEECLSID_WEBRESP,
      (void **)&p->piwres );
   if ( result == SUCCESS )
      result = ISHELL_CreateInstance( p->pis, AEECLSID_SOURCEUTIL,
                                      (void **)&p->pisu );
   if ( result != SUCCESS )
   {
      DateWebEng_Release( (IDateWebEng *)p );
      return result;
   }
   else
   {
      ISHELL_AddRef( p->pis );
      *ppOut = (IDateWebEng *)p;
      return AEE_SUCCESS;
   }
}

Next up is the implementation of the interface inherited from IQueryInterface; these, too, are very simple:

#define ME_FROM_INTERFACE(p) 
   SDateWebEng *pMe = (SDateWebEng *)p; if ( !p ) return EBADPARM;

static int DateWebEng_AddRef( IDateWebEng *p )
{
   ME_FROM_INTERFACE(p);
   return ++pMe->nRefs;
}

static int DateWebEng_Release( IDateWebEng *p )
{
   ME_FROM_INTERFACE( p );
   if ( --pMe->nRefs == 0 ){ CALLBACK_Cancel( &pMe->cb );
      CALLBACK_Cancel( pMe->pcbUser );
      if ( pMe->pis ) ISHELL_Release( pMe->pis );
      if ( pMe->piw ) IWEB_Release( pMe->piw );
      if ( pMe->piwreq ) IWEBREQ_Release( pMe->piwreq );
      if ( pMe->piwres ) IWEBRESPONSE_Release( pMe->piwres );
      if ( pMe->pisu ) ISOURCEUTIL_Release( pMe->pisu );
      FREEIF( szResult );
      FREE( pMe );
      return 0;
   } else return pMe->nRefs;
}

static int DateWebEng_QueryInterface( IDateWebEng *p, AEECLSID cls,
                                      void **ppOut )
{
   cls;
   if ( !p || !ppOut ) return EBADPARM;
   *ppOut = NULL;
}

The AddRef and QueryInterface interfaces are boilerplate. Release is too, although it's important to remember that I'm releasing result structures passed to the application using IWeb like the IWebResponse, so I need to increment its reference count when I return it to the caller.

The meat of the engine is in its Transaction method, which must perform a single transaction asynchronously. In your case, because you don't use the contents of the URL for anything, it's pretty simple, too:

static int DateWebEng_Transaction(IDateWebEng *p, IWeb *piw,
                                  IWebReq *piwreq,
                                  AEECallback *pcb,
                                  IWebResp **ppiwresp)
{
   ME_FROM_INTERFACE( p );
   pMe->piw = piw;
   IWEB_AddRef( pMe->piw );
   pMe->piwreq = piwreq;
   IWEBREQ_AddRef( pMe->piwreq );
   pMe->pcbUser = pcb;
   IDATEWEBENG_AddRef( p );
   CALLBACK_Init( &pMe->cb, (PFNNOTIFY)DateWebEng_ReturnDate,
                  (void *)pMe );
   ISHELL_Resume( pMe->pis, &pMe->cb );
   *ppiwresp = pMe->piwres;
   IWEBRESP_AddRef( pMe->piwres );
   return SUCCESS;
}

The transaction must:

  • Cache aside the IWeb pointer in case the IWebEng needs to obtain Web options configured by the caller
  • Set aside the Web request pointer piwreq, from which the desired URL can be obtained using its GetUrl method
  • Set aside the result callback, which the IWebEng invokes on completion of the request
  • Set up the asynchronous transaction
  • Incremement the IWebEng's reference count

Why must the IWebEng increment its own reference count? The answer is simple, although not obvious: the IWeb engine releases the IWebEng as soon as its Transaction method is called because it has no other way of knowing when the transaction is complete! Thus, to maintain its scope, it must increment its own reference count and release itself once it's done processing.

Because BREW runs in a single thread on the handset, the IWebEng runs in the same thread as everything else, and it must operate asynchronously via callbacks or IThreads. Typically, you use IWebUtil to parse the URL for information, make your remote connection, and then use the ISocket or ISockPort interfaces (see last month's article "New Network Interfaces in Qualcomm BREW") to perform the client request. When you're done, you wrap up the resulting data in a source and invoke your client's callback:

static void DateWebEng_ReturnDate( IDateWebEng *p )
{
   char *szResult;
   ISource *pis;
   WebRespInfo *pwri;
   JulianType jt;
   ME_FROM_INTERFACE(p);
   // Create a string with the current date.
   szResult = pMe->pszResult;
   GETJULIANDATE( GETTIMESECONDS(), &jt );
   SPRINTF( szResult, "%2d/%2d/%4d %2d:%2d:%2d",
   jt.wMonth, jt.wDay, jt.wYear,
   jt.wHour, jt.wMinute, jt.wSecond );
   // Now make it a source
   ISOURCEUTIL_SourceFromMemory( pMe->pisu, szResult,
                                 STRLEN( szResult ) + 1,
                                 NULL, NULL, (ISource **)&pis );
   // Detatch ourselves from the result
   pMe->pszResult = NULLl
   // Now completely fill out the result structure.
   pwri = IWEBRESP_GetInfo( pMe->piwres );
   pwri->cpszCharset = NULL;
   pwri->cpszContentType = "text/plain";
   pwri->lContentLength = STRLEN( szResult ) + 1;
   pwri->nCode = 200;
   pwri->pisMessage = pis;
   pwri->tExpires = GETTIMESECONDS();
   pwri->tModified = GETTIMESECONDS();
   // Now call the user back
   ISHELL_Resume( pMe->pis, pMe->pcbUser );
   // Clean up after that.
   CALLBACK_Init( &pMe->cb, (PFNNOTIFY)DateWebEng_Release, (void *)p ) ;
   ISHELL_Resume( pMe->pis, &pMe->cb );
}

The code begins simply enough, using a JulianDate structure and SPRINTF to create a time string for the result. You wrap this string in a source using ISourceUtil, making sure to nil your reference to the string so you don't release it, too. Then, you get the response's WebRespInfo pointer, and fill out the results for your client. Finally, you call the client's callback, and then release yourselves asynchronously as well.

For all of this to work, the extension needs to register itself with the BREW shell using either ISHELL_RegisterHandler or by registering the protocol name and class ID in the Module Information File using the type HTYPE_BROWSE.

Choosing to Extend IWeb

Although extending IWeb is not difficult, it's not for the faint at heart, either. From a distribution perspective, this can be a private extension if you're the only developer planning on using the protocol, or you can make the extension available to others as a public extension. Extending IWeb is best if:

  • Your protocol is a client-server (request-response) protocol
  • Your protocol returns a stream of data
  • Your data resides in a database as well as on the Web and you want to abstract away from the database representation
  • You want to control your application's state machine via a combination of the IHTMLViewer and IWeb

Good examples of this sort of behavior include the Internet protocols FTP, SMTP, SNMP, POP, and IMAP.

It makes little sense for a connectionless protocol, however, or one that doesn't follow the client-server paradigm, because these don't follow the IWeb API model. Bidirectional streaming might be an example of this, although with enough BREW API subclasses, you certainly could make it work (say, by using an outgoing ISource for the data being streamed from the handset, and an incoming ISource for the incoming data).

Conclusion

Although most developers treat IWeb as simply an engine for loading Web, resource, and file resident content, it's possible to do quite a bit more by creating IWebEng subclasses. If you're looking for a way to generalize IWeb, or need a generic interface for client-server activities, check out the relationship among the IWeb, IWebEng, IWebRequest, and IWebResponse classes.

About the Author

Ray Rischpater is the chief architect at Rocket Mobile, Inc., specializing in the design and development of messaging and information access applications for today's wireless devices. Ray Rischpater is the author of several books on software development, including eBay Application Development and Software Development for the QUALCOMM BREW Platform, both available from Apress, and is an active amateur radio operator. Contact Ray at kf6gpe@lothlorien.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date