Table of Contents Previous Chapter



Att skriva Spinnermoduler

Jag vill påpeka att Spinner från och med den 9:e september 1996 heter Roxen och 
det är ett registrerat varumärke hos Roxen INC. Även vissa funktioner, språk och 
filer som nämns i den här artikeln har bytt namn. spinnerlib -> roxenlib, SPML -> 
RXML m.fl. Om du kör en Roxen-server måste du alltså ta beskrivningarna med en 
nypa salt.

Vad är Spinner?

Spinner är den WWW-server som Lysator använder. Det är också en WWW-server som är synnerligen utbyggbar. Servern är mestadels skriven i uLPC, se artikeln om uLPC i denna tidning.

Spinner i sig kan inte mycket, det enda det centrala programmet kan är modulhantering, det vet ingenting om filer, HTTP eller andra udda saker. Allt sådant hanteras av moduler.

När jag beskriver modulskrivande kommer jag att utgå från att du har grundläggande kunskaper i uLPC, så om du inte har det är det lämpligt att läsa lite om uLPC innan du skriver din modul.

Vad finns det då för typer av moduler?

De tre vanligaste modultyperna är helt klart location moduler, file extension moduler och parser moduler.

Sedan finns det en hög med modultyper som inte är så vanliga att jag kommer att beskriva dem ingående här.

Varje modul kan ha en eller flera olika modultyper (i teorin kan man faktiskt ha moduler helt utan typ, men det blir lite meningslöst, eftersom en sådan modul aldrig kommer att anropas av spinner.)

I bilden nedan är 'mapping' 'object' och '-1' returtypen (och i fallet -1 även värdet) från respektive modul.

Skiss över skickandet mellan moduler

Hur skriver man då en modul?

Mitt första tips är att man konfigurerar upp sin egen testserver. Om man vill använda samma filer som lysators huvudserver använder, förutom konfigurationsfilerna, skriver man:

cd /usr/www/spinner/server 

./start --config-dir=$HOME/.spinner_cfg

Detta kommer att starta din aldeles egna spinner, sedan är det bara att koppla upp sig till dess konfigurationsinterface och konfigurera den. Mer om det finns att läsa på URLen
http://spinner.infovav.se/.

Lämplig variabel att ändra på är Global Variables -> Module directory, som anger var den ska leta efter moduler, Detta är en kommaseparerad lista av directoryn. Ett lämpligt värde kan ju vara modules/, localmodules/, /users/<dittnamn>/spinner_modules/ eller liknande.

Nu har jag en spinner. Vad gör jag sedan?

Nu kan du börja skriva din egen modul, nedan följer en sorts steg-för-steg beskrivning av hur man gör det. Jag utgår som sagt från att du har grundläggande kunskaper i uLPC programmering.

Vi börjar med ett modulskelett, de funktioner och ärvningar som alla moduler måste innehålla.


#include <module.h>

inherit "module";

inherit "spinnerlib";



array register_module()

{

  return ({

    MODULTYP,

    "modulnamn",

    "moduldokumentation",

    0,

    0 eller 1, 

  });

}

Först inkluderas filen 'module.h'. Den innehåller en massa defines för konstanter, t.ex. alla modultyper. Den innehåller även en del makron som kan användas när man vill ha värdet på en viss variabel.

Sedan ärvs filen module. Den definierar en massa funktioner av typen defvar(), set() och query(), samt tillhandahåller skönsvärden till många olika statusfunktiner som man kan implementera i sin modul om man vill, men som inte behövs i de allra flesta fall.

Filen spinnerlib som sedan ärvs inkluderar en hel massa bekvämlighetsfunktioner, och är alltså inte helt nödvändig, men det är mycket bra att ha med den. Du kan ju titta på avsnittet om 'Att returnera något' för att se varför.

Sedan kommer någonting som är intressantare, funktionen register_module.

Denna (något ointuitiva) funktion ska returnera en array som definierar modulens funktion, samt vad den heter, och om den kan ha mer än en samtidig instans aktiv i varje server.

Det första elementet i arrayen är modultypen. Detta är en bitvis-eller (|) av modultyper. Vanligast är att varje modul bara har en typ, men t.ex. htmlparse.lpc i normaldistributionen av Spinner är en MODULE_MAIN_PARSER, en MODULE_PARSER och en MODULE_FILE_EXTENSION.

Modultyp-define att använda

Det andra elementet är modulens namn, det tredje en (hyffsat) kortfattad beskrivning av vad modulen gör, det fjärde elementet är reserverat för framtida användning och det femte elementet är '0' om det kan finnas mer än en kopia av modulen samtidigt i varje virtuell server (som filsystemsmodulen), eller 1 om det bara kan finnas en kopia i varje virtuell server (som 'tablify' modulen).

Eftersom vi i detta exempel vill skriva en modul som adderar ett par 'taggar' till SPML, väljer vi nedanstående utseende på register_module funktionen:

array register_module()

{

  return ({ 

    MODULE_PARSER,

    "Gazonk",

    "The gazonk module. "

    "This module adds a container and a non-container: "

    "&lt;foo&gt;&lt;/foo&gt; and &lt;bar&gt;",

    0,

    1,

  });

}

Nu kan modulen registrera sig, men det räcker ju inte riktigt, hur får man den att verkligen hantera <foo> </foo> och <bar>?

Modultypsspecifika callbacksfunktioner

Nu kommer vi till modultypsspecifika callbacksfunktioner (kom på ett bättre namn själv!).

TABELL 1.

--------------------------------------------------------------------------------------------------
Funktion             Modult  Förklaring                                                             
                     yper                                                                           
--------------------------------------------------------------------------------------------------
void create();       Alla    Anropas av uLPC automatisk när objektet (instansen av din              
                             modul) skapas. Här definierar man lämpligtvis sina modullokala         
                             Spinner variabler med hjälp av 'defvar'.                               
void start();        Alla    Anropas av spinner precis innan modulen ska vara redo att ta           
                             emot requests (requests hanteras med de modultypsspecifika call        
                             backfunktionerna).                                                     
string status();     Alla    Anropas av konfigurationsinterfacet i tid och otid för att få status   
                             om modulen. Ska returnera en sträng, beståendes av HTML som            
                             tål att stoppas i en <dd> i en <dl> lista.                             
string info();       Alla    Om man defenierar den här funktionen används resultatet istället       
                             för element tre i det som register_module() returnerar för             
                             att beskriva modulen i konfigurationsinterfacet.                       
string|void          Alla    Om man behöver kontrollera värdet hos variabler innan de sätts         
check_variable(str           defenitivt så gör man lämpligtvis det i den här funktionen. Oftast     
ing variable,                behöver man ju dock inte kontrollera variablers giltighet, efter       
mixed                        som alla värden som man kan sätta dem till är giltiga. Mer infor       
will_be_set_to);             mation om spinnervariabler i allmänhet kommer senare.                  
string               Alla    Ska returnera namnet på modulen, används istället för element          
query_name();                två i arrayen som register_module() returnerar för att                 
                             beskriva modulen i konfigurationsinterfacet. Kan vara mycket           
                             användbart om man kan ha mer än en kopia av en modul. Använ            
                             daren kan dock själv sätta vilket namn som helst på modulen i          
                             konfigurationsinterfacet.                                              
array auth(array     Auth    Se userdb.lpc modulen. Oftast behöver man inte implementera en         
from);                       egen användardatabas och authentifieringsmodul, och det hör            
array                        inte till grunderna i spinnerprogrammering.                            
userinfo(string                                                                                     
username);                                                                                          
array userlist();                                                                                   
array                                                                                               
user_from_uid(int                                                                                   
uid);                                                                                               
mapping              Direc   Givet en intern url (i request_id->not_query) ska den här              
parse_directory(ob   tories  funktionen returnera en directorylistning i HTML.                      
ject request_id);                                                                                   
array (string)       Exten   Returnera en array med extensions som modulen tänker hantera.          
query_extensions()   sion    Lämpligtvis implementeras enligt följande:                             
;                            array (string) query_extensions()                                      
array (string)               {                                                                      
query_file_extensi             return query("extensions");                                          
ons();                       }                                                                      
                             (förutsatt att variabeln extensions är definierad, som en              
                             TYPE_STRING_LIST, se avsnittet om variabler senare).                   
mapping              Exten   Returnera antingen ett resultat eller 0 (noll). Anropas av Spinner     
handle_extension(s   sion    när modulen ska hantera en extension, i fallet                         
tring extension,             handle_extension innan en location modul har hittat en fil             
object                       som passar (används knappast alls, den enda modulen som finns          
request_id);                 som använder det är explicit timestamp modulen, som returnerar         
mapping                      last modification date (mtime) på en fil, om man lägger till           
handle_file_extens           .timestamp till filnamnet.)                                            
ion(object file,             Vanligtvis gör man file_extension moduler, då är det det senare        
string ext, object           fallet som gäller. 'file' är en klon av /precompiled/file, se          
request_id);                 uLPC dokumentationen.                                                  
                             Kortfattat kan man nämna att 'file' har metoden read(), kan            
                             vara lämpligt i det här fallet.                                        
mapping              First   Används bland annat av logger.lpc, den där modulen som loggar          
first_try(object             requests i användarnas egna directoryn på Lysator. Om man              
request_id);                 returnerar en response mapping så kommer det att tas som svaret.       
                             Den här funktionen anropas aldrig vid interna requests, dvs, vid       
                             <insert> och liknande.                                                 
mapping              Last    Används av relay modulen, om man sätter priority till last.            
last_resort(object           Anropas bara om ingen annan modul har lyckats hitta ett match          
 request_id);                ande dokument, och aldrig vid interna requests.                        
string               Loca    Returnera vilken position i det virtuella filsystemet modulen ska      
query_location();    tion    få. Även här använder man lämpligen värdet av en variabel,             
                             eftersom användaren då själv kan välja var modulen ska hamna.          
object|mapping       Loca    Returnera ett öppet filobjekt (see uLPC dokumentation om /pre          
find_file(string     tion    compiled/file) om det finns en fil som matchar file_name som den       
file_name, object            här modulen hanterar, eller en response mapping (lämpligen             
request_id);                 genererad med http_* funktionerana) om man inte vill att               
                             extension moduler ska användas igenom. Mappingen kan vara              
                             mycket praktisk om man vill returnera att användaren ska skriva        
                             in ett lösenord, eller en redirect till en ny fil.                     
                             En bra funktion att använda i den här funktionen kan vara              
                             open(filename, mode), som returnerar just ett sådant                   
                             öppet filobjekt.                                                       
                             Observera att om modulen har location (query_location                  
                             returnerar) "/foo/", och filen "/foo/bar/" efterfrågas, kom            
                             mer location modulen att få "bar/" som första argument.                
                             request_id finns dokumenterad i en annan artikel i denna tid           
                             ning, den om skript i spinner, liksom response mapping.                
array                Loca    Returnera en array med filnamn eller noll. dir_name som                
find_dir(string      tion    file_name hos find_file.                                               
dir_name, object             Det finns en default find_dir i module.lpc som returnerar              
request_id);                 0. Så om man inte vill returnera några directorylistningar kan         
                             man bara låta bli att defeniera den här funktionen.                    
array                Loca    file_name som hos find_file.                                           
stat_file(string     tion    Returnera resultatet av en file_stat (se uLPCdokumentatio              
file_name, object            nen) eller noll.                                                       
request_id);                 Om man vill kan man skippa även den här funktionen, directory          
                             modulen och en del andra moduler kan dock kräva att den finns          
                             för att din modul ska fungera precis som ett "vanligt" filsystem.      
string               Loca    Returnera vad filen file_name egentligen heter (t.ex om                
real_file(string     tion    modulen sune hittar sina filer i /usr/www/foo/, och filen bar          
file_name, object            efterfrågas i den, så ska den returnera /usr/www/foo/bar).             
request_id);                 Den här metoden är inte alls nödvändig, det kan ju t.ex. hända att     
                             dina filer egentligen inte existerar på disken, men den snabbar        
                             upp saker som cgimoduler och uLPC skript.                              
mapping              Parser  Anropas från main_parsern för att registrera en parse modul.           
query_tag_callers(           Returvärdet är en mapping enligt: ([ "tag":funktion,                   
);                           ... ]), eller en tom mapping ([]).                                     
mapping                                                                                             
query_container_ca                                                                                  
llers();                                                                                            
object|mapping       URL     Returnerar antingen request_id (med iallafall en eller annan           
remap_url(object             variabel modifierad, förhoppningsvis), i vilket fall den används       
request_id);                 för att göra en ny request (vilket innebär att modulen kommer att      
                             anropas igen, se upp!), eller också så kan funktionen returnera en     
                             mapping med någon av http_* funktionerna (se nedan).                   
                             Slutligen så kan man returnera noll, i vilket fall allt bara fortsät   
                             ter.                                                                   
                                                                                                    
--------------------------------------------------------------------------------------------------

Ett lite konkretare exempel - Vår gazonkmodul

Vi kan se i tabellen att en dylik (MODULE_PARSER, om ni har glömt bort vilken typ det skulle vara) modul ska ha initieringsfunktionerna query_tag_callers och query_container_callers, men vad ska de egentligen returnera?

Funktionerna ska returnera en mapping av tag-namn till funktionspekare. Den första funktionen (query_tag_callers) ska returnera en mapping med funktioner som ska anropas om tagen står ensam (utan avslutande </tag-namn>), och den andra funktionen ska returnera en mapping med funktioner som ska anropas för containers (<tag-namn></tag-namn>). Den största skillnaden mellan dem är att container-funktionerna även får med texten inne i tagen som ett argument.

Argumenten till funktionerna är:

Containers:

string 

tag_handler(          string tag_name, mapping tag_arguments,

          string contents, object request_id, 

          object file, mapping defines);

Tags:

Alla argument är som för containers, förutom det att det inte finns några contents.

Hela modulen:


/* A simple parse type module for spinner. This module defines two new 
tags, foo and bar, one is a container and the other one is a stand alone 
tag. */

#include <module.h>

inherit "module";

inherit "spinnerlib";



array register_module()

{

  return ({ MODULE_PARSER,"Gazonk","The gazonk module. "

  "This module adds a container and a non-container: "

  "&lt;foo&gt;&lt;/foo&gt; and &lt;bar&gt;",

    0, 1,});

}



/* A container gets the contents as the third argument. 

  Example: Hi! -->

  tag_foo("foo","Hi!",(["bar":"gazonk"]),...); */

string tag_foo(          string tag_name, string contents, mapping arguments,

          object request_id, object file, mapping defines)

{

  if(arguments->lower) 

    return lower_case(contents);

  if(arguments->upper) 

    return upper_case(contents); 

  if(arguments->reverse) 

    return reverse(contents);

}



string tag_bar(          string tag_name, mapping arguments, object request_id,

          object file, mapping defines)

{

  int i;

  string res="";



  if(arguments->num) 

    i=(int)arguments->num;

  else

    i=30;



#define SUNE (("abcdefghijklmnopqrstuvwxyzåäö")/"")

  while(i--)

    res += SUNE[random(sizeof(SUNE))];

#undef SUNE

  return res;

}



mapping query_tag_callers()

{ 

  return ([ "bar":tag_bar,]);

}



mapping query_container_callers()

{

  return ([ "foo":tag_foo,]);

}

Nu har vi faktiskt en färdig parse modul i och för sig kanske inte så spännande, men den gör iallafall någonting!

Dokumentation om variabeln request_id, och om hur man kan returnera data, finns i artikeln om uLPC skript.

Att returnera data

Från alla moduler: returtypen är en mapping eller noll


Från location moduler: returtypen kan även vara ett objekt eller -1.


Specialfallet URL-moduler: Ingen returtyp.

Spinnervariabler

Om du har tittat ett tag på spinners konfigurationsinterface så har du säkert sett att nästan alla moduler har en massa variabler som man kan sätta.

Hur gör man då om man vill ha några sådana i sin fina modul, för det är ju bra att användaren själv kan konfigurera?

Jo, man använder funktionen defvar i create, och sedan query om man vill veta vad en av variablerna har för värde.

int defvar(        string name, mixed value, 

        string long_name, int type, 

        string documentation_string[, array choices]);

Ett litet exempel kanske kan hjälpa.

void create() {

  defvar(      "BG", 1, "Configuration interface background", 

        TYPE_FLAG, 

        "Should the background be set by the " 

        "configuration interface?");

  defvar(      "NumAccept", 1, "Number of accepts to attempt",

        TYPE_MULTIPLE_INT,

        "The maximum number of accepts to attempt for "

        "each read callback from the main socket. <p>"

        "Increasing this will make the server faster "

        "for users making many simultaneous "

        "connections to it, or if you have a very busy "

        "server.",

        ({     1, 2, 4, 8, 16, 32, 64, 128, 256, 

            512, 1024 }));
  defvar(      "ConfigurationPort", 22202,

        "Configuration port", 

        TYPE_INT, 

        "the portnumber of the configuration "

        "interface. Anything will do, but you will "

        "have to be able to remember it to configure "

        "the server.");

}

Den här kodsnutten defenierar tre variabler (de kommer egentligen från spinners centrala delar, och inte från en modul, men det fungerar precis lika dant).

Först en flagga, vars skönskvärde är på, sedan ett heltal som väljs från en lista av tal (2^n, 0 <= n <= 10), och sist ett heltal.

Så nu är det bara att sätta igång med din modul!

WWW-sidorna uppdateras kontinuerligt med nya punkter. Det som står närmast på tur är att scanna och presentera alla gamla GARB vi kan komma över.

Vi som jobbar med projektet är främst Kent Engström (huvudförfattare) och Magnus Bark (scanning av dokument). Lars Aronsson har hjälp till med bland annat WWW-design.

Url: http://www.lysator.liu.se/history/

Lysators historia på WWW

Sedan november 1995 finns Lysators historia dokumenterad på WWW. Förutom en tidslinje som beskriver historien kronologiskt så finns en rad scannade dokument, t.ex. brevet som fixade Lysators första tigg (en D21-stordator från Datasaab) och brevet där ISY beklagar att man måste köra ner Lysator i underjorden till Pul-17.

Table of Contents Next Chapter