Table of Contents
Previous Chapter
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.
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.
De tre vanligaste modultyperna är helt klart location moduler, file extension moduler och parser moduler.
/cgi-bin/. Det är också den modultyp som (oftast) hittar de filer som sedan skickas vidare till extension 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
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 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.
MODULE_AUTH
- Directoryparsningsmodul, kan bara finnas en aktiv i taget
MODULE_DIRECTORIES
- Extension modul (före location)
MODULE_EXTENSION
- Extension modul (efter location)
MODULE_FILE_EXTENSION
- "First try" moduler
MODULE_FIRST
- "Last resort" moduler
MODULE_LAST
- Location modul, tillhandahåller filer
MODULE_LOCATION
- Parser, hanterar en eller flera "tags"
MODULE_PARSER
- URL modul, kan göra interna omriktingar av förfrågningar
MODULE_URL
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: "
"<foo></foo> and <bar>",
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>?
Nu kommer vi till modultypsspecifika callbacksfunktioner (kom på ett bättre namn själv!).
--------------------------------------------------------------------------------------------------
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.
--------------------------------------------------------------------------------------------------
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);
tag_arguments är en mapping med argument, <tag-name foo=bar> innehär att arguments->foo blir "bar".
contents är all data inne i tagen.
request_id är, precis som vanligt, just requesten.
file är en pekare till den (öppna) filen som används som "råmaterial" eller noll.
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: "
"<foo></foo> and <bar>",
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.
Från alla moduler: returtypen är en mapping eller noll
Från location moduler: returtypen kan även vara ett objekt eller -1.
open(filnamn, "r") returnerar.
Specialfallet URL-moduler: Ingen returtyp.
request_id direkt.
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]);
query("name"), eller QUERY(name))
value är skönskvärdet för variabeln.
long_name är det namn som användaren ser i konfigurationsinterfacet.
type är vilken typ variabeln har:

------------------------------------------------------------------------------------ typ förklaring ------------------------------------------------------------------------------------TYPE_STRINGEn sträng. (t.ex."foo bar")TYPE_FILEEn fil. Ingen egentlig skillnad mot en sträng just nu, men ledtexten till användaren skiljer sig.TYPE_LOCATIONEn position i det virtuella filsystemet. Hanteras precis somTYPE_STRING, men ledtexten till användaren skiljer sig.TYPE_INTEtt heltal.TYPE_DIREtt directoy. Användaren kan bara skriva in directoryn som finns, och de slutar alltid på '/'.TYPE_FLOATEtt flyttal.TYPE_TEXT_FIELDEtt fält med text i, kan innehålla flera rader.TYPE_FLAGAntingen sant eller falskt, dvs, man kan användaif(query("var_name")).TYPE_COLOREn färg, som ett heltal mellan 0 och 2^24, bittar för R G och B, respektive. Användes i konfigurationsinterfacet en gång i tiden.TYPE_PASSWORDEtt lösenord. Krypteras automatiskt när användaren sätter variabeln.TYPE_STRING_LISTEn array med strängar, dvs({ "foo", "bar", "gazonk", })t.ex.TYPE_MULTIPLE_STRINGEn av många strängar. Strängarna väljs ur den array man skickar med ichoicesfältet.TYPE_INT_LISTEn array med heltal.TYPE_MULTIPLE_INTSomTYPE_MULTIPLE_STRING, men heltal istället.TYPE_DIR_LISTEn array med directoryn.TYPE_FILE_LISTEn array med filer, hanteras somTYPE_STRING_LISTjust nu, men ledtexten till användaren skiljer sig. ------------------------------------------------------------------------------------
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/
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.