Application Technologies • 59:17
Wondering how to integrate Bonjour or Soap into your Cocoa application? How about other Cocoa networking solutions and APIs? Learn which APIs are the right ones to use to optimize your Cocoa networking implementation.
Speakers: Chris Parker, Chris Kane
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning. Welcome to session 132, Implementing Networking Using the Cocoa Classes. My name is Chris Parker. I work on the Cocoa Frameworks Team in the foundation and mainly our foundation and core foundation. But we're going to be talking a lot today about working on networking in Cocoa. As a quick, just a quick show of hands, how many people have actually used like the NS stream classes or CF network, stuff like that? Okay. So some of this will be fairly comfortable for a lot of you. We'll talk today about a lot of the aspects of working with networking in Cocoa. So we'll talk about how to write some basic TCP servers and clients. Using Bonjour effectively, publishing on the net.
When to use what classes? Cocoa doesn't cover all of the networking stack, partly because there's a lot of stuff to control down there that's going to be particular to your application. So we'll show some basic examples on using CF socket and then wrapping that using NS stream. We'll show some examples later of NSURL and actually using a little bit of CF HTTP message to store some state in an example we'll do later. The examples that we'll build up will start fairly simple. We'll work all the way up through to an HTTP server example and then we'll do some more complicated client side stuff. So let's start with the really basic clients and servers.
The example code that's on your download image has a TCP server, a Cocoa Echo example that contains a TCP server, which is a simple code that's on your download image. It has a TCP server, a Cocoa Echo example, and a Cocoa Echo service. So it's a simple wrapper around the CF socket to listen for incoming connections and then it'll go ahead and publish a port both on IPv4 and V6. And this will advertise with Bonjour automatically and it's by default implemented through delegate-based connection handling. So when a connection comes in, the delegate will be told about it. But you can override that. It's designed to be subclassable. So you can use this in your own code.
The Cocoa Echo example is just a simple Echo service. So it's very similar to the one that we're using in the client. The server just subclasses TCP server and it manages connection pairs. So we'll show you how that looks in the code in a minute. And the client is just a simple GUI application. It has a browser for services and then we'll be able to click on some fields and send some text and get it back.
You'll notice some things in the code that these things do and don't do. So what they do is they offer services on both V4 and V6. And we'd like to encourage you to do this in your own programs as well, especially since the local network provides a lot of services. browsing the Discovery Bonjour stuff, all publishes addresses on both V4 and V6.
It'll show you how to publish the stuff with Bonjour and it actually handles events as asynchronous events. So we're not going to poll these streams for events. We're going to wait for the stream to tell us that something's happening and respond to those events. What they don't do is treat IPv4 and IPv6 differently. If you're listening on those endpoints, you're just going to be getting data, right? So whether it's a V4 address or a V6 address isn't terribly interesting but we're going to show you how to do both of that.
It doesn't show the UI for the Cocoa Echo example. It won't show the user host names or ports, right? So for Bonjour browsing, especially if I'm clicking on things, your user is mainly interested in being able to just connect to the service and use it rather than, "Oh, well, it's on this host and it's on this port and you have to connect to it in this way." No, they just want to click on it and start going. And I'm actually not resolving the discovered services. There's some new API in Tiger that lets me do that. So... You'll see in the Echo example that we use code from all the way down in BSD sockets up through into some stuff from Core Foundation.
We use very little in CF Network. We use a lot of stuff in Foundation and then the Cocoa Echo example wraps those classes. So we're using some stuff from BSD sockets. CF socket from Core Foundation we used to actually create things. And then we'll use NSInputStream and NSOutputStream as the primary way to talk to those services.
The Echo server, right, servers do two things pretty much. They set up to listen for incoming connections and they manage those connection pairs. So the Echo server, when it handles a new connection, it's going to get an incoming connection request. From there, it will create a pair of streams. It will create an input stream and an output stream to the remote side that's connecting and those will be put into a dictionary.
I'm just using a dictionary here in order to manage these connections. We'll show a new, different way to do that The Echo Server, once it handles data, this becomes the delegate of the streams. So every time an event happens, the delegate of the stream gets a callback on the stream handle event method. Okay, so incoming data comes in on the NS input stream for the source and we get stream handle event. And the Echo Server turns around and looks up the corresponding NS output stream and writes that data right out to the output stream. Okay.
Let's take a look at this in code. Could I demo one, please? I have the Echo server here, which if I blow this font size up, we'll see that it's just printing out a little diagnostic here that says it's starting the server on this port, but that's mainly for my convenience when I was debugging. If I run the Cocoa Echo client here, we'll see that the server is, the service is discovered as demo Echo service. And actually if I kill this here, I should see it disappear. So there's Bonjour running.
Fire that up again. So we have a new demo echo service. And when I click on it, if I just type up here in the top box, is there an echo in here? And here, return, I get that response back from the server. So it's a very simple example. Let me take a look at the code here.
The TCP server itself is pretty straightforward. We have a delegate object that it keeps track of, some state about what domain and name and type we're going to be using to publish this service, and we actually keep track of the V4 and V6 sockets as well. There are some basic accessors. The main event here is handle new connection from address.
Every time a new connection comes in, this method is going to get called with the input and output streams for that. We'll be able to use that as a subclassing point later on. By default, this is designed to hand a TCP server did receive connection from address input stream and output stream to the delegate. So let's take a look at where we listen for connections.
This start code here is where we actually set things up. So you'll see here at CF socket create, we're creating two sockets. One's a V4 socket, one's a V6 socket. We're passing the V4 and V6 INET families here as what we want to listen for. And we're telling the socket create call that we want to be able to get callbacks on the, for any accept events that come in.
And we'll be called back on this accept callback function. We try and handle some errors well, providing an NSError that can be bubbled up to the user. And we set some configuration options here. We set up the V4 endpoint. We're letting this, this has the option to let the kernel choose a port for us.
Once we get through all of this and get everything successfully set up, we'll actually do the same thing for the V6 endpoint. And this is all the same code. So you'll see some code that does some stuff for the V4 endpoint, some code that does some things, the same things for the V6 endpoint. And then once we get down here to a success state, we'll be able to get the V4 endpoint.
Once the two sources are set up, we schedule these on the run loop and then in the main program we'll actually start going on the run loop. If we were given a type, we'll go ahead and publish this on Bonjour and that's just a matter of making sure that we're publishing the correct domain with the right name and then we just alloc and init the net service and go ahead and tell it to publish. Okay, so the TCP server just starts all this stuff up. It's a fairly straightforward class. And if we stop the service, then we have to stop the net service, invalidate the sockets, and just basically clean up after ourselves.
Let's take a look at dispatching that callback. When we get the accept event, the TCP server accept callback is a function that gets called through CF as a result of the CF socket getting an accept event. And we just go through and set up some additional information to be able to pass it off to the client. You can go take a look at some of the documentation for things like get peer name and what this is doing.
And that's all on the developer CD. We create a pair with socket here with CF stream create pair with socket. But once these streams are created, we're going to take advantage of the toll-free bridge and start referring to those as NS input streams and NS output streams once this gets moving. So, let's see. And here is where this callback calls handle new connection from address. And this is where we can override. So if we look at that. This is pretty simple. Let me make this bigger. Get a little more text on the screen here.
All this does is if the delegate implements the appropriate method, we go ahead and call it. So this will be where we subclass things in the Echo server. So the TCP server class is just a basic wrapper for all the stuff that you have to do to get a server up and running. So let's take a look at the Cocoa Echo server here.
Just fire things up on the Echo server. We have a main function here, which just gives it a, we set up a name, a type, and we just ask it to start. So this is the TCP server start method that gets fired up. Whenever this gets a new connection, okay, we're actually overriding in Cocoa Echo server use the pop-up here.
"Handle new connection from address." So this is where we override this to do our own connection management, and I'm just creating a dictionary if it's not already there, and I put the--and I set up the input stream and the output stream. This method just goes ahead and it opens the streams up, schedules them on a run loop and it also sets this class as the delegate of these streams, okay? And that's all the setup input stream and output stream method does. But now every time an event occurs on one of these streams, this instance is going to get callbacks on the stream handle event method.
And that's, oddly enough, the next method in the example code. Whenever the input stream has bytes available, you get an NSStreamHasBytesAvailable event as the stream event parameter here to stream handle event. And all we do here is look up the corresponding output stream and then read in everything from the input stream because it has those bytes available. And we turn around and we just write them back out on the output stream.
And if the end is encountered, then we do some cleanup here. So every time I hit one of the, you know, when I type in the text and I hit return, the server is getting that data from the client and this is where it handles that. And in the client, the client code is this echo client app delegate.
So when things start up, we just start a browser going. And I'm using bindings here in order to get the stuff into the service list, so you can play around with that. But when a server is clicked, we actually use this-- when somebody clicks on a server in that table view, we use this selected service, get input stream, output stream.
So if anybody's looked at the picture sharing examples, when we wrote that, we had to go through and construct a file handle and hook it up on an FD and do all this stuff in order to be able to set up all the callbacks. This is a one-line method in order to get the two streams to the other end of this discovered service.
So you can just call that and get the streams immediately. And then we go ahead and open those streams. So and now, once again, we're seeing stream handle events. So we get the event in here when we get bytes available. We go ahead and read those in until we see a carriage return. And at that point, we get Create the string, set the string value, put it in that bottom field, and display the result to the user. And then if the remote side closes it, then we just clean up and close these streams up.
So the HTTP, the Cocoa Echo server rather, also does the sending the text when I hit Send Text Method. When I hit the return key in the top field, this actually just gathers up the string and writes everything to the output string. We actually sit there and write everything out until it's done.
And that's it for the TCP server. If I could have the slides back, please. So again, it's been a while since we've seen this slide, but what they do is they try very carefully to do things on both v4 and v6 and publish stuff on Bonjour so that your users can discover these things without having to go to too much trouble.
We're not blocking the run loop because we're going to handle events asynchronously, but they're also not doing specific things. And one of them is not resolving the discovered services, right? Causing too much network traffic is something that makes everybody's network administrator yell at them. So if you can do this without actually doing a full net service resolve on that, you can use the streams get net service code to do that. So we're going to use this code. We're going to use the TCP server code now to take a look at a basic HTTP server.
You want me to get that? It subclasses TCP Server. We're going to override the handle new connection method again. And we're going to do its own HTTP connection object. So rather than just using a dictionary to manage our connections, we'll use an actual full object that's going to become the delegate of these streams.
So every time a new HTTP request comes in, we'll get one of these new connection objects. And it inherits all the other TCP server behavior. So this will get published on Bonjour. We'll see that. It will do it on both v4 and v6. And you'll be able to connect that way as well. So every time one of these new connection comes in, we're going to look at an HTTP connection object. So the server fires up. It just listens for new connections.
A new connection comes in. We create an HTTP connection object. This will produce HTTP server request objects. And it will inform the delegate about them. So it has a delegate also. It's going to offer to send those responses over there. The responses are processed in the order that the requests were received.
Because that's what the protocol says we have to do. But they may not necessarily be the same. It may not necessarily be the order the delegate chooses to fulfill them. So there may be some additional tracking here. We have some default behavior built into this example. That's pretty nice.
Basic get method processing is done if no delegate is present. And that's the default behavior. So if you don't actually assign a delegate, we'll just do some basic fulfillment here. And this example actually, specifically the HTTP connection class, shows some more sophisticated asynchronous handling. And it involves buffering up the bytes that come in.
And we'll see that as well. The server now has... It is subclass TCP server. So we're not going to see a whole lot of API from the BSD sockets layer or from core foundation. But in CF network, we're going to be using CF HTTP message refs as just a way to store things in the server request objects. And this URL will be seen a little bit in order to set up the document root for the server. But it's not part of the networking code.
And the server just creates...uses all of those to wrap these three classes. So... When an HTTP server fires up, it's going to get a connection event. So it's going to get an incoming connection which consists of somebody typing in something into a browser and the browser is going to make the network request.
At that point, it creates an HTTP connection object. That HTTP connection object is handling all those bytes that are being presented to it as a request for, you know, a GET or a HEAD request from HTTP. When that request comes in, it's going to service all of those bytes, pick them up all off the wire, collect them into an HTTP server request object and send it off to the delegate if one's present.
If it isn't present, we'll actually do some default processing for this. So let's take a look at the code and do a quick demo. Close out our Cocoa Echo example here and kill that. So I fired up just a little Cocoa HTTP server here. And it says that it started on port 49167. So let's go over here to Safari and take a look here.
If there were a Bonjour listing here, you could click on it and you'd see the Cocoa HTTP server. You should be able to do that with the code that's on your example code here. But let's just take a quick look at how this works. If I go to localhost.com, We've configured the document root of this server to be root.
So we haven't implemented any security on this. So you can happily go ahead using this thing the way it's configured right now and get Etsy password off this computer. I'm not going to show you that. But I can look at Etsy host config, let's say. And we see that this actually does return that file.
Apparently we're starting up a bunch of things automatically. Let's take a look at the code. There are actually in http server.h three classes declared. The first is this http server class. All this is, it has a very small role in this code. It just fires up and listens for connections. You'll see here that we actually override the handle new connection method. We have a little set document root method here as well. That's how we configure it to point at a particular location on the hard drive. We look at the interface for http connection.
We've got some streams here. That's what's going to be doing the talking to the web browser or the client that's talking to us. We have some buffering going on, an input stream, an input buffer and an output buffer. This is where we're going to keep the bytes as we service them and then when events come in on the stream, that's when we'll actually touch off the reads and writes.
There's some other bookkeeping here, but we won't cover that. And this HTTP server request object, I'm not going to talk too much about this because this is basically just a model object to hold and encapsulate the request as it's being processed. So this will contain things like the body and the header and things like that for the actual handling of the HTTP messages. So these are what get handed off to the delegate Let's take a look at HTTP Server. We're going to handle new connections here.
and HTTP Server just by creating a new HTTP connection. Setting the connections delegate as the server's delegate. And then just saying if the delegate responds to the selector HTTP did make new connection, then we send that message off. But at this point we've got this HTTP connection around. It's going to take care of doing all the heavy lifting for us. So let's take a look at that.
The streams are the delegate here and when we get a stream handle event, the input stream will tell us, "Hey, I've got bytes available." A get request is coming in. So we go through and read as many bytes as we can from that and then just go in here to self-process incoming bytes. And all processing incoming bytes does is construct an HTTP server request object. So it's building up bytes as a request comes in. For a big request, it might come in in three or four reads.
So Process Incoming Bytes really just goes through and collects all of this information. So it collects the headers, the content length. If it's a post, it will actually collect all of that body information although we're not going to use that in this particular example. And then once we've got a full request, we create one of these server request objects and we hand it off to the delegate.
So the delegate is going to either get--if there is a delegate, it will get a chance to handle it. If not, we'll do this default request Okay, so we've gotten bytes, we've built up the server request object, we're going to hand it off to our default request handling method.
Which really just sort of crawls through the CFHGTV message ref and here's the interesting bit here where it's if method is equal to get or head. So we've made it, we had a get or a head request made of us. We grab all those bytes, copy some information out, set up the response and just try to write those bytes out. And that's actually, I'm going Let's see my note here. So the default request handling, that was this method here and then That just queues up all the bytes into a buffer.
So we've got them all set here, but we haven't actually put them back out to the outgoing wire. So Safari, the web browser that we're using, hasn't actually gotten a chance to see these bytes yet. When an output stream is available to write, it has space available, you'll get this has space available event on the stream handle event. And all that does is it calls process outgoing bytes.
Process outgoing bytes just goes through and says I've got all this data, write as big a chunk as I can and get out. So it may actually take two or three different writes to be able to get all this data out. So every time a new stream event comes in that says I've got bytes, I have space available, I'm going to go ahead and write those bytes out. So we buffer the stuff up. Every time a new event comes in, we just write as many bytes out as we can.
And that's really about it. We do some cleanup for when an error occurs or when the streams end, but it's pretty straightforward as far as the networking code goes. So there are things to keep in mind there about it. But most of the work is going to be touched off by stream events coming in. So if I could have the slides back, please.
This is not a complete HTTP server. No, you cannot try and serve your website off of this and expect it to survive a slash dotting. It hands off most of the processing to the delegate. So if there is a delegate, it can do most of the heavy lifting, populate the server request object, and then allow that to be sent back to the connection for processing. So it doesn't do things like, you know, it doesn't handle chunk transfer coding, so you can't do stuff in series. But if you do want to make this into the next Apache killer, first of all, more power to you. Knock yourselves out.
RFC 2616 describes the HTTP 1.1 protocol. So this server actually does do some Keepa lifestyle stuff. But this is, it's about as entertaining reading as you might expect, but it's where you have to go to do this. So at this point, I'd like to invite Doug Davidson up on stage to talk about updating your application. He's going to do some stuff for NSURL download.
Thank you, Chris. So what we've just seen covers how to handle general networking at a fairly low level. What I want to talk about is how Cocoa makes it easy to deal with some standard net protocols at a rather higher level. And naturally we have a sample application to demonstrate this.
And the problem that we've chosen to tackle is that of having an application check for updates to itself over the network and then download them. And we're not going to deal with all of that problem. We're mostly ignoring questions like when you schedule checking for updates and how you actually install the updated version once it arrives. We're really just going to deal with the networking aspects of this.
So what is the minimum that you need to do updating? Well, first of all, you have to start somewhere. So you're going to have to have some net address, some URL that's hard-coded into your application. And you're going to have to have a server living at that address for the lifetime of your app.
You will have to have built into your 1.0 version of the app some basic set of policies for when and how to update. You'll have to supply some version info so you can determine whether to update. If you use licensing, you provide licensing info as well possibly. And some user interface to present this to the user.
But of course, the critical question for this session is, what networking APIs are you going to use? As I said earlier, what we want is something that will allow us to deal with standard net protocols at a fairly high level. And what we're recommending here is the use of the NSURL loading classes in Foundation. These are the classes that WebKit uses when it loads a web page or other resource. So they're designed to deal with the network as a modern browser does with all the complexity that that entails.
But they make it simple because almost all of it will be dealt with automatically. You deal only with the parts you're specifically interested in. What are the classes I'm talking about? NSURL request, NSURL response, NSURL connection, and NSURL download. The protocols I handle, HTTP, HTTPS, FTP, a few other minor ones, but those are the major ones.
So you're probably already familiar with NSURL, which represents a general URL with all the various components that a URL can have. Well, NSUR-Request starts with a URL, but it also includes all the other information that might go into specifying a load request, a timeout interval, caching policy for HTTP requests in particular, the request method, the headers, the body. Now, as I said, most of this is going to be generated automatically for you, but there is a mutable subclass, NSMutableUrlRequest that lets you change any of this if you want to. I'm not going to show that the next portion of this talk will.
Then NSUR Response. That represents the metadata associated with a response to a load request. It might come from cache, it might come from the network. This is things like the URL that's finally loaded, the MIME type, content length, encoding, and so on and so forth. So that's the metadata. What about the data? The data is the responsibility of the class that actually does the loading. And there are two of those, NSURLConnection, and NSURLDownload.
Why two classes? Well, NSURLConnection corresponds to the standard browser operation of loading a web page or other resource into memory. NSURLDownload corresponds to the alternative operation of downloading a file to disk. They have a number of differences, some obvious and some subtle. NSURLConnection, as I say, downloads into memory. NSURLDownload to a file on disk. NSURLConnection can do caching.
NSURLDownload doesn't, but it has other features like the possibility of resumption of interrupted downloads. NSURLDownload is probably the best choice if you have huge amounts of data, for example. And there are some other minor differences you can look through the documentation for those. What I want to emphasize, though, is that they both operate fundamentally in the same way. And that is they message their delegate asynchronously for all the various things that may happen as a load request proceeds.
And the delegate responds to as few or as many of those as it wishes. So NSURL connection, for example, sends the data directly to the delegate. NSURL download sends the data to a file on disk and then tells the delegate about it. Your responsibility as a client of either of these classes is to implement a delegate that will handle whichever of the delegate methods you are interested in.
There are a number of other advanced topics that I'm not going to go into today. For those of you who are interested, there's another session after lunch, Best Practices in Networking, that will discuss some of the more interesting things that you may encounter in real-world networks. It's an excellent session. I highly recommend it.
So how is the sample application going to work? The way we've chosen to implement this is that the initial check to see if there are updates available is made with an NSURL connection using an HTTP URL. And we've decided to encode the version info for the current running version of the application in the query string of that URL. Then we send it to the server that way. The server then responds. It's a very simple response. If there is an update available, the response contains a URL from which the updated version is to be downloaded.
We present this to the user and ask if the user wishes to update. And if the user agrees, then we use an NSURL download to download the updated version of the application from that URL. It might be an HTTP URL. It might be an FTP. It's all handled the same Now, there's one thing I think I must mention, that is, if you are going to do something like this for real, you do have to take security into consideration.
My general recommendation is that this is usually best handled end to end, probably with some sort of digital signature so that you can make sure, however the update arrives on the machine, that you can verify it's authentic before you try to use it. Just as a caution. Now, let's confirm where we are on the stack. We're using foundation APIs to deal with the network, and then our application sits on top of those. So now let's take a look at some code. If we could go over to demo one.
It's very simple. There's only one class that's going to act as a delegate for both the URL connection and the URL download. Here's where we start off. Here's our one hard-coded URL. Did you notice that we're encoding in the query string the current version of the application to send it to the server that way? And when we start off, when the user chooses to check for updates, we first of all create a URL request with that URL. And then URL connection with that request. And then we fire it off.
Now, as I said, what's important are the delegate methods for the URL connection. And let's take a look at those. So the first one is the didReceiveResponse delegate method when initial response comes back. And we're just making a note of that and changing our status string. And then there's the did receive data method that will be sent when data arrives. Now as I said, the oral connection delivers the data directly to the delegate. We're the delegate. We're responsible for it. We hang on to it and we do it with immutable data to which we append it.
And then finally, one of two things will happen. The load will either succeed, finish loading, or it will fail, fail with error. And in either case, we're going to present that to the user. If it succeeded, then we're going to look at the response to the data we've accumulated and see if it does represent the URL from which we could download the update.
And if it did, we'll create that URL and then we'll go and ask the user whether they wish to download. And if they do, the process is very similar. We'll create a URL request with that URL. And then a URL download with that request and fire it off. And we set a location on the disk for that file to go.
And again, the important things are the delegate methods. Again, we have a did receive response delegate method. When the initial response is received, we'll make a note of that. And then, as I said, NSURLDownload sends the data to the file and disk and tells the delegate about it.
Now, ordinarily here in this, we have an indefinite progress indicator showing that something is going on, but while we're downloading the update, we want to present a definite progress indicator to show the user how it's progressing. So, the URL download just tells us how many bytes came in.
We keep track of that and we compare the total number of bytes that have come in to the expected length from the header, and that tells us the progress that we can set on our progress indicator. Then again, ultimately one of two things can happen. We either succeed or fail. And in either case we present that to the user. So let's give this a try. Here's our application.
Really, it does almost nothing but update itself. So in this case, we've set it up so the user chooses one to check for updates. So let's try it. We're connecting to our server. I have a bit of a slow server here. So there is an update available and the choice is presented. Shall we try to update? No, let's go for it. Okay. Connecting, it's trying again.
The response has been received, now it's getting data. As the bytes come in, we keep track of the number, we increment the progress indicator, and it proceeds up. finished. And actually, the thing that we downloaded here is a disk image. And so it's been opened for us. And so let's quit this version of the application.
And Go and look for version two. Let's try launching it. Oh, there it is. Version two, we've succeeded. Okay, so let's go back to the slides. Let me summarize. The NSUR Loading Classes Foundation make it easy for you to deal with some standard network protocols at a fairly high level. NSUR Connection, NSUR Download.
Your responsibility as a client of either of these classes is to implement a delegate that will handle as few or as many of the delegate methods as you wish. So now I'd like to turn the stage over to Chris Kane, who's going to talk to you about Soap in the final portion. Thank you, Doug.
Soap. Buzzword probably many of you have heard over the last few years. Soap is a framework for exchanging messages on the Internet. That's all it is. Now I'm using the word framework here in a way that we're not typically using it here at this conference. When we talk about a framework, we talk about a library and its APIs.
In the case I'm using it here, framework means the W3C has defined a spec which lays out the ground rules by which a sender of a message and a receiver of a message must follow in order to be able to communicate back and forth. So that's the sense in which I'm using framework here.
Now, I don't have nearly enough time to go through and actually explain Soap. We would need probably several hours. And frankly, it's pretty boring stuff to get down into all the details. So I'm going to be covering things at a very sort of fast and high level here right now.
So, Soap is a type of message exchange protocol. And the typical use you find today on the Internet is that a web service will expose its various functionalities as Soap messages which you can send to the service in order to invoke the service and produce some result. For example, Google.com. This web search site offers a Soap API which you can use to send a message to Google and do a search just like you would by say going to Google.com in Safari.
Now, we could put Soap underneath distributed objects in Cocoa. You know, one could implement using the DO transport layer APIs, Soap as a transport layer. But in fact, that would be a lot of work and really wouldn't buy you very much. So I'm not going to be showing you that today.
And of course, one can use the Web Services Core APIs in the Core Services Framework. And those are there, you know, fully realized in Panther and Tiger, introduced originally in Jaguar. So, you know, if you're doing Soap things, one might typically go and look at those first in order to implement their Soap thing. But I'm not going to use those either because those have been shown before and there's plenty of documentation on the web for those.
Now, SOAP via HTTP and using XML is what one may typically think of as SOAP. If you have experience with the SOAP protocol, that's what you may think of SOAP as. A blob of XML sent over HTTP and you get an HTTP response back with another blob of XML, which is the SOAP response. But in fact, SOAP is a very open and flexible API and this is probably because it was trying to serve many different masters and a big huge committee of people who all wanted it to do various different things and so they had to make it very open.
And the end result is that, no, XML is, you know, the text version of XML, XML 1.0, is not actually required for Soap. You can use a binary form of XML, for example. HTTP is not required to do Soap messaging. You can use some other protocol or just be writing raw XML bytes on a socket, for example.
So, the, as a client of a web service, the impact of this is that, well, you just have to do whatever the web service has laid out as, here's how you contact us. The Google web service does use XML 1.0 format messages, and we'll see an example of that, over HTTP. But a web service could decide that it wants to do something else. And as a client, you simply have to do whatever the web service has laid out as the rules for contacting and using the web service.
In our particular example today, we're going to simply cover Soap Messaging with Objective-C. That is, I'm going to wrap the Soap Messaging in an Objective-C class. On the server side, if we can go to the demo machine, I'm not going to actually show you--oh wait, no, let me go back to the slides.
I forgot to explain this. I'm not going to actually show you the server side of things today, but all I did was reuse the HTTP server example. On the client side, as you can see in the slides, all I have is a simple SoapAdder class, and the reason for that name will become clear in a moment. And all it does is use the NSXMLDocument class to actually parse and serialize the XML that comprises the Soap message. And it uses the NSURL connection and its related classes to do the HTTP component of the messaging. Now if we could go to the demo machine.
I called it the Soap Adder Class because, in fact, all it does, this example does, is take two arguments, two numbers, send them off to the server and all the service does is add the two numbers and return the result. So it's an extremely trivial activity. What I'm going to show you, I'm going to try to go through the code in the order in which the execution proceeds here. So we begin with the client program in main.
Well the first thing I do is I make sure I have enough arguments on the command line. This is a very trivial tool. But the interesting stuff begins here. I create my SoapAdder class, an instance of my SoapAdder class, and Parse the arguments off the command line. Call the method add to result in error, which is the method I created to encapsulate the logic of sending the soap message off and getting the reply back from the server.
So at this point, the main function is a client of this API and to the client, the client doesn't know where this operation, this add operation is going to go on. All that logic is encapsulated and hidden off in the Soap Adder class. If I get success back from this method, that is I get a response, I simply log it out. Otherwise I log the error out and then the program exits. So there's really nothing much to that.
The Soap Adder Class is very straightforward. Again, nothing much to it either. I take the two arguments, which I decided to call doubles, and I return a double by reference. I return a Boolean indicating success or failure. And if there is a failure, that is if the return value is no, then there will be an NSError returned by reference in the last out parameter.
What do I do in SoapAddr? Well, the only thing I did in the client was I created a SoapAddr and then I sent it the add method. When I create a Soap Adder, I need to look up the web service or, you know, get access to the web service.
Now, normally, for, say, using the Google web service, you wouldn't be using Bonjour to look up, you know, Google. You'd just simply know, oh, I need to use this particular URL. In this particular case, I'm going to start my server on the local machine, and I need to get the port number and the address of that to construct a NSURL that I can use with NSURL connection.
So I'm not going to go through the details of, you know, resolving the net, creating and resolving the net service here. I'm just going to skip quickly through that. And the main point is that the main product of the init method is simply to create the URL with the host name and port number inside of it. The Add Method is where the interesting stuff, if there's anything interesting here, where the interesting stuff goes on.
I'm using a very, very simple technique here. I'm going to marshal my own arguments. I'm going to send the message off on the Internet. I'm going to unpack then the arguments, the result when it comes back. So I'm simply using a template string here to marshal my arguments into a Soap compatible message.
And so, you know, I don't have time to go through all the details of what comprises a Soap compatible message. But the main point here is that here is the body, the actual goodies in the message. And what I'm going to do is I'm going to invoke the method name, add service on my particular server and pass it to parameters.
The %f's then are going to be substituted with those two double arguments that I passed to the add method. I'm using an NSXML document here to simply How to serialize the XML into the NNS data, which I'm going to write out then as part of the HTTP request.
So I created an NSMutable URL request and I set its method to post. I want to include a body, which is the Soap message in this case, with the request. So I need to use the post method. I set the body, which is my data. That's what I got back from the NSXML document. And because there's a body on the HTTP request, I need to make sure that there's a content length header field set properly with the length of the data on the NSMutable URL request. So I do that here.
Then I create an NSURL connection. Actually, I'm using a little tiny subclass with some convenience methods I added. But basically this is an NSURL connection. I create it with the request. I wait until it has a response. Creating the NSURL request sends the request off as part of creating the NSURL connection. I'm waiting for the response, and this is one of the little convenience methods that I've added. Running the run loop while I wait.
When I get a response back, then I use NSXML document to parse out the data for me. Use XPath to go and retrieve the particular node out of the XML that I want. In this case, it's a node buried three levels deep called EX_result. And the result simply contains a string which is the added number.
This is a string representation of the added number. So when I get a node back from the XPath successfully, I go and get its object value and get this double value out of that string. So let me, well let's actually take a look here. We've got a little bit of time. Let's take a look here at my URL connection.
So what do I do here? Well, basically, my URL connection, I just created a little subclass in order to collect the data for me and keep track of when I get a response. So what do I do here? You saw in the previous segment that Doug did the delegate methods of NSURL connection being implemented.
Well, my little subclass implements the delegate methods for me. And as data blocks come in here with the did receive data delegate method, I simply append them, accumulate them in an NSMutable data in memory. When I get the did finish loading method, delegate method, then what I do is I just set my finished flag to yes. So then has response will return yes. Oh yeah, where is terminal? So let's go over to my project here. I've got a example already built. Let's blow this up.
So I'm simply going to start my server. Now my server, I just took the HTTP server example that you saw half an hour ago, and I slapped a little, you know, Soap server on top, which all it does is use NSXML document to parse the data in the Soap message, add the two numbers, and create the Soap response.
Let's see, what did I do wrong here? Oh, I didn't see the-- Okay, so server started up and client program, let's add 2.3 and 3.25 and comes back with 5.55, the sum of the two numbers. Nothing very interesting in that. In fact, I've just spent now, what, a few million cycles to compute what normally you can do in about 10 or so, add two floating point numbers.
And, you know, obviously that's not a very compelling example of a web service in and of itself. But, you know, serves for the purposes of my example because it's very simple and straightforward. Let me just quickly look at, let's see, do I have it here? Yes. Do Google search.
Here's an example of the Soap, a Soap message which is invoking the Google Web Search service. Here we have the body, again, of the message. And so this is the, you know, sort of method invocation, if you will. And the method name, you know, or the function that you're trying to invoke here is the Do Google Search method. Here are the parameters. And of course, you know, they've named the parameters very mnemonically, you know, key and queue and, you know, who knows what LR and IER and so on.
That's all part of their documentation. You simply have to, you know, provide the data they want in the, you know, format that they want. But this would be, you know, this is fairly straightforward. Again, you can see where you would just substitute the parameters in here that you wanted to search for in, you know, the same kind of way that I did it, if you were just trying to be fast and loose and quick and dirty about it, as I was. So let's go back to the slides.
So, you know, obviously my example could be abstracted further and generalized further. I just had the one method, very uninteresting, Soap Service. Nothing much to that. I used a technique, the marshalling my own arguments and parsing the return values, right out of the 1980s. There's nothing interesting in that. I didn't use XDR, and I'm not using SunRPC here. I'm using XML and Soap, which are technologies of the 21st century, but otherwise. It's basically the same kind of thing that one has seen for the last two decades. It's just in new clothing.
The key point though is that it's really the underlying technologies that help me do this that eliminated most of my work. If I didn't have NSXML document to do that, if I had to go down to an XML parser API like a SACS parser API like NSXML parser and do all the XML parsing myself, would I have done that example the way I did it? No, I wouldn't have done that because it would have been a lot of work. I would have done something else like use the Web Services Core API which does the XML parsing and takes care of all those details for you.
Of course, the Web Services API, if you're familiar with it, will also generate stubs for you in C and Objective-C. I'm not sure if it generates any C++ stubs today, but it'll generate stubs for you from the so-called WSDL. You can use the WSDL descriptions of Web Services if you have such a description.
[Transcript missing]