Mastering Internet Programming on Mobile Devices: An Asynchronous Data Exchange

In the previous article, we started to deal with WinInet basics. As you might see, that is not a big deal. Now, we will turn to asynchronous Internet sessions, simple HTTP authentication techniques, and using secure Internet connections. Samples will be given mostly in C++ because C# hides many API calls from the user. Nevertheless, C# programmers always can wrap it via PInvoke.

Displaying Operation Progress Status

Prior to discussing asynchronous operations over the Internet, let's touch on one important part of data transferring. Regardless of the established session type (synchronous or asynchronous), users of your application probably will appreciate it if it somehow indicates that lengthy operations are advancing and still alive. WinInet provides a callback mechanism for such purposes. An application may register such a callback to retrieve asynchronous notifications about the desired operation's progress. You should use the following function:

typedef void (CALLBACK *INTERNET_STATUS_CALLBACK) (
  HINTERNET hInternet,
  DWORD_PTR dwContext,
  DWORD dwInternetStatus,
  LPVOID lpvStatusInformation,
  DWORD dwStatusInformationLength
);

INTERNET_STATUS_CALLBACK WINAPI InternetSetStatusCallback(
  HINTERNET hInternet,
  INTERNET_STATUS_CALLBACK lpfnInternetCallback
);
InternetSetStatusCallback takes a pointer to the application-defined function, which then is called by WinInet when progress is made on the specified operation. Saying it briefly, all you need to do is to define the following function:

void CALLBACK YourInternetCallback(
  HINTERNET hInternet,
  DWORD_PTR dwContext,
  DWORD dwInternetStatus,
  LPVOID lpvStatusInformation,
  DWORD dwStatusInformationLength
);

All parameters are described in detail in online documentation, so we won't copy it here. Let's just highlight several useful points. The second parameter of the callback function is an application-defined context for the hInternet handle. This gives you a convenient way to implement different behavior for each context value; for example, for uploading and downloading data. Another thing to be noted is the dwInternetStatus parameter. You will use its values to inform the user about actual operation progress and to detect the moment when the asynchronous operation is completed. Thus, such a callback may look like the following:

void CALLBACK InternetCallback(HINTERNET hInternet,
                               DWORD_PTR dwContext,
                               DWORD dwInternetStatus,
                               LPVOID lpvStatusInformation,
                               DWORD dwStatusInformationLength)
{
    switch (dwContext)
    {
    case X:
    ...
       switch (dwInternetStatus)
       {
       ...
       case INTERNET_STATUS_CONNECTED_TO_SERVER:
            // Notify the user...
            ...
            break;
       case INTERNET_STATUS_REQUEST_COMPLETE:
            // Asynchronous request was completed
            ...
            break;
       case INTERNET_STATUS_RESPONSE_RECEIVED:
            // Process this event...
            ...
            break;
       ...
       }
       break;
    }
}

The Compact Framework still does not provide the same mechanism as C++ does, but we will see what C# offers for asynchronous calls in the next section.

Establishing Asynchronous Internet Sessions

Now, we are ready to handle asynchronous behavior. The first step is to use INTERNET_FLAG_ASYNC while calling the InternetOpen function:

hOpen = InternetOpen (L"WceHttp", INTERNET_OPEN_TYPE_PRECONFIG,
                      NULL, NULL, INTERNET_FLAG_ASYNC);

The second step is to define the callback to get notified during asynchronous operation:

INTERNET_STATUS_CALLBACK PrevStatusCallback =
     InternetSetStatusCallback(hOpen,InternetCallback);
if ( PrevStatusCallback == INTERNET_INVALID_STATUS_CALLBACK )
{
// Invalid callback was provided, handle it
...
}

I should note here that the only functions that behave asynchronously under Windows CE are InternetReadFile and InternetQueryDataAvailable. For these APIs, you can get completion status notifications. All other functions operate synchronously. Nevertheless, Windows CE supports chunked request transfers. It means that your application can send heavy requests by separate data blocks. The code shown in the next sample gives you an example of how can it be done:

// use HttpSendRequestEx to send large buffers

// set the header
if ( !HttpAddRequestHeaders(hRequest,
    lpszHttpHeader, dwHttpHeaderLength,
    HTTP_ADDREQ_FLAG_ADD) )
{
    // handle error
}

INTERNET_BUFFERS ibBuffer;
ZeroMemory (& ibBuffer, sizeof (INTERNET_BUFFERS));
ibBuffer.dwStructSize  = sizeof (INTERNET_BUFFERS);
ibBuffer.dwBufferTotal = nBufferSize;

if ( !HttpSendRequestEx (hRequest, & ibBuffer,
                         NULL, HSR_INITIATE, 0))
{
    // handle error
}

// now enter the buffer in chunks of 1024 bytes
DWORD         dwBytesSend;
int           nPortion = nBufferSize / 10;
const int     nMinToSend = __max (nPortion, g_HTTPChunkSize);
int           nChunkSize = nMinToSend;
int           nBufferPos = 0;
int           nPrevPercentage = 0;
char          *pBufferPtr = g_szGlobalBuffer;

while (nBufferPos < nBufferSize)
{
    if (! InternetWriteFile (hRequest, pBufferPtr, nChunkSize,
                             & dwBytesSend))
    {
        m_WSError.SetErrorDescr (L"InternetWriteFile", __FILE__,
                                 __LINE__, GetLastError ());
        HttpEndRequest (hRequest, NULL, 0, 0);
        goto Cleanup;
    }

    pBufferPtr += nChunkSize;
    nBufferPos += nChunkSize;
    if (nBufferSize - nBufferPos < nMinToSend)
        nChunkSize = nBufferSize - nBufferPos;
    else
        nChunkSize = nMinToSend;
}


// end the request
if ( !HttpEndRequest (hRequest, NULL, 0, 0))
{
    // handle error
}

A common procedure is something like the following:

  • Initiate request sending by a HttpSendRequestEx call with the HSR_INITIATE parameter.
  • Sequentially call InternetWriteFile to transmit all the request's data to the remote host.
  • Complete the request by a HttpEndRequest call.

In turn, on response reading, InternetReadFile in asynchronous mode returns FALSE with the error set to ERROR_IO_PENDING. Your application then receives progress notifications via the callback we discussed above.

C# makes our life easier in many standard cases; thus, the corresponding task may be implemented as shown in a simple snippet below:

  void btnSubmit_Click(Object sender, EventArgs e)
  {
    HttpWebRequest wreq = (HttpWebRequest)
                          WebRequest.Create(txtURL.Text);
    IAsyncResult r      = (IAsyncResult) wreq.BeginGetResponse(
                          new AsyncCallback(this.RespCallback),
                          wreq);
    Thread.Sleep(5000);
  }

  private void RespCallback(IAsyncResult ar)
  {
    HttpWebRequest req   = (HttpWebRequest) ar.AsyncState;
    HttpWebResponse resp = (HttpWebResponse) req.EndGetResponse(ar);

    int BytesRead = 0;
    char[] Buffer = new char[1024];

    StreamReader Reader = new StreamReader(resp.GetResponseStream(),
                                           System.Text.Encoding.UTF8);
    StringWriter Writer = new StringWriter();

    BytesRead = Reader.Read(Buffer, 0, 1024);
    while (BytesRead != 0 )
    {
      Writer.Write(Buffer, 0, 1024);
      BytesRead = Reader.Read(Buffer, 0, 1024);
    }
    txtOutput.Text = Writer.ToString();
  }

The main business here is calling the response from the server via a BeginGetResponse method. It takes a callback and user-defined 'status' object as parameters. The callback is called when the operation is completed; in other words, when the response data is ready for reading. All other things are obvious enough.

Mastering Internet Programming on Mobile Devices: An Asynchronous Data Exchange

Connecting to Secure Sites

In some cases, Web servers require some kind of authentication and secure connections. Windows CE supports various security protocols such as TSL, SSL, and so forth. WinInet is the simplest way to use these protocols during communication sessions. The basic scheme here is really simple:

  • Use the required protocol in the URL; for example. https://server:port.
  • Use the required port number, as in INTERNET_DEFAULT_HTTPS_PORT for HTTPS.
  • Call HttpOpenRequest with the INTERNET_FLAG_SECURE flag set.
  • If the remote host requires authentication, set INTERNET_FLAG_KEEP_CONNECTION while calling HttpOpenRequest to allow connection maintenance until the authentication process is completed. This flag is also required to keep security options between a number of requests.

If the server requires authentication, error 401 is returned after sending the request. You have several methods to deal with such a situation. First, the application may use InternetErrorDlg to retrieve additional information:

DWORD InternetErrorDlg(
  HWND hWnd,
  HINTERNET hRequest,
  DWORD dwError,
  DWORD dwFlags,
  LPVOID* lppvData
);

This function is smart enough, and depending on passed flags, it may check request headers for any hidden errors and then display dialog boxes if needed. Thus, a sample call is as the following:

...
if( HttpSendRequest(hRequest,0,0,0,0) )
{
    DWORD dwErrorCode = GetLastError();
    
    DWORD dwError = InternetErrorDlg(hWnd,hRequest,dwErrorCode,
                            FLAGS_ERROR_UI_FILTER_FOR_ERRORS |
                            FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS |
                            FLAGS_ERROR_UI_FLAGS_GENERATE_DATA,
                            NULL);
    if ( dwError == ERROR_INTERNET_FORCE_RETRY )
    {
       // resend request
       ...
    }
    
    if ( dwError == ERROR_SUCCESS )
    {
       // read response data
       ...
    }
    ...
}

You will find a detailed description of this function in the online help. Here, we'll just note that displaying GUI dialogs is not always a desired behavior. In many cases, your application can know the user and password for authentication, so there is no need to prompt it at all. Instead, you can use an InternetSetOption call. This function is a way to set up different properties of HHTP requests, not just the user and password:

  • INTERNET_OPTION_CONTEXT_VALUE
  • INTERNET_OPTION_CONNECT_TIMEOUT
  • INTERNET_OPTION_CONNECT_RETRIES
  • INTERNET_OPTION_CONNECT_BACKOFF
  • INTERNET_OPTION_CONTROL_SEND_TIMEOUT
  • INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT
  • INTERNET_OPTION_DATA_SEND_TIMEOUT
  • INTERNET_OPTION_DATA_RECEIVE_TIMEOUT
  • INTERNET_OPTION_READ_BUFFER_SIZE
  • INTERNET_OPTION_WRITE_BUFFER_SIZE
  • INTERNET_OPTION_USERNAME
  • INTERNET_OPTION_PASSWORD
  • INTERNET_OPTION_PROXY
  • INTERNET_OPTION_USER_AGENT

Its usage is demonstrated in the following code snippet:

DWORD dwStatus, dwStatusSize = sizeof(DWORD);
if ( HttpQueryInfo(hRequest, HTTP_QUERY_FLAG_NUMBER |
                             HTTP_QUERY_STATUS_CODE, &dwStatus,
                             &dwSTatusSize) )
{
    if ( dwStatusCode == HTTP_STATUS_DENIED )
    {
        InternetSetOption(hRequest, INTERNET_OPTION_USERNAME,
                          lpszUserName, _tcslen(lpszUserName) + 1);
        InternetSetOption(hRequest, INTERNET_OPTION_PASSWORD,
                          lpszPassword, _tcslen(lpszPassword) + 1);
    }
}

In the same manner, you can define timeout values, proxies, and other request parameters. The HttpQueryInfo API is used to get request status information along with all sorts of header variables. Your application should call it after each HttpSendRequest to verify transmission status.

Finally, remote hosts may support or require cookies. Cookies are used to track data settings or data for a particular Web site. The client can save them on a handheld device and then send them to the Web server along with a request. Windows CE provides the following functions to manage a cookie database:

  • InternetSetCookie
  • InternetSetCookieEx
  • InternetGetCookie

The first two functions are pretty similar. The "Ex" version extends InternetSetCookie by dealing with so-called third-party cookies. InternetGetCookie retrieves cookie data from the local database. The Web server exchanges cookie information through the HTTP header. Thus, your application can set/get it from there. The general format is represented below:

Request:
Cookie: <name>=<value>[; <name>=<value>]...

Response:
Set-Cookie: <name>=<value>[; <name>=<value>]...
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][; httponly]

So, speaking C/C++, you might code something like this:

TCHAR szCookie[] = _T("Cookie: datasrc=maindb;");
HttpAddRequestHeaders(hResuest,szCookie,_tcslen(szCookie),
                      HTTP_ADDREQ_FLAG_ADD);

When using cookies, you should keep in mind a couple of flags that influence cookies' behavior:

  • INTERNET_FLAG_NO_COOKIES—does not automatically add cookie headers to requests and save them into the local database on response
  • INTERNET_FLAG_NO_UI—disables the cookie dialog box that can pop up in some cases to prompt for cookie acceptance

The Compact Framework does not support cookies directly, but you can precede all required items via a Header collection of HttpWebRequest or HttpWebResponse classes:

string strCookie = null;
...
// Sending cookie
req = (HttpWebRequest)WebRequest.Create(strURL);
req.Headers["Cookie"] = strCookie;
req.Credentials = new NetworkCredential(...);
...

// Receving cookie
HttpWebResponse resp = null;
string strCookie = null;
try
{
   resp = req.GetResponse() as HttpWebResponse;
}
catch(WebException e)
{
   if(e.Status == WebExceptionStatus.ProtocolError)
   {
      resp = e.Response as HttpWebResponse;
      if((int)resp.Status == 401)
      {
         strCookie = resp.Headers["Set-Cookie"];
      }
      ...
   }
}

Conclusion

In this article, we have discussed more complicated but yet not so awful stuff about WinInet. Hopefully, CF.NET will provide richer capabilities in the next SPs, but now it is still far from being as powerful as the native C/C++ environment. Nevertheless, in both cases you have all that you need to implement complex Internet conectivity features in your own applications.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds