Difference between revisions of "User:Void Singer/Teacup"

From Second Life Wiki
Jump to: navigation, search
(bug update and disabled some broken code)
m (Replaced old <LSL> block with <source lang="lsl2">, also for <javascript>)
 
(3 intermediate revisions by one other user not shown)
Line 5: Line 5:
  
 
{{void-box
 
{{void-box
|title=Teacup/Saucer
+
|title=Teacup
 
|content=
 
|content=
 
=== [[File:Teacup.png|frameless|left|256px|alt=Teacup/Saucer]]What is it? ===
 
=== [[File:Teacup.png|frameless|left|256px|alt=Teacup/Saucer]]What is it? ===
 
----
 
----
Teapot is an open source webserver front end for LSL's HTTP-in + {{HoverText|MOAP|Media On A Prim}} functionality. It builds on the work of several other people including but not limited to, [[User:Kelly_Linden|Kelly Linden]], [[User:Torley_Linden|Torley Linden]], [[User:Tali_Rosca|Tali Rosca]], [[User:Vegas_Silverweb|Vegas Silverweb]], and special mentions to [[User:Kate_Linden|Kate]] and/or [[User:Edelman_Linden|Edelman]] Linden. The idea is to make it easy to serve web content from within SL, with a minimum of work or understanding, in the most standards compliant and flexible way possible.
+
Teacup is an open source webserver front end for LSL's HTTP-in + {{HoverText|MOAP|Media On A Prim}} functionality. It builds on the work of several other people including but not limited to, [[User:Kelly_Linden|Kelly Linden]], [[User:Torley_Linden|Torley Linden]], [[User:Tali_Rosca|Tali Rosca]], [[User:Vegas_Silverweb|Vegas Silverweb]], and special mentions to [[User:Kate_Linden|Kate]] and/or [[User:Edelman_Linden|Edelman]] Linden. The idea is to make it easy to serve web content from within SL, with a minimum of work or understanding, in the most standards compliant and flexible way possible.
  
 
=== How does it work? ===
 
=== How does it work? ===
Line 19: Line 19:
 
Well I happen to like steam punk stuff, and it has a sort of pseudo-Victorian-era feel to it, but in all truth it was sparked by a bit of geek humor. There was an old [http://en.wikipedia.org/wiki/April_Fools%27_Day_RFC april fool's joke] passed around as an [http://tools.ietf.org/html/rfc2324 official sounding memo] about creating a protocol for [http://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol controlling coffee pots with HTTP] (back before they actually started making net enabled appliances) and part of that proposal was that teapots should return an error response '''418 "I'm a teapot"''' if you tried to make it brew coffee... since these servers are small, and 418 is safe to use internally because it wouldn't normally be encountered, I figured hey, Teapot --> Teacup --> Victorian Imagery --> Steam Punk --> teacup that is it's own teapot --> {{HoverText|SIP|Server In a Prim}} --> many different types of tea --> Success!
 
Well I happen to like steam punk stuff, and it has a sort of pseudo-Victorian-era feel to it, but in all truth it was sparked by a bit of geek humor. There was an old [http://en.wikipedia.org/wiki/April_Fools%27_Day_RFC april fool's joke] passed around as an [http://tools.ietf.org/html/rfc2324 official sounding memo] about creating a protocol for [http://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol controlling coffee pots with HTTP] (back before they actually started making net enabled appliances) and part of that proposal was that teapots should return an error response '''418 "I'm a teapot"''' if you tried to make it brew coffee... since these servers are small, and 418 is safe to use internally because it wouldn't normally be encountered, I figured hey, Teapot --> Teacup --> Victorian Imagery --> Steam Punk --> teacup that is it's own teapot --> {{HoverText|SIP|Server In a Prim}} --> many different types of tea --> Success!
  
... And thus was born the [[Server In a Prim|SIP]], [[User:Void_Singer/Teacup|Teacup]] server, and [[User:Void_Singer/Red_Tea|Red Tea]] File Service<br>(developers are encouraged to keep the theme, but it's not a requirement)
+
... And thus was born the [[Server_In_a_Prim|SIP]], [[User:Void_Singer/Teacup|Teacup]] server, and [[User:Void_Singer/Red_Tea|Red Tea]] File Service<br>(developers are encouraged to keep the theme, but it's not a requirement)
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
}}
 
}}
Line 28: Line 28:
 
So you don't want to read a bunch of tech babble, you just want to make a website in a prim right? Well I can sympathize so here are some (mostly) easy steps to get you up and running fast...
 
So you don't want to read a bunch of tech babble, you just want to make a website in a prim right? Well I can sympathize so here are some (mostly) easy steps to get you up and running fast...
 
# Follow the instructions on [[User:Void_Singer/Red_Tea|This Page]] or choose a File Service from the [[User:Void_Singer/Teacup#Compatible_File_Services_and_Extensions|Compatible File Services and Extensions]] and follow those instructions.
 
# Follow the instructions on [[User:Void_Singer/Red_Tea|This Page]] or choose a File Service from the [[User:Void_Singer/Teacup#Compatible_File_Services_and_Extensions|Compatible File Services and Extensions]] and follow those instructions.
# Copy the text in the grey box below [[User:Void_Singer/Teacup#Saucer|Code: Saucer]] into a script named "Saucer v0.3", save it, and drop it in your rezzed box
+
# Optional: Copy the text in the grey box below [[User:Void_Singer/Region_Stats#Region_Stats|Code: Region Stats]] into a script named "Region Stats", save it, and drop it into your rezzed box.
# Copy the text in the grey box below [[User:Void_Singer/Teacup#Teacup|Code: Teacup]] into a script named "Teacup v0.3", save it, and drop it in your rezzed box
+
# Optional: Copy the text in the grey box below [[User:Void_Singer/Saucer#Saucer|Code: Saucer]] into a script named "Saucer", save it, and drop it in your rezzed box.
 +
# Optional: Copy the text in the grey box below [[User:Void_Singer/Tea_Strainer#Tea_Strainer|Code: Tea Strainer]] into a script named "Tea_Strainer", save it, and drop it in your rezzed box.
 +
# Copy the text in the grey box below [[User:Void_Singer/Teacup#Teacup|Code: Teacup]] into a script named "Teacup v0.5", save it, and drop it in your rezzed box.
 
# Click on the top of your box if your webpage isn't already showing.
 
# Click on the top of your box if your webpage isn't already showing.
 
#* At this point you may want to rotate and/or resize your box.
 
#* At this point you may want to rotate and/or resize your box.
#* If you like, you have my express permission to download (right click, "save as") and edit (with whatever image editor you like) the [[:File:Teacup.png|Teacup.png]] file to use as the default texture of your website prim.
+
#* If you like, you have my express permission to download (right click, "save as") and edit (with whichever image editor you prefer) the [[:File:Teacup.png|Teacup.png]] file to use as the default texture of your website prim.
# Show it off to your friends with {{HoverText|MOAP|Media On A Prim}} enabled viewers
+
# Show it off to your friends with {{HoverText|MOAP|Media On A Prim}} enabled viewers.
  
  
Line 45: Line 47:
 
=== Teacup ===
 
=== Teacup ===
 
----
 
----
* Save in a script named "Teacup"
+
* Save in a [[Mono#How_to_use_Mono|MONO]] script named '''"Teacup v0.5"'''
<lsl>/*( Teacup Server v0.3 )*/
+
<source lang="lsl2">/*( Teacup Server v0.5 )*/
  
//-- stores the entire bootstrap or error page for touch handling
 
string gStrErr0 = "data:text/html;charset=utf-8,
 
<html>
 
    <body>
 
        <h1 style='align: center;'>Out of Service</h1>
 
        <hr/>
 
        <p>Url Interface Unavailable or a Server script failed to respond</p>
 
        <hr/>
 
        <p style='text-align:center;font-size:50%;'>Teacup/Saucer v0.3<br/>
 
          <a href='secondlife:///app/agent/";
 
string gStrErr1 = "/about'>Contact Owner</a></p>
 
    </body>
 
</html>";
 
  
//-- pending request queue for pages waiting to be served
+
/*//-- Teacup Boot Strap Variables --//*/
//-- this (hopefully) prevents reponding after SL send a timeout.
+
//-- Server Default page start (always loads)
list  gLstPndKey;
+
string  gStrTBS1xx = "data:text/html;charset:utf-8,<html>\n";
list  gLstPndTim;
+
//-- Head Section start: loads with URL_REQUEST_GRANTED
 +
string  gStrTBS1a1 = " <head>\n <base href='";
 +
//-- Head Section end & Address intsertion point: loads with URL_REQUEST_GRANTED
 +
string  gStrTBS1a2 = "'/>\n <script type='text/javascript' defer='' src='teacup.js'></script>\n </head>\n";
 +
//-- Server Default message (always loads)
 +
string  gStrTBS2xx = " <body>\n <h1>Teacup Server</h1>\n <hr/>\n <h2>If you are reading this, the <em>server</em> is working</h2>\n";
 +
//-- Default Error message: loads with URL_REQUEST_GRANTED
 +
string  gStrTBS2a1 = " <h3>(But the File Service may not be)</h3>\n";
 +
//-- URL Failure MessageL included for URL_REQUEST_DENIED
 +
string  gStrTBS2b1 = " <h3>(But the region failed to provide a URL)</h3>\n";
 +
//-- Server Default Footer start (always loads)
 +
string  gStrTBS3xx = " <hr/>\n <p>Teacup/Saucer v0.3<br/><a href='secondlife:///app/agent/";
 +
//-- Server Default Footer end & Contact Owner key insertion point (always loads)
 +
string  gStrTBS4xx = "/about'>Contact Owner?</a></p>\n </body>\n</html>";
  
key    gKeyRqs;
+
/*//-- Pending Request Variables --//*/
integer gIntRqs;
+
list    gLstPndKey; //-- Key queue (pages waiting to be served)
 +
list    gLstPndTim; //-- Timeout Value queue (Request Timeout)
 +
//-- These (hopefully) prevent multiple responses if SL sends a timeout.
  
default{
 
    state_entry(){
 
        //-- Request URL on start up
 
        llRequestURL();
 
    }
 
   
 
    on_rez( integer vIntBgn ){
 
        //-- Clear pending and request URL on rez
 
        gLstPndKey = gLstPndTim = [];
 
        llRequestURL();
 
    }
 
   
 
    changed( integer vBitChg ){
 
        if ((CHANGED_REGION_START | CHANGED_REGION) & vBitChg){
 
            //-- Clear pending and request URL on region restart, or region change
 
            gLstPndKey = gLstPndTim = [];
 
            llRequestURL();
 
        }
 
    }
 
    /* Disabled until effectively supported by LL, see Known Bugs below
 
    touch_end( integer vIntTch ){
 
        if (!llDetectedTouchFace( 0 )){
 
            llMessageLinked( llGetLinkNumber(), 418, "", gKeyRqs  = llDetectedKey( 0 ));
 
            if (!~llListFindList( gLstPndKey, [gKeyRqs = " " + (string)gKeyRqs] )){
 
                ++gIntRqs;
 
                gLstPndKey = [gKeyRqs] + gLstPndKey;
 
                gLstPndTim = [llGetUnixTime() + 2] + gLstPndTim;
 
                llSetTimerEvent( 3.0 );
 
            }
 
        }
 
    }
 
    */
 
    http_request( key vKeySrc, string vStrMth, string vStrBdy ){
 
        if (URL_REQUEST_GRANTED == vStrMth){ //-- Got a url, push the bootsrap page
 
            llSetPrimitiveParams( [PRIM_TEXT, vStrBdy + "/", ZERO_VECTOR, 0.0] );
 
            llMessageLinked( llGetLinkNumber(), 418, "", gKeyRqs = llGetKey() );
 
            ++gIntRqs;
 
            gLstPndKey = [gKeyRqs = " " + (string)gKeyRqs] + gLstPndKey;
 
            gLstPndTim = [llGetUnixTime() + 2] + gLstPndTim;
 
            llSetTimerEvent( 3.0 );
 
        }else if (URL_REQUEST_DENIED == vStrMth){ //-- didn't get a url, push a warning page
 
            llSetPrimitiveParams( [PRIM_TEXT, "", ZERO_VECTOR, 0.0] );
 
            vStrBdy = gStrErr0 + (string)llGetOwner() + gStrErr1;
 
            llSetPrimMediaParams( 0, [PRIM_MEDIA_HOME_URL, vStrBdy, PRIM_MEDIA_CURRENT_URL, vStrBdy] );
 
        }else if ("GET" == vStrMth){ //-- got a page request
 
            //-- reuse the method string to see if it's a valid file path or the root
 
            //-- if the request has already timed out, it'll be blank and ignored
 
            if (vStrMth = llUnescapeURL( llDeleteSubString( llGetHTTPHeader( vKeySrc, "x-path-info" ), 0, 0 ) )){
 
                //-- send a request for the content we want
 
                llMessageLinked( LINK_SET, 418, vStrMth, vKeySrc );
 
                //-- store the request and a timeout value so we don't try to send pages after SL has already sent a timeout
 
                gLstPndKey += [vKeySrc];
 
                gLstPndTim += [llGetUnixTime() + 20];
 
                if (gLstPndTim == [""]){
 
                    llSetTimerEvent( 3.0 );
 
                }
 
            }
 
        }
 
    }
 
   
 
    link_message( integer vIntSrc, integer vIntDta, string vStrDta, key vKeyDta ){
 
        //-- only accept return status codes we understand.
 
        if (~vIntSrc = llListFindList( [101, 200, 201, 202, 203, 404], [vIntDta] )){
 
            if (!vIntSrc){ //-- 101 is special handling
 
                vKeyDta = " " + (string)(gKeyRqs = vKeyDta);
 
            }
 
            //-- is the request still waiting to be served?
 
            if (~vIntSrc = llListFindList( gLstPndKey, [vKeyDta] )){
 
                if (vKeyDta){
 
                    //-- server the request, remove it from the pending queue
 
                    llHTTPResponse( vKeyDta, vIntDta, vStrDta );
 
                }else{
 
                    if (llGetKey() == gKeyRqs){
 
                        llSetPrimMediaParams( 0, [PRIM_MEDIA_HOME_URL, vStrDta, PRIM_MEDIA_CURRENT_URL, vStrDta] );
 
                    }else{
 
                        llLoadURL( gKeyRqs, "Teacup Server: " + llGetObjectName(), vStrDta );
 
                    }
 
                }
 
                gLstPndKey = llDeleteSubList( gLstPndKey, vIntSrc, vIntSrc );
 
                gLstPndTim = llDeleteSubList( gLstPndTim, vIntSrc, vIntSrc );
 
            }
 
        }
 
    }
 
   
 
    timer(){ //-- optimally, this runs once on an empty list for 3sec worth of server requests
 
        if (gLstPndTim != []){ //-- are there items in the pending queue?
 
            while (llList2Integer( gLstPndTim, 0 ) < llGetUnixTime()){
 
                if (gIntRqs){
 
                    if (gKeyRqs = llDeleteSubString( llList2String( gLstPndKey, 0 ), 0, 0 )){
 
                        if (llGetKey() == gKeyRqs){
 
                            llSetPrimMediaParams( 0, [PRIM_MEDIA_HOME_URL, gStrErr0 + (string)llGetOwner() + gStrErr1,
 
                                                      PRIM_MEDIA_CURRENT_URL, gStrErr0 + (string)llGetOwner() + gStrErr1] );
 
                        }else{
 
                            llLoadURL( gKeyRqs, "Teacup Server: " + llGetObjectName(), gStrErr0 + (string)llGetOwner() + gStrErr1 );
 
                        }
 
                        --gIntRqs;
 
                    }
 
                } //--loop through and remove expired pending requests
 
                gLstPndTim = llDeleteSubList( gLstPndTim, 0, 0 );
 
                gLstPndKey = llDeleteSubList( gLstPndKey, 0, 0 );
 
                if (gLstPndTim == []){ //-- if the queue is emptied, stop the timer and exit
 
                    llSetTimerEvent( (float)(gIntRqs = 0) );
 
                    return;
 
                }
 
            }
 
        }else{
 
            llSetTimerEvent( 0.0 );
 
        }
 
    }
 
}
 
/*//--                          License Text                          --//*/
 
/*//  Free to copy, use, modify, distribute, or sell, with attribution.  //*/
 
/*//    (C)2011 (CC-BY) [ http://creativecommons.org/licenses/by/3.0 ]    //*/
 
/*//  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ]  //*/
 
/*//  All usages must contain a plain text copy of the previous 2 lines.  //*/
 
/*//--                                                                  --//*/</lsl>
 
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
=== Saucer ===
 
----
 
* Save in a script named "Saucer"
 
<lsl>/*( Saucer v0.3_ )*/
 
 
/*//-- Server Default Page Variables --//*/
 
string  gStrTS0 = "data:text/html;charset:utf-8,
 
<html>
 
    <head>
 
        <base href='"; //-- Insertion point: Base server address (pulled from prim description)
 
string gStrTS1 = "/'>
 
        <title>"; //-- Insertion point: Title (pulled from prim name)
 
string gStrTS2 = "</title>
 
        <script type='text/javascript' defer='1' src='teacup.js'></script>"; //-- Insertion point: site js/css if present (unsupported as of yet)
 
string  gStrTS3 = "
 
    </head>
 
    <body>
 
        <h1>Teacup Server Test Page</h1>
 
        <hr/>
 
        <p>ZOMG it works. If you are Still reading this, The File Service failed to respond or there is no index page available</p>
 
        <hr/>
 
        <p style='text-align:center;font-size:50%;'>"; //-- Insertion point: Known installed services
 
string  gStrTS4 = "<br/><a href='secondlife:///app/agent/"; //-- Insertion point:  Owners key for contact
 
string  gStrTS5 = "/about'>Contact Owner</a>
 
    </body>
 
</html>";
 
list    gLstSvc = ["Teacup/Saucer v0.3"]; //-- Known Services (only partially implemented)
 
 
/*//-- Request Handler variables --//*/
 
list    gLstPag = [""]; //-- List of known pages
 
list    gLst404Tim; //-- 404 timeout
 
list    gLst404Key; //-- 404 request key
 
list    gLst404Pag; //-- 404 page name (used for discovery only)
 
integer gIntLnk;    //-- link number to save on function calls
 
  
 
default{
 
default{
    state_entry(){
+
state_entry(){ //-- Request URL on start up
        gIntLnk = llGetLinkNumber();
+
llRequestURL();
        llMessageLinked( LINK_SET, 418, " Server Start", NULL_KEY );
+
}
    }
+
   
+
on_rez( integer vIntBgn ){ //-- Clear pending and request new URL on rez
    link_message( integer vIntSrc, integer vIntDta, string vStrDta, key vKeyDta ){
+
gLstPndKey = gLstPndTim = [];
        if (vKeyDta){ //-- server traffic?
+
llRequestURL();
            if (418 == vIntDta && gIntLnk == vIntSrc){ //-- server request?
+
}
                if (!~vIntDta = llListFindList( gLstPag, [vStrDta = llList2String( llParseStringKeepNulls( vStrDta, ["?", "#"], [] ), 0 )] )){ //-- is that an invalid page?
+
                    gLst404Key += [vKeyDta];
+
changed( integer vBitChg ){ //-- Clear pending and re-request URL on region change/restart
                    gLst404Pag += [vStrDta];
+
if ((CHANGED_REGION_START | CHANGED_REGION) & vBitChg){
                    gLst404Tim += [llGetUnixTime() + 2]; //-- this actually amount to a ~2.25sec timeout
+
gLstPndKey = gLstPndTim = []; //-- Consider sending custom 404's or 205's here instead
                    if (gLst404Tim == [""]){ //-- only bump the timer if this is the only 404 pending.
+
llRequestURL();
                        llSetTimerEvent( 0.75 ); //-- give unadvertised backend services time to respond
+
}
                    }
+
}
                }else if (!vIntDta){
+
                    llMessageLinked( vIntSrc,
+
http_request( key vKeySrc, string vStrMth, string vStrBdy ){
                                    101, //-- Server Push Root Mode //-- Next line: -- Insert Address
+
integer vIntMth = llListFindList( [URL_REQUEST_DENIED, URL_REQUEST_GRANTED, "POST", "GET"], [vStrMth] );
                                    gStrTS0 + llList2String( llGetPrimitiveParams( [PRIM_TEXT] ), 0 ) +
+
if (4 & vIntMth){
                                    gStrTS1 + llGetObjectName() +                //-- Insert Title
+
//-- trap unused here, check we hasve a valid non root file name on the next line
                                    gStrTS2 + //-- not yet implemented            //-- Insert site js/css
+
}else if (2 & vIntMth){ if (vStrMth = llUnescapeURL( llDeleteSubString( llGetHTTPHeader( vKeySrc, "x-path-info" ), 0, 0 ) )){
                                    gStrTS3 + llDumpList2String( gLstSvc, " " ) + //-- Insert Known Services
+
llMessageLinked( LINK_SET,
                                    gStrTS4 + (string)llGetOwner() +              //-- Insert Owner
+
                418,
                                    gStrTS5,                                     //-- End Default file
+
                vStrMth + "?" + llGetHTTPHeader( vKeySrc, "x-query-string" ) +
                                    vKeyDta );
+
                "#ip=" + llGetHTTPHeader( vKeySrc, "x-remote-ip" ) + "&" + vStrBdy,
                }
+
                vKeySrc );
            }else if (~vIntSrc = llListFindList( gLst404Key, [vKeyDta] )){
+
if (gLstPndTim == []){
                if (200 == vIntDta){ //-- auto discovery of  unadvertised pages
+
llSetTimerEvent( 3.0 );
                    gLstPag += [llList2String( gLst404Pag, vIntSrc )];
+
}
                }
+
gLstPndKey += [vKeySrc];
                gLst404Tim = llDeleteSubList( gLst404Tim, vIntSrc, vIntSrc );
+
gLstPndTim += [llGetUnixTime() + 20];
                gLst404Key = llDeleteSubList( gLst404Key, vIntSrc, vIntSrc );
+
}//-- can handle root requests here later if we want
                gLst404Pag = llDeleteSubList( gLst404Pag, vIntSrc, vIntSrc );
+
}else{ //-- URL request response: set hover text, push proper page format to prim face
            }
+
if (vIntMth || llList2String( llGetPrimitiveParams( [PRIM_TEXT] ), 0 ) != "No URL"){
        }else if (22 == llAbs( vIntDta ) && NULL_KEY == (string)vKeyDta){
+
llSetPrimitiveParams( [PRIM_TEXT, llList2String( ["No URL", (vStrBdy += "/")], vIntMth ), <(float)(!vIntMth), 0.5, 0.5>, 0.0] );
            //-- add handler for svc string and site js/css
+
vStrBdy = gStrTBS1xx + llList2String( ["", gStrTBS1a1 + vStrBdy + gStrTBS1a2], vIntMth ) + gStrTBS2xx +
            if (vIntDta & 0x800000){ //-- negative = remove
+
  llList2String( [gStrTBS2a1, gStrTBS2a1], vIntMth ) + gStrTBS3xx + (string)llGetOwner() + gStrTBS4xx;
                if (~vIntDta = llListFindList( gLstPag, [vStrDta] )){
+
llSetPrimMediaParams( 0, [PRIM_MEDIA_HOME_URL, vStrBdy, PRIM_MEDIA_CURRENT_URL, vStrBdy] );
                    gLstPag = llDeleteSubList( gLstPag, vIntDta, vIntDta );
+
llMessageLinked( LINK_SET, 418, "Teacup URL changed", NULL_KEY );
                }
+
} //-- Next Line: abuse Sensor Repeat to retry on no URL in 5 mins
            }else{ //-- positive == add
+
llSensorRepeat( "", llGetKey(), AGENT, 0.0001 * !vIntMth, PI, 300.0 * !vIntMth );
                if (!~vIntDta = llListFindList( gLstPag, [vStrDta] )){
+
}
                    //-- add handling for default js/css injection.
+
}
                    gLstPag += [vStrDta];
+
                }
+
link_message( integer vIntSrc, integer vIntDta, string vStrDta, key vKeyDta ){
            }
+
if (~vIntSrc = llListFindList( [200, 201, 202, 204, 403, 404], [vIntDta] )){ //-- valid return code?
        }
+
if (~vIntSrc = llListFindList( gLstPndKey, [vKeyDta] )){ //-- request still pending?
    }
+
llHTTPResponse( vKeyDta, vIntDta, vStrDta ); //-- Serve request & remove from pending queue
   
+
gLstPndKey = llDeleteSubList( gLstPndKey, vIntSrc, vIntSrc );
    timer(){
+
gLstPndTim = llDeleteSubList( gLstPndTim, vIntSrc, vIntSrc );
        //-- timer for 404 messages
+
}
        if (gLst404Tim != []){ //-- are there items in the pending queue?
+
}
            while (llList2Integer( gLst404Tim, 0 ) < llGetUnixTime()){
+
}
                //--send 404 then loop through and remove expired pending 404s
+
                llMessageLinked( llGetLinkNumber(), 404, "Not Found", llList2Key( gLst404Key, 0 ) );
+
timer(){ //-- Optimally, runs once on an empty list for 3sec worth requests
                gLst404Tim = llDeleteSubList( gLst404Tim, 0, 0 );
+
if (gLstPndTim != []){ //-- Pending requests?
                gLst404Key = llDeleteSubList( gLst404Key, 0, 0 );
+
while (llList2Integer( gLstPndTim, 0 ) < llGetUnixTime()){ //-- are any expired?
                gLst404Pag = llDeleteSubList( gLst404Pag, 0, 0 );
+
gLstPndTim = llDeleteSubList( gLstPndTim, 0, 0 ); //-- remove expired
                if (gLst404Tim == []){ //-- if the queue is emptied, stop the timer and exit
+
gLstPndKey = llDeleteSubList( gLstPndKey, 0, 0 );
                    llSetTimerEvent( 0.0 );
+
if (gLstPndTim == []){ //-- Pending Queue empty?
                    return;
+
llSetTimerEvent( 0.0 ); //-- Stop timer, and exit
                }
+
return;
            }
+
}
        }else{
+
}
            llSetTimerEvent( 0.0 );
+
}else{ //-- No pending requests, turn off the timer
        }
+
llSetTimerEvent( 0.0 );
    }
+
}
 +
}
 +
 +
sensor( integer vIntNul ){
 +
//-- Dummy Sensor event required by SCR-53
 +
}
 +
 +
no_sensor(){ //-- URL re-request 5 mins after failure
 +
llSensorRemove();
 +
llRequestURL();
 +
}
 
}
 
}
 
/*//--                          License Text                          --//*/
 
/*//--                          License Text                          --//*/
Line 296: Line 159:
 
/*//  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ]  //*/
 
/*//  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ]  //*/
 
/*//  All usages must contain a plain text copy of the previous 2 lines.  //*/
 
/*//  All usages must contain a plain text copy of the previous 2 lines.  //*/
/*//--                                                                  --//*/</lsl>
+
/*//--                                                                  --//*/</source>
 
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
  
 
=== teacup.js ===
 
=== teacup.js ===
 
----
 
----
* [[User:Void_Singer/Red_Tea|Red Tea]] expects this file saved in a notecard named "teacup.js"
+
* [[User:Void_Singer/Red_Tea|Red Tea]] expects this file saved in a notecard named "'''teacup.js'''"
 
* Other File systems may have different expectations for loading plain text files; Consult their documentation for details.
 
* Other File systems may have different expectations for loading plain text files; Consult their documentation for details.
  <javascript>/*  teacup.js v0.3 <!-- */
+
  <source lang="javascript">/*  teacup.js v0.5 <!-- */
                   
+
function addEventListener( instance, eventName, listener ){
+
    var listenerFn = listener;
+
    if (instance.addEventListener){
+
        instance.addEventListener( eventName, listenerFn, false );
+
    }else if (instance.attachEvent){
+
        listenerFn = function(){ listener( window.event ); }
+
        instance.attachEvent( 'on' + eventName, listenerFn );
+
    }
+
}
+
  
function uBoil( vEvent ){
+
var u0=function(){//-- faster!
    if (vEvent.preventDefault){
+
if(window.addEventListener){
        vEvent.preventDefault();
+
return function(l0,l1,l2){
    }
+
l0.addEventListener(l1,l2,false);
    if (typeof( this.href ) != 'undefined'){
+
};
        uSteep( this.href );
+
}else if(window.attachEvent){
    }else{
+
return function(l0,l1,l2){
        var vSugar = document.createElement( 'link' );
+
l0.attachEvent('on'+l1,l2);
        vSugar.setAttribute( 'rel', 'stylesheet' );
+
};
        vSugar.setAttribute( 'type', 'text/css' );
+
}
        document.getElementsByTagName( 'head' )[ 0 ].appendChild( vSugar );
+
}();
        vSugar.href = 'teacup.css';
+
        uSteep( vEvent );
+
    }
+
}
+
  
function uSteep( vPage ){
+
function u1(l0){
    vTeabag = document.createElement( 'script' );
+
if (l0.preventDefault){
    vTeabag.setAttribute( 'type', 'text/javascript' );
+
l0.preventDefault();
    document.getElementsByTagName( 'head' )[ 0 ].appendChild( vTeabag );
+
}//-- cross-compat?
    vEggTimer = setTimeout( 'uPour( \'504 Gateway Timeout\' )', 23000 );
+
v1=document.createElement('script');
    addEventListener( vTeabag, 'load', uPour );
+
document.head.appendChild(v1);
    vTeabag.src = vPage;
+
u0(v1,'load',u2);//-- no 'type' breaks standards
 +
v1.src=(typeof(this.href)!='undefined')?this.href:l0;
 +
v2=setTimeout('u2(\'504 Gateway Timeout or 404 Not Found\')',23000);
 
}
 
}
 +
//-- v0,v1,v2 exist here
 +
function u2(l0){//-- Achtung Baby!
 +
clearTimeout(v2);
 +
if(typeof(v0)=='undefined'){
 +
if (typeof(l0)!='object'){
 +
alert(l0+'\n'+v1.src.substring(81)+'\n');
 +
}//-- catches timeouts, send js to alert others
 +
}else{
 +
document.body.innerHTML=v0;
 +
for(var l1=0;l1<document.links.length;l1++){
 +
if (~document.links[l1].href.indexOf('.tsp')){
 +
u0(document.links[l1],'click',u1);
 +
}
 +
}//-- the double split seemed more effective than the regex
 +
document.title=decodeURI(v1.src.split(document.head.getElementsByTagName('base')[0].href)[1].split('.tsp')[0]);
 +
var l2;
 +
v2=document.body.getElementsByTagName('script');
 +
for(l1=0;l1<v2.length;l1++){
 +
l2=document.createElement('script');
 +
if(~v2[l1].src.indexOf('.js')){
 +
l2.src=v2[l1].src;//-- no 'type' breaks standards
 +
}else if(v2[l1].childNodes.length){
 +
l2.appendChild(v2[l1].childNodes[0]);
 +
}
 +
v2[l1].parentNode.replaceChild(l2,v2[l1]);
 +
}//-- extend script jumpstarter to link/css?
 +
}//-- next line: clean up; aisle 2
 +
document.head.removeChild(v1);
 +
v0=v1=v2=(function(){})();
 +
}//-- like that evil 'undefined' hack?
  
function uPour( vErr ){
+
window.onload=function(){//-- webkit is stupid and doesn't obey script tag defer/async attributes
    clearTimeout( vEggTimer );
+
document.head=document.head||document.getElementsByTagName('head')[0];
    if (typeof( vTea ) == 'undefined'){
+
document.body=document.body||document.getElementsByTagName('body')[0];
        if (typeof( vErr ) != 'text'){
+
u1('index.tsp');//-- default page name
            vErr == '000 Malformed Page';
+
};
        }
+
        alert( vErr + '\n' + vTeabag.src.substring( 81 ) );
+
    }else{
+
        document.getElementsByTagName( 'body' )[ 0 ].innerHTML = vTea;
+
        for (i = 0; i < document.links.length; i++){
+
            if (~document.links[ i ].href.indexOf( '.tsp' )){
+
                addEventListener( document.links[ i ], 'click', uBoil );
+
            }
+
        }
+
    }
+
    document.getElementsByTagName( 'head' )[ 0 ].removeChild( vTeabag );
+
    vTeabag = vTea = vEggTimer = (function(){})();
+
}
+
 
+
uBoil( 'index.tsp' );
+
  
 
/* -->
 
/* -->
 
   (C)2011 (CC-BY) [ http://creativecommons.org/licenses/by/3.0 ]
 
   (C)2011 (CC-BY) [ http://creativecommons.org/licenses/by/3.0 ]
  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ] */</javascript>
+
  Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ] */</source>
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
+
'''NOTE''': I recommend removing all the comments to make the file smaller (anything starting with '''//--''' to the end of the line)
 
+
=== teacup.css ===
+
----
+
* [[User:Void_Singer/Red_Tea|Red Tea]] expects this file saved in a notecard named "teacup.css"
+
* Other File systems may have different expectations for loading plain text files; Consult their documentation for details.
+
<css>/* Teacup.css v0.3 <!-- */
+
 
+
h1 {
+
    color: red;
+
}
+
p {
+
    color: green;
+
}
+
 
+
/*-->*/</css>
+
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
}}
 
}}
  
 
{{void-box
 
{{void-box
|title=Protocol
+
|title=Protocols
 
|content=
 
|content=
This section is not required reading for users, it is really only of use to scripters looking understand of extend it.
+
This section is not required reading for users, it is really only of use to scripters looking understand or extend it.
 
=== Server Protocols ===
 
=== Server Protocols ===
 
----
 
----
==== File Requests ====
+
==== Outgoing Messages ====
The server communicates it's needs in the following format
+
----
<lsl>llMessageLinked( LINK_SET, REQUEST_CODE, REQUEST_PAGE, REQUEST_KEY );</lsl>
+
The server has only two outgoing message formats. The first is general advertisement, and can be used by any File Service or extension to advertise information about itself.
* LINK_SET: Request is broadacst to all prims (exception, the root page "" is requested in the local prim only)
+
<source lang="lsl2">llMessageLinked( LINK_SET, TEACUP_MESSAGE, "Message", NULL_KEY );</source>
* REQUEST_CODE: at this time, for simplicity, there is only one request code, 418
+
Where "Message" is anything the script wants to advertise
* REQUEST_PAGE: the format is <nowiki>[<page name>?<search string>]</nowiki>
+
The server only sends one such message "'''Teacup URL Changed'''", and ignores messages sent by other scripts using this format.
** this will be extended for POST pages, format is undecided at this time
+
:When this message is sent, The current Address of the Server is placed in the prims hover text, other scripts may use the following to retrieve it<source lang="lsl2">string vStrAddress = llList2String( llGetLinkPrimitiveParams( SERVER_LINK_NUMBER, [PRIM_TEXT] ), 0 );</source>
* REQUEST_KEY: the return key for the requested data.
+
::The text will either be the servers URL, the text "'''No URL'''".
** NULL_KEY and a page name of " Server Start" will be sent when the server file availability handler resets.
+
  
==== Server Notes ====
+
The second Outgoing Message is a file request and has the format
* The server takes the site title from the Prim name it's in
+
<source lang="lsl2">llMessageLinked( LINK_SET, TEACUP_MESSAGE, REQUEST_PAGE, REQUEST_KEY );</source>
* The hovertext of the prim the server is in will contain it's base address (invisibly)
+
* TEACUP_MESSAGE: '''418'''
* The Server is '''2''' scripts (Teacup & Saucer) and 2 default files (teacup.js & teacup.css)
+
* REQUEST_PAGE: the format is <nowiki>"<page name>?<search string>#ip=<decimal IP Address>&<post data>]</nowiki>
** The default files require a File Service (Like [[User:Void Singer/Red_Tea|Red Tea]]) to load
+
** Ex: "'''index.tsp?#ip=127.0.0.1&'''" ''<-- file has no search string or post data''
* The Server includes a quick 404 handler to prevent missing or unknown files from slowing down page loading. It accepts some special commands listed below in the "File Service Protocol: Advanced" section. Any unknown page received with a 200 code for a pending request will automatically be added to the known list.
+
** the page name can be parsed with<source lang="lsl2">string vStrPageName = llList2String( llParseStringKeepNulls( REQUEST_PAGE, [], ["?","#"] ), 0 );</source>
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
+
* REQUEST_KEY: the return key for the requested data.
 +
valid Server Requests can be detected by checking that REQUEST_KEY is a valid key, and that TEACUP_MESSAGE is 418.
  
=== File Service Protocol ===
+
==== Incoming Messages ====
 
----
 
----
==== Basics ====
+
The server expects it will receive a return message in the following format for any file request made...
File Services receive data via link messages, in the following manner
+
<source lang="lsl2">llMessageLinked( REQUEST_SOURCE, RESPONSE_CODE, FILE_TEXT, REQUEST_KEY ); //-- sent from the File Service</source>
<lsl>link_message( integer REQUEST_SOURCE, integer REQUEST_CODE, string REQUEST_PAGE, key REQUEST_KEY )</lsl>
+
* REQUEST_SOURCE: The link number of the Teacup server that sent the request
File Services should minimally check that REQUEST_CODE is 418 (file request), and that REQUEST_KEY is a valid key to confirm it's a valid server request.
+
* RESPONSE_CODE: the following standard HTTP Response codes are currently recognized
: REQUEST_PAGE can be parsed with <lsl>list vLstPage = llParseStringKeepNulls( REQUEST_PAGE, ["?"], [] ); //-- The page name will be contained in: llList2String( vLstPage, 0 )</lsl>
+
** '''100''' "Continue": The server ignores this, make sure you send a normal response code as a follow up (see [[Saucer]] for details)
If the page name is ''NOT'' a page that this File Service handles, ''it should do nothing''. Otherwise it should return it's data in the following format:
+
** '''200''' "Success": Basic "yes we have it, here you go". anything that returns data can return this
<lsl>llMessageLinked( REQUEST_SOURCE, RESPONSE_CODE, FILE_TEXT, REQUEST_KEY );</lsl>
+
** '''201''' "Created": File was created for this request. probably best if the file was created for the request
* REQUEST_SOURCE: the link number that the server request came from.
+
** '''202''' "Accepted": Request acknowledged/received (content optional) probably best for commands that generate in world actions
* RESPONSE_CODE: one of the following integer values (they are all normal HTTP RESPONSE CODES)
+
** '''204''' "No Content": We got the request/data, but don't need to return anything. probably best for form data, FILE_TEXT should be blank.
** 200 (Success: default value, can be used for anything that returns content)
+
** '''403''' "Forbidden": We got the request, but aren't allowed to serve it. Expansion space for per user access (unused as of this writing).
** 201 (Created: optional alternative, probably best if the file was created for the request)
+
** '''404''' "Not Found": The file does not exist. Avoid using this unless you are reasonably sure the file isn't being served from another script.
** 202 (Accepted: optional alternative, probably best for commands that generate in world actions)
+
** 204 (No Content: optional alternative, probably best for form data, FILE_TEXT should be blank)
+
 
* FILE_TEXT: text of the file that was requested.
 
* FILE_TEXT: text of the file that was requested.
 
* REQUEST_KEY: server request key for this file.
 
* REQUEST_KEY: server request key for this file.
: Any return should be done within 20 seconds or it's results will be ignored.
+
: Any return should be done within 20 seconds or it's results will be ignored (Mostly an LSL limitation)
  
==== Advanced ====
+
==== Special Ranges and limitations ====
There are two style in which a File System can behave. Passive, and Active. Active is preferred.
+
----
 
+
:To promote predictable data flows within a scripted object, The following are "Official" limits for scripts to be included as compatible resources
Active File System services should advertise each file as soon as it's available using
+
# The '''0xx''' Range is off limits and may not be used (for compatibility with scripts that may use low numbers)
: ''File Added'': Sent when file is available in the file system<lsl>llMessageLinked( LINK_SET, 22, "name of the file", NULL_KEY ); //-- key MUST be the constant NULL_KEY</lsl>
+
# All normal [http://en.wikipedia.org/wiki/List_of_HTTP_status_codes HTTP Response codes] and ranges ('''1xx-5xx''') are reserved... do not use them unless they make sense or the server can actually send them properly.
When a file is removed from the file system it should send
+
# the Range of 6xx codes is reserved for use by File System scripts, limited to no more than 10 per range per script and will be registered by request, first (working script) come, first served. if you reasonably need more than 10, use the 8xx-9xx range
: ''File Removed'':Sent when a file is removed from the file system<lsl>llMessageLinked( LINK_SET, -22, "name of the file", NULL_KEY ); //-- key MUST be the constant NULL_KEY</lsl>
+
#* the following '''6xx''' codes are are registered at this time
Additionally, if and Active File System receives the " Server Start" message (418 code, plus " Server Start" text, and NULL_KEY), it should resend "File Added" for each of the files still available, but no faster than 10/second for file system.
+
#** [[Saucer]]
: Alternatively, if the file system can respond to normal requests in under 2 seconds, any file that is sent with response code 200 will automatically be added, and those would not need to be sent.
+
#*** '''600''' "File Removed" (sent with a file name and NULL_KEY)
 
+
#*** '''601''' "File Added" (sent with a file name and NULL_KEY)
 
+
# '''7xx''' codes are reserved for future expansion
Passive File Systems do not need to advertise their available files, but if they receive a request that they might reasonably expect to take more than 2 seconds to fill, they should immediately send
+
# '''8xx-9xx''' are unregulated, use at your own risk
: ''File Exists (Continue)'': Sent to prevent a 404 message for a file from slow passive file system elements. <lsl>llMessageLinked( REQUEST_SOURCE, 100, "", REQUEST_KEY );</lsl>
+
# All Codes must be below 1000 (this is to guarantee safe data transfer for other unrelated scripts)
and then follow up with their normal response. the 20 second return limit is still in effect.
+
:To Promote Maximum Stability and Flexibility
: Passive systems should ''NOT'' use response code 200, or if they do, ''MUST'' send "File Removed" messages when the file is removed. Use codes 201, 202, and 204 instead.
+
# Any Script that resides in the server prim should expect link messages of up to 40KiB
 +
#* File Service Scripts should attempt to send files 30KiB or smaller
 +
# File Service Scripts should not broadcast any link message larger than 4KiB using [[LINK_THIS]]
 +
#* Responses to requests should be targeted to the Server prim that requested the data for larger data sizes (optimally for ALL responses)
 +
# Scripts residing outside of the server prim should expect link messages with up to 4KiB of data (the max the server is able to request with normally)
 +
# {{HoverText|".tsp"|Teacup Server Page}} pages with inline scripts should avoid using the following variable/function names
 +
#* v0, v1, v2, v3 - v9 (these variables are used to: wrap the page for transport, serve as a loading container for pages, handle timeout messages for non-loading pages, and the rest being reserved for future support)
 +
#* u0, u1, u2, u3 - u9 (these functions handle: watching for {{HoverText|".tsp"|Teacup Server Page}} links to intercept, setting up the container for new {{HoverText|".tsp"|Teacup Server Page}} requests, loading the {{HoverText|".tsp"|Teacup Server Page}} files, and the rest being reserved for future support)
 +
#** you can request a new page from javascript by calling '''u1('PageName.tsp');''', however you may want to include some random info in the search string if refreshing the same page
 
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
^ [[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
  
Line 446: Line 303:
 
----
 
----
 
Teacup serves a modified html page called a Teacup Server Page (".tsp"), to overcome limitations in the LSL HTTP-in behavior.
 
Teacup serves a modified html page called a Teacup Server Page (".tsp"), to overcome limitations in the LSL HTTP-in behavior.
: if a requested filename ends with {{HoverText|".tsp"|Teacup Server Page}} it needs to conform to a specific format that is similar but not identical to the contents of a normal html webpage. To convert an html page into a tsp page, the follow actions must take place (an example of all of them may be found in the [[User:Void_Singer/Red Tea]] File Service
+
: if a requested filename ends with {{HoverText|".tsp"|Teacup Server Page}} it needs to conform to a specific format that is similar but not identical to the contents of a normal html webpage. To convert an html page into a tsp page, the follow actions must take place (an example of all of them may be found in the [[User:Void_Singer/Red Tea|Red Tea]] File Service)
 
* The file name should end with {{HoverText|".tsp"|Teacup Server Page}}
 
* The file name should end with {{HoverText|".tsp"|Teacup Server Page}}
 
* The contents are limited to what's valid ''inside'' a normal html "body" tag.
 
* The contents are limited to what's valid ''inside'' a normal html "body" tag.
Line 452: Line 309:
 
*# all backslash characters must be converted to double backslashes (may be applied on a line by line basis)
 
*# all backslash characters must be converted to double backslashes (may be applied on a line by line basis)
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"\\"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\\\"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"\\"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\\\"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
*# all single quote characters must be prefixed with a backslash (may be applied on a line by line basis)
+
*# all single quote characters must be prefixed with a literal backslash (may be applied on a line by line basis)
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"'"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\'"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"'"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\'"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
*# all line breaks should be converted to "\n" literals (may be applied on a line by line basis)
+
*# all line breaks should be converted to "'''\n'''" literals (may be applied on a line by line basis)
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"\n"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\n"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
 
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> [[llDumpList2String|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llDumpList2String</span>]]<span style="color: rgb(102, 204, 102);">(</span> [[llParseStringKeepNulls|<span style="color: rgb(160, 0, 0); font-weight: bold; text-decoration: none;">llParseStringKeepNulls</span>]]<span style="color: rgb(102, 204, 102);">(</span> file_string, <span style="color: rgb(102, 204, 102);">[<span style="color: rgb(0, 160, 0);">"\n"</span>]</span>, <span style="color: rgb(102, 204, 102);">[] )</span>, <span style="color: rgb(0, 160, 0);">"\\n"</span> <span style="color: rgb(102, 204, 102);">)</span>;</div>
*# The page should be prefixed with "vTea='" and suffixed with "';" (applies only to the whole page)
+
*# The page should be prefixed with "'''<nowiki>v0='</nowiki>''''" and suffixed with "'''<nowiki>';</nowiki>'''" (applies only to the whole page)
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(0, 160, 0);">"vTea='"</span> <span style="color: rgb(102, 204, 102);">+</span> file_string <span style="color: rgb(102, 204, 102);">+</span> <span style="color: rgb(0, 160, 0);">"';"</span>;</div>
+
*#* eg:<div style="font-family:monospace;font-size:12px;background-color:#EEE;">&nbsp;file_string <span style="color: rgb(102, 204, 102);">=</span> <span style="color: rgb(0, 160, 0);">"v0='"</span> <span style="color: rgb(102, 204, 102);">+</span> file_string <span style="color: rgb(102, 204, 102);">+</span> <span style="color: rgb(0, 160, 0);">"';"</span>;</div>
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
:[[User:Void Singer/Teacup#Return_to_Void_Singers_user_page|Return to top]]
 
}}
 
}}
Line 466: Line 323:
 
=== Compatible File Systems and Extensions ===
 
=== Compatible File Systems and Extensions ===
 
----
 
----
[[User:Void_Singer/Red_Tea|Red Tea]] - A very simple File Service System for a starting point.
+
* [[User:Void_Singer/Red_Tea|Red Tea]] - A very simple [[Server_In_a_Prim|SIP]] back end File Service for a starting point.
 +
* [[User:Void_Singer/Region_Stats|Region Stats]] - A single page Extension that gets live data from the region and refreshes every minute
 +
* [[User:Void_Singer/Saucer|Saucer]] - Optional Fast 404 Extension for preventing long timeouts for missing/bad pages
 +
* [[User:Void_Singer/Tea_Strainer|Tea Strainer]] - Optional Troubleshooting Monitor for checking messages being sent and received.
 +
* [[User:Void_Singer/To_Go_Cup|To Go Cup]] - Experimental Loader for Internal Browsers on v1x viewers (v1.23, Snowglobe, Cool, Phoenix, Imprudence, Ascent, Singularity, and others without {{HoverText|MOAP|Media On A Prim}} support)
  
 
=== Known Bugs ===
 
=== Known Bugs ===
 
----
 
----
* inline script/style elements are not evaluated at load time (inline attributes should work normally)
+
* inline style ''elements'' are not evaluated at load time (inline ''attributes'' should work normally)
** images have their own events, and can be used to trigger page load javascript
+
** using JavaScript to write these is a viable workaround at the moment
* document/body onload events are not triggered for {{HoverText|".tsp"|Teacup Server Page}} loads
+
* document/body onload events are not triggered for page loads
** images have their own events, and can be used to trigger page load javascript
+
** images have their own events, and can be used to trigger page load JavaScript
* Long object names may result in truncated server bootstrap page
+
** may not support this now that inline scripts work, but it's still under consideration
** Going to try shorting that page, in the meantime, use short names for the prim the server will reside in.
+
* Touch loads for viewers that don't support shared media fail to load to internal browsers ([https://jira.secondlife.com/browse/VWR-25554 VWR-25554])
+
** There's also a possibility that mangled pages will crash older webkit viewers. '''Touch handler is disabled until LL fix this.'''
+
 
* Fails to load directly to external browsers ([http://jira.secondlife.com/browse/VWR-25555 VWR-25555])
 
* Fails to load directly to external browsers ([http://jira.secondlife.com/browse/VWR-25555 VWR-25555])
** v2 internal browsers will only load it from the shared media bar "Load URL in browser" button ([https://jira.secondlife.com/browse/VWR-25554 VWR-25554])
+
** v2 internal browsers will only load it from the shared media bar "Load URL in browser" button ([http://jira.secondlife.com/browse/VWR-25554 VWR-25554])
 
*** Workaround for v2 is to load it via the shared media button, to the internal browser, then cut and paste into an external browser (should work with everything but IE)
 
*** Workaround for v2 is to load it via the shared media button, to the internal browser, then cut and paste into an external browser (should work with everything but IE)
 +
* POST from forms is wonky, it forces a page load, which we can only serve text to.
 +
** There isn't any way via JavaScript to intecept form POST data, nor a way to call it from the container we use
 +
** It may be possible to rework the server to use Frames and detect plain text loads of pages... will look into this.
 +
* normal onLoad event for the pages scripts are unsupported
 +
** unfortunately the webkit browser is stupid and doesn't obey async/defer script attributes so we have to use onload for the main page
 +
** fortunately, scripts will get refreshed in page order, so just calling the code is a viable alternative
  
 
=== Requested Feature Upgrades ===
 
=== Requested Feature Upgrades ===
 
----
 
----
* Add handling for POST action
+
* see about faking support for defer/async behavior in the tsp inline script tags
** ''Accepted'': would like feedback on how to handle this internally though.
+
** this will require supporting page onLoad events
* Add support per page javascript and css
+
* Trigger at least the normal load events manually
+
 
* Find a simple way to serve base64 encoded content such as images or sounds.
 
* Find a simple way to serve base64 encoded content such as images or sounds.
 
** '''NEED ASSISTANCE''': I can (probably) do it the same way we load pages, but this makes it hard for page writers.
 
** '''NEED ASSISTANCE''': I can (probably) do it the same way we load pages, but this makes it hard for page writers.
* Find a convenient way to set title on per page basis... probably use the page name
+
** considering a double-tap method where the server js library parses img tags for failures and bootstraps them with a modified name
** ''Accepted'': Page titles should match the internal file name minus the ".tsp" extension
+
** may be able to partially support this via css, but it's ugly for page writers =(
  
 
=== Change Log / Old Versions ===
 
=== Change Log / Old Versions ===
 
----
 
----
 
Most Recent Changes are at the top, old version links below.
 
Most Recent Changes are at the top, old version links below.
* [[User:Void_Singer/Teacup|Teacup/Saucer v0.3]]
+
* Code will be considered stable when it reaches 1.0
 +
* [[User:Void_Singer/Teacup|Teacup v0.5]]
 +
** Added support for inline script elements for .tsp files
 +
** Added IP Address to File Service Requests, to work towards authentication
 +
** Tweaked the teacup.js to run a bit faster, and condensed it to take a little less space.
 +
** Added Page Titles (matches the file name minus the {{HoverText|".tsp"|Teacup Server Page}} extension (only visible in external browsers)
 +
** Removed default CSS, this should be loadable per page now by JavaScript if nothing else.
 +
* [http://wiki.secondlife.com/w/index.php?title=User:Void_Singer/Teacup&oldid=1141627 Teacup v0.3.1]
 +
** Reabsorbed the bootstrap html page into this script and limited it's size for compatibility due to feedback
 +
** Moved Saucer to it's own page, as an option resource
 +
** Removed dead touch code due to incompatibility with llLoadURL ([http://jira.secondlife.com/browse/VWR-25554 VWR-25554] and [http://jira.secondlife.com/browse/VWR-25555 VWR-25555])
 +
** Added handling for POST data. Page request format should be stable now.
 +
* [http://wiki.secondlife.com/w/index.php?title=User:Void_Singer/Teacup&oldid=1141137 Teacup/Saucer v0.3]
 
** Server Test Page moved to saucer file availability handler
 
** Server Test Page moved to saucer file availability handler
** Server javascript page loader library (Teacup.js) moved to File Service System
+
** Server JavaScript page loader library (Teacup.js) moved to File Service System
 
*** Users should have less trouble editing the file now and can include their own functions in the same file
 
*** Users should have less trouble editing the file now and can include their own functions in the same file
 
** Sitewide CSS supported via "teacup.css"
 
** Sitewide CSS supported via "teacup.css"
Line 505: Line 379:
 
** Site address is discoverable by object scripts in the (hidden) prim hovertext
 
** Site address is discoverable by object scripts in the (hidden) prim hovertext
 
** Server now runs from any prim, not just the root.
 
** Server now runs from any prim, not just the root.
* [https://wiki.secondlife.com/w/index.php?title=User:Void_Singer/Teacup&oldid=1140984 Teacup v0.2]
+
* [http://wiki.secondlife.com/w/index.php?title=User:Void_Singer/Teacup&oldid=1140984 Teacup v0.2]
 
** Initial Public Release
 
** Initial Public Release
  

Latest revision as of 14:57, 22 January 2015

Teacup

Teacup/Saucer
What is it?


Teacup is an open source webserver front end for LSL's HTTP-in + MOAP functionality. It builds on the work of several other people including but not limited to, Kelly Linden, Torley Linden, Tali Rosca, Vegas Silverweb, and special mentions to Kate and/or Edelman Linden. The idea is to make it easy to serve web content from within SL, with a minimum of work or understanding, in the most standards compliant and flexible way possible.

How does it work?


Externally, when Teacup starts, it gets a URL and creates a Micro HTML page, which it bootstraps onto the prim face with a "data:" URN. Once loaded, that page requests a JavaScript library, which loads normally. Once loaded, the library bootstraps the specially prepared ".tsp" HTML content onto the current page, and captures all links targeting other ".tsp" files, so that it can do the same when they are requested. Internally the server broadcasts a message when it gets a page request, and any installed File Service scripts can reply with content, or the server will simply report back that it doesn't have the file requested if it doesn't receive a reply in a set time.

Why all the tea jokes?


Well I happen to like steam punk stuff, and it has a sort of pseudo-Victorian-era feel to it, but in all truth it was sparked by a bit of geek humor. There was an old april fool's joke passed around as an official sounding memo about creating a protocol for controlling coffee pots with HTTP (back before they actually started making net enabled appliances) and part of that proposal was that teapots should return an error response 418 "I'm a teapot" if you tried to make it brew coffee... since these servers are small, and 418 is safe to use internally because it wouldn't normally be encountered, I figured hey, Teapot --> Teacup --> Victorian Imagery --> Steam Punk --> teacup that is it's own teapot --> SIP --> many different types of tea --> Success!

... And thus was born the SIP, Teacup server, and Red Tea File Service
(developers are encouraged to keep the theme, but it's not a requirement)

Return to top

Quick Start

So you don't want to read a bunch of tech babble, you just want to make a website in a prim right? Well I can sympathize so here are some (mostly) easy steps to get you up and running fast...

  1. Follow the instructions on This Page or choose a File Service from the Compatible File Services and Extensions and follow those instructions.
  2. Optional: Copy the text in the grey box below Code: Region Stats into a script named "Region Stats", save it, and drop it into your rezzed box.
  3. Optional: Copy the text in the grey box below Code: Saucer into a script named "Saucer", save it, and drop it in your rezzed box.
  4. Optional: Copy the text in the grey box below Code: Tea Strainer into a script named "Tea_Strainer", save it, and drop it in your rezzed box.
  5. Copy the text in the grey box below Code: Teacup into a script named "Teacup v0.5", save it, and drop it in your rezzed box.
  6. Click on the top of your box if your webpage isn't already showing.
    • At this point you may want to rotate and/or resize your box.
    • If you like, you have my express permission to download (right click, "save as") and edit (with whichever image editor you prefer) the Teacup.png file to use as the default texture of your website prim.
  7. Show it off to your friends with MOAP enabled viewers.


If you run into any problems, simply start a thread with the problem you are having here, and I or some other technical wizard will assist you.

Return to top

Code

Teacup


  • Save in a MONO script named "Teacup v0.5"
/*( Teacup Server v0.5 )*/
 
 
 /*//-- Teacup Boot Strap Variables --//*/
 //-- Server Default page start (always loads)
string  gStrTBS1xx = "data:text/html;charset:utf-8,<html>\n";
 //-- Head Section start: loads with URL_REQUEST_GRANTED
string  gStrTBS1a1 = "	<head>\n		<base href='";
 //-- Head Section end & Address intsertion point: loads with URL_REQUEST_GRANTED
string  gStrTBS1a2 = "'/>\n		<script type='text/javascript' defer='' src='teacup.js'></script>\n	</head>\n";
 //-- Server Default message (always loads)
string  gStrTBS2xx = "	<body>\n		<h1>Teacup Server</h1>\n		<hr/>\n		<h2>If you are reading this, the <em>server</em> is working</h2>\n";
 //-- Default Error message: loads with URL_REQUEST_GRANTED
string  gStrTBS2a1 = "		<h3>(But the File Service may not be)</h3>\n";
 //-- URL Failure MessageL included for URL_REQUEST_DENIED
string  gStrTBS2b1 = "		<h3>(But the region failed to provide a URL)</h3>\n";
 //-- Server Default Footer start (always loads)
string  gStrTBS3xx = "		<hr/>\n		<p>Teacup/Saucer v0.3<br/><a href='secondlife:///app/agent/";
 //-- Server Default Footer end & Contact Owner key insertion point (always loads)
string  gStrTBS4xx = "/about'>Contact Owner?</a></p>\n	</body>\n</html>";
 
 /*//-- Pending Request Variables --//*/
list    gLstPndKey; //-- Key queue (pages waiting to be served)
list    gLstPndTim; //-- Timeout Value queue (Request Timeout)
//-- These (hopefully) prevent multiple responses if SL sends a timeout.
 
 
default{
	state_entry(){ //-- Request URL on start up
		llRequestURL();
	}
 
	on_rez( integer vIntBgn ){ //-- Clear pending and request new URL on rez
		gLstPndKey = gLstPndTim = [];
		llRequestURL();
	}
 
	changed( integer vBitChg ){ //-- Clear pending and re-request URL on region change/restart
		if ((CHANGED_REGION_START | CHANGED_REGION) & vBitChg){
			gLstPndKey = gLstPndTim = []; //-- Consider sending custom 404's or 205's here instead
			llRequestURL();
		}
	}
 
	http_request( key vKeySrc, string vStrMth, string vStrBdy ){
		integer vIntMth = llListFindList( [URL_REQUEST_DENIED, URL_REQUEST_GRANTED, "POST", "GET"], [vStrMth] );
		if (4 & vIntMth){
			//-- trap unused here, check we hasve a valid non root file name on the next line
		}else if (2 & vIntMth){ if (vStrMth = llUnescapeURL( llDeleteSubString( llGetHTTPHeader( vKeySrc, "x-path-info" ), 0, 0 ) )){
			llMessageLinked( LINK_SET,
			                 418,
			                 vStrMth + "?" + llGetHTTPHeader( vKeySrc, "x-query-string" ) +
			                 "#ip=" + llGetHTTPHeader( vKeySrc, "x-remote-ip" ) + "&" + vStrBdy,
			                 vKeySrc );
			if (gLstPndTim == []){
				llSetTimerEvent( 3.0 );
			}
			gLstPndKey += [vKeySrc];
			gLstPndTim += [llGetUnixTime() + 20];
		}//-- can handle root requests here later if we want
		}else{ //-- URL request response: set hover text, push proper page format to prim face
			if (vIntMth || llList2String( llGetPrimitiveParams( [PRIM_TEXT] ), 0 ) != "No URL"){
				llSetPrimitiveParams( [PRIM_TEXT, llList2String( ["No URL", (vStrBdy += "/")], vIntMth ), <(float)(!vIntMth), 0.5, 0.5>, 0.0] );
				vStrBdy = gStrTBS1xx + llList2String( ["", gStrTBS1a1 + vStrBdy + gStrTBS1a2], vIntMth ) + gStrTBS2xx +
				  llList2String( [gStrTBS2a1, gStrTBS2a1], vIntMth ) + gStrTBS3xx + (string)llGetOwner() + gStrTBS4xx;
				llSetPrimMediaParams( 0, [PRIM_MEDIA_HOME_URL, vStrBdy, PRIM_MEDIA_CURRENT_URL, vStrBdy] );
				llMessageLinked( LINK_SET, 418, "Teacup URL changed", NULL_KEY );
			} //-- Next Line: abuse Sensor Repeat to retry on no URL in 5 mins
			llSensorRepeat( "", llGetKey(), AGENT, 0.0001 * !vIntMth, PI, 300.0 * !vIntMth );
		}
	}
 
	link_message( integer vIntSrc, integer vIntDta, string vStrDta, key vKeyDta ){
		if (~vIntSrc = llListFindList( [200, 201, 202, 204, 403, 404], [vIntDta] )){ //-- valid return code?
			if (~vIntSrc = llListFindList( gLstPndKey, [vKeyDta] )){ //-- request still pending?
				llHTTPResponse( vKeyDta, vIntDta, vStrDta ); //-- Serve request & remove from pending queue
				gLstPndKey = llDeleteSubList( gLstPndKey, vIntSrc, vIntSrc );
				gLstPndTim = llDeleteSubList( gLstPndTim, vIntSrc, vIntSrc );
			}
		}
	}
 
	timer(){ //-- Optimally, runs once on an empty list for 3sec worth requests
		if (gLstPndTim != []){ //-- Pending requests?
			while (llList2Integer( gLstPndTim, 0 ) < llGetUnixTime()){ //-- are any expired?
				gLstPndTim = llDeleteSubList( gLstPndTim, 0, 0 ); //-- remove expired
				gLstPndKey = llDeleteSubList( gLstPndKey, 0, 0 );
				if (gLstPndTim == []){ //-- Pending Queue empty?
					llSetTimerEvent( 0.0 ); //-- Stop timer, and exit
					return;
				}
			}
		}else{ //-- No pending requests, turn off the timer
			llSetTimerEvent( 0.0 );
		}
	}
 
	sensor( integer vIntNul ){
		//-- Dummy Sensor event required by SCR-53
	}
 
	no_sensor(){ //-- URL re-request 5 mins after failure
		llSensorRemove();
		llRequestURL();
	}
}
/*//--                           License Text                           --//*/
/*//  Free to copy, use, modify, distribute, or sell, with attribution.   //*/
/*//    (C)2011 (CC-BY) [ http://creativecommons.org/licenses/by/3.0 ]    //*/
/*//   Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ]  //*/
/*//  All usages must contain a plain text copy of the previous 2 lines.  //*/
/*//--                                                                  --//*/

^ Return to top

teacup.js


  • Red Tea expects this file saved in a notecard named "teacup.js"
  • Other File systems may have different expectations for loading plain text files; Consult their documentation for details.
/*  teacup.js v0.5 <!-- */
 
var u0=function(){//-- faster!
	if(window.addEventListener){
		return function(l0,l1,l2){
			l0.addEventListener(l1,l2,false);
		};
	}else if(window.attachEvent){
		return function(l0,l1,l2){
			l0.attachEvent('on'+l1,l2);
		};
	}
}();
 
function u1(l0){
	if (l0.preventDefault){
		l0.preventDefault();
	}//-- cross-compat?
	v1=document.createElement('script');
	document.head.appendChild(v1);
	u0(v1,'load',u2);//-- no 'type' breaks standards
	v1.src=(typeof(this.href)!='undefined')?this.href:l0;
	v2=setTimeout('u2(\'504 Gateway Timeout or 404 Not Found\')',23000);
}
//-- v0,v1,v2 exist here
function u2(l0){//-- Achtung Baby!
	clearTimeout(v2);
	if(typeof(v0)=='undefined'){
		if (typeof(l0)!='object'){
			alert(l0+'\n'+v1.src.substring(81)+'\n');
		}//-- catches timeouts, send js to alert others
	}else{
		document.body.innerHTML=v0;
		for(var l1=0;l1<document.links.length;l1++){
			if (~document.links[l1].href.indexOf('.tsp')){
				u0(document.links[l1],'click',u1);
			}
		}//-- the double split seemed more effective than the regex
		document.title=decodeURI(v1.src.split(document.head.getElementsByTagName('base')[0].href)[1].split('.tsp')[0]);
		var l2;
		v2=document.body.getElementsByTagName('script');
		for(l1=0;l1<v2.length;l1++){
			l2=document.createElement('script');
			if(~v2[l1].src.indexOf('.js')){
				l2.src=v2[l1].src;//-- no 'type' breaks standards
			}else if(v2[l1].childNodes.length){
				l2.appendChild(v2[l1].childNodes[0]);
			}
			v2[l1].parentNode.replaceChild(l2,v2[l1]);
		}//-- extend script jumpstarter to link/css?
	}//-- next line: clean up; aisle 2
	document.head.removeChild(v1);
	v0=v1=v2=(function(){})();
}//-- like that evil 'undefined' hack?
 
window.onload=function(){//-- webkit is stupid and doesn't obey script tag defer/async attributes
	document.head=document.head||document.getElementsByTagName('head')[0];
	document.body=document.body||document.getElementsByTagName('body')[0];
	u1('index.tsp');//-- default page name
};
 
/* -->
  (C)2011 (CC-BY) [ http://creativecommons.org/licenses/by/3.0 ]
 Void Singer [ https://wiki.secondlife.com/wiki/User:Void_Singer ] */

NOTE: I recommend removing all the comments to make the file smaller (anything starting with //-- to the end of the line)

Return to top

Protocols

This section is not required reading for users, it is really only of use to scripters looking understand or extend it.

Server Protocols


Outgoing Messages


The server has only two outgoing message formats. The first is general advertisement, and can be used by any File Service or extension to advertise information about itself.

llMessageLinked( LINK_SET, TEACUP_MESSAGE, "Message", NULL_KEY );

Where "Message" is anything the script wants to advertise The server only sends one such message "Teacup URL Changed", and ignores messages sent by other scripts using this format.

When this message is sent, The current Address of the Server is placed in the prims hover text, other scripts may use the following to retrieve it
string vStrAddress = llList2String( llGetLinkPrimitiveParams( SERVER_LINK_NUMBER, [PRIM_TEXT] ), 0 );
The text will either be the servers URL, the text "No URL".

The second Outgoing Message is a file request and has the format

llMessageLinked( LINK_SET, TEACUP_MESSAGE, REQUEST_PAGE, REQUEST_KEY );
  • TEACUP_MESSAGE: 418
  • REQUEST_PAGE: the format is "<page name>?<search string>#ip=<decimal IP Address>&<post data>]
    • Ex: "index.tsp?#ip=127.0.0.1&" <-- file has no search string or post data
    • the page name can be parsed with
      string vStrPageName = llList2String( llParseStringKeepNulls( REQUEST_PAGE, [], ["?","#"] ), 0 );
  • REQUEST_KEY: the return key for the requested data.

valid Server Requests can be detected by checking that REQUEST_KEY is a valid key, and that TEACUP_MESSAGE is 418.

Incoming Messages


The server expects it will receive a return message in the following format for any file request made...

llMessageLinked( REQUEST_SOURCE, RESPONSE_CODE, FILE_TEXT, REQUEST_KEY ); //-- sent from the File Service
  • REQUEST_SOURCE: The link number of the Teacup server that sent the request
  • RESPONSE_CODE: the following standard HTTP Response codes are currently recognized
    • 100 "Continue": The server ignores this, make sure you send a normal response code as a follow up (see Saucer for details)
    • 200 "Success": Basic "yes we have it, here you go". anything that returns data can return this
    • 201 "Created": File was created for this request. probably best if the file was created for the request
    • 202 "Accepted": Request acknowledged/received (content optional) probably best for commands that generate in world actions
    • 204 "No Content": We got the request/data, but don't need to return anything. probably best for form data, FILE_TEXT should be blank.
    • 403 "Forbidden": We got the request, but aren't allowed to serve it. Expansion space for per user access (unused as of this writing).
    • 404 "Not Found": The file does not exist. Avoid using this unless you are reasonably sure the file isn't being served from another script.
  • FILE_TEXT: text of the file that was requested.
  • REQUEST_KEY: server request key for this file.
Any return should be done within 20 seconds or it's results will be ignored (Mostly an LSL limitation)

Special Ranges and limitations


To promote predictable data flows within a scripted object, The following are "Official" limits for scripts to be included as compatible resources
  1. The 0xx Range is off limits and may not be used (for compatibility with scripts that may use low numbers)
  2. All normal HTTP Response codes and ranges (1xx-5xx) are reserved... do not use them unless they make sense or the server can actually send them properly.
  3. the Range of 6xx codes is reserved for use by File System scripts, limited to no more than 10 per range per script and will be registered by request, first (working script) come, first served. if you reasonably need more than 10, use the 8xx-9xx range
    • the following 6xx codes are are registered at this time
      • Saucer
        • 600 "File Removed" (sent with a file name and NULL_KEY)
        • 601 "File Added" (sent with a file name and NULL_KEY)
  4. 7xx codes are reserved for future expansion
  5. 8xx-9xx are unregulated, use at your own risk
  6. All Codes must be below 1000 (this is to guarantee safe data transfer for other unrelated scripts)
To Promote Maximum Stability and Flexibility
  1. Any Script that resides in the server prim should expect link messages of up to 40KiB
    • File Service Scripts should attempt to send files 30KiB or smaller
  2. File Service Scripts should not broadcast any link message larger than 4KiB using LINK_THIS
    • Responses to requests should be targeted to the Server prim that requested the data for larger data sizes (optimally for ALL responses)
  3. Scripts residing outside of the server prim should expect link messages with up to 4KiB of data (the max the server is able to request with normally)
  4. ".tsp" pages with inline scripts should avoid using the following variable/function names
    • v0, v1, v2, v3 - v9 (these variables are used to: wrap the page for transport, serve as a loading container for pages, handle timeout messages for non-loading pages, and the rest being reserved for future support)
    • u0, u1, u2, u3 - u9 (these functions handle: watching for ".tsp" links to intercept, setting up the container for new ".tsp" requests, loading the ".tsp" files, and the rest being reserved for future support)
      • you can request a new page from javascript by calling u1('PageName.tsp');, however you may want to include some random info in the search string if refreshing the same page

^ Return to top

Teacup Server Page (".tsp") Format


Teacup serves a modified html page called a Teacup Server Page (".tsp"), to overcome limitations in the LSL HTTP-in behavior.

if a requested filename ends with ".tsp" it needs to conform to a specific format that is similar but not identical to the contents of a normal html webpage. To convert an html page into a tsp page, the follow actions must take place (an example of all of them may be found in the Red Tea File Service)
  • The file name should end with ".tsp"
  • The contents are limited to what's valid inside a normal html "body" tag.
  • It also requires the following minor changes, made in order:
    1. all backslash characters must be converted to double backslashes (may be applied on a line by line basis)
    2. all single quote characters must be prefixed with a literal backslash (may be applied on a line by line basis)
    3. all line breaks should be converted to "\n" literals (may be applied on a line by line basis)
    4. The page should be prefixed with "v0=''" and suffixed with "';" (applies only to the whole page)
      • eg:
         file_string = "v0='" + file_string + "';";
Return to top

Notes

Compatible File Systems and Extensions


  • Red Tea - A very simple SIP back end File Service for a starting point.
  • Region Stats - A single page Extension that gets live data from the region and refreshes every minute
  • Saucer - Optional Fast 404 Extension for preventing long timeouts for missing/bad pages
  • Tea Strainer - Optional Troubleshooting Monitor for checking messages being sent and received.
  • To Go Cup - Experimental Loader for Internal Browsers on v1x viewers (v1.23, Snowglobe, Cool, Phoenix, Imprudence, Ascent, Singularity, and others without MOAP support)

Known Bugs


  • inline style elements are not evaluated at load time (inline attributes should work normally)
    • using JavaScript to write these is a viable workaround at the moment
  • document/body onload events are not triggered for page loads
    • images have their own events, and can be used to trigger page load JavaScript
    • may not support this now that inline scripts work, but it's still under consideration
  • Fails to load directly to external browsers (VWR-25555)
    • v2 internal browsers will only load it from the shared media bar "Load URL in browser" button (VWR-25554)
      • Workaround for v2 is to load it via the shared media button, to the internal browser, then cut and paste into an external browser (should work with everything but IE)
  • POST from forms is wonky, it forces a page load, which we can only serve text to.
    • There isn't any way via JavaScript to intecept form POST data, nor a way to call it from the container we use
    • It may be possible to rework the server to use Frames and detect plain text loads of pages... will look into this.
  • normal onLoad event for the pages scripts are unsupported
    • unfortunately the webkit browser is stupid and doesn't obey async/defer script attributes so we have to use onload for the main page
    • fortunately, scripts will get refreshed in page order, so just calling the code is a viable alternative

Requested Feature Upgrades


  • see about faking support for defer/async behavior in the tsp inline script tags
    • this will require supporting page onLoad events
  • Find a simple way to serve base64 encoded content such as images or sounds.
    • NEED ASSISTANCE: I can (probably) do it the same way we load pages, but this makes it hard for page writers.
    • considering a double-tap method where the server js library parses img tags for failures and bootstraps them with a modified name
    • may be able to partially support this via css, but it's ugly for page writers =(

Change Log / Old Versions


Most Recent Changes are at the top, old version links below.

  • Code will be considered stable when it reaches 1.0
  • Teacup v0.5
    • Added support for inline script elements for .tsp files
    • Added IP Address to File Service Requests, to work towards authentication
    • Tweaked the teacup.js to run a bit faster, and condensed it to take a little less space.
    • Added Page Titles (matches the file name minus the ".tsp" extension (only visible in external browsers)
    • Removed default CSS, this should be loadable per page now by JavaScript if nothing else.
  • Teacup v0.3.1
    • Reabsorbed the bootstrap html page into this script and limited it's size for compatibility due to feedback
    • Moved Saucer to it's own page, as an option resource
    • Removed dead touch code due to incompatibility with llLoadURL (VWR-25554 and VWR-25555)
    • Added handling for POST data. Page request format should be stable now.
  • Teacup/Saucer v0.3
    • Server Test Page moved to saucer file availability handler
    • Server JavaScript page loader library (Teacup.js) moved to File Service System
      • Users should have less trouble editing the file now and can include their own functions in the same file
    • Sitewide CSS supported via "teacup.css"
    • Saucer Script now handles page availability for quick 404 returns to speed page loading.
    • Site Title is pulled from the object name
    • Site address is discoverable by object scripts in the (hidden) prim hovertext
    • Server now runs from any prim, not just the root.
  • Teacup v0.2
    • Initial Public Release

NOTE TO WIKI EDITORS


I have kept this page as a sub page of my user page to denote that it should NOT be edited except to correct errors, add confirmed bugs, or add related resources.
The intent is to ensure executive control is in one person's hands to retain a cohesive vision of it's development.
If you have an improvement suggestion, or request, please use the discussion page.

Return to top

Questions or Comments?

Feel free to leave me a note on the discussion page.