Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Networking With Open Transport / Part 1 - Open Transport Essentials
Chapter 2 - Getting Started With Open Transport


Downloading a URL With HTTP

The sample code shown in Listing 2-4 downloads a URL from a web server. It includes two functions: a simple notifier, YieldingNotifier, and a function, MyDownloadHTTPSimple, that downloads the URL. Because the function MyDownloadHTTPSimple contains synchronous calls to Open Transport, the notifier is used to call the function YieldToAnyThread, which cedes time to the processor while a synchronous operation waits to complete. A detailed discussion is contained in the sections following the listing.

The code shown in Listing 2-4 begins by initializing required debugging flags and including the appropriate header files. The OTDebugStr function is not defined in any of the Open Transport header files, but it is exported by the libraries, so its prototype is included.

Listing 2-4 Downloading a URL With HTTP

#ifndef qDebug/* The OT debugging macros in <OTDebug.h> */
#define qDebug 1/* require this variable to be set.*/
#endif

#include <OpenTransport.h>
#include <OpenTptInternet.h> /* header for TCP/IP */
#include <OTDebug.h>/* header for OTDebugBreak, OTAssert macros */
#include <Threads.h>/* declaration for YieldToAnyThread */
#include "OTSimpleDownloadHTTP.h" /* header for our own protype */

extern pascal void OTDebugStr(const char* str);

enum 
   { kTransferBufferSize = 4096 };/* define size of buffer */

/* define notifier */

static pascal void YieldingNotifier(void* contextPtr, OTEventCode code, 
                              OTResult result, void* cookie)
{
   #pragma unused(contextPtr)
   #pragma unused(result)
   #pragma unused(cookie)
   OSStatus junk;
   
   switch (code) 
   {
      case kOTSyncIdleEvent:
         junk = YieldToAnyThread();
         OTAssert("YieldingNotifier: YieldToAnyThread failed",
                              junk == noErr);
         break;
      default:
         /* do nothing */
         break;
   }
}


/* Define function that downloads a URL from a web server. */

OSStatus MyDownloadHTTPSimple(const char *hostName,
                     const char *httpCommand,
                     const short destFileRefNum)
{
   OSStatus err;
   OSStatus junk;
   Ptr      transferBuffer = nil;
   EndpointRef ep = kOTInvalidEndpointRef;
   TCall    sndCall;
   DNSAddress hostDNSAddress;
   OTFlags  junkFlags;
   OTResult bytesSent;
   OTResult bytesReceived;
   OTResult lookResult;
   Boolean  bound    = false;
   
   /* Allocate a buffer for storing the data as we read it. */
   
   err = noErr;
   transferBuffer = OTAllocMem(kTransferBufferSize);
   if ( transferBuffer == nil ) 
       err = kENOMEMErr; 
      
   /* Open a TCP endpoint. */
   
   if (err == noErr) 
   {  
      ep = OTOpenEndpoint(OTCreateConfiguration(kTCPName), 0, nil,
                                          &err);
   }
      
   /* If the endpoint opens successfully... */
   
   if (err == noErr) 
   {
      junk = OTSetSynchronous(ep);
      OTAssert("MyDownloadHTTPSimple: OTSetSynchronous failed",
                                    junk == noErr);
      
      junk = OTSetBlocking(ep);

      OTAssert("MyDownloadHTTPSimple: OTSetBlocking failed",
                                    junk == noErr);
      
      junk = OTInstallNotifier(ep, YieldingNotifier, nil);
      OTAssert("MyDownloadHTTPSimple: OTInstallNotifier failed",
                                    junk == noErr);
      
      junk = OTUseSyncIdleEvents(ep, true);
      OTAssert("MyDownloadHTTPSimple: OTUseSyncIdleEvents failed",
                                       junk == noErr);

      /* Bind the endpoint. */
            
      err = OTBind(ep, nil, nil);
      bound = (err == noErr);
   }
      
   /* Initialise the sndCall structure and call OTConnect. */ 
   if (err == noErr) 
   {
      OTMemzero(&sndCall, sizeof(TCall));
      sndCall.addr.buf = (UInt8 *) &hostDNSAddress;
      sndCall.addr.len = OTInitDNSAddress(&hostDNSAddress, (char *)
                                       hostName);
      
      err = OTConnect(ep, &sndCall, nil);
   }
   
   /* Send the HTTP command to the server. */
   
   if (err == noErr) 
      { 
         bytesSent = OTSnd(ep, (void *) httpCommand,
                           OTStrLength(httpCommand), 0); 
      if (bytesSent > 0) 
         err = noErr; 
      else err = bytesSent; 
      }        
   /* Now we receive the data coming back from the server. */
   if (err == noErr) 
   {
      do 
      {
         bytesReceived = OTRcv(ep, (void *) transferBuffer,
                        kTransferBufferSize, &junkFlags);
         
         if (bytesReceived > 0) 
             err = FSWrite(destFileRefNum, &bytesReceived,
                                    transferBuffer); 
         else err = bytesReceived; 
         
      } while (err == noErr); /* Loop until we get an error. */
   }

   /* Now handle the various forms of error that can occur. */
   if (err == kOTLookErr) 
   {
      lookResult = OTLook(ep);

      switch (lookResult) 
         {
            case T_DISCONNECT:
            
               err = OTRcvDisconnect(ep, nil);
               break;
            
            case T_ORDREL:
                        
               err = OTRcvOrderlyDisconnect(ep);
               if (err == noErr) 
                  {err = OTSndOrderlyDisconnect(ep);}
               break;
            
            default:
               
               break;
         }
   }

   if ( (err == noErr) && bound ) 
   {
      junk = OTUnbind(ep);
      OTAssert("MyDownloadHTTPSimple: OTUnbind failed.",
                                 junk == noErr);
   }
   
   /* Clean up. */
   if (ep != kOTInvalidEndpointRef) 
   {
      junk = OTCloseProvider(ep);
      OTAssert("MyDownloadHTTPSimple: OTCloseProvider failed",
                                 junk == noErr);
   }
   if (transferBuffer != nil) 
    OTFreeMem(transferBuffer); 
      
   return (err);
}

Using Threads for Easy Synchronous Processing

The notifier shown in Listing 2-4 is used to yield time to the processor whenever the endpoint receives a kOTSyncIdleEvent. Open Transport sends this event whenever it's waiting for a synchronous operation to complete. In response, your notifier should call the function YieldToAnyThread.

static pascal void YieldingNotifier(void* contextPtr, OTEventCode code, 
                              OTResult result, void* cookie)
{
   #pragma unused(contextPtr)
   #pragma unused(result)
   #pragma unused(cookie)
   OSStatus junk;
   
   switch (code) 
   {
      case kOTSyncIdleEvent:
         junk = YieldToAnyThread();
         OTAssert("YieldingNotifier: YieldToAnyThread failed",
                              junk == noErr);
         break;
      default:
         /* do nothing */
         break;
   }
}

Specifying the Host Names and HTTP Commands

The next section of code in Listing 2-4 calls the function MyDownloadHTTPSimple. This function accepts three parameters: the name of the host to which you want to connect, the command to send to the host, and a reference to the file to which you want to download the URL.

OSStatus MyDownloadHTTPSimple(const char *hostName,
                     const char *httpCommand,
                     const short destFileRefNum)

/* declarations go here */
The parameter hostName is a pointer to a string that contains the DNS 
address of the web server. The DNS address must have the suffix :<port>, 
where port is the port number the web server is operating on. 
The parameter httpCommand is a pointer to the HTTP command to send. Typically this command has the following form, where <x> is the URL path.

Get <X> HTTP/1.0\0x13\0x10\0x13\0x10
For example, if you were to download the URL

http://devworld.apple.com/dev/technotes.shtml

You would set hostName to devworld.apple.com:80. (The default port for HTTP is 80.) And you would set httpCommand to

"GET /dev/technotes.shtml HTTP/1.0\0x13\0x10\0x13\0x10"
The parameter destFileRefNum is the reference number of the file to which the results of the HTTP command are written. The function MyDownloadHTTPSimple does not parse the returned HTTP header. The entire incoming stream is written to the file.

Opening an Endpoint and Setting the Mode of Operation

The first section of the function MyDownloadHTTPSimple shown in Listing 2-4 opens a TCP endpoint, sets the mode of operation, and installs the notifier YieldingNotifier.

if (err == noErr) 
      ep = OTOpenEndpoint(OTCreateConfiguration(kTCPName), 0, nil,
                                          &err); 
      /* If the endpoint opens successfully... */
   
   if (err == noErr) 
   {
      junk = OTSetSynchronous(ep);
      OTAssert("MyDownloadHTTPSimple: OTSetSynchronous failed",
                                    junk == noErr);
      
      junk = OTSetBlocking(ep);

      OTAssert("MyDownloadHTTPSimple: OTSetBlocking failed",
                                    junk == noErr);
      
      junk = OTInstallNotifier(ep, YieldingNotifier, nil);
      OTAssert("MyDownloadHTTPSimple: OTInstallNotifier failed",
                                    junk == noErr);
      
      junk = OTUseSyncIdleEvents(ep, true);
      OTAssert("MyDownloadHTTPSimple: OTUseSyncIdleEvents failed",
                                       junk == noErr);

      /* Bind the endpoint. */
            
      err = OTBind(ep, nil, nil);
      bound = (err == noErr);
   }
The OTOpenEndpoint function opens the endpoint and returns an endpoint reference (ep). You need to specify this endpoint reference when you set the mode of execution for the endpoint, when you bind the endpoint to an address, and, later, when you establish a connection, receive data, and close the endpoint. The mode of operation for the endpoint is set as synchronous blocking with the call to the OTSetSynchronous function and the OTSetBlocking function. The call to the function OTInstallNotifier installs the notifier YieldingNotifier. The call to the function OTUseSyncIdleEvents tells Open Transport to send kOTSyncIdleEvents to this endpoint; the notifier responds to this event by yielding time to other processes, as noted in "Using Threads for Easy Synchronous Processing".

Using a synchronous blocking mode of operation results in a simpler programming model, and the use of the notifier function to yield time to other processes prevents the machine from hanging when synchronous operations are waiting to complete.

Finally, the call to the OTBind function binds the endpoint to a TCP address. (A connection-oriented endpoint can initiate a connection only after the endpoint is bound or queue incoming connection requests.) The second parameter requests the address to which you want to bind the endpoint. In this case, a value of nil is passed; because this is an outgoing connection, it does not particularly matter what address the endpoint is bound to. The third parameter to the OTBind function returns the address to which Open Transport has actually bound the endpoint. The code passes nil because we don't need that information.

Connecting to the Host and Sending Data

The next section of the function MyDownloadHTTPSimple, shown in Listing 2-4 , connects to the host and sends an HTTP command to the server. The OTConnect function, which is used to connect the endpoint, passes three parameters: in this case, ep (the endpoint reference), &sndCall (a pointer to the address of the remote endpoint), and a pointer to a buffer in which OTConnect can return information about the connection. Because no data or options were specified with the sndCall parameter, it is not necessary to examine any information returned by the function, so the third parameter is set to nil.

   if (err == noErr) 
   {
      OTMemZero(&sndCall, sizeof(TCall));
      sndCall.addr.buf = (UInt8 *) &hostDNSAddress;
      sndCall.addr.len = OTInitDNSAddress(&hostDNSAddress, (char *)
                                       hostName);
      err = OTConnect(ep, &sndCall, nil);
   }
   
   /* Send the HTTP command to the server. */
   
   if (err == noErr)
   {
       bytesSent = OTSnd(ep, (void *) httpCommand,
                           OTStrLength(httpCommand), 0); 
      if (bytesSent > 0) 
         err = noErr; 
      else err = bytesSent; 
   }
Before calling the OTConnect function, the sndCall structure is initialized. In this case, only the address fields are specified because we are neither sending data with the connection request nor asking for specific option values. The specification of the address is described in detail in "Storing an Address in a TNetBuf Structure".

After connecting to the server, the OTSnd function is called to send the HTTP command. The OTSnd function takes four parameters: in this case, ep (the endpoint reference), httpCommand (a pointer to the data being sent), OTStrLength(httpCommand) (the length of the data), and 0 (specifying that no flags are set). The OTSnd function returns the number of bytes sent or a negative number representing an error code if an error occurred. Because the endpoint is in synchronous mode, the function won't return until it has sent all the bytes or it returns an error. The code following the call to OTSnd tests to see whether the return value of the function is greater than zero to determine whether or not an error occurred.

Receiving Data From the Remote Endpoint

As shown in Listing 2-4 after establishing the connection and sending the data, the function MyDownloadHTTPSimple calls the function OTRcv, which returns the number of bytes received or a negative (error code) number.

if (err == noErr) 
   {
      do 
      {
         bytesReceived = OTRcv(ep, (void *) transferBuffer,
                        kTransferBufferSize, &junkFlags);
         
         if (bytesReceived > 0) 
             err = FSWrite(destFileRefNum, &bytesReceived,
                                    transferBuffer); 
         else err = bytesReceived; 
         
      } while (err == noErr); /* Loop until we get an error. */
   }
Because the endpoint is in synchronous blocking mode, the function won't return until it has received all the data you asked for, or it returns an error. The OTRcv function is called repeatedly until it gets an error, which indicates that there is no data left to receive. The function OTRcv takes four parameters: in this case ep (the endpoint reference), transferBuffer (a pointer to the buffer in which data is to be placed), kTransferBufferSize (which specifies the size of the buffer), and &junkFlags (a pointer to a buffer for flags information), which this sample ignores.

As it receives data, the function MyDownloadHTTPSimple calls the FSWrite function to write the data to a file.

Error Handling

The next section of the function MyDownloadHTTPSimple in Listing 2-4 handles errors that might be returned. The most common error is kOTLookErr. This indicates that some event has happened that you need to look at. To do this, the function MyDownloadHTTPSimple calls the function OTLook, which returns an event code for the pending event.

if (err == kOTLookErr) 
{
      lookResult = OTLook(ep);

      switch (lookResult) 
      {
            case T_DISCONNECT:
            
               err = OTRcvDisconnect(ep, nil);
               break;
            
            case T_ORDREL:
                        
               err = OTRcvOrderlyDisconnect(ep);
               if (err == noErr) 
                  {err = OTSndOrderlyDisconnect(ep);}
               break;
            
            default:
               break;
      }
}
The switch statement includes cases for the most common types of events, T_DISCONNECT and T_ORDREL, and handles them appropriately. The event T_DISCONNECT signals that the remote peer has initiated a disorderly disconnect. HTTP servers will often just disconnect to indicate the end of the data, so all that is needed in response is to clear the event by calling the function OTRcvDisconnect. The event T_ORDREL signals that the remote peer has initiated an orderly disconnect. This means it has no more data to send. In response, your function clears the T_ORDREL event by calling the OTRcvOrderlyDisconnect function and then calls the OTSndOrderlyDisconnect to let the remote peer know that it received and processed the event.

Unbinding the Endpoint and Final Clean-Up

As Listing 2-4 shows, having received the data requested, the function MyDownloadHTTPSimple unbinds the endpoint. The conditional call to the OTCloseProvider function closes the endpoint. The following call to the OTFreeMem function frees up memory for the buffer that was allocated to receive data from the OTRcv function.

if ( (err == noErr) && bound ) 
{
	junk = OTUnbind(ep);
	OTAssert("MyDownloadHTTPSimple: OTUnbind failed.", 
											junk == noErr);
}
	
/* Clean up. */
if (ep != kOTInvalidEndpointRef) 
{
	junk = OTCloseProvider(ep);
	OTAssert("MyDownloadHTTPSimple: OTCloseProvider failed.",
											junk == noErr);
}


Subtopics
Using Threads for Easy Synchronous Processing
Specifying the Host Names and HTTP Commands
Opening an Endpoint and Setting the Mode of Operation
Connecting to the Host and Sending Data
Receiving Data From the Remote Endpoint
Error Handling
Unbinding the Endpoint and Final Clean-Up

Previous Book Contents Book Index Next

© Apple Computer, Inc.
15 JAN 1998