# MOO and HTTP

Colin McCormick (colin@tripod.tripod.com)
Thu, 12 Sep 1996 12:29:36 PDT

- Messages sorted by:[ date ][ thread ][ subject ][ author ]

    On Wed, 11 Sep 1996, Cheng Soo Yin wrote:

    >         I am seeking for someones out there who had implemented the MOO-HTTP
    > server or likewise. Please kindly share with me your precious experiences.
    > Alternatively, you may like to give me some pointers and these are mostly
    > welcome. I will like to discuss with those who are interested in this area.

    Ooo boy, did you ask a fun question! :)

    There are a lot of people out there who are running MOO-HTTP servers, and
    I imagine you'll be getting a lot of replies.  I'll shoot this out for
    what it's worth, but be aware there are probably other solutions to
    making MOO and HTTP work together.

    First of all, I recommend you read carefully the LambdaMOO Programmer's
    Manual for version 1.8.0p5, the latest release of LambdaMOO.  (It's at
    ftp://ftp.parc.xerox.com/pub/MOO/ProgrammersManual.*).  Assuming you more
    or less know how MOO works, here are three basic ways that I know of to make
    a MOO perform like an HTTP server:

    1) The Anonymous Method

    Whenever a connection is opened to a MOO, each line of input from that
    connection is sent as an argument to a database verb, called
    "do_login_command" and defined on the 'listening object' for that
    connection.  MOOs usually have only one listening object, which is usally
    #0.  Each listening object deals with connections on a specific (and
    unique) port.  The key to this method is to make a listening object that
    listens on port 80, and has a "do_login_command" verb that can read and
    deal with HTTP requests.  To read them, it can simply do something like
    the following:

      set_connection_option(player, "hold-input", 1);
      lines = {};
      while (line = read(player,1))
        lines = {@lines, line};
      endwhile
      set_connection_option(player, "hold-input", 0);
      "...deal with the request ... ";
      boot_player(player);
      return 0;

    It's up to you how you want to deal with the request once it's been read
    in.  One good way is to make a GET and POST (and maybe HEAD) verb on the
    same listening object and have "do_login_command" figure out what method
    the request is using, then pass the lines of the request to that verb.

    The tricky thing at this point is deciding what URLs mean.  What URLs are
    asking for is clear with conventional HTTP servers: the URL
    "http://www.w3.org/pub/index.html" means you are asking the server at
    www.w3.org for the file "pub/index.html" from the filesystem.  But MOOs
    don't have a filesystem, so you have to come up with a protocol for the
    URLs indicating just what a browser is requesting from the server.  For
    instance, it could be that you'd do something like:

      http://moo.myplace.org/345/bork

    which would be a request for the text in the "bork" property on object
    345.  Whatever you do, it should be something flexible and useful.  (Side
    note: if you want to have access to the filesystem of the machine the MOO
    is running on, I can certainly recommend Jaime Prilusky and Gustavo
    Glusman's File Utilities Package, available at
    ftp://bioinformatics.weizmann.ac.il/software/MOOsupport/FUP/.)

    So once you've decided on a protocol, you simply have to write your GET,
    POST and HEAD verbs to parse the URL according to this protocol (i.e.
    extract the "345" and "bork" from the URL string, and turn "345" into
    #345), get the appropriate data (read #345.bork) and then send it preceded
    by the appropriate HTTP headers.  For example:

      notify(player, "HTTP/1.0 200 Okay");
      notify(player, "Date: " + ctime());
      notify(player, "Server: MyMOO-HTTP");
      notify(player, "");
      notify(player, "<H1>Hello from MyMOO-HTTP!</H1");
      notify(player, #345.bork);

    The "boot_player()" line in the code snippet several paragraphs above will
    close the connection, and if #345.bork is a beautiful piece of HTML, the
    person browsing will get a lovely web page, just like from more
    conventional servers.

    2) The Authenticated Method

    This method is similar, but allows you to authenticate requests.  For
    instance, if #345.bork is readable only to player ABC, you'd want to make
    sure player ABC was making the HTTP request.  HTTP specifies that
    authentication is sent as the line

      Authentication: Basic XXXXX

    where XXXX is the uuencoded version of the string "NAME:PASSWORD".  The
    problem is that you've got to find a way to uudecode this string.  The
    solution I've used is to write a builtin function uudecode() (the guts of
    which are stolen from the Apache server) but I'll bet there's someone out
    there who's implemented uudecode in-MOO.  (Anyone?) Either way should
    work.  Once the request is authenticated and you know which player is
    making the request, your GET/POST/HEAD verbs (which should probably be
    wizard-owned) can check to see if the player is allowed to read #345.bork.
    If so, read & send it, if not, send a 401 Unauthorized.

    3) The Interactive Method

    The final method I know about (and the one I'm most interested in) is
    probably something this list has heard too much about from me ( :) ) but
    I'll mention it quickly here.

    As I said above, "do_login_command" is called for each line of input from
    a new connection, with that line as its argument.  If "do_login_command"
    returns a valid player object number, the connection is logged in as that
    player.  It is possible to have "do_login_command", each time it is
    called, save its string argument to some convenient property (or even as
    Gustavo cleverly suggested into the ".flush" connection option) until it
    detects the last line of an HTTP request.  (Usually an empty line
    immediately preceded by a line starting with "Content-length: X".)

    This final instantiation of "do_login_command" (with the empty line) can
    then check back through the saved header lines for the Authentication:
    line, uudecode the authentication string, and if the name and password
    authenticate correctly, return a valid player object.  The connection is
    now "logged in", and will appear to other players in the MOO as a normal
    player.  If this is combined with the trick of *not* sending the final
    "boot_player()" line in the code snippet from Method (1), the connection
    from the MOO server to the HTTP browser will be kept open, and other
    players can say/emote/etc. anything to the player who is connected via the
    Web browser.

    This becomes particularly exciting if your URL protocol has some way of
    specifying commands to be entered as MOO commands, i.e.

      http://moo.myhome.org/command/say+hello

    Then the GET/POST/HEAD verb (wizard-owned) can use force_input() to make
    those commands be entered as if the browser-connected player had typed
    them via a normal telnet connection.  Voila: interactive Web Browsing!
    (And state-maintaining HTTP, because you can save any data you want to
    about a player in the MOO database.)

    I actually have been having a lot of luck with this approach, and would be
    happy to tell you about it in more detail if you want, but I suspect this
    message is too long already.

    Best of luck! :)

    Colin

    Colin McCormick
    Tripod, Inc.
    http://www.tripod.com