Application • 56:18
CFNetwork is at the heart of the fast and reliable networking built into Mac OS X. This session is for those interested in accessing the network from within an application's normal event context without getting into the details of raw sockets or specific networking protocols. We cover CFNetwork's feature set, highlighting what's new, how and when to use CFNetwork, and how CFNetwork fits within the Mac OS X architecture. We also provide detailed examples of HTTP and FTP transactions, including integrating proxy and authentication support, as well as exporting and discovering Rendezvous services and asynchronous DNS lookups.
Speaker: Becky Willrich
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
And with that, I would like to introduce Becky Willrich, and she'll talk about modern networking using CFNetwork. Hi, I'm Becky Willrich. I'm the technical lead for CFNetwork, and I'll be talking to you about how you can use CFNetwork in a Mac OS X application. What we're going to cover today is we'll start out with an introduction and overview.
We're going to breeze through that pretty fast because I figure that's review for most everybody at this point. We're going to talk about what's new in Tiger. In particular, we're going to highlight two new classes, CFNet Diagnostics and CFHTTP Authentication. Then we're going to look at a couple common CFNetwork tasks that we know from feedback from you as something you want a little more guidance with.
We're going to look at how you manage proxies using CFNetwork and how you can use CFNetwork to perform asynchronous host lookups. Finally, at the end, we're going to wrap up by comparing CFNetwork with some of the other URL loading APIs on the system and help you figure out which one is most appropriate for the programming task you have. So I'm going to dive straight into the introduction and overview now. What we're going to do is start out by talking about what is CFNetwork, and then we're going to look and see how CFNetwork fits into the Mac OS X stack as a whole.
And then finally, we're going to look at the feature set in depth. So, CFNetwork is chock full of networky goodness. I have this on high authority from a coworker. What CFNetwork is, is a framework that provides high-level APIs for a number of different internet protocols, and I listed them here.
Well, what do I mean by high-level? I mean higher level than BSD sockets. I do not mean high-level like, you know, just give me the URL and go do some magic and give me the data. It's not that high-level, but it is better than sockets. It's better than managing select yourself and parsing yourself.
We also provide support for advanced features like proxies and authentication, so you don't have to program those yourself. One of our goals is to provide web compatibility because, you know, wonder of wonders, not every server out there is actually HTTP compliant. We do our best to provide full access to those servers anyway. Becky Willrich And it's worth noting that the HTTP engine that underlies Safari is in fact CFNetwork. So every time you download a URL off the web using Safari, you're going through CFNetwork code.
CFNetwork's APIs are all in the style of core foundation that has two important consequences. The first one is that you're constantly using CF types. So you're going through the whole CF retain, CF release, reference counting thing. We use a couple types from core foundation pretty heavily, CF read stream and CF write stream.
We also add new types in CFNetwork. Those are all CF types as well. I listed three common ones here, CF host, CF net services, CF html, and CF network. All introduced in CFNetwork, all also CF types. The other important consequence of having an API based on core foundation is that we produce all of our asynchrony using CF run loop.
So the advantage of that is that it fits in very easily with your application's natural event model. Carbon events and NS run loop are both built on top of CF run loop. So CFNetwork fits in very naturally with your application's natural event model. However, it also means that if you want to use CFNetwork to its best effect, you are going to need to understand CF run loop to program it effectively.
That's not going to be important for this talk, but just to warn you, when you get into the code itself, you're going to want to have a good understanding of CF run loop. So I said before, CFNetwork is not this convenience API where you hand over the URL and you're like, please just download the data. I don't want to know the details. There are APIs on the system that do that for you, but CFNetwork is not it. Pardon me.
So for example, we do not automatically apply any settings for you. CFNetwork is intended to be a power API where you go through and explicitly set each bit and flag that you are interested to customize the process. The other part of the goal is to expose the full power of the protocols that it implements.
So you can very explicitly turn on and off every individual feature of the protocol you're using. Now, the upside is that you do in fact have full control. The downside is that you really need to have some understanding of the protocol before you're going to be able to use CFNetwork to good effect.
So here's an architecture diagram of Mac OS X. You guys have probably seen this a few million times by now. Darwin, our Unix implementation, is the Core OS. And then we have various layers of libraries all the way up to the applications at the top. CFNetwork fits into this core services layer. So that's below the graphics layer at the point where all of the basic services that pretty much every process on the system uses, unless it's a pure Unix process.
And if you look specifically at the networking pieces in the stack, here's how they line up. CFNetwork is what's available at the core services layer. It's built on top of Berkeley SOPCITS, TCP/IP, Rendezvous and DNS implementations, all available from the Core OS Darwin layer. Not much going on in the networking world at the application services layer, which isn't surprising.
That's where the graphics and user event pieces are added. But then above in the application framework layers, NSURL and WebKit become available. And then built on top of all of this stack, you'll see all of the major networking apps on our system, like iChat, Sherlock, Safari, Mail, iTunes.
So specifically, what feature set does CFNetwork provide? Well, first of all, it provides an abstraction for TCP socket streams. We went beyond supporting basic TCP, though, to add in support for TLS and SSL and SOX proxies. CFHost gives you access to DNS, and in particular, provides asynchronous host resolution.
Then we have streams to support both the HTTP and the FTP protocols. CFNet Services is your API into Rendezvous. CFNet Diagnostics is new, and we'll talk about it some more in a little bit. And then, of course, we have integrated proxy support at all these layers, so that if you need to apply a proxy setting, you can do that.
That's as much of an overview as I want to give. And from here, I'm going to dive into what's new in Tiger. I've already said that we have two new types, CFNet Diagnostics and CFHTTP authentication. We also have some new CFNet services APIs, and we have some new settings on socket streams specifically to allow you to customize SSL and TLS sessions.
So what is CFNet diagnostics? The basic scenario is this. You're in the middle of some networking task, and for some reason it's not working. You don't know why. You just get an error code back from a download, for instance, or some kind of a POSIX error that tells you that the network has gone bad.
How do you report that back to the user in some meaningful way, and how do you help the user move along to fix the problem so that your program can work? Becky Willrich CFNet diagnostics provides an easy interface for you to lead the user through these tasks. The user just did not enter the right thing in the network system preferences panel.
CFNet Diagnostics gives you a way to prompt the user about the problem and then move through it and help the user to correct the problem. Looks like this. So what would happen is you would encounter this network failure. You'd lead the user to this panel, essentially. And this panel would go through the five steps you see on the left to try and determine what's gone wrong. Once it hits a problem in the network settings, it tells the user about it and tells them what they can do to correct it. Once the user's corrected it, it goes further to tell the user that the situation's been corrected.
So what's the advantage of CFNet diagnostics? Well, the biggest advantage is you don't have to write the code. But the second biggest advantage is it provides a uniform interface to the user. It allows you, the programmer, to easily query what the network connectivity status is. Is the network functioning properly or not? And if it's not, it gives you a localized string that you can present directly to the user to explain what's gone wrong. Having done that, it further leads the user to that panel we just saw, so that the user can then go and try and correct the problem themselves.
There is sample code available showing how CFNet diagnostics works. Unfortunately, due to a mastering error, it does not appear at this path on your Tiger CD. However, it is included, along with every other sample code piece that I'm going to talk about, in the disk image for this session. So I encourage you to download the disk image for the session and grab the sample code from there.
So how's it going to look in your application? It's actually a very simple, minimal API. And what I've got here are the three lines of code that you're going to need to add to your program to integrate CFNet diagnostics. You can see at the top what we've done is we've read bytes from a stream, and we got a negative one return, which tells us that an error has occurred. So where do we go from there? Well, it's pretty straightforward. We take the failed read stream, the read stream that we could not read from, and instantiate a net diagnostic object from it.
Having done that, we call net diagnostic copy network status to retrieve an error string that we can now display to the user. And we're going to display it in some kind of an alert panel that has an OK button to just dismiss it and continue, and a diagnose button to lead the user to further perform diagnosis. If the user clicks the diagnose button, we're going to call this line at the bottom, diagnose problem interactively. That's going to return immediately.
From your program's point of view, but from the user's point of view, it's going to launch another application and bring up a panel that will help the user start to diagnose the problem. So that's Net Diagnostics, and now I'm going to move on to talk some about HTTP authentication.
We've had authentication support for HTTP in CFNetwork for a long time now, at least a couple, three years. However, we've made some substantial changes in Tiger. Historically, HTTP authentication has supported basic and digest schemes. Since I last talked to you here last year, we've added support for NTLM and Spanago, which are a couple schemes that are common in the Windows world.
And the existing old API, the one that's been available for years, was designed for single-shot transactions. You'd issue a request and get back an authentication challenge. Once you get the challenge, you call the old API, it corrects the request, you issue the request again, and away you go. Becky Willrich And it worked fine. The problem was that you don't usually issue just one request to a server.
You usually issue 10 or 20. And it seems a shame to have to go through this process for each and every request. Becky Willrich So that's why we added CFH-GDP authentication in Tiger, to allow you to carry state forward from one request to another, going to the same authenticating server.
So here's the old API, CFHTTP message ad authentication. Every request is processed individually. Each time you receive a challenge, you get the credentials from the user, you apply the credentials to the request, and then you issue the entire request again. And there was no persistency across multiple requests to the same servers.
The new API uses an object, a CFHTTP authentication object, to carry that state across from request to request. And that gives you much better performance, particularly when you're talking to some host that has one of these very expensive authentication schemes that requires, for instance, multiple legs just to compute keys and negotiate protocols.
So how's it going to work in code? In code, you're going to maintain a set of CFH-TTP authentication objects. When you receive an authentication challenge, you're going to look through the set of objects to try and find one that applies. If you find one, great. If not, you're going to create one.
Once you have that object, you apply it to the request along with credentials from the user, and CFNetwork will use the information stored in that object to process the request as efficiently as possible. In the same way, you can use CFNetwork to create a set of CFH-TTP authentication objects. When you're done, you can use CFNetwork to create a set of CFH-TTP authentication objects.
So here are the APIs you're going to use along the way. From the store of authentication objects, you'll use CFH-GTP authentication applies to request to find out whether the auth object applies to the request you're trying to handle. If you have to create one from scratch, you'll use create from response. Once you have that auth object, you need to check to see if the object is valid. The reason why you need to do that is because the authentication object may go stale over time.
Assuming it's valid, you get the credentials from the user or from some shared store that you have in code. You can use requires username and password and requires account domain to figure out what information you need to collect from the user. And then finally, you're going to apply all of the pieces to the request to correct the request and prepare it for an authenticated transaction with the server. And you're going to do that using apply credentials dictionary.
Now, there are a couple tricks that you have to be aware of when you're using HTTP authentication. The first one is that some authentication schemes will only work if the subsequent requests go out over precisely the same socket as the initial request went out over. So that means you have to turn on persistent connections. So when you're in a situation where you're trying to handle authentication, make sure you have persistent connections turned on.
But further than that, in order for the persistent connections to be guaranteed to stay open, you have to make sure to keep open one stream to hold open that socket inside CFNetwork for the subsequent requests. So what I recommend is that as you're going from request to request, don't close the stream for the old request until you've opened the stream for the new request. That'll guarantee that the socket underlying the connection will be kept open and reused.
The other thing to be aware of is that some of the authentication schemes require multiple legs back and forth to the server just to negotiate hash values, protocols, whatever. So that means that even though you have applied authentication once to a particular request, that doesn't mean that it'll automatically work from there. You may have to keep reapplying it over and over again until finally the server negotiation gets to a point where the server's ready to return the data.
The simple rule is just keep reapplying the authentication object until or unless it goes invalid. The auth object knows how many legs are required. It knows when the credentials are simply wrong or when the server has decided to shut down the transaction. At that point, you can fall back and figure out how to handle a failure case.
So with that, I'm going to switch over to the demo. What I've got here is a simple application which downloads URLs. So I can type in-- www.apple.com and click start. You can click start. There we go. Scared me for a moment there. You can see that the URL was downloaded down here. Here we're looking at all the text for www.apple.com. At the top, I've just displayed a summary of the status line.
Now, there are a couple things that are not working properly in this demo. The first one is that failures are not handled in a particularly interesting way. So if I go to www.idontexist.net and click start, Thanks, Keith. It should come back with an error code. It should come back with a DNS error, but it's not. I should, it depends how quickly the DNS server responds. All right. Well, suppose it came back with a little error string right here.
[Transcript missing]
jigsaw.w3.org. This is a little site set up by the W3 Consortium to let you test authentication code. So we go there.
There we go. And it comes back with a 401 unauthorized because we haven't provided username and password. Well, even if I provide the correct username and password, it's still going to come back with this 401. So what we're going to do is we're first going to hook up NetDiagnostics so we can give better feedback to the user, and then we're going to add authentication to this application. So I'm going to quit this app and come and look at the code here. So this code is also on the disk image. It's a very simple Cocoa application. It has just one class, DownloadController, which is wired up to the UI.
And to integrate Net Diagnostics, what I'm going to do is go to the handle error method. And here's the simple error string that you would have seen. It does nothing but pull the error out of the read stream and format it and display it in the text view. That's not very interesting, so we're going to get rid of it.
And we're going to replace it with net diagnostics code. So CFNet diagnostic ref, let's just do diagnostics, get CFNet diagnostics create with streams. Null allocator. Stream is the instance variable that's storing the failed read stream, and we don't have a write stream, so that's there. Now we need a string ref for the errors.
Error string. CFNet diagnostics. Copy network status passively. Diag at error. Okay, so what that call is going to do is it's going to use the diagnostics object and ask for an error string describing the failure. That error string is going to be put into error. Now, if you're not a Cocoa programmer, I'm going to ask you to just take my word on this. I'm about to add a line that's going to run an alert panel.
NS run alert panel. First argument is the title. Second argument is a printf string for the contents of the panel. Third argument is button, default button. Third argument is the alternate button. So we're going to have an alternate button of diagnose, which if the user clicks, we're going to use to head off into CFNet diagnostics to bring up that diagnostic panel. Don't have a third button, and finally the argument for the printf.
Okay. So that's going to run an alert panel. And what we're going to do is if the result from that is NSAlertAlternateReturn,
[Transcript missing]
And we're just going to call CFNet diagnostic diagnose problem interactively. I'm really a much better typist when I'm not on stage. And that's it. We're done. Oh, except for a little cleanup. We should release the objects we've created.
There we go. Oh, good. All right. So now let's just see what happens. Oh yes, thank you. And is it CF? Ah yes, and there is no S here either. Let's try this again. That does not look good. There we go. All right. It's just taking a little time. All right. So given that the DNS server doesn't seem to be too happy with us right now, I'm going to create a much simpler networking failure. That's a very simple networking failure. Now let's see if we can go to www.apple.com.
No, we can't. And here's that alert panel. It comes up and it shows us a string chosen by CFNet Diagnostics. The system's internet connection appears to be down. All right, let's see what CFNet Diagnostics can tell us about that. So here's the diagnostics panel, and it was going to go through these five different types of networking errors, but in fact it failed immediately at the first one. The built-in Ethernet is not working. So are we trying to connect to the internet for the first time? No, we're not. We're trying to configure the ethernet.
And now it says, well, the Ethernet port's not active. Let's check the following things. And you'll notice in this list, one of them is, is the Ethernet cable plugged in? Well, look at that. No, it's not. So let's just plug it back in. Let's just plug it back in. There we go. And now you can see NetDiagnostics discovered that we've corrected the problem and is going through and checking the remainder of our configuration.
If I go back here, I can download Apple.com. Now, I have to make a little confession here. What you're looking at up here is pure demo ware. If you were to build this program on the WWDC seed that you received, you would not see that series of panels. Instead, you'd see a placeholder panel, which would tell you that yes, net diagnostics fired correctly. It would tell you what application had brought it up and what the code entry point was.
But it wouldn't actually lead you through that series of panels. The series of panels will, of course, be there for the final Tiger release. We didn't have them quite ready to where we were willing to include them in the seed. So for the seed, you'll just see the placeholder panel, which is hopefully enough to allow you to develop your code. So having done that, now let's add authentication. Authentication is a little more complex than just using CFNet diagnostics. To do that, I'm going to need to add two new instance variables.
So this is the auth object that I'm going to use to store the current authentication state. And I'm going to add a CF mutable array to store the set of all authentication pieces that I've received. So that's the change to the header file. Now over here in the source file, we need a couple bookkeeping things. First of all, we'd better release the objects when we're deallocated so we don't leak.
Secondly, in the stop download method, this is what happens when the user clicks stop or when the stream is finally exhausted and the data has been successfully loaded. We need to throw out the auth object if one exists so it's not carried forward to future requests. Okay, so that's the bookkeeping. Now to actually do the work. We have a method here, evaluate headers.
This is going to fire the first time we get a look at the headers back from the server. So what we want to do is look for an authentication failure, which would be a status code of 401 or 407. And if we have such a response, we want to attempt authentication. So here if we got a response if CFH-TTP message, get response status code, response. We're just going to deal with 401s for the demo. 407s would be a proxy authentication failure.
It's pretty much straightforward in the same code for that. So if we got a 401, we're going to attempt authentication and we'll pass the response we received. And if that succeeds, so if this method returns true, then we're going to return no, telling the surrounding code, ignore the stream you've been looking at, there's a new stream coming.
Okay, and that's it for that code. Now all we need to do is write this attempt authentication method. Well, I'm not going to force you to sit here and watch me introduce a million typos. I actually have that method already written up here, and what I'm going to do is just uncomment it and walk you through it.
The first thing we do is look and see if we already have an authentication object. If not, we're going to try and find one. And we'll do that by walking through AuthArray. For each item in AuthArray, we ask it, does it apply to our request? If so, that's still not good enough. We want to know if it's still valid. If it is valid, all right, great. Then we store it in our instance variable and break out of the loop.
Otherwise, there's no need to hold on to invalid authentication items, so we dispose it and do some updating for the loop. Once we reach, once we're outside of that loop, if we still don't have an authentication object, that means we couldn't find one. So now we're going to create one. So we call CFHTTP authentication create from response.
Now, it is actually possible for a newly created authentication item to be invalid off the bat. What that usually means is that the server has chosen to send an unauthorized response without any information about how you can correct the error. The server is not going to let you authenticate, period. There are some other possibilities.
There could be some missing headers in the response, but the main one is that the server is simply not going to let you in. So you do have to check to make sure that the authentication object you just created is in fact valid. If it is terrific, we'll add it to the shared store. If not, then we give up.
So now coming down here, at this point, if we don't have an authentication object, we're simply not going to get one. And we'll see at the end here that we simply punt in that case. However, assuming we did get an authentication object, we look to see if it requires an account domain.
And if it does, again, we're going to punt because my UI wasn't clever enough to prompt for a domain. That mostly means we're giving up on NTLM. That's the one authentication scheme that requires a domain in addition to username. So in that case, we release auth and just return no.
Otherwise, we come down into this code. If the authentication requires a username and password, we go and haul it out of the UI. And then we call HTTP message apply credentials to apply the authentication we've got to the request. Assuming if that fails, return no and bail. Otherwise, we're going to ultimately return yes to say that the authentication has succeeded.
Now comes the part where we have to deal with the problem of persistency. We cannot dispose of the old stream until the new stream is well established. So right here, we do all the work of setting up the new stream. This is all standard CFNetwork code. Create the new stream, set the client, schedule it with the run loop, set whatever properties you need, and then open the stream.
Once we've got that stream set up, we can now go and dispose of the old stream, close it, set the client to null, unschedule and release, and ultimately set our instance variable to point to the new stream. At this point, we're done, and we can return, yes, authentication has succeeded. So let's see if it actually works.
Okay, so let's go to jigsaw.dub3.org, that same test site. So without a username and password, Now, it comes back with a 401 unauthorized. But this time, when we provide the username and password, click Start, We come back with a 200 OK. And if we were to look at the data we received, yay, your browser made it. Okay, but if I give the wrong username and password, again, we get the 401 unauthorized. So there, in about 10 minutes, we've added net diagnostic capability and HTTP authentication to a simple download application.
So if I could get back to the slides. If I could have the slides back. Okay, so there are a couple other new APIs I want to mention. We redid the CFNet services APIs. That's the APIs that give you access to Rendezvous via the run loop. We redid it in Tiger, not just because we love making you change your code. We did it because we needed to fix a few problems that we uncovered with the earlier APIs. And at the same time, we found that the earlier APIs were insufficient to pick up some new features being exposed by CFNetwork. CoreOS.
We are deprecating the old API. That doesn't mean that it will suddenly stop working. It'll still work as it always did. But one of the big things that you'll pick up by going to the new APIs is better network IO performance. So we encourage you to transition when you're ready to. The transition is as simple as we could make it. It's very nearly search and replace. And what I've done is I've listed here what the old calls were and what the new calls would be.
To help you with the transition, the release notes, which are on the Tiger CD and also in the disk image for this session, give full instructions for how to transition to the new API. We also updated the Echo and Echo Client examples, which use CFNet services to discover the client in the server, to use the new API. So you can look there for more examples.
There is one important difference that I want to mention. CFNet Service Resolve has been replaced with CFNet Service Resolve with Timeout. One of the reasons why is that CFNet Service Resolve would leave open the resolution until it was explicitly canceled. What we found out is that this was completely unexpected by most people.
So, they would call Net Service Resolve, they would get back the resolution, so they would get back the information they were looking for, and then they would assume that the Net Service was done. Well, it wasn't. It's still consuming bandwidth. It's still consuming network time until it's explicitly canceled. So CFNet service resolve with timeout has slightly different behavior. It now will exit when either a suitable address has been found or the timeout has been reached. And as a result, it consumes considerably less network bandwidth.
Now, there was one legitimate time when you would want to leave a net service open and running forever. You would want to do that if you were monitoring a single net service and waiting for changes to occur. To address that problem, we've added a new type, CFNet Service Monitor.
New in Tiger, and where you used to use a long-running CFNet Service Resolve call together with GET protocol-specific information so you could find out what had changed about that net service, instead just use Net Service Monitor. It has a much more efficient, smaller network footprint in terms of the traffic.
[Transcript missing]
The other property is KCF Stream property SSL settings. This is a settable property. Its value is a dictionary. The keys of the dictionaries are a number of different configuration options, which you can find in cfsocketstream.h. And it allows you to control how CFNetwork will judge the peer certificate, by and large, and how CFNetwork will handle the TLS or SSL handshake.
I'm not going to go through these in huge detail. These are all the different properties you can set. SSL level lets you choose TLS v1 or SSL v3 or what kind of fallback you want. Allows expired certificates and allows expired routes, allows any route, pretty much self-explanatory. Validates the certificate chain, whether you want CFNetwork to even look at the certificate chain or whether you want to do that yourself or you want to leave yourself wide open to whatever the server gives you.
SSL Peer Name allows you to set the expected name that you're expecting to see on the certificate from the remote side, because you might connect by IP address, but the certificate is going to display a host name, and so you'd need to set that separately. SSL Certificates allows you to set the certificates for your side of the SSL transaction. So that's what you would use if you wanted to do client-side certificate checking, or if you wanted to write a server yourself.
Speaking of servers, you'd set SSL is server if you were performing the server side of the transaction, as opposed to the client side. Default values are all documented in cfsocketstream.h. In general, the defaults are for the strongest security possible and the maximum amount of checking. So you would need to set these to make the requirements more lax.
That's the new APIs that we have available in Tiger. Now I'm going to take a moment to talk about a couple different CFNetwork tasks. In particular, we're going to talk about how you use proxies using CFNetwork and how you would perform an asynchronous DNS lookup. So one of CFNetwork's strengths over working with other networking stacks or writing your own is that it's prepared to handle complex combinations of proxies.
So if you look at the system settings, the user's turned on a SOX proxy and an HTTP proxy and an HTTPS proxy, and you don't know which one you're going to use, and you're not sure how the different communications are handled. Well, CFNetwork does. CFNetwork can figure out which proxy should be applied and then do the correct communication for that type of proxy. However, CFNetwork's not intended as a convenience API, so you have to explicitly turn proxies on to get proxy support from CFNetwork.
You're always going to do that by calling CFReadStreamSetProperty. And the key, or the property you're going to set is whatever proxy setting is appropriate for the kind of stream you're using. Well, so one of the questions we get is, well, there's an HTTP proxy and there's a SOX proxy, but I'm working with an HTTP stream. Do I set the SOX property, a proxy property, or the HTTP proxy property? Becky Willrich Always set the highest level.
So in that case, you would be setting the HTTP proxy. If you were working with an FTP stream, you would set the FTP proxy property, even if the proxy you're applying is SOX, for instance. You should also always apply the proxy settings before opening the stream, because we need that information before we establish the correct socket.
And what we found is that our developers use proxies basically in two different ways. Either they simply want to apply the default system proxy configuration, or they know that their case is special in some way, and they have a custom proxy server that they wish to apply regardless of what other settings may be present.
So here's probably the more common one. You just want to use whatever the current system proxy settings are. Well, the good news is that while you have to be explicit about this, it's really fairly straightforward. Just call scdynamic store copy proxies. That will get you the default proxy dictionary or the system proxy dictionary out of system config and apply it blindly to the stream.
Don't look at it. Don't tear it apart. Just pass the whole schmear through. CF read stream set property, stream, property HTTP proxy, and the dictionary you got back from system config. That's it. CFNetwork will pull the dictionary apart, figure out what the correct settings are, and use them.
If, on the other hand, you need to use a custom proxy configuration, you're going to need to create the custom proxy dictionary yourself. Look in the correct stream header file to find out what the keys are. You're going to need a key for the host and a key for the port, and those are the only two keys that you need present in your dictionary. Some proxies, most notably SOCKS, have some additional keys you could set if you would like.
You can take a look at those. Those aren't actually required. Once you have constructed that dictionary, you apply it exactly the same way by calling set property stream HTTP proxy dictionary. So here's what it looks like in code. Create a mutable CFDictionary. Set value proxy host, whatever the host is. Set value proxy port, whatever the port is. Then call CFReadStream set property exactly as you did before.
So another task that's pretty common that we hear about is the need to perform an asynchronous host lookup. What do I mean by that? Well, you have a host name, you want the IP address. It's that straightforward. And the traditional Unix calls for this are get host by name or the slightly newer get adder info.
But the problem with those calls is that they're blocking. They're going to tie up a thread, and they're not going to return until they have the answer. That makes it very difficult to process multiple host names at once. So the solution to that is to use CFHost instead.
CFHost is going to report its results back to you asynchronously via the CF run loop. Excuse me. That way you can have as many open queries as you'd like. They're all going to happen on the same thread, and they're all going to report the results back asynchronously. And incidentally, CFHost will perform both IPv4 and IPv6 lookups for you.
Incidentally, CFHost supports a number of other things, too. You can do reverse DNS lookups, so you have the address, you want to know the common host names. You can use it to determine whether a given host is reachable just now. If for some reason you want to use a blocking API, you can use CFHost in that fashion, although we always recommend that you use the asynchronous non-blocking API instead. That's the power of the object. And for our purposes here today, we're going to focus on just basic host lookups.
So using CFHost is pretty much the same as using every other CFNetwork object. You start by setting it up. You create it, set your callback, all of that good stuff. Then you wait for the callback to come in. When the callback comes in, you field it. And then finally, you clean up. Every CFNetwork object follows this basic pattern. Set it up, wait for the events, wait for the callbacks to come in, and then dispose of the object.
So here's the setup. Here we're doing a DNS lookup of www.apple.com. CFHost create with name, www.apple.com. Then we need to set up the client callback. We use CFHost set client. That's just how you specify what function CFHost should call when it receives the information that you're looking for. Schedule it with a run loop. Here we're scheduling it with the current run loop in the default mode. That tells CFHost where you want to receive that callback.
You can schedule it on another thread if you want. And then having done all that setup, you start CFHost at its work by calling CFHost start info resolution. And here you pass the host object and then the type of information you're looking for. We're looking for the addresses, so that's kcfhost addresses.
And you're done. Then the run loop runs, and at some point later, CFHost has its answer. Once that happens, it's going to call your callback on the run loop you specified. So here we're fielding the callback. First argument is going to be the host object whose resolution has completed. Second argument tells you what resolution has completed. We only signed up for one, so we know what that's going to be.
Third argument is an error, if an error has occurred. And then finally is a context pointer, which you could have provided to pass information or state around inside your own program. To handle it, first we look at the error. If an error has occurred, then we're going to need to field it in some fashion.
Assuming no error has occurred, we can now safely call CFHost getAddressing, knowing that the host already has the address available. So that will return immediately, providing the addresses that it just received. Your program will probably want to do something more specific or more sophisticated than this, but just to show you how we would get the information out, here we get the value of the first item in the array.
That's going to be CFDataRef. The CFData is essentially a sock adder, so CFData get byte pointer is going to give you a pointer to a sock adder. CFData get length will give you the length of that sock adder. You can pass it into any Unix call. Here I'm just calling connect to connect a socket.
Finally, we need to clean up. Any asynchronous process is going to require a little extra cleanup compared to simply releasing a reference. Basically three phases. First, unschedule it from the run loop. That makes sure that the run loop releases any reference it has on the host and that any data or state that's being maintained by the host object or by the run loop gets freed up. CF host set client null tells the host that you're done, you don't ever want to receive a callback again. And then finally, CF release releases your reference on the host object.
And that's what I wanted to say about those two basic CFNetworking tasks. Now we're running a little tight on time. So I'm going to fly through this next section, where we're going to talk about some of the other URL loading APIs on the system. The two other major URL loading APIs on a Mac OS X system are the Foundation URL loading pieces and URL Access Manager. The Foundation pieces are basically represented by these three classes, NSURL Request, Response, and Connection. URL Access Manager is the Carbon API that became available, I think, starting in 9, maybe a little before that, and is now deprecated. It has been for a couple years now.
So way back when I was talking about how CFNetwork is intended as a power API that exposes all of the pieces, all of the knobs and buttons on a given protocol. Foundation is the convenience API. Foundation provides a nice, smooth, easy API to allow you to download a URL just like a browser would. Whatever it is that a browser does in terms of cookies, authentication, caching, all of that magic is just done automatically at the Foundation level.
So there are two consequences of that. First, as you'd expect, at the foundation level, we have support for a lot more features. Caching cookie support, better authentication support in the sense that there will be some automatic credential management. The callbacks are a little cleaner at the foundation level than what you saw at the CFNetwork level. Becky Willrich But the other consequence is that there are a lot more constraints because the URL loading system has made some choices for you. You must use an asynchronous delegation model. You must use the threading model that Foundation dictates. And you must use Foundation scheduling policy.
So for instance, there's no way to say download these URLs on this thread and these others on another. There's no way to halt a download in progress. There's no way to give a particular URL download a higher priority than other ones. And then just as an added note, the foundation always uses the system proxies. You cannot do any custom proxy configuration at the foundation layer.
So how do you decide which one you're going to use? Well, use NSURL, use the foundation APIs if you need the additional features. But more generally, use NSURL if your task is really to download something pretty much exactly like a browser would. So the classic example for me of this is when you're downloading HTML mail. You're going to display it just the way you would expect a browser to display it. You want to download all the images exactly the same way, with the same cookies, same authentication model that a browser would use.
On the other hand, use CFNetwork if you don't want to link that high. You don't want the extra convenience and the extra pieces. You don't want to link foundation or Objective-C. You want fine-tuned control of the threading model. Or more generally, you're not doing a basic browser-like download.
So a classic example of that is you're downloading a software update. You're only going to download it once. You don't want it cached. You know there are no cookies involved. Whatever authentication needs to be performed, you already know all the details about it. You can use foundation to do that kind of a download if you want, but it's more work to go in and turn off each and every one of those things than to just start with CFNetwork and do the download from there.
So then the other API, URL Access Manager. You should never use URL Access Manager anymore. Really, it's dead. We've been saying that for at least two years now. And then I was thinking, well, OK, is there any time I would recommend that you use URL Access Manager? Well, if you still need compatibility with Mac OS 9, then you're stuck. Yeah, you're going to have to use URL Access Manager.
But to give you a little incentive to kind of migrate that old code off of URL Access Manager, I've listed here the major shortcomings of URL Access that have been fixed with CFNetwork and with Foundation, but will never be fixed in URL Access Manager. No authenticating proxies, no HTTPS proxies, tied to a couple obsolete APIs, the Thread Manager and FS specs, and no support never will be for IPv6.
So if I want to migrate from URL Access Manager to CFNetwork or Foundation, what do I need to do? Three basic entry points to URL Access Manager is URL Simple Download and URL Download. And then there's URL Open, which is the asynchronous API for URL Access Manager. If you're doing a download to memory, use these NSURL methods instead. If you're doing a download to file, use NSURL download. It's designed for that purpose.
You can use CFNetwork instead of NSURL. It takes a little more work. I listed NSURL as the first choice because that's the one-line replacement. Using CFNetwork is maybe 50, something like, depending on how sophisticated a download you're doing. And there is some good sample code available at developer.com. to show you how to do a CF network download.
URL open is considerably more sophisticated and provides a number of different flags. So what you're going to do if you're calling URL open depends on why you're using URL open. If the only reason you're using URL open is to get some asynchrony support, use NSURL connection instead. If, on the other hand, you need a high level of customization or detailed threading and buffer control, drop to CFNetwork. That'll give you the extra knobs you need to control the download.
That's all I want to say about those APIs. We are out of time, so I'm just going to point out that there are about a million references, a bunch of documentation and sample code available. All of this information is available in the disk image, so just get the disk image and you'll see all of this information replicated there.