Difference between revisions of "Http response"

From Second Life Wiki
Jump to navigation Jump to search
m (Replaced <source> with <syntaxhighlight>)
 
(19 intermediate revisions by 12 users not shown)
Line 1: Line 1:
{{LSL_Event|event_id=32|event_delay|event=http_response
{{LSL_Event|event_id=32|event_delay|event=http_response
|p1_type=key|p1_name=request_id|p1_desc=Matches return from [[llHTTPRequest]]
|p1_type=key|p1_subtype=handle|p1_name=request_id|p1_desc=Matches return from [[llHTTPRequest]]
|p2_type=integer|p2_name=status|p2_desc=HTTP code (like 404 or 200)
|p2_type=integer|p2_name=status|p2_desc=[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes HTTP status code] (like 404 or 200)
|p3_type=list|p3_name=metadata|p3_desc=List of HTTP_* constants and attributes
|p3_type=list|p3_name=metadata|p3_desc=List of HTTP_* constants and attributes
|p4_type=string|p4_name=body|p4_desc
|p4_type=string|p4_name=body|p4_desc
Line 16: Line 16:
{{!}} Truncation point in bytes
{{!}} Truncation point in bytes
{{!}}}
{{!}}}
|spec=<h3>status 499</h3>
|spec=<h3>Status 415 "Unsupported or unknown Content-Type"</h3>
Besides the usual HTTP status codes, SL implements a special '''status''' code 499.  This code isn't generated by the remote web server but by SL's servers, it can indicate:
The remote server did reply to your request but the Content-Type of the reply (such as XML, JSON, Atom, RSS, PLS) is not recognized by the LL server and so is not passed back to the script. You can assume that 415 means the server heard your request and did reply.
<h3>Status 499</h3>
Besides the usual [https://en.wikipedia.org/wiki/List_of_HTTP_status_codes HTTP status codes], SL implements a special {{LSLP|status}} code 499.  This code isn't generated by the remote web server but by SL's servers (see [[Simulator_IP_Addresses]]), it can indicate:
*Request timeout (60 seconds)
*Request timeout (60 seconds)
*SSL failure
*SSL failure
*A space was present in the url (escape your URL with [[llEscapeURL]]).
*A space was present in the URL (escape your URL with [[llEscapeURL]]).
|caveats
<h3>Status 502</h3>
|examples=<lsl>key http_request_id;
The proxy server received an invalid response from an upstream server. This error occurs when you send an [[llHTTPRequest]] to an object in-world, and it does not reply with an [[llHTTPResponse]] [[llHTTPResponse#Caveats|in time]].
<h3>Status 503</h3>
This error occurs when you send an [[llHTTPRequest]] to an object in-world, but the request was throttled before it reached the object. The throttle allows many requests per second to the set of scripts in the region that have the same owner. The response returns an HTTP Retry-After header to inform the caller how soon it may be attempted, but at present there is no way to access response headers in LSL.
|caveats=*This events will be triggered in every script in the prim, not just in the [[llHTTPRequest|requesting]] script.
*It is ''not'' guaranteed that there will be an [[http_response]] for every [[llHTTPRequest]]().
**If the script moves to a different region before the remote HTTP server can respond, the response will be lost. [https://lists.secondlife.com/pipermail/secondlifescripters/2011-August/006309.html]
|examples=
<syntaxhighlight lang="lsl2">
key http_request_id;


default
default
Line 38: Line 48:
         }
         }
     }
     }
}</lsl>
}
</syntaxhighlight>
To parse POST content:
<syntaxhighlight lang="lsl2">
string get_post_value(string content, string returns)
{
//  this parses application/x-www-form-urlencoded POST data
 
//  for instance if the webserver posts 'data1=hi&data2=blah' then
//  calling get_post_value("data1=hi&data2=blah","data1"); would return "hi"
//  written by MichaelRyan Allen, Unrevoked Clarity
 
    list params =  llParseString2List(content,["&"],[]);
    integer index = ~llGetListLength(params);
 
    list keys;// = [];
    list values;// = [];
 
    // start with -length and end with -1
    while (++index)
    {
        list parsedParams =  llParseString2List(llList2String(params, index), ["="], []);
        keys += llUnescapeURL(llList2String(parsedParams, 0));
        values += llUnescapeURL(llList2String(parsedParams, 1));
    }
 
    integer found = llListFindList(keys, [returns]);
    if(~found)
        return llList2String(values, found);
//  else
        return "";
}
</syntaxhighlight>
Another Example:
{{KBcaution|Remember to release URLs that you have requested! They are region resources just like prims, and it is possible to use them all and break other scripts.}}
<syntaxhighlight lang="lsl2">
string url;
key urlRequestId;
key selfCheckRequestId;
 
request_url()
{
    llReleaseURL(url);
    url = "";
 
    urlRequestId = llRequestURL();
}
 
throw_exception(string inputString)
{
    key owner = llGetOwner();
    llInstantMessage(owner, inputString);
 
    // yeah, bad way to handle exceptions by restarting.
    // However this is just a demo script...
 
    llResetScript();
}
 
default
{
    on_rez(integer start_param)
    {
        llResetScript();
    }
 
    changed(integer change)
    {
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
        {
            llReleaseURL(url);
            url = "";
 
            llResetScript();
        }
 
        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
            request_url();
    }
 
    state_entry()
    {
        request_url();
    }
 
    http_request(key id, string method, string body)
    {
        integer responseStatus = 400;
        string responseBody = "Unsupported method";
 
        if (method == URL_REQUEST_DENIED)
            throw_exception("The following error occurred while attempting to get a free URL for this device:\n \n" + body);
 
        else if (method == URL_REQUEST_GRANTED)
        {
            url = body;
            key owner = llGetOwner();
            llLoadURL(owner, "Click to visit my URL!", url);
 
            // check every 5 mins for dropped URL
            llSetTimerEvent(300.0);
        }
        else if (method == "GET")
        {
            responseStatus = 200;
            responseBody = "Hello world!";
        }
        // else if (method == "POST") ...;
        // else if (method == "PUT") ...;
        // else if (method == "DELETE") { responseStatus = 403; responseBody = "forbidden"; }
 
        llHTTPResponse(id, responseStatus, responseBody);
    }
 
    http_response(key id, integer status, list metaData, string body)
    {
        if (id == selfCheckRequestId)
        {
            // If you're not usually doing this,
            // now is a good time to get used to doing it!
            selfCheckRequestId = NULL_KEY;
 
            if (status != 200)
                request_url();
        }
 
        else if (id == NULL_KEY)
            throw_exception("Too many HTTP requests too fast!");
    }
 
    timer()
    {
        selfCheckRequestId = llHTTPRequest(url,
                                [HTTP_METHOD, "GET",
                                    HTTP_VERBOSE_THROTTLE, FALSE,
                                    HTTP_BODY_MAXLENGTH, 16384],
                                "");
    }
}
</syntaxhighlight>
|helpers
|helpers
|also_header
|also_header
|also_events
|also_events
|also_functions={{LSL DefineRow||[[llHTTPRequest]]}}
|also_functions={{LSL DefineRow||[[llHTTPRequest]]}}
{{LSL DefineRow||[[llHTTPResponse]]}}
|also_articles
|also_articles
|also_footer
|also_footer
|notes=
|notes=
===Parsing Problems===
===Parsing Problems===
If for some reason while using [[llHTTPRequest]]/[[http_response]] you are unable to parse a known good RSS feed or some other form of web contents, you will need to work around it outside of SecondLife. This is unlikely to change in the near future since checking the headers requires more overhead at the simulator level.
If for some reason while using [[llHTTPRequest]]/[[http_response]] you are unable to parse a known good RSS feed or some other form of web contents, you will need to work around it outside of Second Life. This is unlikely to change in the near future since checking the headers requires more overhead at the simulator level.


===Unicode===
===Unicode===


When serving content with UTF-8 characters be sure your server sets the outgoing "Content-Type" header so that it includes "charset=utf-8" otherwise it will be interpreted incorrectly. See [http://www.w3.org/International/O-HTTP-charset W3C:Setting the HTTP charset parameter] for further details.
When serving content with UTF-8 characters be sure your server sets the outgoing <code>Content-Type</code> header so that it includes <code>charset=utf-8</code> otherwise it will be interpreted incorrectly. See [http://www.w3.org/International/O-HTTP-charset W3C:Setting the HTTP charset parameter] for further details.


===Request Headers===
===Request Headers===
Line 61: Line 211:
|cat2=XML-RPC
|cat2=XML-RPC
|cat3=HTTP/Client
|cat3=HTTP/Client
|cat4
|cat4=Events/Prim
}}
}}

Latest revision as of 10:59, 17 February 2024

Description

Event: http_response( key request_id, integer status, list metadata, string body ){ ; }

Triggered when task receives a response to one of its llHTTPRequests

• key request_id Matches return from llHTTPRequest
• integer status HTTP status code (like 404 or 200)
• list metadata List of HTTP_* constants and attributes
• string body

Specification

Status 415 "Unsupported or unknown Content-Type"

The remote server did reply to your request but the Content-Type of the reply (such as XML, JSON, Atom, RSS, PLS) is not recognized by the LL server and so is not passed back to the script. You can assume that 415 means the server heard your request and did reply.

Status 499

Besides the usual HTTP status codes, SL implements a special status code 499. This code isn't generated by the remote web server but by SL's servers (see Simulator_IP_Addresses), it can indicate:

  • Request timeout (60 seconds)
  • SSL failure
  • A space was present in the URL (escape your URL with llEscapeURL).

Status 502

The proxy server received an invalid response from an upstream server. This error occurs when you send an llHTTPRequest to an object in-world, and it does not reply with an llHTTPResponse in time.

Status 503

This error occurs when you send an llHTTPRequest to an object in-world, but the request was throttled before it reached the object. The throttle allows many requests per second to the set of scripts in the region that have the same owner. The response returns an HTTP Retry-After header to inform the caller how soon it may be attempted, but at present there is no way to access response headers in LSL.

Constant Type Description
HTTP_BODY_TRUNCATED 0 integer Truncation point in bytes

Caveats

  • This events will be triggered in every script in the prim, not just in the requesting script.
  • It is not guaranteed that there will be an http_response for every llHTTPRequest().
    • If the script moves to a different region before the remote HTTP server can respond, the response will be lost. [1]


Examples

key http_request_id;

default
{
    state_entry()
    {
        http_request_id = llHTTPRequest("url", [], "");
    }

    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == http_request_id)
        {
            llSetText(body, <0,0,1>, 1);
        }
    }
}

To parse POST content:

string get_post_value(string content, string returns)
{
//  this parses application/x-www-form-urlencoded POST data

//  for instance if the webserver posts 'data1=hi&data2=blah' then
//  calling get_post_value("data1=hi&data2=blah","data1"); would return "hi"
//  written by MichaelRyan Allen, Unrevoked Clarity

    list params =  llParseString2List(content,["&"],[]);
    integer index = ~llGetListLength(params);

    list keys;// = [];
    list values;// = [];

    // start with -length and end with -1
    while (++index)
    {
        list parsedParams =  llParseString2List(llList2String(params, index), ["="], []);
        keys += llUnescapeURL(llList2String(parsedParams, 0));
        values += llUnescapeURL(llList2String(parsedParams, 1));
    }

    integer found = llListFindList(keys, [returns]);
    if(~found)
        return llList2String(values, found);
//  else
        return "";
}

Another Example:

KBcaution.png Important: Remember to release URLs that you have requested! They are region resources just like prims, and it is possible to use them all and break other scripts.
string url;
key urlRequestId;
key selfCheckRequestId;

request_url()
{
    llReleaseURL(url);
    url = "";

    urlRequestId = llRequestURL();
}

throw_exception(string inputString)
{
    key owner = llGetOwner();
    llInstantMessage(owner, inputString);

    // yeah, bad way to handle exceptions by restarting.
    // However this is just a demo script...

    llResetScript();
}

default
{
    on_rez(integer start_param)
    {
        llResetScript();
    }

    changed(integer change)
    {
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
        {
            llReleaseURL(url);
            url = "";

            llResetScript();
        }

        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
            request_url();
    }

    state_entry()
    {
        request_url();
    }

    http_request(key id, string method, string body)
    {
        integer responseStatus = 400;
        string responseBody = "Unsupported method";

        if (method == URL_REQUEST_DENIED)
            throw_exception("The following error occurred while attempting to get a free URL for this device:\n \n" + body);

        else if (method == URL_REQUEST_GRANTED)
        {
            url = body;
            key owner = llGetOwner();
            llLoadURL(owner, "Click to visit my URL!", url);

            // check every 5 mins for dropped URL
            llSetTimerEvent(300.0);
        }
        else if (method == "GET")
        {
            responseStatus = 200;
            responseBody = "Hello world!";
        }
        // else if (method == "POST") ...;
        // else if (method == "PUT") ...;
        // else if (method == "DELETE") { responseStatus = 403; responseBody = "forbidden"; }

        llHTTPResponse(id, responseStatus, responseBody);
    }

    http_response(key id, integer status, list metaData, string body)
    {
        if (id == selfCheckRequestId)
        {
            // If you're not usually doing this,
            // now is a good time to get used to doing it!
            selfCheckRequestId = NULL_KEY;

            if (status != 200)
                request_url();
        }

        else if (id == NULL_KEY)
            throw_exception("Too many HTTP requests too fast!");
    }

    timer()
    {
        selfCheckRequestId = llHTTPRequest(url,
                                [HTTP_METHOD, "GET",
                                    HTTP_VERBOSE_THROTTLE, FALSE,
                                    HTTP_BODY_MAXLENGTH, 16384],
                                "");
    }
}

Notes

Parsing Problems

If for some reason while using llHTTPRequest/http_response you are unable to parse a known good RSS feed or some other form of web contents, you will need to work around it outside of Second Life. This is unlikely to change in the near future since checking the headers requires more overhead at the simulator level.

Unicode

When serving content with UTF-8 characters be sure your server sets the outgoing Content-Type header so that it includes charset=utf-8 otherwise it will be interpreted incorrectly. See W3C:Setting the HTTP charset parameter for further details.

Request Headers

Headers sent by the simulator in the course of calling llHTTPRequest.
Header Description Example data
Connection Connection options close
Cache-Control Maximum response age accepted. max-age=259200
X-Forwarded-For Used to show the IP address connected to through proxies. 127.0.0.1
Via Shows the recipients and protocols used between the User Agent and the server. 1.1 sim10115.agni.lindenlab.com:3128 (squid/2.7.STABLE9)
Content-Length The size of the entity-body, in decimal number of octets. 17
Pragma The message should be forwarded to the server, even if it has a cached version of the data. no-cache
X-SecondLife-Shard The environment the object is in. "Production" is the main grid and "Testing" is the preview grid Production
X-SecondLife-Region The name of the region the object is in, along with the global coordinates of the region's south-west corner Jin Ho (264448, 233984)
X-SecondLife-Owner-Name Legacy name of the owner of the object Zeb Wyler
X-SecondLife-Owner-Key UUID of the owner of the object 01234567-89ab-cdef-0123-456789abcdef
X-SecondLife-Object-Name The name of the object containing the script Object
X-SecondLife-Object-Key The key of the object containing the script 01234567-89ab-cdef-0123-456789abcdef
X-SecondLife-Local-Velocity The velocity of the object 0.000000, 0.000000, 0.000000
X-SecondLife-Local-Rotation The rotation of the object containing the script 0.000000, 0.000000, 0.000000, 1.000000
X-SecondLife-Local-Position The position of the object within the region (173.009827, 75.551231, 60.950001)
User-Agent The user-agent header sent by LSL Scripts. Contains Server version. Second Life LSL/16.05.24.315768 (http://secondlife.com)
Content-Type The media type of the entity body. text/plain; charset=utf-8
Accept-Charset Acceptable character sets from the server. Q being the quality expected when sending the different character sets. utf-8;q=1.0, *;q=0.5
Accept Media types the server will accept. text/*, application/xhtml+xml, application/atom+xml, application/json, application/xml, application/llsd+xml, application/x-javascript, application/javascript, application/x-www-form-urlencoded, application/rss+xml
Accept-Encoding Acceptable content encodings for the server. deflate, gzip
Host The internet host being requested. secondlife.com
  • CGI environments may place the headers into variables by capitalizing the entire name, replacing dashes with underscores, and prefixing the name with "HTTP_", e.g. "X-SecondLife-Object-Name" becomes "HTTP_X_SECONDLIFE_OBJECT_NAME".
  • HTTP header names are case insensitive [2]. Some ISPs may modify the case of header names, as was seen in BUG-5094.

See Also

Functions

•  llHTTPRequest
•  llHTTPResponse

Deep Notes

Signature

event void http_response( key request_id, integer status, list metadata, string body );