Important: The information in this document is obsolete and should not be used for new development.
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 functionYieldToAnyThread
, 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 akOTSyncIdleEvent
. Open Transport sends this event whenever it's waiting for a synchronous operation to complete. In response, your notifier should call the functionYieldToAnyThread
.
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 functionMyDownloadHTTPSimple
. 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 parameterhttpCommand
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\0x10For example, if you were to download the URL
http://devworld.apple.com/dev/technotes.shtml
You would set
hostName
todevworld.apple.com:80
. (The default port for HTTP is 80.) And you would sethttpCommand
to
"GET /dev/technotes.shtml HTTP/1.0\0x13\0x10\0x13\0x10"The parameterdestFileRefNum
is the reference number of the file to which the results of the HTTP command are written. The functionMyDownloadHTTPSimple
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 functionMyDownloadHTTPSimple
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); }TheOTOpenEndpoint
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 theOTSetSynchronous
function and theOTSetBlocking
function. The call to the functionOTInstallNotifier
installs the notifierYieldingNotifier
. The call to the functionOTUseSyncIdleEvents
tells Open Transport to sendkOTSyncIdleEvents
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 ofnil
is passed; because this is an outgoing connection, it does not particularly matter what address the endpoint is bound to. The third parameter to theOTBind
function returns the address to which Open Transport has actually bound the endpoint. The code passesnil
because we don't need that information.Connecting to the Host and Sending Data
The next section of the functionMyDownloadHTTPSimple,
shown in Listing 2-4 , connects to the host and sends an HTTP command to the server. TheOTConnect
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 whichOTConnect
can return information about the connection. Because no data or options were specified with thesndCall
parameter, it is not necessary to examine any information returned by the function, so the third parameter is set tonil
.
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 theOTConnect
function, thesndCall
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. TheOTSnd
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). TheOTSnd
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 toOTSnd
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 functionMyDownloadHTTPSimple
calls the functionOTRcv
, 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. TheOTRcv
function is called repeatedly until it gets an error, which indicates that there is no data left to receive. The functionOTRcv
takes four parameters: in this caseep
(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 theFSWrite
function to write the data to a file.Error Handling
The next section of the functionMyDownloadHTTPSimple
in Listing 2-4 handles errors that might be returned. The most common error iskOTLookErr
. This indicates that some event has happened that you need to look at. To do this, the functionMyDownloadHTTPSimple
calls the functionOTLook
, 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; } }Theswitch
statement includes cases for the most common types of events,T_DISCONNECT
andT_ORDREL
, and handles them appropriately. The eventT_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 functionOTRcvDisconnect
. The eventT_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 theT_ORDREL
event by calling theOTRcvOrderlyDisconnect
function and then calls theOTSndOrderlyDisconnect
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 functionMyDownloadHTTPSimple
unbinds the endpoint. The conditional call to theOTCloseProvider
function closes the endpoint. The following call to theOTFreeMem
function frees up memory for the buffer that was allocated to receive data from theOTRcv
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