MobileMastering Internet Programming on Mobile Devices: First Steps

Mastering Internet Programming on Mobile Devices: First Steps

So, you need to develop an application that will be able to send and receive some data over the Internet or an intranet from/to remote Web servers. This article set will help you figure out how to do it using WinInet for many common cases. This article will review the initial steps. Let’s say a couple of words regarding which language to use or mainly which environment to use: CF.NET or native C/C++ code. With all the enhancements that CF.NET SP2 has, it still experiences particular troubles with communications; for example, communications through proxy servers requiring NTLM authentication. Native code provides many more opportunities on this topic. Thus, when making your final decision, keep all this in mind. In this article, we will use both C++ and C# for code snippets.

Managing URLs

Prior to establishing any connection, your application should have a valid URL to connect to. WinInet provides a wide set of functions to deal with. First, there are a couple of calls to verify whether there is an opportunity to start communications:

DWORD InternetAttemptConnect(DWORD dwReserved);
BOOL InternetCheckConnection(LPCSTR lpszUrl,DWORD dwFlags,
                             DWORD dwReserved);

You can use them to be sure the device is connected to the network and the request may be executed.

Other useful API calls are:

  • InternetGetConnectedState—retrieves the connected state of the local system
  • InternetGetConnectedStateEx—retrieves the connected state of the specified Internet connection

They show the existing capabilities on a particular system. Via these functions, you may deetct whether the system uses a proxy server to establish connection.

The next group of functions helps you deal with URLs. They are listed below:

  • InternetCrackUrl
  • InternetCreateUrl
  • InternetCombineUrl
  • InternetCanonicalizeUrl

You may use the above calls to manipulate by URLs. Applications can easily build a target URL from blocks, break the URL into its base and relative parts, replace unsafe characters in the URL, and so forth. Actually, all this is intuitive enough, so we will not dig in too much, just a simple code snippet for an example:

TCHAR szServer[1024];
TCHAR szPath[1024];

URL_COMPONENTS crackedURL;
memset(&crackedURL, 0, sizeof(crackedURL));

crackedURL.dwStructSize     = sizeof(crackedURL);
crackedURL.lpszHostName     = szServer;
crackedURL.dwHostNameLength = 1024;
crackedURL.lpszUrlPath      = szPath;
crackedURL.dwUrlPathLength  = 1024;

InternetCrackUrl (pszURL, 0, 0, &crackedURL);

As a result, crackedURL will contain a URL divided into base host address, relative path, port number, and so on. If you’re developing in C#, compact framework classes do such conversions inside, so you don’t need to worry about it in your code.

Initializing the Internet Library

The very first step in working with any library is its initialization. Before using any WinInet functions, your application should call the InternetOpen API to make all required initialization:

HINTERNET WINAPI InternetOpen(
   LPCTSTR lpszAgent,
   DWORD dwAccessType,
   LPCTSTR lpszProxy,
   LPCTSTR lpszProxyBypass,
   DWORD dwFlags
);

You can specify any string as a lpszAgent value; for example, TEXT(“CeHttp”). This name then will be used as the user agent in HTTP. The dwAccessType parameter is usually equal to INTERNET_OPEN_TYPE_PRECONFIG; in other words, all data is taken from the Registry. In this case, you can leave the next two parameters zeroed. Alternatively, you can specify the desired proxy and proxy bypass list. Note here that an empty string as a proxy name is treated as a legal one, so set lpszProxy to NULL if you don’t need to connect via proxy servers. And finally, dwFlags is 0 or one of the following values:

  • Make asynchronous requests only—INTERNET_FLAG_ASYNC
  • No network requests at all—INTERNET_FLAG_FROM_CACHE or INTERNET_FLAG_OFFLINE

Thus, a typical initialization call may look like the following:

HINTERNET hOpen = InternetOpen (L"CeHttp",
                                INTERNET_OPEN_TYPE_PRECONFIG, NULL,
                                NULL, 0);

Making Simple Connections to Remote Hosts

Once WinInet is initialized, we’re ready to establish connections to the desired URLs. You have two main options:

  • Establish the connection to the specified URL through InternetOpenUrl.
  • Retrieve the session handle via InternetConnect and then use it to send requests.

In this section, we will discuss the first method—the InternetOpenUrl function. It is quite useful when you need to proceed with some simple operation; for example, to only download data file from a remote host. Thus, the code may look like:

void CWceHttpDlg::OnGo()
{
   HINTERNET hOpen = InternetOpen (L"WceHttp",
                                   INTERNET_OPEN_TYPE_PRECONFIG,
                                   NULL, NULL, 0);
   if ( !hOpen )
   {
      AfxMessageBox(L"Failed to open WinInet");
      return;
   }
   HINTERNET hOpenUrl = InternetOpenUrl(
      hOpen,m_sURL,
      NULL,0,
      INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_KEEP_CONNECTION,
      0);

   if ( !hOpenUrl )
   {
      DWORD dwErr = GetLastError();

      //Handle error
      .................
      AfxMessageBox(L"Failed to open WinInet");
      InternetCloseHandle(hOpen);
      return;
   }

   char szBuffer[4096];
   DWORD dwNumberOfBytesRead = 0;
   while ( InternetReadFile(hOpenUrl, szBuffer, 4096,
                            &dwNumberOfBytesRead) &&
                            dwNumberOfBytesRead )
   {
           // Do something with received block
           ....................................
   }
   InternetCloseHandle(hOpenUrl);
   InternetCloseHandle(hOpen);
}

InternetOpenUrl internally establishes a connection to the remote host and returns a handle to the Internet session, which may be used later in all I/O operations; for example, InternetReadFile calls. After all, opened handles are released by InternetCloseHandle calls.

Working with Sessions and Synchronous Requests

An alternative way to get connected is to use InternetConnect-based APIs. The common working flow may be organized as follows:

  1. Call InternetConnect to get session handle.
  2. Call HttpOpenRequest to define all desired request parameters and request type (HTTP or FTP).
  3. Call HttpSendRequest or HttpSendRequestEx to send the request to the remote host.
  4. Read the possible answer with InternetReadFile.
  5. Close the request handle.
  6. Repeat all from Step 2.
  7. Close the session handle.

In C# terms, all the business looks even simpler:

  1. Create an instance of the WebRequest (actually HttpWebRequest) class.
  2. Set up all required headers’ values if needed.
  3. Retrieve the host response as an instance of the HttpWebResponse class.
  4. Read data from the response through the Stream and StreamReader classes.

To illustrate all that was mentioned above, let’s consider a couple of samples in both the C/C++ and C# languages:

// C/C++ sample, GET method
void CWceHttpDlg::OnButton2()
{
   UpdateData();

   BOOL             bRC = TRUE;
   HINTERNET        hGETRequest;
   DWORD            dwFlags = INTERNET_FLAG_NO_CACHE_WRITE |
                              INTERNET_FLAG_KEEP_CONNECTION |
                              INTERNET_FLAG_IGNORE_CERT_CN_INVALID |
                              INTERNET_FLAG_IGNORE_CERT_DATE_INVALID|
                              INTERNET_FLAG_PRAGMA_NOCACHE;
   LPTSTR           pszAcceptTypes [] = {TEXT("text/*"), NULL};
   TCHAR            szServer [1024];
   TCHAR            szEndpoint [1024];
   URL_COMPONENTS   crackedURL;
   int              nPort;
   HINTERNET        hConnect;
   HINTERNET        hOpen;
   CString          sHTTPHeader;
   CString sInfo;

   if (m_sURL.IsEmpty ())
      return;

   //Crack URL ...
   ZeroMemory (& crackedURL, sizeof (URL_COMPONENTS));
   crackedURL.dwStructSize     = sizeof (URL_COMPONENTS);
   crackedURL.lpszHostName     = szServer;
   crackedURL.dwHostNameLength = 1024;
   crackedURL.lpszUrlPath      = szEndpoint;
   crackedURL.dwUrlPathLength  = 1024;

   InternetCrackUrl (m_sURL, 0, 0, &crackedURL);

   nPort = crackedURL.nPort;

   hOpen = InternetOpen (L"WceHttp", INTERNET_OPEN_TYPE_PRECONFIG,
                         NULL, NULL, 0);
   if ( !hOpen )
   {
      AfxMessageBox(L"Failed to open WinInet");
      return;
   }

   hConnect = InternetConnect (hOpen, szServer, nPort, L"", L"",
                               INTERNET_SERVICE_HTTP, 0, 0);
   if ( !hConnect )
   {
      sInfo.Format(L"InternetConnect failed: %lu", GetLastError ());
      AfxMessageBox(sInfo);
      return;
   }

   // Open an HTTP request handle...
   hGETRequest = HttpOpenRequest (hConnect, L"GET", szEndpoint,
                                  NULL, NULL, (LPCTSTR*)
                                  pszAcceptTypes, dwFlags, 0);
   if ( !hGETRequest )
   {
      sInfo.Format(L"HttpOpenRequest failed: %lu", GetLastError ());
      AfxMessageBox(sInfo);
      InternetCloseHandle (hConnect);
      return;
   }

   // send the request...
   sHTTPHeader = L"Content-Type: text/*rn";
   if (! HttpSendRequest (hGETRequest, (LPCTSTR) sHTTPHeader,
                          sHTTPHeader.GetLength (), NULL, 0))
   {
      sInfo.Format(L"HttpSendRequest failed: %lu", GetLastError ());
      AfxMessageBox(sInfo);
      InternetCloseHandle (hGETRequest);
      InternetCloseHandle (hConnect);
      return;
   }

   char szBuffer[4096];
   DWORD dwNumberOfBytesRead = 0;
   TCHAR wszTmp[4097];
   int i = 0;
   DWORD dwTotal = 0;
   while ( InternetReadFile(hGETRequest, szBuffer, 4096,
                            &dwNumberOfBytesRead) &&
                            dwNumberOfBytesRead )
   {
      memset(wszTmp,0,sizeof(wszTmp));
      MultiByteToWideChar(CP_ACP,0,szBuffer,dwNumberOfBytesRead,
                          wszTmp,sizeof(wszTmp));
      i++;
      dwTotal += dwNumberOfBytesRead;
   }

   sInfo.Format(L"Read %d block(s) - %lu byte(s)",i,dwTotal);
   AfxMessageBox(sInfo);

   InternetCloseHandle (hGETRequest);
   InternetCloseHandle (hConnect);
   InternetCloseHandle (hOpen);
}


// C# sample, GET method
private void cmdConnect_Click(object sender, System.EventArgs e)
{
   string url = txtURL.Text;
   string proxy = txtProxy.Text;

   try
   {
      if(!"".Equals(txtProxy.Text))
      {
         WebProxy proxyObject = new WebProxy("10.168.2.25:8080",true);
         proxyObject.Credentials = new NetworkCredential
                                   ("alexg", "England2007", "posnet");

         // Disable proxy use when the host is local.
         proxyObject.BypassProxyOnLocal = true;

         // HTTP requests use this proxy information.
         GlobalProxySelection.Select = proxyObject;
      }

      WebRequest req = WebRequest.Create(url);
      WebResponse result = req.GetResponse();
      Stream ReceiveStream = result.GetResponseStream();
      Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
      StreamReader sr = new StreamReader( ReceiveStream, encode );

      // Read the stream into arrays of 1024 characters
      Char[] read = new Char[1024];
      int count = sr.Read( read, 0, 1024 );
      while (count > 0)
      {
         String str = new String(read, 0, count);
         txtOutput.Text += str;
         count = sr.Read(read, 0, 1024);
      }
   }
   catch(WebException ex)
   {
      string message = ex.Message;
      HttpWebResponse response = (HttpWebResponse)ex.Response;
      if(null != response)
      {
         message = response.StatusDescription;
         response.Close();
      }
      txtOutput.Text = message;
   }
   catch(Exception ex)
   {
      txtOutput.Text = ex.Message;
   }
}

Both samples synchronously download some file from a Web server by the HTTP “GET” method. If you need to use the “POST” method, the only additional steps you will be required to do are:

Win32 API:

  • Define request content type—”text/xml” or “application/x-www-form-urlencoded”.
  • The request verb should be set to “POST” when calling either the HttpOpenRequest or HttpWebRequest.Method property.
  • Provide lpOptional and dwOptionalLength parameters to HttpOpenRequest as form data according to the HTTP specification.

Sample:

HttpSendRequest(hRequest, (LPCTSTR) sHTTPHeader,
                sHTTPHeader.GetLength (), lpFormData,
                dwFormDataLength))

C#:

  • Set the HttpWebRequest.ContentType property to the desired content type.
  • The HttpWebRequest.Method property should be set to “POST”.
  • Create an instance of the Stream class on the HttpWebRequest object and then call its Write method.

Sample:

...
// data is an array of length nLen which should be sent
WebRequest req = WebRequest.Create(url);
req.Method= "POST";
req.ComtentType = "text/xml";
req.ContentLength = nLen;

Stream reqStream = req.GetRequestStream();
reqStream.Write(data,0,nLen);
reqStream.Close();

WebResponse resp = req.GetResponse)();
...

After such manipulations, data will be sent to the remote server. In turn, the server can return some response that you can interpret as required in your partucular situation.

Conclusion

In this article, we have discussed the very first and simple tasks of Internet programming on mobile devices. By using the provided information, you are already able to transmit data from/to Web servers. The next articles will overview asynchronous requests, cookies, secure connections, and XML HTTP features.

Downloads

Download the accompanying C++ code’s zip file here.

Download the accompanying C# code’s zip file here.

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories