Re: CFReadStreamCreateForHTTPRequest
Re: CFReadStreamCreateForHTTPRequest
- Subject: Re: CFReadStreamCreateForHTTPRequest
- From: Chris Branch <email@hidden>
- Date: Wed, 05 Apr 2006 14:22:23 -0400
Chris Branch wrote:
>
> I'm trying to create a simple C++ class that is able to send an HTTP
> request and retrieve the response on OS X (10.2 and later). For
> example, suppose the interface for my class looks like this:
>
> class cInternetRequest {
> public:
> cInternetRequest();
> virtual ~cInternetRequest();
>
> bool Open(const char *url);
> bool Read(char *buffer, int bytesToRead, int *bytesRead) const;
> void Close();
>
> bool GetHttpStatusCode(int *statusCode) const;
>
> ... more stuff omitted ...
> };
>
> Sample usage of this class might look like:
>
> if (request.Open("www.somedomain.com/example.php")) {
> if (request.GetHttpStatusCode(&httpStatus) && httpStatus == 200) {
> do {
> success = request.Read(buffer, sizeof(buffer), &numBytes);
> // ...process response...
> } while (success && numBytes > 0);
> }
>
> request.Close();
> }
>
> It looks like the implementation of this should be pretty
> straightforward using CFHTTPMessageCreateRequest() and
> CFReadStreamCreateForHTTPRequest(), but there are a few aspects that
> Apple's documentation does not seem to cover.
>
> In my Open() method, I would call CFHTTPMessageCreateRequest() to create
> the request, then call CFReadStreamCreateForHTTPRequest() and
> CFStreamOpen() to actually send the request. Questions:
>
> 1) Does CFReadStreamCreateForHTTPRequest() take a "snapshot" of the
> CFHTTPMessageRef parameter? In other words, is it safe to CFRelease the
> CFHTTPMessageRef immediately after calling
> CFReadStreamCreateForHTTPRequest, or does the CFHTTPMessageRef have to
> persist until the stream is closed/released?
>
> 2) Does CFReadStreamOpen() block until HTTP response headers are
> available? If no, how do I know when it's "safe" to call
> CFReadStreamCopyProperty() to get the response headers? If
> CFReadStreamOpen() DOES block, what happens if there's a network error?
> Will it timeout? How long?
>
> Thanks!
> Chris
I was finally able to implement my C++ class for sending HTTP requests.
I got stuck a few times, so I wanted to post what I learned in the hope
that this will save someone else a few hours if they happen to run
across this message thread while searching the archives.
First, let me answer the questions I asked in my original post:
1) Does CFReadStreamCreateForHTTPRequest() take a "snapshot" of the
CFHTTPMessageRef parameter? In other words, is it safe to CFRelease the
CFHTTPMessageRef immediately after calling
CFReadStreamCreateForHTTPRequest,
Answer: Yes, CFReadStreamCreateForHTTPRequest() takes a snapshot so it's
safe to CFRelease the CFHTTPMessageRef after calling
CFReadStreamCreateForHTTPRequest().
2) Does CFReadStreamOpen() block until HTTP response headers are
available? If no, how do I know when it's "safe" to call
CFReadStreamCopyProperty() to get the response headers?
Answer: CFReadStreamOpen() does not block. The only way to wait for the
response header to be returned is to schedule the stream on the run loop
via CFReadStreamScheduleWithRunLoop() and wait for the appropriate event
to arrive in the callback. So with that in mind, here's the code I came
up with to implement a synchronous CFReadStreamOpen():
bool cInternetRequest::SendRequestWithTimeout()
{
CFStreamClientContext context;
bool gotHeader;
int result;
gotHeader = false;
/*
** Schedule the stream on the current run loop, then open the stream
(which
** automatically sends the request). Wait for at least one byte of
data to
** be returned by the server. As soon as at least one byte is
available,
** the full HTTP response header is available. If no data is returned
** within the timeout period, give up.
*/
memset(&context, 0, sizeof(context));
context.info = &gotHeader;
if (CFReadStreamSetClient(_readStreamRef, kNetworkEvents,
_StreamCallback, &context)) {
CFReadStreamScheduleWithRunLoop(_readStreamRef, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
if (CFReadStreamOpen(_readStreamRef))
CFRunLoopRunInMode(kCFRunLoopDefaultMode, TIMEOUT_IN_SECONDS, false);
CFReadStreamSetClient(_readStreamRef, kCFStreamEventNone, NULL, NULL);
}
return(gotHeader);
}
static void _StreamCallback(CFReadStreamRef /*readStreamRef*/,
CFStreamEventType eventType, void *clientData)
{
switch(eventType) {
case kCFStreamEventHasBytesAvailable:
*((bool *) clientData) = true;
CFRunLoopStop(CFRunLoopGetCurrent());
break;
case kCFStreamEventErrorOccurred:
case kCFStreamEventEndEncountered:
CFRunLoopStop(CFRunLoopGetCurrent());
break;
}
}
A few notes:
* Originally, I thought that kCFStreamEventOpenCompleted was the correct
event to look for within the callback to determine when the request had
been sent. This is not correct. You must wait for
kCFStreamEventHasBytesAvailable to know that the request has been sent
(i.e., the HTTP response is in the process of being returned from the
server).
* Be careful with kCFRunLoopCommonModes vs. kCFRunLoopDefaultMode.
Don't pass kCFRunLoopCommonModes to CFRunLoopRunInMode() -- it won't
work! (I found another posting in the archives for this list where
someone else made the same mistake, so I don't feel too bad :-).
* The last parameter to CFReadStreamSetClient() is NOT optional. If you
set this parameter to NULL, your callback will not be called. The
documentation does say this, but I initially dismissed it as typo. It
made this mistake because: 1) it doesn't really make sense that
clientContent be required, and 2) the wording in the documentation is
very similar for last two parameters ("If NULL, the current client is
removed.") which made me think it was a copy/paste error:
clientCB
The client callback function to be called when one of the events
requested in streamEvents occurs. If NULL, the current client for stream
is removed.
clientContext
A structure holding contextual information for the stream client. The
function copies the information out of the structure, so the memory
pointed to by clientContext does not need to persist beyond the function
call. If NULL, the current client for stream is removed.
The documentation really is correct although it still doesn't make sense
why the last parameter is required if I don't need any extra info in my
callback. The final implementation I posted above does end up using
clientContent, but my initial proof-of-concept version did not and I was
confused as to why my callback was never called.
Hope this saves someone else some time!
Chris
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Macnetworkprog mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden