Understanding the Network Connections in LambdaMOO

(source: LambdaMOO Programmers Manual Online documentation about LambdaMOO)

Accepting and Initiating Network Connections

When the server first accepts a new, incoming network connection, it is given the low-level network address of computer on the other end. It immediately attempts to convert this address into the human-readable host name that will be entered in the server log and returned by the connection_name() function. This conversion can, for the TCP/IP networking configurations, involve a certain amount of communication with remote name servers, which can take quite a long time and/or fail entirely. While the server is doing this conversion, it is not doing anything else at all; in particular, it it not responding to user commands or executing MOO tasks.

By default, the server will wait no more than 5 seconds for such a name lookup to succeed; after that, it behaves as if the conversion had failed, using instead a printable representation of the low-level address. If the property name_lookup_timeout exists on $server_options and has an integer as its value, that integer is used instead as the timeout interval.

When the open_network_connection() function is used, the server must again do a conversion, this time from the host name given as an argument into the low-level address necessary for actually opening the connection. This conversion is subject to the same timeout as in the in-bound case; if the conversion does not succeed before the timeout expires, the connection attempt is aborted and open_network_connection() raises E_QUOTA.

After a successful conversion, though, the server must still wait for the actual connection to be accepted by the remote computer. As before, this can take a long time during which the server is again doing nothing else. Also as before, the server will by default wait no more than 5 seconds for the connection attempt to succeed; if the timeout expires, open_network_connection() again raises E_QUOTA. This default timeout interval can also be overridden from within the database, by defining the property outbound_connect_timeout on $server_options with an integer as its value.

Associating Network Connections with Players

When a network connection is first made to the MOO, it is identified by a unique, negative object number. Such a connection is said to be un-logged-in and is not yet associated with any MOO player object.

Each line of input on an un-logged-in connection is first parsed into words in the usual way (see the chapter on command parsing for details) and then these words are passed as the arguments in a call to the verb $do_login_command(). For example, the input line

connect Munchkin frebblebit

would result in the following call being made:

$do_login_command("connect", "Munchkin", "frebblebit")

In that call, the variable player will have as its value the negative object number associated with the appropriate network connection. The functions notify() and boot_player() can be used with such object numbers to send output to and disconnect un-logged-in connections. Also, the variable argstr will have as its value the unparsed command line as received on the network connection.

If $do_login_command() returns a valid player object and the connection is still open, then the connection is considered to have logged into that player. The server then makes one of the following verbs calls, depending on the player object that was returned:


;listeners() 	=> {{#650, 7778, 0}, {#0, 7777, 1}}
			=> #0  (The System Object) => #650  (HTTP Service)
#0:do_login_command   this none this
 1:  "...This code should only be run as a server task...";
 2:  if (callers())
 3:    return E_PERM;
 4:  endif
 5:  if (typeof(h = $network:incoming_connection(player)) == OBJ)
 6:    "connected to an object";
 7:    return h;
 8:  elseif (h)
 9:    return 0;
10:  endif
11:  host = $string_utils:connection_hostname(connection_name(player));
12:  if ($login:redlisted(host))
13:    boot_player(player);
14:    server_log(tostr("REDLISTED: ", player, " from ", host));
15:    return 0;
16:  endif
17:  "...checks to see if the login is spamming the server with too many commands...";
18:  if (!$login:maybe_limit_commands())
19:    args = $login:parse_command(@args);
20:    return $login:(args[1])(@listdelete(args, 1));
21:  endif

#650:do_login_command   this none this
 1:  if (length(args) > 1)
 2:    try
 3:      if (args[1] == "get" || args[2] == "post")
 4:        this:(args[1])(@listdelete(args, 1));
 5:      endif
 6:    except v (ANY)
 7:    endtry
 8:    "A hack to delay for output to reach the client.";
 9:    suspend(2);
10:    boot_player(player);
11:    return -1;
12:  else
13:    return -1;
14:  endif

The first of these is used if the returned object number is greater than the value returned by the max_object() function before $do_login_command() was invoked, that is, it is called if the returned object appears to have been freshly created. If this is not the case, then one of the other two verb calls is used. The $user_connected() call is used if there was no existing active connection for the returned player object. Otherwise, the $user_reconnected() call is used instead.

Fine point: If a user reconnects and the user's old and new connections are on two different listening points being handled by different objects (see the description of the listen() function for more details), then user_client_disconnected is called for the old connection and user_connected for the new one.

If an in-bound network connection does not successfully log in within a certain period of time, the server will automatically shut down the connection, thereby freeing up the resources associated with maintaining it.

When any network connection (even an un-logged-in or outbound one) is terminated, by either the server or the client, then one of the following two verb calls is made:


The first is used if the disconnection is due to actions taken by the server (e.g., a use of the boot_player() function or the un- logged-in timeout described above) and the second if the disconnection was initiated by the client side.

It is not an error if any of these five verbs do not exist; the corresponding call is simply skipped.

Note: Only one network connection can be controlling a given player object at a given time; should a second connection attempt to log in as that player, the first connection is unceremoniously closed (and $user_reconnected() called, as described above). This makes it easy to recover from various kinds of network problems that leave connections open but inaccessible.

When the network connection is first established, the null command is automatically entered by the server, resulting in an initial call to $do_login_command() with no arguments. This signal can be used by the verb to print out a welcome message, for example.

Warning: If there is no $do_login_command() verb defined, then lines of input from un-logged-in connections are simply discarded. Thus, it is necessary for any database to include a suitable definition for this verb.

#650:get   this none this
 1:  {path, ?version = "HTTP/1.0", @excess} = args;
 2:  if (length(args) > 1 && args[2] == "HTTP/1.0")
 3:    revision = "1.0";
 4:  else
 5:    revision = "0.9";
 6:  endif
 7:  if (subs = match(path, "^/+/"))
 8:    path[1..subs[2] - 1] = "";
 9:  endif
10:  headers = {};
11:  Authorizations = {};
12:  referer = 0;
13:  if (revision == "1.0")
14:    while (line = read(player))
15:      if (index(line, "Authorization:"))
16:        parts = $string_utils:explode(line, " ");
17:        if (parts[2] == "Basic")
18:          Authorizations = {@Authorizations, this.httpd:base_64_decode(parts[3])};
19:        endif
20:      else
21:        headers = {@headers, line};
22:      endif
23:    endwhile
24:  endif

	[...deleted debugging lines...]
55:    "httpd actully handles the processing and we send it back to the client.";
56:    ctype = "";
57:    try
58:      {ctype, output} = this.httpd:get(path, headers, Authorizations);
59:      notify(player, "HTTP/1.0 200 OK");
60:    except v (ANY)
61:      notify(player, "HTTP/1.0 500 Oops");
62:      output = {"<html><title><head>Server Error</head></title><body>A server error 
                    has occurred.</body></html>"};
63:      ctype = "text/html";
64:    endtry
65:    notify(player, tostr("Server: Strange/0.1 LambaMOO/", server_version()));
66:    notify(player, "Content-Type: " + tostr(ctype));
67:    notify(player, "Last-modified: " + ctime());
68:    content_length = 0;
69:    for line in (output)
70:      content_length = content_length + length(line) + 2;
71:      $command_utils:suspend_if_needed(1);
72:    endfor
73:    notify(player, "Date: " + ctime());
74:    notify(player, "Content-length: " + tostr(content_length));
75:    "appearantly no one expects Content-length: anymore";
76:    notify(player, "");
77:    counter = 1;
78:    while (counter <= length(output))
79:      notify(player, output[counter]);
80:      $command_utils:suspend_if_needed(1);
81:      counter = counter + 1;
82:    endwhile
83:  endif
84:  return;

=> #648  (Page Server)
@list #648:get
#648:get   this none this
 1:  "HTTPD:get(path)";
 2:  {path, ?headers = {}, ?Authorizations = {}} = args;
 3:  contents = this:find_page(path, headers, Authorizations);
 4:  if (ticks_left() < 600)
 5:    suspend(1);
 6:  endif
 7:  return contents;

@list #648:find_page
#648:find_page   this none this
 1:  "HTTPD:find_page returns {} if nothing is found, or else returns the";
 2:  "set of lines for the requested 'page'.  Since I'm writing this, and";
 3:  "the whole concept of path is meaningless since this is all potentially";
 4:  "dynamic html, I'm not bothering with file extensions.";
 5:  {path, ?headers = {}, ?Authorizations = {}} = args;
 6:  page = {};
 7:  path = strsub(path, "%20", " ");
 8:  chunks = $string_utils:explode(path, "/");
 9:  if (length(chunks) > 0)
10:    {Location, @rest} = chunks;
11:    OtherLoc = Location + "/";
12:    for Thing in (this.contents)
13:      if (Thing.name == Location || Thing.name == OtherLoc)
14:        return Thing:get_content(rest, headers, Authorizations);
15:      endif
16:    endfor
17:    return this.unknown_page:get_content(path, headers, Authorizations);
18:  else
19:    return this.root:get_content({"/"}, headers, Authorizations);
20:  endif

=> "Root (#397), objects/ (#1484), rooms/ (#1485), and map/ (#1486)"
@list #397:get_content
#397:get_content   none none none
 1:  {path, headers, authorization} = args;
 2:  {?next = "/", @rest} = path;
 3:  if (next == "/" && length(rest) == 0)
 4:    DoSelf = 1;
 5:  else
 6:    DoSelf = 0;
 7:  endif
 8:  if (DoSelf == 0)
 9:    other_next = next + "/";
10:    for Thing in (this.contents)
11:      if (next == Thing.name || other_next == Thing.name)
12:        return Thing:get_content(rest, headers, authorization);
13:      endif
14:    endfor
15:  endif
16:  Content = {"<html>"};
17:  Content = {@Content, "<title>Blackwood WWW Interface</title>"};
18:  Content = {@Content, "<body bgcolor=ffffff><font size=+2>"};
19:  Content = {@Content, "<h1>Blackwood WWW Interface</h1>"};
20:  Content = {@Content, "Welcome to the Blackwood MOO server WWW Interface."};
21:  Content = {@Content, "<p>You are welcome to look around our town."};
22:  Content = {@Content, "<br>Start by looking at the "};
23:  Content = {@Content, "<a href=\"" + "rooms/" + tostr(tonum($player_start)) + 
                              "\">Town Square</a>"};
24:  Content = {@Content, "</font>"};
25:  Content = {@Content, "<p><img align=top 
26:  Content = {@Content, "This page is under construction."};
27:  Content = {@Content, "<br>Comments?: email slator@cs.ndsu.edu"};
28:  return {"text/html", Content};

oit:Retailer~[90]> telnet lions.cs.ndsu.nodak.edu 7778
Connected to lions.cs.ndsu.nodak.edu.
Escape character is '^]'.
=> get /
HTTP/1.0 200 OK
Server: Strange/0.1 LambaMOO/1.8.0r5
Content-Type: text/html
Last-modified: Mon Apr 19 21:03:15 1999 CDT
Date: Mon Apr 19 21:03:15 1999 CDT
Content-length: 461

Blackwood WWW Interface

Blackwood WWW Interface

Welcome to the Blackwood MOO server WWW Interface.

You are welcome to look around our town.
Start by looking at the Town Square

This page is under construction.
Comments?: email slator@cs.ndsu.edu