TweetFollow Us on Twitter

Using Newton Internet Enabler to Create a Web Server

Using Newton Internet Enabler to Create a Web Server

Ray Rischpater

With Newton Internet Enabler (NIE), a whole host of TCP/IP-based applications become possible on the Newton. Internet clients for e-mail, Web browsers, and other common services are springing up, and developers are using NIE to provide portable access to legacy systems and databases. But how do you get started? This article will explore the details of using NIE by presenting a sample Internet application -- a working Web server.

The long-awaited Newton Internet Enabler (NIE for short) opens up the Internet to Newton users -- the personal communications platform can now speak TCP/IP to the rest of the world. With so many possible applications that could use it, programming under NIE is not only useful, it's sure to be a lot of fun!

I've chosen to introduce NIE by looking at a sample application called nHTTPd -- a Newton-based Web server that implements the HTTP protocol. Throughout the article, I'll assume you're familiar with the basics of Newton software development and TCP/IP; see the Newton Programmer's Guide and the NIE documentation for reference.

SAMPLE APPLICATION BASICS

Before delving into the sample application, let's take a moment to see what's included in NIE. NIE provides support for three distinct entities. There's a Link Controller for sharing an underlying link (that is, the low-level PPP or SLIP connection) between multiple applications and performing expect-response scripting. It also provides a Domain Name Service (DNS) to map back and forth between host names and IP addresses. Finally, of course, there's an implementation of TCP and UDP through NewtonScript's endpoints. For details of how the different parts of NIE work, see "NIE in a Nutshell."


    NIE IN A NUTSHELL

    NIE's Link Controller is responsible for establishing the link and passing a prepared link to the SLIP or PPP manager for the TCP/IP stack to use. In NIE 1.0, authentication is necessary before the stream is switched to PPP -- there is no support for PAP or CHAP. The only Internet links supported in NIE 1.0 are PPP or SLIP over serial or modem links. (NIE 1.1 adds support for PAP, CHAP, and interactive authentication.) There's a setup utility for users to enter information about their Internet service provider, such as the access number, link-level protocol, and a connection script.

    The NIE 1.0 DNS is admittedly austere. You can map host names to IP addresses and the reverse, or you can look up a mail server for a particular host, but less common kinds of queries are not supported. Name resolution is nonrecursive -- NIE won't resubmit queries based on earlier responses. Mappings are cached for a period of time to minimize repeated network queries. If your application requires more general domain name functionality, you'll find yourself writing your own, but for most applications this shouldn't be necessary.

    Access to TCP or UDP sockets is available through the standard endpoint API, with new options to support the different things you may want to do with such an endpoint.


Our sample application (which accompanies this article on this issue's CD and develop's Web site) is a bare-bones HTTP version 0.9 server. To help you understand the code, here are the basics of the HTTP 0.9 protocol:
  • Objects (usually Web pages) are referred to by a URL, which is a concatenation of the protocol being used (http), the host name serving the document (such as www.lothlorien.com), and the path and filename of the document being served (such as "/" for the root document). A typical URL is http://www.lothlorien.com/.
  • Objects are fetched with the GET instruction. A request comes in containing the word GET followed by the partial URL (the path and filename, but not the protocol or host name) of the object being fetched, followed by a carriage return and linefeed pair. For example:

    GET /

  • Form data can be retrieved with an extension of the URL: the URL is followed by a question mark and some text. Within this text, field names may be declared to the left of an equal sign (=), with the data in the field to the right of the equal sign, although data is not required. Multiple field declarations are separated by an ampersand (&). A form with two fields might look like this:

    GET /myapp/search.html?field1=hello&field2=world

  • In response, the HTTP server dynamically creates a response object that's returned to the client.

    For the latest version of the sample application, check out the latest CD and develop's Web site, as this code is perpetually evolving.*

HANDLING REQUESTS

Since the Newton doesn't have conventional directories or files, we need to make some compromises to translate directory- and file-oriented specifications into something more meaningful on a Newton. My implementation uses a registry to correlate Newton data (such as application soup entries) with translators capable of creating Web objects from Newton data. These translators convert frames or soup entries to HTML or pure text, and nHTTPd has an API with three methods to allow other programs to register as translators:
  • nHTTPd:RegTranslator(inAppSym, inPathStr, inCallbackSpec) registers the callback specification frame inCallbackSpec for your application (whose application symbol is inAppSym) and the partial URL path indicated by inPathStr. (More about the callback specification frame in a moment.) It's strongly urged that you use a path beginning with your application symbol to prevent collisions in the path name space.

  • nHTTPd:UnRegTranslator(inAppSym, inPathStr) removes the callback specification frame for the partial URL path indicated by inPathStr.

  • nHTTPd:UnRegTranslators(inAppSym) removes all registered translators for the application whose symbol is inAppSym.
The callback specification frame provides a mechanism for nHTTPd to invoke a translator and has the same format as a regular communications callback specification. The only required slot is a CompletionScript slot (since all calls are asynchronous), containing a function that is passed, in order, these three arguments:
  • inEp, the endpoint associated with the request

  • inData, a frame containing the data received from the client

  • inErr, an error code, or nil if the request began with no errors
The inData frame has at least four slots:
  • raw is the original partial URL.

  • data is the partial URL without the path.

  • path is the path of the partial URL without the filename.

  • tag is the partial URL with neither path nor any form data.
A form slot, if present, contains slots named for fields in the request; each slot contains a string bearing the value of the named field. Listing 1 shows what our second GET example above would expand to.


Listing 1. A processed URL frame

{
   raw: "/myApp/search.html?field1=hello&field2=world",
   data: "search.html?field1=hello&field2=world",
   path: "myApp",
   tag: "search.html",
   form: {
      field1: "hello",
      field2: "world"
   }
}

We'll probably also want to include a _parent slot in the callback specification, so that it can inherit from a useful context within the translator (see Listing 2).


Listing 2. The translator callback specification frame

{
   _parent: self,
   CompletionScript := func(inEp, inData, inErr) 
   begin
      inEp:Output("Why did you want " & inData.tag & "?", nil, {
         async: true,
         completionScript: func(inEp, inOpt, inErr)
         begin
            // Do something useful!
            ...
         end,
         }
      );
      inEp:Close();
   end,
}

OUR STATE MACHINE

Like most communications programs, our application can be represented as a state machine. The application can be in one of a finite number of states: while in one state, the application waits for an event, and the functionality of the program is encapsulated in the actions performed during a state change. The nHTTPd state machine is shown in Figure 1. The arrows between states indicate transitions -- where the program actually does something. For clarity, my implementation of this state machine uses symbols to denote each state, but using integer constants would save memory.

Figure 1. The nHTTPd server's state machine

Using a state machine provides many advantages during application development. Primarily, it's an organizational tool, allowing us to accurately plan and visualize what's going on inside our application. There's something very soothing (even if you're as bad a mechanic as I am) in imagining an application running as a set of gears clanking around, and it's a lot easier to follow than a spaghetti of method calls. There's also the benefit that most existing protocols are specified and implemented as state machines, which makes porting somebody else's code a little easier. While the Newton is generally a difficult target for porting, porting from a state machine is significantly easier than porting from most other kinds of implementation.

Our state machine is a little odd, because there are really two separate machines running at once. We use a protoTCPServer (discussed in detail later) to manage the Internet link and provide endpoints listening on port 80 (the standard HTTP server port number). This state machine invokes an instance of protoHTTPServer, which itself is a smaller state machine that invokes your application.

GETTING A REQUEST

nHTTPd's protoTCPServer deals with all the details of establishing a link, as well as instantiating and listening on a TCP socket. The protoTCPServer has two methods:
  • Start requests the link, instantiates an endpoint, and awaits a connection. When a connection is made, it's accepted, and the callback specification's CompletionScript is called with the resulting endpoint. The callback passed to Start is invoked whenever nHTTPd receives a connection on the monitored port.

  • Stop cancels any open endpoints, disconnects and destroys them, and releases the open link. When we're done with the protoTCPServer, we use the Stop method to shut down all pending connections and close down the link. The callback for Stop is called when the teardown is complete and the link is released.

USING THE LINK CONTROLLER

Looking at the Start method gives a good idea of what's involved in using the Link Controller for the average application. The Link Controller APIs are all asynchronous, so as part of the arguments you must provide a context frame and the symbol of the completion script to be invoked (we'll see the details in a bit). Generally, you'll perform the following steps:
  1. Your application will give the user a chance to configure the link by calling InetOpenConnectionSlip.

  2. Your application will call InetGrabLink to request a link from the system.

  3. Periodically as the link is established, the callback you provided to InetGrabLink will be invoked with status information. In your callback, you'll call InetDisplayStatus to notify the user of the progress of the link establishment.

  4. Once the link is established, you'll use NIE endpoints as you would any other endpoint. You can also call InetGetLinkStatus to determine the current status of the link, to help determine when you're ready to begin using an NIE endpoint.

  5. When you're through with the link, you'll call InetReleaseLink to end the connection.
The InetOpenConnectionSlip method presents the user with a slip for selecting an Internet service provider if no link is currently active. You pass three arguments to InetOpenConnectionSlip: the initial ID of the link to be offered (or nil to have the system select one for you), the frame to receive the callback message, and the name of the slot in the frame that contains the callback method to be invoked.

Once a selection is made, the callback is invoked with a single argument, either nil (indicating that no connection is to be made) or 'connect. If a link is active, the slip is not displayed, the existing link is used, and the callback is immediately invoked with the 'connect symbol. Listing 3 shows how we present the slip along with a callback method to handle the user's choice (there are several callbacks -- one in the listing for the user response, one passed to Start to be called in response to an incoming connection, and one to be called during link acquisition, as shown later in Listing 4).


Listing 3. Choosing a connection and handling the user's choice

Start := func(inCallbackSpec)
begin
   if fState then return;
   fState := 'choosing;
   InetOpenConnectionSlip(nil, self, 'mConnectSlipCb);
   fCallbackSpecs := Clone(kProtoCallbacks);
   fCallbackSpecs.fStart := inCallbackSpec;
end;

mConnectSlipCb := func(inAction)
begin
   if inAction = 'connect then
   begin
      fState := 'linking;
      fStatusView := BuildContext({_proto: 
         GetLayout("protoInetStatusTemplate")});
      fStatusView:Open();
      InetGrabLink(nil, self, 'mGrabCb);
   end
   else fState := nil;
end;

Once the user has selected the preferred link mechanism, a status slip is created and presented to the user. The nHTTPd status slip is based on the Newton DTS sample Internet status slip, showing only a text view indicating the current status, along with a Stop button and a close box. Your status slip can contain anything you find appropriate, as long as it inherits from the protoInetStatusTemplate. Optionally, NIE can create a default status view for your application.

With the status view built and displayed, call InetGrabLink to request the link that was indicated by the user. InetGrabLink uses the same arguments as InetOpenConnectionSlip, although the callback itself takes three arguments: the link ID being used, the current status of the link as an NIE status frame (the only slot you need to worry about is the linkStatus slot), and a result code (which is nil for a successfully established link).

The process of acquiring a link can take a relatively long period of time, since the device may have to dial a modem and then execute a chat script to establish a PPP or SLIP stream before being able to conclude successfully. To keep your application informed, it invokes a progress callback periodically. In your callback, you can use the function InetDisplayStatus to keep the user apprised of status (see Listing 4).


Listing 4. The InetGrabLink status callback

mGrabCb := func(inLinkID, inStat, inErr)
begin
   fLinkID := inLinkID;
   if inErr then
   begin
      :mNotifyError("InetGrabLink", inErr);
      fState := nil;
   end
   else 
   begin
      InetDisplayStatus(inLinkID, fStatusView, inStat);
      if inStat.linkStatus = 'connected then
      begin
         fState := 'linked;
         InetDisplayStatus(inLinkID, fStatusView, nil);
         fStatusView := nil;
         :mInstantiateAndBind();
      end;
   end;
end

InetDisplayStatus is a versatile function; depending on its arguments, it can do many things. You pass the current link ID, a reference to your status view, and a reference to the status frame from your callback. In turn, the function will show or hide the status dialog or update the status indicator with the text of the current status as necessary. Table 1 shows the relationship between the arguments to InetDisplayStatus and the resulting behavior.


Table 1. InetDisplayStatus arguments and results

Second ArgumentThird ArgumentResult
(view status)(status)
nilnilReturns a reference to a default status view, and opens the view for you
------
Template ofStatus frameUses the template as the status view
a status view(non-nil)and displays the status
------
A currently shownnilCloses and destroys
status templatethe status view

Note that InetDisplayStatus can even create a status view for you, if you're so inclined. This status view is built on the protoInetStatusView and provides text indications of status to the user.

USING ENDPOINTS

Once the link is established, you're free to use endpoints to initiate TCP/IP connections. Our application uses an array of endpoints to service incoming requests; one endpoint is always listening for new connections, while other endpoints may be open responding to existing requests.

Our endpoints begin their life in the method mInstantiateAndBind, shown in Listing 5.


Listing 5. Creating and binding an endpoint

mInstantiateAndBind := func()
begin
   local myEp; 
   if NOT fEndpoints then
      fEndpoints := [];
   myEp := {
      _proto: protoBasicEndpoint,
      _parent: self,
      fState: nil, 
      ExceptionHandler: func(inExp)
         begin
            local myErr;
            if HasSlot(inExp, 'data) then
               myErr := inExp.data;
            if myErr <> kCancellationException then
               :Notify(kNotifyAlert, kAppName,
                  "Something bad happened - " & myErr);
            :mTearDown(self);
         end,
      EventHandler: func(inEvent)
         begin
            if inEvent.eventCode = kCommToolEventDisconnected then
            begin
               :Notify(kNotifyAlert, kAppName,
                  "The other side has disconnected." & myErr);
               :mTearDown(self);
            end
            else
               print("Unexpected event(" & inEvent.eventCode & ")");
         end,
   };
   AddArraySlot(fEndpoints, myEp);
   try
      myErr := myEp:Instantiate(myEp, call kGetEndpointConfigOptions
                  with (fLinkID, kTCP));
   onexception |evt| do
   begin
      :mNotifyError("Instantiate", 0);
      return;
   end;
   if myErr then
   begin
      :mNotifyError("Instantiate", myErr);
      return;
   end;
   myEp.fState := 'instantiated;
   try
      myErr := myEp:Bind(
         call kINetBindOptions with (0, fPort), {
            _parent: self, 
            async: true, 
            CompletionScript: mBindCb,
         }
      );
   onexception |evt| do
   begin
      :mNotifyError("Bind", 0);
      :mTearDown(myEp);     
      return;
   end;
   if myErr then
   begin
      :mNotifyError("Bind", myErr);
      :mTearDown(nyEp);     
      return;
   end;
end

Note that setting up your endpoint works just as you'd expect. In addition, there's a host of compile-time utility functions that aren't officially supported but they're useful enough that I've included them with the program and summarized some of them in Table 2.


Table 2. NIE constant functions defined by include files

FunctionArgumentsPurpose
kNumToHostAddrinAddrArrTurns an IP address into a string in the form "a.b.c.d"
kHostAddrToNuminAddrStrTurns a string address in the form "a.b.c.d" into the 4-byteIP address
kGetEndpointConfigOptionsinLinkID, inProtocolCreates an options array for use when an NIE endpoint is instantiated
kINetBindOptionsinLocalPort, inUseDefaultPortCreates an options array for use when an NIE endpoint is bound
kTCPConnectOptionsinRemoteAddrArr, inRemotePortCreates an options array for use when an NIE TCP endpoint is connected
kUDPPutBytesOptionsinAddrArr, inPortCreates an options array for use with a UDP endpoint when data is to be sent

The function kGetEndpointConfigOptions generates the options array necessary to use the NIE endpoint. There are options to request NIE (the inet option), specify the link kind (the ilid option), and indicate the desired transport (a flag indicating TCP or UDP is passed with the itsv option).

When binding your endpoint, you'll have a little more to consider. At the time your endpoint is bound, you'll pass different options depending on whether your endpoint is going to be listening (inbound) or connecting (outbound), and whether the endpoint is UDP or TCP. If you're using a UDP endpoint, or if your TCP endpoint is inbound, you'll need to specify a local port number. If your endpoint is an outbound TCP connection, NIE provides a randomly assigned port number for your endpoint. In the event that you need to set an option, you can use the function kINetBindOptions.

When would you want to specify a port? Most standard Internet applications have the notion of a reserved, or well-known, port. The server listens on the well-known port and clients specify that port as the destination port. Clients are free to use a random port for the source.

    If you're writing intranet applications with dedicated servers, be sure that the port numbers you pick do not conflict with well-known ports. An index of well-known ports is available from Internet RFC (Request for Comments) number 1700. In general, ports numbered below 1024 are reserved as well-known ports.*
The function in Listing 6 creates an ilpt option frame containing the desired local port. We're invoking Bind asynchronously, and when it's complete, notification is achieved through the invocation of mBindCb.


Listing 6. Completing a Bind call

mBindCb := func(inEp, inOpt, inRes)
begin
   if inRes then
   begin
      :mTearDown(inEp);
      if inRes <> kCancellationException then
         :mNotifyError(inRes);
   end
   else 
   begin
      inEp.fState := 'bound;
      try
         inEp:Listen(nil, {
               _parent: self, 
               async: true, 
               CompletionScript: mListenCb,
            }
         );
      onexception |evt| do
      begin
         :mNotifyError("Listen", 0);
         :mTearDown(inEp);      
         return;
      end;
   end;
end;

From this point, what you do depends on whether you're expecting an incoming connection or initiating an outgoing request. Since I'm doing the former, I call my endpoint's Listen method, which instructs NIE to wait until an incoming request occurs. If you were calling Connect on a TCP socket to initiate an outgoing connection, this would be where you specify the destination address and port using the irts option -- you can programmatically generate the appropriate arguments with a call to kTCPConnectOptions. In our case, the Newton waits until an incoming request is detected, and then notifies the application by calling the completion script originally passed to the Listen function (see Listing 7).


Listing 7. Processing an incoming request

mListenCb := func(inEp, inOpt, inRes)
begin
   if inRes then
   begin
      :mTearDown(inEp);
      if inRes <> kCancellationException then
         :mNotifyError(inRes);
   end
   else 
   begin
      inEp.fState := 'listening;
      inEp:Accept(nil, {
         _parent: self, 
         async: true, 
         CompletionScript: mAcceptCb,
      });
   end;
end

The way we've written it, nHTTPd is an indiscriminate server -- we accept connections from anywhere. If you wanted to do access control, or log where incoming connections were from, you could do it before we return control to the protoTCPServer's callback in mAcceptCb, shown in Listing 8.


Listing 8. Accepting an incoming request

mAcceptCb := func(inEp, inOpt, inRes)
begin
   if kDebugOn then print("mAcceptCb");
    
   if inRes then
   begin
      :mTearDown(inEp);
      if inRes <> kCancellationException then
         :mNotifyError(inRes);
   end
   else 
   begin
      inEp.fState := 'connected;
      inEp.Close := func()
      begin
         if kDebugOn then print("Close");
         :mTearDown(inEp);
      end;
      fCallbackSpecs.fStart:?CompletionScript(inEp, nil, nil);
   end;
end

At this point, control is passed to the client of the protoTCPServer, and the request phase of the connection has begun.

That's the basics of the application -- creating endpoints, accepting configuration from the user, waiting for requests, and accepting remote connections. We still need to read serial data to process URLs, do something useful with them, and write out response data. But first, let's do some more work with the DNS.

USING THE DOMAIN NAME SERVICE

For nHTTPd to be truly useful, it needs to track and log the host names generating incoming requests. We could simply log the IP addresses of incoming hosts, but these would mean little to the user. Instead, nHTTPd uses the DNS of NIE to ascertain the host name of incoming queries.

The DNS, like the Link Controller, uses asynchronous calls to do its work. You'll pass information to one of the DNS functions and receive a response through a completion script. The DNS provides several global functions for your use:

  • DNSGetAddressFromName, which converts a host name string to its IP equivalent

  • DNSGetMailAddressFromName, which discovers the IP address for a mail server given a host name in a particular domain

  • DNSGetMailServerFromName, which returns the name of the mail server given a host name

  • DNSGetNameFromAddress, which returns a host name string given the IP address

  • DNSCancelRequests, which cancels all requests associated with a specific context

The first four functions take three arguments: the key for the data to be retrieved, the client context, and a symbol denoting the callback. The cancellation function takes only a client context and a callback symbol. For all but the last function, the callback receives a results array and result code. The results array contains result frames, each with one or more of the following slots:

  • type, the result type (kDNSAddressType or kDNSDomainType)

  • resultDomainName, a string containing the resulting domain name

  • resultIPAddress, an array containing the 4-byte IP address
Listing 9 shows a code fragment that determines the host name for a particular IP address and logs it to a soup.


Listing 9. Translating a name to an address and logging it

DNSGetAddressFromName(fDestAddr, self, 'mDNSResultCb);

mDNSResultCb := func(inResultArr, inResultCode)
begin
   if inResultCode then 
   begin
      // Rats. It failed.
      print("DNS lookup failed because of " && inResultCode);
   end
   else
   begin
      local myAddrFrame;
      local myHandyAddresses := foreach myAddrFrame in inResultArr
                              collect myAddrFrame.resultDomainName;
      fLogSoup:AddToDefaultStoreXmit( {
            hostnames: myHandyAddresses,
            request: fRequestString 
         },
         kAppSymbol);
   end;
end;

RECEIVING INPUT

Now that we have a connection and have logged the domain it came from, we need to do the work of reading the URL request. Getting data into an application is done as with any other endpoint: set up an input specification and wait for your data to come to you. The input specification used by nHTTPd is simple, as shown in Listing 10.


Listing 10. The input specification

local myInputSpec := {
   form: 'string,
   termination: { endSequence: unicodeCR },
   filter: {
      byteproxy: [
         { byte: kUnicodeBS, proxy: nil },
         { byte: kUnicodeLF, proxy: nil },
      ]
   },
   inputScript: func(inEp, inData, inTerm, inOpt)
   begin
      local myResult, myClient;
    
      // Optimization - why bother if nobody's registered?
      if Length(fRegistry) = 0 then
         :mReportBogusAndClose(myEp);

      if StrLen(inData) = 0 then return;
      // Change stuff like %20 to ' '.
      :mFilterPercentEscapes(inData);
      // Break out the data frame for the application callback.
      myResult := :mCreateArguments(inData); 
      foreach myClient in fRegistry do
         if StrEqual(myClient.path, myResult.path) then
            break;
      if NOT StrEqual(myClient.path, myResult.path) then
         :mReportBogusAndClose(myEp);
      else
         myClient.callback:CompletionScript(inServer, myResult, nil);
   end,
   completionScript: func(inEp, inOpt, inRes)
   begin
      if inRes <> kCancellationException then
      begin
         print("Input spec saw error " & inRes);
         :mTearDown(myEp);
      end;
   end,
};

myEp:SetInputSpec(myInputSpec);

If you've done Newton communications work before, this is nothing new. If you haven't, it's worth taking a moment to consider the notion of an input specification.

Rather than providing a traditional stream for input, the Newton OS provides the notion of an input specification, which describes the format of the information your application expects. You build an input specification (or "input spec" for short) using things like the class of the incoming data, its length or an array of possible termination characters, how long to wait for input, or communications flags that denote the end of input. The Newton uses the input spec to watch for input in the background while your application is running. Once the conditions of the input spec are met, its inputScript is invoked; if the input spec is never satisfied, the CompletionScript may be invoked to notify you that the input spec was never filled. This will happen if you timeout on a receive, or if the endpoint is closed while you're expecting input. As your application runs, it can change which input spec is active with the endpoint method SetInputSpec. This aspect of the Newton communications model makes writing applications based on state machines incredibly easy, and porting most code from stream-based environments painfully difficult. For more on communications state machines, see "Use a State Machine."


    USE A STATE MACHINE

    When planning an application, take pains to be sure you've developed a good state machine to represent it. Effort during the design phase will be repaid a thousandfold when your application's communications code is almost entirely an array of input specifications and their scripts. If you're new to working with state machines and communications, check out the Newton DTS sample CommsFSM, which has enough to get you started.

    If you're porting code, take heart, and do the same. Virtually all protocols can be well represented by a state machine, and if you do so you'll find it's easier to implement. At all costs avoid simulating a UNIX ®-style stream with an endpoint -- it's expensive, and you'll almost never need the functionality of a byte stream.


There's an exception to input handling that you should know about, even if you never use it. When you use a TCP endpoint, expedited data isn't passed to your application through the normal input spec means. Instead, any expedited data is sent as an event to your endpoint's event handler. Listing 11 shows such an event handler (the method mTearDown, called in this event handler and many other places, is shown later).


Listing 11. Receiving expedited data in an event handler

EventHandler:func(inEvent)
   begin
      if inEvent.eventCode = kCommToolEventDisconnected then
      begin
         GetRoot():Notify(kNotifyAlert, kAppName,
            "The other side has disconnected.");
         :mTearDown(self);
      end
      else if inEvent.eventCode=kEventToolSpecific then
         if inEvent.data < 0 then
            print("Got an error (" & inEvent.data & ")");
         else
            print("Expedited data! The byte was" & inEvent.data);
      else
         print("Unexpected event (" & inEvent.eventCode & ")");
   end,

WRITING A RESPONSE

By now, I hope that you've guessed how to send data: you use the endpoint's Output method, shown in Listing 12.


Listing 12. Sending data

inEp:Output("Why did you want " & inData.tag & "?", nil, {
   async: true,
   completionScript: func(inEp, inOpt, inErr)
   begin
      // Do something useful!
      ...
   end,
});

NIE provides two options of interest for output. The first is the expedited data option, which can be used to set the expedited flag on a TCP packet. Expedited data is often used, for example, to signal the other end of the connection that it should cease transmitting data immediately. An expedited data byte is denoted with the iexp option, with a frame like the one shown in Listing 13 (where inByte is the byte to be expedited).


Listing 13. An expedited data frame

{
   label: "iexp",
   type: 'option,
   opCode: opSetRequired,
   result: nil,
   form: 'template,
   data: {
      argList: [ inByte ],
      typeList: ['struct, 'byte ],
   },
}

The other option is required for UDP communications -- the address and port of the destination. This is required when output operations are performed, because UDP is connectionless (and you didn't specify the remote address during the call to your endpoint's Connect method). More on that in a bit.

DISCONNECTING WHEN DONE

Tearing down and cleaning up is exactly the same as with a standard endpoint: cancel any pending operations, disconnect, unbind, and then dispose of your endpoint. The protoTCPServer uses the method mTearDown, in conjunction with an endpoint's state variable fState, to determine the appropriate behavior (see Listing 14).


Listing 14. Disconnect and cleanup

mTearDown := func(inEp)
begin
   if inEp.fState = nil then
      print("We do nothing.");
   else if inEp.fState = 'instantiated then
      :mUnBindCb(inEp, inOpt, inErr);
   else if inEp.fState = 'bound then
      :mDisconnectCb(inEp, inOpt, inErr);
   else if inEp.fState = 'listening then
   begin
      inEp:Cancel({
         _parent: self,
         async: false, // avoid async race condition
         completionScript: func(inEp, inOpt, inErr)
            :mDisconnectCb(inEp, inOpt, inErr)
      }); 
   end
   else if inEp.fState = 'connected then
      inEp:mDisconnect();
   else if kDebugOn then print("Endpoint is already closing!");
end

mDisconnectCb := func(inEp, inOpt, inRes)
begin
   try
      inEp:Unbind({
         _parent: self,
         async: true, 
         completionScript: func(inEp, inOpt, inRes)
         begin
            :mUnbindCb(inEp, inOpt, inRes);
         end;
         }
      );
   onexception |evt| do nil;
end;
/* and the other callbacks look similar...*/

There's not much to discuss about the teardown procedure, except a couple of hard knocks you may get when actually using NIE endpoints (or any other kind):

  • It's much better form to track your endpoint's state using a separate value than to retrieve it with the endpoint's State method, so that the application can use the state in other areas such as user interface or as part of the overall state machine.

  • Be sure you wrap the endpoint's teardown methods in exception handlers. Although nothing's supposed to go wrong during an endpoint's teardown and cleanup, you never know, and you don't want an application throwing exceptions to your end users. This is especially true if your endpoint is being torn down after something bad has already happened to an open connection.

Unlike other endpoints, once you've disposed of an NIE endpoint, there's still a link hanging around that you'll need to get rid of. You do this with a call to the Link Controller's InetReleaseLink function, which will drop the link only if no other application is currently using it. Our protoTCPServer invokes this method after disposing of its last endpoint, as shown in Listing 15.


Listing 15. Disposing of endpoints and releasing the link

mUnbindCb := func(inEp, inOpt, inErr)
begin
   try
      inEp:Dispose();
   onexception |evt| do nil;
   SetRemove(fEndpoints, inEp);
   if Length(fEndpoints) = 0 then :mReleaseLink();
end;
    
mReleaseLink := func()
begin
   fState := 'stopping;
   InetReleaseLink(fLinkID, self, 'mReleaseCb);
end

mReleaseCB := func(inLinkID, inStat, inErr)
begin
   if inErr then
   begin
      :mNotifyError("InetReleaseLink", inErr);
      fState := nil;
      fEndpoints := nil;
      fCallbackSpecs.fStop:?CompletionScript(self, nil, inErr);
      fCallbackSpecs := nil;
   end
   else
   begin
      if inStat.linkStatus = 'idle then
      begin
         InetDisplayStatus(inLinkID, fStatusView, nil);
         fState := nil;
         fEndpoints := nil;
         fCallbackSpecs.fStop:?CompletionScript(nil, nil, nil);
      end;
   end;
end

InetReleaseLink takes the same arguments as InetGrabLink -- the link ID, a reference to a status view if appropriate, and what to put into the status view (or nil to conceal it entirely). Although it's obvious, be sure your application has the same number of InetGrabLink calls and InetReleaseLink calls. It's embarrassing to leave the link open or clobber it on another application.

In addition to releasing the link when you're done with it, you'll want to release it if you won't be using it for a long period of time (over 15 minutes or so) to avoid battery drain from an internal modem or the like.

WHAT IF THIS WERE UDP?

The Newton endpoint model is connection-oriented; it doesn't directly work with a connectionless protocol such as UDP. Invocation of a UDP endpoint is essentially similar to a TCP endpoint, except that you never indicate the destination IP and port at the time you connect. Instead, you'll indicate them during your call to the endpoint's Output method, using the iuds option.

When performing input or output with UDP, be sure you always indicate packet boundaries. You do this by forcing a packet boundary on output, by including the kPacket and kEOP flags in your output and input specifications (see Listing 16).


Listing 16. Using packet boundaries for UDP

local myInputSpec := {
   form: 'string,
   termination: {useEOP: true},
   rcvFlags: kPacket,
   rcvOptions: {
      label: "iuss",
      type: 'option,
      opCode: opGetCurrent,
      result: nil,
      form: 'template,
      data: {
         arglist: [
            [ 0,0,0,0 ],   // Host address
            0,             // Host port
         ],
         typelist: [
            ['array, 'byte, 4],
            'struct,
         ], 
      },
   inputScript: func(inEp, inData, inTerm, inOpt)
   begin
      // Do something with the data.
      ...
   end,
   completionScript: func(inEp, inOpt, inRes)
   begin
      if inRes <> kCancellationException then
      begin
         print("Input spec saw error " & inRes);
         :mTearDown(inEp);
      end;
   end,
};
fEndpoint:SetInputSpec(myInputSpec);
fEndpoint:Output("Hello world!", 
   call kUDPPutBytesOptions with (fAddr, fPort), {
      async: true,
      sendFlags: kPacket+kEOP,
      completionScript: func(inEp, inOpt, inErr)
      begin
         // Do something useful!
         ...
      end,
   });

Table 3 indicates which options you'll set at which times for which kinds of connections. You can use this as a summary to help you in creating your own applications.


Table 3. NIE options and their uses

UsedInbound/
OptionProvided bywithoutboundPurpose
inetkGetEndpointConfigOptionsBothBothSpecify NIE at instantiate
ilidkGetEndpointConfigOptionsBothBothSpecify link ID at instantiate
itsvkGetEndpointConfigOptionsBothBothSpecify either TCP or UDP at instantiate
ilptkINetBindOptionsTCPInboundSpecify local port for inbound TCP during bind
ilptkINetBindOptionsUDPBothSpecify local port for UDP during bind
irtskTCPConnectOptionsTCPOutboundSpecify remote IP and port for TCP during connect
iexpN/ATCPBothSpecify to expedite data during output
iusskUDPPutByteOptionsUDPBothSpecify remote host and port during UDP output

GO OUT THERE AND NEWTONIZE THE INTERNET!

With the exception of link-level management, using NIE is the same as using any other kind of Newton endpoint. The NIE's Link Controller and DNS use new global functions to provide support for a unified link-level interface and domain name service. The design of NIE strongly encourages asynchronous programming techniques.

Using NIE to port existing applications is easy, as is writing new ones. With a little work, your application can soon be merging on the infobahn among the desktop machines already cruising the Net.


    RELATED READING

    • "ATSlat: A Shared Whiteboard for Newton" by Ray Rischpater, PDA Developers, July 1996.

    • TCP/IP Illustrated (3 volumes) by W. Richard Stevens (Addison-Wesley, 1994-95).

    • Newton Programmer's Guide for Newton 2.0 by Apple Computer, Inc. (Addison-Wesley, 1996), Chapter 24, "Built-in Communications Tools."

    • Newton Internet Enabler (Apple Computer, Inc., 1996). This is provided on the Newton Developer CD as the NIE API Guide and can also be found on the Web at http://www.devworld.apple.com/dev/newton/tools/nie.html.


RAY RISCHPATER (ray_rischpater@allpen.com) is a Software Craftsman currently employed at AllPen Software, Inc., developing custom applications for the Newton platform. His hobbies include writing freeware for PDA devices, amateur radio, and channeling technical articles for journals such as develop and PDA Developers from his Siberian Husky, Sake.*



Thanks to our technical reviewers T. Erik Browne, Dan Chernikoff, Gary Hillerson, Jim Schram, and Bruce Thompson. Thanks also to Todd Courtois and Rachel Rischpater for their feedback, and of course the staff at develop for putting the journal around the article.*

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

djay Pro 1.1 - Transform your Mac into a...
djay Pro provides a complete toolkit for performing DJs. Its unique modern interface is built around a sophisticated integration with iTunes and Spotify, giving you instant access to millions of... Read more
Vivaldi 1.0.118.19 - Lightweight browser...
Vivaldi browser. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind that users are... Read more
Stacks 2.6.11 - New way to create pages...
Stacks is a new way to create pages in RapidWeaver. It's a plugin designed to combine drag-and-drop simplicity with the power of fluid layout. Features: Fluid Layout: Stacks lets you build pages... Read more
xScope 4.1.3 - Onscreen graphic measurem...
xScope is powerful set of tools that are ideal for measuring, inspecting, and testing on-screen graphics and layouts. Its tools float above your desktop windows and can be accessed via a toolbar,... Read more
Cyberduck 4.7 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Labels & Addresses 1.7 - Powerful la...
Labels & Addresses is a home and office tool for printing all sorts of labels, envelopes, inventory labels, and price tags. Merge-printing capability makes the program a great tool for holiday... Read more
teleport 1.2.1 - Use one mouse/keyboard...
teleport is a simple utility to let you use one single mouse and keyboard to control several of your Macs. Simply reach the edge of your screen, and your mouse teleports to your other Mac! The... Read more
Apple iMovie 10.0.8 - Edit personal vide...
With an all-new design, Apple iMovie lets you enjoy your videos like never before. Browse your clips more easily, instantly share your favorite moments, and create beautiful HD movies and Hollywood-... Read more
Box Sync 4.0.6233 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Fantastical 2.0.3 - Create calendar even...
Fantastical 2 is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more

SoundHound + LiveLyrics is Making its De...
SoundHound Inc. has announced that SoundHound + LiveLyrics, will be one of the first third-party apps to hit the Apple Watch. With  SoundHound you'll be able to tap on your watch and have the app recognize the music you are listening to, then have... | Read more »
Adobe Joins the Apple Watch Lineup With...
A whole tidal wave of apps are headed for the Apple Watch, and Adobe has joined in with 3 new ways to enhance your creativity and collaborate with others. The watch apps pair with iPad/iPhone apps to give you total control over your Adobe projects... | Read more »
Z Steel Soldiers, Sequel to Kavcom'...
Kavcom has released Z Steel Soldiers, which continues the story of the comedic RTS originally created by the Bitmap Brothers. [Read more] | Read more »
Seene Lets You Create 3D Images With You...
Seene, by Obvious Engineering, is a 3D capture app that's meant to allow you to create visually stunning 3D images with a tap of your finger, and then share them as a 3D photo, video or gif. [Read more] | Read more »
Lost Within - Tips, Tricks, and Strategi...
Have you just downloaded Lost Within and are you in need of a guiding hand? While it’s not the toughest of games out there you might still want some helpful tips to get you started. [Read more] | Read more »
Entertain Your Pet With Your Watch With...
The Petcube Camera is a device that lets you use live video to check in on your pet, talk to them, and play with them using a laser pointer - all while you're away. And the Petcube app is coming to the Apple Watch, so you'll be able to hang out with... | Read more »
Now You Can Manage Your Line2 Calls With...
You'll be able to get your Line2 cloud phone service on the Apple Watch very soon. The watch app can send and receive messages using hands-free voice dictation, or by selecting from a list of provided responses. [Read more] | Read more »
R.B.I. Baseball 15 (Games)
R.B.I. Baseball 15 1.01 Device: iOS Universal Category: Games Price: $4.99, Version: 1.01 (iTunes) Description: The legendary Major League Baseball franchise returns to the diamond. Make History. ** ALL iPOD Touch, the iPad 2 and the... | Read more »
Here's How You Can Tell if an App W...
The Apple Watch is pretty much here, and that means a whole lot of compatible apps and games are going to be updated or released onto the App Store. That's okay though, beacause Apple has quietly updated their app description pages to make things... | Read more »
Forgotten Memories : Alternate Realities...
Forgotten Memories : Alternate Realities 1.0.1 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.1 (iTunes) Description: + REDUCED PRICE ONLY THE LAUNCHING WEEK + "The most exciting horror game of 2015." - AppSpy... | Read more »

Price Scanner via MacPrices.net

Intel Compute Stick: A New Mini-Computing For...
The Intel Compute Stick, a new pocket-sized computer based on a quad-core Intel Atom processor running Windows 8.1 with Bing, is available now through Intel Authorized Dealers across much of the... Read more
Heal to Launch First One-Touch House Call Doc...
Santa Monica, California based Heal, a pioneer in on-demand personal health care services — will offer the first one-touch, on-demand house call doctor app for the Apple Watch. Heal’s Watch app,... Read more
Mac Notebooks: Avoiding MagSafe Power Adapter...
Apple Support says proper usage, care, and maintenance of Your Mac notebook’s MagSafe power adapter can substantially increase the the adapter’s service life. Of course, MagSafe itself is an Apple... Read more
12″ Retina MacBook In Shootout With Air And P...
BareFeats’ rob-ART morgan has posted another comparison of the 12″ MacBook with other Mac laptops, noting that the general goodness of all Mac laptops can make which one to purchase a tough decision... Read more
FileMaker Go for iPad and iPhone: Over 1.5 Mi...
FileMaker has announced that its FileMaker Go for iPad and iPhone app has surpassed 1.5 million downloads from the iTunes App Store. The milestone confirms the continued popularity of the FileMaker... Read more
Sale! 13-inch 2.7GHz Retina MacBook Pro for $...
 Best Buy has the new 2015 13″ 2.7GHz/128GB Retina MacBook Pro on sale for $1099 – $200 off MSRP. Choose free shipping or free local store pickup (if available). Price for online orders only, in-... Read more
Minimalist MacBook Confirms Death of Steve Jo...
ReadWrite’s Adriana Lee has posted a eulogy for the “Digital Hub” concept Steve Jobs first proposed back in 2001, declaring the new 12-inch MacBook with its single, over-subscribed USB-C port to be... Read more
13-inch 2.7GHz Retina MacBook Pro for $1234 w...
Adorama has the 13″ 2.7GHz/128GB Retina MacBook Pro in stock for $1234.99 ($65 off MSRP) including free shipping plus a free LG external DVD/CD optical drive. Adorama charges sales tax in NY & NJ... Read more
13-inch 2.5GHz MacBook Pro available for $999...
 Adorama has the 13-inch 2.5GHz MacBook Pro on sale for $999 including free shipping plus NY & NJ sales tax only. Their price is $100 off MSRP. Read more
Save up to $600 with Apple refurbished Mac Pr...
The Apple Store is offering Apple Certified Refurbished Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Service-Learning Counselor, *APPLE* Corps -...
…CONTRACT TITLE Higher Education Assistant FLSA Exempt CAMPUS SPECIFIC INFORMATION APPLE Corps (Academic Preparation Program for Law Enforcement), a partnership between Read more
*Apple* iOS Specialist - Kforce (United Stat...
Our client is seeking an Apple iOS Specialist to join their team in Quincy, Massachusetts (MA). Duties: * Responsible for configuration and distribution of desktop, Read more
*Apple* Systems Engineer - Axius Technologie...
* Must be Apple -Certified specialist and will be responsible for all hardware: device settings, images creation/deployment, security implementation * Responsible for all Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.