Difference between revisions of "LSL HTTP server"

From Second Life Wiki
Jump to navigation Jump to search
Line 3: Line 3:


== Design ==
== Design ==
(Updated by [[User:Kelly Linden|Kelly Linden]] 11:28, 13 June 2008 (PDT))
This design is in flux during development.  Please comment on the discussion page, as comments here tend to get lost when the design is updated.  Thanks.
[http://wiki.secondlife.com/wiki/Image:Lsl_http_server.JPG A teaser image.]
=== URLs / Namespace ===
=== URLs / Namespace ===
  public url: ''https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221''
  public url: ''https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221''
  request: ''curl https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221/{untrusted-path}''
  request: ''curl https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221{untrusted-arg}''
{untrusted-arg} examples: ''/foo/bar'', ''?foo=bar'', ''/foo/bar?one=1''


=== LSL ===
=== LSL ===
* '''llHTTPServerRequestURL(string trusted_path)'''
* '''key llRequestPublicURL(string trusted_path)'''
: Request a new LSL Server public URL.  
: Request a new LSL Server public URL.  
: The 'trusted-path' will be passed into all http_server events from the public url from this request
: The 'trusted-path' will be available as a header for all http_request events from the public url from this request
: An http_server event will be triggered with success or failure and include the public url
: An http_request event will be triggered with success or failure and include the returned key
  lsl: llHTTPServerRequestURL("test-case/foo");  
  lsl: request_id = llRequestPublicURL("test-case/foo");  
* '''llHTTPServerClearURL(string url)'''
* '''llReleasePublicURL(string url)'''
: Clear the specific public url
: Clear the specific public url
  lsl: llHTTPServerClearURL("https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221");
  lsl: llReleasePublicURL("https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221");
* '''llHTTPServerClearAllURLs()'''
* '''http_request(key id, string method, string body)'''
:* Clears all URLs associated with this task.
: Event triggered when an URL is hit:
lsl: llHTTPServerClearAllURLs();
:* id is unique to this request
* '''llHTTPServerGetURLs()'''
:* Supported methods are GET/POST/PUT/DELETE
: Requests a list of all URLs associated with this task
:* body: The body of the request.
: Results are handled by the http_server event
: Event also triggered with response to llRequestPublicURL
lsl: llHTTPServerGetURLs();
:* id matches the key returned by llRequestPublicURL
 
:* method == URL_REQUEST_GRANTED for success, URL_REQUEST_DENIED for failure to get an URL
* '''http_server(string method, list meta, string body)'''
:* body is the public URLIf unable to get a public URL body will be empty.
: Event triggered when an URL is hit or with results of llHTTPServerRequest
* '''llHTTPResponse(key id, integer status, string body)'''
:* method is GET/POST/PUT/DELETE for normal hits
::* meta: A list of headers of the format [header1,value1,header2,value2], standard headers:
:::* "x-trusted-path": A string as set when the url is requested, essentially trusted meta data associated with a specific cap.
:::* "x-untrusted-path": A string that is any trailing characters from the external request
:::* "x-forwarded-for": The host that made the request
:::* "x-forwarded-for-port": The port from the request (is this the port of the requester?)
::* body: The body of the request.
:* method is one of the following string constants for special case events.
::* URL_REQUEST_GRANTED: The script now has a public url.  The url is in the body.
::* URL_REQUEST_FAILED: Unable to get an URL, if possible a reason is given in the body.
::* GET_URLS: meta is a list of urls held by this task
::* URL_LOST: body is the url that was lost.  
  Is there a case where we won't know what urls were lost, just that they all were?
[[User:Kelly Linden|Kelly]] 15:30, 14 May 2008 (PDT)
 
http_server(string method, list meta, string body)
{
    if (method == URL_REQUEST_GRANTED)
    {
      string public_url = body;
      // We now have the url, probably want to let someone else know - llHTTPRequest or similar
    }
    else if (method == URL_REQUEST_FAILED)
    {
      // Retry and/or let owner know we are broken.
    }
    else if (method == "GET")
    {
      integer i = llListFindList(meta,TRUSTED_PATH);
      string trusted = llList2String(meta,i+1);
      if (trusted == "echo")
      {
          llHTTPServerResponse(200,body);
      }
      else if (trusted == "status")
      {
          i = llListFindList(meta,UNTRUSTED_PATH);
          string untrusted = llList2String(meta,i+1);
          llRequestAgentData(AGENT_ONLINE,(key)(untrusted));
          // !! Won't have anything to return via llHTTPServerResponse until we get a dataserver() event!
      }
    }
}
 
* '''integer llHTTPServerResponse(integer status, string body)'''
: Send ''body'' to the requester with status code ''status''
: Send ''body'' to the requester with status code ''status''
: Returns TRUE if the response was sent, FALSE if the connection was no longer available to respond on.
:* id is the id from http_request that maps to the specific request
Can we do this outside this event? 
* '''string llHTTPHeader(key id, string header)'''
We need to time out pending requests anyway if the script takes too long.
: Returns the string for the specified header in the specified request
[[User:Kelly Linden|Kelly Linden]] 15:30, 14 May 2008 (PDT)
:* Potential headers are:
 
::* "x-trusted-path": The portion of the path as specified in the request
If we're going to respond outside of the event, every request will need an id.
::* "x-untrusted-argument": A string that is any trailing characters from the external request
Another bad thing about doing it that way is that it allows people to hold
::* "x-forwarded-for": The host that made the request
onto a file descriptor for a long time.
::* "x-forwarded-for-port": The port from the requester
[[User:Phoenix Linden|Phoenix]] 16:32, 14 May 2008 (PDT)
* '''system_changed(integer change)''' (possibly a subset of existing ''changed'' event)
 
: Tells scripts about system events, allows script to maintain public urls
Any more than if they plunk a while(1) in there? Or just take a really long
:* SYSTEM_CAPS_RESET
time to do whatever?  We need to handle the case of 'they
:* SYSTEM_REGION_RESTART
are taking too long to send a response' even if we limit them to sending
responses from within the event.  Allowing responses to be sent outside the
event allows for a lot more flexibility - sensors, notecard reading, link messages
and all the dataserver requests all become possibilities.
[[User:Kelly Linden|Kelly Linden]] 13:46, 16 May 2008 (PDT)


=== Simulator ===
=== Simulator ===
What the simulator needs to do:
What the simulator needs to do:
* '''Passes the method, body and these headers into the lsl script:'''
* '''Pass these headers into the lsl script:'''
:* 'x-forwarded-for' and 'x-forwarded-for-port': for the ip/port of the requester.
:* 'x-forwarded-for' and 'x-forwarded-for-port': for the ip/port of the requester.
:* 'x-trusted-path': The string passed into the url request
:* 'x-trusted-path': A string specified by the user at url request
:* 'x-untrusted-path': A header appended by the cap server containing any path appended to the cap
:* 'x-untrusted-argument': A header appended by the cap server containing any path and/or arguments appended to the cap


* '''Clear/invalidate caps in some situations''':
* '''Clear/invalidate caps in some situations''':
Caps will be automatically be revoked when the region goes down.   
:* Caps will automatically be revoked when the region goes down.   
The caps can also be granted using the object as the "key" - and revoked when the object is destroyed.
:* When the script is reset, resaved or deleted.  
[[User:Zero Linden|Zero Linden]] 14:37, 16 November 2007 (PST)
 
Need more details about this.
[[User:Kelly Linden|Kelly Linden]] 11:15, 14 May 2008 (PDT)
:* Object removed from world
:* Object removed from world
:* Object region change
:* Object region change
:* Object owner change
:* Object owner change
:* Region startup (clear all by region)
:* Region startup (clear all by region)
:* Script request
:* Script request (llReleasePublicURL)
* '''Trigger an lsl http_server event with URL_LOST whenever a cap becomes invalid but the object and script still exist'''
* '''Scripts should know when they need to re-request urls'''
:* Object first rezed
: Goal: Scripts should be able to know when public urls are lost and recover from that loss
:* Object changed regions
:* Existing events: default/state_entry (new script), on_rez, changed(region cross, owner)
:* Region restarted
:* New events: cap server is restarted, region is restarted
:* Capability was successfully removed by script request
* '''The simulator tracks LSL Cap URLs as a [[Limited Script Resource]].'''
:* Object owner changed
:* This is a first use for a more general Limited Script Resource system that should eventually also handle script memory and cpu cycles.
* '''Track LSL Public URLs as a [[Limited Script Resource]].'''
:* Not all requests for an url will succeed, the scripter is expected to handle the failure case.
:* This is a first use for a more general Limited Script Resource system that could eventually also handle script memory and cpu cycles.
:* Not all requests for an url will succeed, the scripter is expected to handle the failure case - we must let the scripter know when it fails.
:* The number of available urls will be based on the amount of land owned in the region
:* The number of available urls will be based on the amount of land owned in the region
:* Need the ability to figure out how many/much of a resource is used and available
:* '''integer llGetFreePublicURLs()''' returns how many public URLs are available.
''Do we need to support transient users or objects (where they own no land)?''
''Do we have special handling for sandbox regions?''
''How do we handle rented parcels in private estates?  What controls (if any) do we need to give estate owners?''
[[User:Kelly Linden|Kelly Linden]] 15:58, 14 May 2008 (PDT)


=== Cap Server ===
=== Cap Server ===
This is the cap server on the same host as the region
* Grants caps per normal that look like:
* Grants caps per normal that look like:
  public url: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221
  public url: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221
* Trailing path after the cap ID is passed to the internal url as the 'x-untrusted-path' header:
* Trailing path after the cap ID is passed to the internal url as part of the header:
  request: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221/foo/bar
  request: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221/foo/bar?one=1
  forwarded to: <internal-url>
  forwarded to: <internal_url>
   w/ header: x-untrusted-path: foo/bar
   w/ header: x-untrusted-path: foo/bar?one=1
* Caps are cleared when a region shuts down or restarts
* Regions are aware of cap server resets (to notify scripts)
* Caps are cleared by lsl request (via specific lsl function)
* Caps are cleared by region request (object deleted or moves to another region)
* Simulator must be able to verify a cap still exists.
* Separate apache config and/or resource pools for agent and viewer related caps vs task/LSL caps
** Would give us opportunities to ensure good behavior for agent critical features at the expense of LSL/Caps performance if needed
** Potentially allow us to minimize the impact of DOS style attacks against the LSL caps.


==== Questions / Issues ====
== Questions / Issues ==
* Should these caps time out?
* Should these caps time out?
* Define response codes - no script/object found, request throttled.
* Define response codes - no script/object found, throttled.
* Define mime-handling
* Define mime-handling
  I believe that we should, like the llHTTPRequest() call, be very clear in our handling of bodies and mime-types.   
  I believe that we should, like the llHTTPRequest() call, be very clear in our handling of bodies and mime-types.   
Line 162: Line 102:
: It would be nice if this could happen before the simulator on a per-cap basis, but throttling in the simulator handler would probably work as well.
: It would be nice if this could happen before the simulator on a per-cap basis, but throttling in the simulator handler would probably work as well.
Capserver:
Capserver:
* TBD: More info on cap server's load characteristics.  We believe this will be ok but we need to verify.
* TBD: More info on cap server's load characteristics.  We believe this will be ok, especially in the world of unified cap server, multi-threaded apache and memcached - but we need to verify.
* Separate apache config and/or resource pools for agent caps vs task/LSL caps would give us opportunities to ensure good behavior for agent critical features at the expense of LSL/Caps performance if needed and allow us to minimize the impact of DOS style attacks against the LSL caps.
* Separate apache config and/or resource pools for agent caps vs task/LSL caps would give us opportunities to ensure good behavior for agent critical features at the expense of LSL/Caps performance if needed and allow us to minimize the impact of DOS style attacks against the LSL caps.
TODO:
TODO:
* Get statistics on # of XMLRPC connections, llHTTPRequests and llEmails per unit of time per region to set expectations for level of usage.
* Get statistics on # of XMLRPC connections, llHTTPRequests and llEmails per unit of time per region to set expectations for level of usage.
  We currently have 2 XMLRPC servers each processing about 30 concurrent requests.
  We currently have 2 XMLRPC servers each processing about 30-40 concurrent requests.
  I don't know the actual rate of requests, but I do know that we start failing at ~150
  I don't know the actual rate of requests, but I do know that we start failing at ~150
  concurrent requests (out of a theoretical max of 200) per server, or ~300 total.
  concurrent requests (out of a theoretical max of 200) per server, or ~300 total.
Line 177: Line 117:


== Limitations ==
== Limitations ==
* Size of the body of the requests will probably need to be limited.  At least to something that will fit within script memory - 2k is probably right.
* Size of the body of the requests will probably need to be limited.  At least to something that will fit within script memory.
  This is needed to keep from overloading scripts and to reduce the overall potential load of handling large sets of data.   
  This is needed to keep from overloading scripts and to reduce the overall potential load of handling large sets of data.   
  We probably also need to limit the size of returned data
  We also need to limit the size of returned data in llHTTPResponse()
[[User:Kelly Linden|Kelly Linden]] 12:58, 14 May 2008 (PDT)
* Incoming requests will need to be throttled at some rate.  Script event queuing acts as a throttle for what connections the script will be able to hanlde, but that probably isn't enough.  I would guess that near the rate of llHTTPRequest throttling is probably good - ~1 / second / cap.
Can we throttle before we reach the sim? As part of apache, the cap server or squid?
  [[User:Kelly Linden|Kelly Linden]] 12:58, 14 May 2008 (PDT)
  [[User:Kelly Linden|Kelly Linden]] 12:58, 14 May 2008 (PDT)
* Need to limit the size of the 'trusted-path' and 'untrusted-path'
* There will be a max number of 'in process' requests allowed per script (requests that have been made but not responded to or timed out yet), requests when past this limit will receive an appropriate http status code.  This will be roughly on the order of maximum number of queued events in a script, give or take a factor of 2.
* We may wish to throttle at the CAP server level as well, if possible.


== Interactions ==
== Interactions ==
Line 192: Line 130:


== Testing ==
== Testing ==
TODO: Export internal test plan pages, once they exist. :)
TODO: Export internal test plan pages.


== Previous Design ==
== Previous Design ==

Revision as of 11:28, 13 June 2008

Goals

Create an alternative to the XMLRPC server and email gateway for communication with LSL scripts initiated from outside Second Life that is easy to use and scalable. Extra bonus for enabling LSL -> LSL communication at the same time.

Design

(Updated by Kelly Linden 11:28, 13 June 2008 (PDT))

This design is in flux during development. Please comment on the discussion page, as comments here tend to get lost when the design is updated. Thanks.

A teaser image.

URLs / Namespace

public url: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221
request: curl https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221{untrusted-arg}
{untrusted-arg} examples: /foo/bar, ?foo=bar, /foo/bar?one=1

LSL

  • key llRequestPublicURL(string trusted_path)
Request a new LSL Server public URL.
The 'trusted-path' will be available as a header for all http_request events from the public url from this request
An http_request event will be triggered with success or failure and include the returned key
lsl: request_id = llRequestPublicURL("test-case/foo"); 
  • llReleasePublicURL(string url)
Clear the specific public url
lsl: llReleasePublicURL("https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221");
  • http_request(key id, string method, string body)
Event triggered when an URL is hit:
  • id is unique to this request
  • Supported methods are GET/POST/PUT/DELETE
  • body: The body of the request.
Event also triggered with response to llRequestPublicURL
  • id matches the key returned by llRequestPublicURL
  • method == URL_REQUEST_GRANTED for success, URL_REQUEST_DENIED for failure to get an URL
  • body is the public URL. If unable to get a public URL body will be empty.
  • llHTTPResponse(key id, integer status, string body)
Send body to the requester with status code status
  • id is the id from http_request that maps to the specific request
  • string llHTTPHeader(key id, string header)
Returns the string for the specified header in the specified request
  • Potential headers are:
  • "x-trusted-path": The portion of the path as specified in the request
  • "x-untrusted-argument": A string that is any trailing characters from the external request
  • "x-forwarded-for": The host that made the request
  • "x-forwarded-for-port": The port from the requester
  • system_changed(integer change) (possibly a subset of existing changed event)
Tells scripts about system events, allows script to maintain public urls
  • SYSTEM_CAPS_RESET
  • SYSTEM_REGION_RESTART

Simulator

What the simulator needs to do:

  • Pass these headers into the lsl script:
  • 'x-forwarded-for' and 'x-forwarded-for-port': for the ip/port of the requester.
  • 'x-trusted-path': A string specified by the user at url request
  • 'x-untrusted-argument': A header appended by the cap server containing any path and/or arguments appended to the cap
  • Clear/invalidate caps in some situations:
  • Caps will automatically be revoked when the region goes down.
  • When the script is reset, resaved or deleted.
  • Object removed from world
  • Object region change
  • Object owner change
  • Region startup (clear all by region)
  • Script request (llReleasePublicURL)
  • Scripts should know when they need to re-request urls
Goal: Scripts should be able to know when public urls are lost and recover from that loss
  • Existing events: default/state_entry (new script), on_rez, changed(region cross, owner)
  • New events: cap server is restarted, region is restarted
  • This is a first use for a more general Limited Script Resource system that should eventually also handle script memory and cpu cycles.
  • Not all requests for an url will succeed, the scripter is expected to handle the failure case.
  • The number of available urls will be based on the amount of land owned in the region
  • integer llGetFreePublicURLs() returns how many public URLs are available.

Cap Server

  • Grants caps per normal that look like:
public url: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221
  • Trailing path after the cap ID is passed to the internal url as part of the header:
request: https://sim123.agni/cap/f23b4b94-012d-44f2-bd0c-16c328321221/foo/bar?one=1
forwarded to: <internal_url>
  w/ header: x-untrusted-path: foo/bar?one=1
  • Regions are aware of cap server resets (to notify scripts)

Questions / Issues

  • Should these caps time out?
  • Define response codes - no script/object found, throttled.
  • Define mime-handling
I believe that we should, like the llHTTPRequest() call, be very clear in our handling of bodies and mime-types.  
In particular, accept only text/* mime types, and be sure to do proper charset handling and conversion into the 
UTF-8 that LSL strings use.  (The code should all be cullable from the llHTTPRequest() implementation.)
Zero Linden 14:37, 16 November 2007 (PST)

Interface Requirements

  • No GUI components.
  • LSL Functions are written in stone, must get them right.

Performance Requirements

This should add no database, assetserver or viewer load.

Simulator:

  • In general load should be no higher than existing alternatives (xmlrpc, llemail and llhttprequest) for any single action.
  • Connections need to be throttled.
It would be nice if this could happen before the simulator on a per-cap basis, but throttling in the simulator handler would probably work as well.

Capserver:

  • TBD: More info on cap server's load characteristics. We believe this will be ok, especially in the world of unified cap server, multi-threaded apache and memcached - but we need to verify.
  • Separate apache config and/or resource pools for agent caps vs task/LSL caps would give us opportunities to ensure good behavior for agent critical features at the expense of LSL/Caps performance if needed and allow us to minimize the impact of DOS style attacks against the LSL caps.

TODO:

  • Get statistics on # of XMLRPC connections, llHTTPRequests and llEmails per unit of time per region to set expectations for level of usage.
We currently have 2 XMLRPC servers each processing about 30-40 concurrent requests.
I don't know the actual rate of requests, but I do know that we start failing at ~150
concurrent requests (out of a theoretical max of 200) per server, or ~300 total.
Kelly Linden 12:52, 14 May 2008 (PDT)

Security Impact

Creating a server accessible in any way from outside needs to be done with care. The cap server already does this, and security concerns should already be handled here. This isn't something to take for granted though.

  • Scripts should not be blocked from using llHTTPRequest to contact the public interface of the cap server.
  • Need to be careful about this since these are requests that originate from inside our network.

Limitations

  • Size of the body of the requests will probably need to be limited. At least to something that will fit within script memory.
This is needed to keep from overloading scripts and to reduce the overall potential load of handling large sets of data.  
We also need to limit the size of returned data in llHTTPResponse()
Kelly Linden 12:58, 14 May 2008 (PDT)
  • There will be a max number of 'in process' requests allowed per script (requests that have been made but not responded to or timed out yet), requests when past this limit will receive an appropriate http status code. This will be roughly on the order of maximum number of queued events in a script, give or take a factor of 2.
  • We may wish to throttle at the CAP server level as well, if possible.

Interactions

Testing

TODO: Export internal test plan pages.

Previous Design

Previous design and comments