Server Smalltalk Guide

SstHttpClientExample

Tip icon
This section describes SstHttpClientExample, which is a minimal HTTP client which you can use to bash your favorite web server and repeatedly fetch URLs. Try running the example using:
SstHttpClientExample exampleWith:
   #('http://foo.com:80/index.htm' 'http://bar.com:80/us.htm')

You won't see much but if you look at how the example works you can put in a breakpoint or two and see what is being returned. Or you can inspect (SstHttpClientExample on: 'http://') startUp and then execute:

(self fetch: 'http://foo.com:80/index.htm') inspect

You will get an inspector on the contents of index.htm. Don't forget to run shutDown on the example object when you are done.

The SST communications infrastructure supports direct interaction with user applications. Users whose applications are inherently not based on method invocation or interact with non-object-oriented elements may find this useful. The HTTP client example does exactly this. It models a multithreaded application which is continuously fetching web pages from various servers. The application is strictly client-oriented as it will not accept and process incoming requests.

The example has three major elements:

  1. URL fetch mechanism
  2. Request-reply mapping table
  3. Background process waiting for replies

The URL fetching code is shown below with error cases and comments removed:

fetch: url
   | result endpoint request key |
   endpoint := SstRemoteEndpoint fromUrl: url sstAsUrl.
   [	request := self requestFor: endpoint.
      key := local send: request to: endpoint.
      result := (self registerFutureFor: key) touch]
         critical.
   self close: key.
   ^result contents asString

Given a URL, you construct an endpoint. This is the destination of your request in terms the underlying transport understands. You then build an HTTP request and sent it to the destination server. The HTTP transport returns a unique key for each connection it makes. Since SST currently supports HTTP/1.0, a new connection is made for every request so the key can be used as a request identifier. In the implementation this key is a modified copy of the destination endpoint but you should not rely on any properties of the key other than two different keys will not be #=.

Using this key you can maintain a request-reply mapping table and value holder. The method below creates a future (a protected value holder) to hold the result and provide a location for the sending process to block. You then register this future in a table keyed by the key from the transport. Note that this is done in critical to protect the table from shared accesses.

registerFutureFor: key
   ^[| future |
      (future := SstExplicitFuture new) sstSource: key.
      requestTable at: key put: future.
      future]
         critical

After registering the key, the sending process touches the future. This causes it to block until there is a value for the future; that is, until the reply has come.

Since the sender is blocked, there must be some other process receiving and handling incoming replies. When the example is started, such a process is forked using the code below.

startReceiving
   workers := [| message |
      [true] whileTrue: [
         (message := local receive) isSstError
            ifTrue: [self error: 'Could not receive on ' , local
                 sstAsUrl printString].
         self setResultFor: message]]
            fork

This code is an endless loop performing a blocking read on the client's transport (endpoint). When a message comes in it has already been fully parsed by the HTTP transport so it appears as a communication message with an HTTP header and the data for the fetched URL as the contents. All messages received by transports are tagged with an indication of the sender of the message. This indication is the same value as was provided when the original request was sent (the key). Using the message sender you get the future out of the table and set its value to the contents of the message. This causes the original requestor to wake and continue executing in fetch:.

This model is quite simple and easy to use. Furthermore, the code footprint is minimal. It does, however, depend on certain properties of the transport which may not be available on all transports.


[ Top of Page | Previous Page | Next Page | Table of Contents | Index ]