Add file server request handler
Add a file server request handler that serves notecards from the prim's inventory.
This commit is contained in:
14
file server/README.md
Normal file
14
file server/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# prim-dns file server
|
||||
|
||||
The prim-dns file server is a request handler script which will serve specially-named notecards in the prim's inventory as "files". Notecards must be named after the path of the file, including slashes. The example [index.xhtml](index.xhtml) notecard would actually be named `/index.xhtml` in the prim's inventory.
|
||||
|
||||
The script will determine the MIME type from the "extension" in the name of the notecard. For example, `/index.xhtml` will use `CONTENT_TYPE_XHTML` (`application/xhtml+xml`), while `/data.json` will use `CONTENT_TYPE_JSON` (`application/json`). [The available MIME types are limited](https://wiki.secondlife.com/wiki/LlSetContentType) and cannot be customized. Anything without a known extension will be treated as `CONTENT_TYPE_TEXT` (`text/plain`).
|
||||
|
||||
Paths that end in `/` are treated as "directories". The script will attempt to serve a corresponding `index.xhtml` notecard for the "directory". For example, if the path is `/example/`, the script will attempt to server the contents of a notecard named `/example/index.xhtml`.
|
||||
|
||||
Paths with no file extension that do not match a notecard with the appropriate name are also treated as "directories", but will redirect to add the trailing `/`. For example, if the path is `/example`, and there is no notecard named `/example` in the inventory, then the page will redirect to `/example/`.
|
||||
|
||||
When using the file server, all paths that do not match a notecard will return a 404 error. In order to combine the file server with other request handler scripts that dynamically handle some paths, those scripts must register the paths they will handle with the file server. This is done by sending the `prim-dns:file-server:register-path` notification to the file server script via a JSON-RPC link message:
|
||||
```lsl
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:file-server:register-path", JSON_OBJECT, ["path", "/data.json"]);
|
||||
```
|
||||
12
file server/index.xhtml
Normal file
12
file server/index.xhtml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>prim-dns file server default page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>It works!</h1>
|
||||
<p>If you can read this, the file server is working!</p>
|
||||
</body>
|
||||
</html>
|
||||
111
file server/prim-dns file server cache.lsl
Normal file
111
file server/prim-dns file server cache.lsl
Normal file
@@ -0,0 +1,111 @@
|
||||
/* The content of the current notecard being read. */
|
||||
string notecard_content;
|
||||
|
||||
/* The current line of the current notecard being read. */
|
||||
integer notecard_line;
|
||||
|
||||
/* The index of the current notecard being read. */
|
||||
integer notecard_index;
|
||||
|
||||
/* The name of the current notecard being read. */
|
||||
string notecard_name;
|
||||
|
||||
/* The index of the last notecard that this cache script should read. */
|
||||
integer max_notecard_index;
|
||||
|
||||
key notecard_query;
|
||||
|
||||
/* A strided list of notecard names and their contents. */
|
||||
list cache;
|
||||
|
||||
string jsonrpc_notification(string method, string params_type, list params)
|
||||
{
|
||||
return llList2Json(JSON_OBJECT, ["jsonrpc", "2.0", "method", method, "params", llList2Json(params_type, params)]);
|
||||
}
|
||||
|
||||
jsonrpc_link_notification(integer link, string method, string params_type, list params)
|
||||
{
|
||||
llMessageLinked(link, 0, jsonrpc_notification(method, params_type, params), NULL_KEY);
|
||||
}
|
||||
|
||||
/* Read the next notecard into the cache. */
|
||||
read_next_notecard()
|
||||
{
|
||||
notecard_content = "";
|
||||
|
||||
if (notecard_index <= max_notecard_index)
|
||||
{
|
||||
notecard_name = llGetInventoryName(INVENTORY_NOTECARD, notecard_index++);
|
||||
notecard_line = 0;
|
||||
notecard_query = llGetNotecardLine(notecard_name, notecard_line++);
|
||||
}
|
||||
else
|
||||
{
|
||||
notecard_name = "";
|
||||
llOwnerSay(llGetScriptName() + ": " + (string) (llGetUsedMemory() / 1024) + " KiB");
|
||||
}
|
||||
}
|
||||
|
||||
/* Send a cached notecard's contents back to the main script if this script has it. */
|
||||
send_cached_notecard(integer sender, key request_id, string name)
|
||||
{
|
||||
integer index = llListFindList(cache, [name]);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
jsonrpc_link_notification(sender, "prim-dns:file-server:cache:response", JSON_OBJECT, ["request-id", request_id, "body", llList2String(cache, index + 1)]);
|
||||
}
|
||||
|
||||
default
|
||||
{
|
||||
link_message(integer sender, integer num, string str, key id)
|
||||
{
|
||||
string method = llJsonGetValue(str, ["method"]);
|
||||
|
||||
if (method == "prim-dns:file-server:cache:read")
|
||||
{
|
||||
string script = llJsonGetValue(str, ["params", "script"]);
|
||||
|
||||
if (script != llGetScriptName())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cache = [];
|
||||
|
||||
notecard_index = (integer) llJsonGetValue(str, ["params", "start"]);
|
||||
max_notecard_index = (integer) llJsonGetValue(str, ["params", "end"]);
|
||||
|
||||
read_next_notecard();
|
||||
}
|
||||
else if (method == "prim-dns:file-server:cache:send")
|
||||
{
|
||||
key request_id = (key) llJsonGetValue(str, ["params", "request-id"]);
|
||||
string name = llJsonGetValue(str, ["params", "name"]);
|
||||
|
||||
send_cached_notecard(sender, request_id, name);
|
||||
}
|
||||
}
|
||||
|
||||
dataserver(key id, string data)
|
||||
{
|
||||
if (id != notecard_query)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (data == EOF)
|
||||
{
|
||||
cache += [notecard_name, notecard_content];
|
||||
read_next_notecard();
|
||||
}
|
||||
else
|
||||
{
|
||||
notecard_content += data + "\n";
|
||||
notecard_query = llGetNotecardLine(notecard_name, notecard_line++);
|
||||
}
|
||||
}
|
||||
}
|
||||
241
file server/prim-dns file server.lsl
Normal file
241
file server/prim-dns file server.lsl
Normal file
@@ -0,0 +1,241 @@
|
||||
/* The prefix for scripts which will act as caches for the file server. */
|
||||
string cache_prefix = "prim-dns file server cache";
|
||||
|
||||
/* The name used for the index notecard in each "directory". Note: XHTML is preferred because HTML can only be used by the internal SL browser, and only works for the owner of the linkset. */
|
||||
string index_notecard = "index.xhtml";
|
||||
|
||||
/* A list of cached notecards, the contents of which are stored in the cache scripts. */
|
||||
list cached_notecards;
|
||||
|
||||
/* Paths registered by other request handler scripts that should not be treated as files. */
|
||||
list registered_paths;
|
||||
|
||||
/* JSON-RPC functions */
|
||||
string jsonrpc_request(string method, string params_type, list params, string id)
|
||||
{
|
||||
if (id == "") id = (string) llGenerateKey();
|
||||
return llList2Json(JSON_OBJECT, ["jsonrpc", "2.0", "id", id, "method", method, "params", llList2Json(params_type, params)]);
|
||||
}
|
||||
|
||||
string jsonrpc_notification(string method, string params_type, list params)
|
||||
{
|
||||
return llList2Json(JSON_OBJECT, ["jsonrpc", "2.0", "method", method, "params", llList2Json(params_type, params)]);
|
||||
}
|
||||
|
||||
string jsonrpc_link_request(integer link, string method, string params_type, list params, string id)
|
||||
{
|
||||
if (id == "") id = (string) llGenerateKey();
|
||||
llMessageLinked(link, 0, jsonrpc_request(method, params_type, params, id), NULL_KEY);
|
||||
return id;
|
||||
}
|
||||
|
||||
jsonrpc_link_notification(integer link, string method, string params_type, list params)
|
||||
{
|
||||
llMessageLinked(link, 0, jsonrpc_notification(method, params_type, params), NULL_KEY);
|
||||
}
|
||||
|
||||
/* Begin reading all the notecards into the cache */
|
||||
store_notecards_in_cache()
|
||||
{
|
||||
llOwnerSay("Updating notecard cache...");
|
||||
|
||||
/* Clear the existing list of cached notecards */
|
||||
cached_notecards = [];
|
||||
|
||||
/* The prim will have one or more cache scripts that start with the cache_prefix.
|
||||
* The contents of the cached notecards will be stored evenly among these scripts,
|
||||
* allowing the cache to be as big as desired by simply adding more cache scripts.
|
||||
*/
|
||||
|
||||
/* First, we get a list of all the cache scripts in the prim. */
|
||||
list cache_scripts;
|
||||
integer scripts = llGetInventoryNumber(INVENTORY_SCRIPT);
|
||||
integer i;
|
||||
|
||||
for (i = 0; i <= scripts; ++i)
|
||||
{
|
||||
string name = llGetInventoryName(INVENTORY_SCRIPT, i);
|
||||
|
||||
if (llGetSubString(name, 0, llStringLength(cache_prefix) - 1) == cache_prefix)
|
||||
{
|
||||
cache_scripts += name;
|
||||
}
|
||||
}
|
||||
|
||||
integer total_cache_scripts = llGetListLength(cache_scripts);
|
||||
|
||||
/* If there are no cache scripts, abort. */
|
||||
if (total_cache_scripts == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Next, get a list of all the notecards we need to cache. */
|
||||
integer notecards = llGetInventoryNumber(INVENTORY_NOTECARD);
|
||||
|
||||
for (i = 0; i < notecards; ++i)
|
||||
{
|
||||
string name = llGetInventoryName(INVENTORY_NOTECARD, i);
|
||||
|
||||
/* Only cache notecards starting with / */
|
||||
if (llGetSubString(name, 0, 0) == "/")
|
||||
{
|
||||
cached_notecards += llGetInventoryName(INVENTORY_NOTECARD, i);
|
||||
}
|
||||
}
|
||||
|
||||
integer total_cached_notecards = llGetListLength(cached_notecards);
|
||||
|
||||
/* Calculate how many notecards will be cached per script. */
|
||||
integer notecards_per_script = llCeil(total_cached_notecards / total_cache_scripts);
|
||||
|
||||
/* Assign cache scripts to ranges of notecards based on the above. */
|
||||
for (i = 0; i < total_cache_scripts; ++i)
|
||||
{
|
||||
integer min = notecards_per_script * i;
|
||||
integer max = notecards_per_script * (i + 1) - 1;
|
||||
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:file-server:cache:read", JSON_OBJECT, ["script", llList2String(cache_scripts, i), "start", min, "end", max]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform a redirect using XHTML + Javascript, since we can't set the Location header of a response */
|
||||
redirect(integer sender, key request_id, string location)
|
||||
{
|
||||
jsonrpc_link_notification(sender, "prim-dns:set-content-type", JSON_OBJECT, ["request-id", request_id, "content-type", CONTENT_TYPE_XHTML]);
|
||||
jsonrpc_link_notification(sender, "prim-dns:response", JSON_OBJECT, ["request-id", request_id, "status", 200, "body", "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><script>window.location = '" + location + "';</script></head><body/></html>"]);
|
||||
}
|
||||
|
||||
/* Determine the MIME type from the notecard's extension.
|
||||
* SL only supports certain predefined MIME types.
|
||||
*/
|
||||
integer get_content_type(string extension)
|
||||
{
|
||||
if (extension == ".html")
|
||||
{
|
||||
return CONTENT_TYPE_HTML;
|
||||
}
|
||||
else if (extension == ".xml")
|
||||
{
|
||||
return CONTENT_TYPE_XML;
|
||||
}
|
||||
else if (extension == ".xhtml")
|
||||
{
|
||||
return CONTENT_TYPE_XHTML;
|
||||
}
|
||||
else if (extension == ".json")
|
||||
{
|
||||
return CONTENT_TYPE_JSON;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CONTENT_TYPE_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
default
|
||||
{
|
||||
state_entry()
|
||||
{
|
||||
store_notecards_in_cache();
|
||||
}
|
||||
|
||||
changed(integer change)
|
||||
{
|
||||
/* Re-cache notecards if inventory changes. */
|
||||
if (change & CHANGED_INVENTORY)
|
||||
{
|
||||
store_notecards_in_cache();
|
||||
}
|
||||
}
|
||||
|
||||
link_message(integer sender, integer num, string str, key id)
|
||||
{
|
||||
string jsonrpc_method = llJsonGetValue(str, ["method"]);
|
||||
|
||||
if (jsonrpc_method == "prim-dns:request")
|
||||
{
|
||||
key request_id = (key) llJsonGetValue(str, ["params", "request-id"]);
|
||||
string method = llJsonGetValue(str, ["params", "method"]);
|
||||
string headers = llJsonGetValue(str, ["params", "headers"]);
|
||||
string body = llJsonGetValue(str, ["params", "body"]);
|
||||
|
||||
/* Read the trailing path from the headers */
|
||||
string path = llJsonGetValue(headers, ["x-path-info"]);
|
||||
|
||||
/* If no path was given then redirect to the root path (/) */
|
||||
if (path == "" || path == JSON_INVALID)
|
||||
{
|
||||
redirect(sender, request_id, llJsonGetValue(headers, ["x-script-url"]) + "/");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode the path into plain text. */
|
||||
string name = llUnescapeURL(path);
|
||||
|
||||
/* Ignore paths registered by other request handler scripts. */
|
||||
if (llListFindList(registered_paths, [name]) != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the path ends in a /, treat it as a "directory". */
|
||||
if (llGetSubString(name, -1, -1) == "/")
|
||||
{
|
||||
name += index_notecard;
|
||||
}
|
||||
|
||||
/* Get the extension of the name, or empty string if there is not . */
|
||||
string extension;
|
||||
integer dot_index = llSubStringIndex(name, ".");
|
||||
|
||||
/* If there is an extension, store it. */
|
||||
if (dot_index > -1)
|
||||
{
|
||||
extension = llGetSubString(name, dot_index, -1);
|
||||
}
|
||||
/* If there is no extension, and no notecard with the name exists, treat this as a "directory". */
|
||||
else
|
||||
{
|
||||
if (llListFindList(cached_notecards, [name]) == -1)
|
||||
{
|
||||
redirect(sender, request_id, llJsonGetValue(headers, ["x-script-url"]) + name + "/");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the notecard name in the list of cached notecards */
|
||||
integer index = llListFindList(cached_notecards, [name]);
|
||||
|
||||
/* If there was no cached notecard with the specified name, return a 404 error */
|
||||
if (index == -1)
|
||||
{
|
||||
jsonrpc_link_notification(sender, "prim-dns:response", JSON_OBJECT, ["request-id", request_id, "status", 404, "body", "Not found: " + name]);
|
||||
}
|
||||
/* If a notecard matching the name was found, message the cache scripts to get its content */
|
||||
else
|
||||
{
|
||||
/* Set the appropriate MIME type based on the notecard extension */
|
||||
jsonrpc_link_notification(sender, "prim-dns:set-content-type", JSON_OBJECT, ["request-id", request_id, "content-type", get_content_type(extension)]);
|
||||
|
||||
/* Send a message to the cache scripts to find the cached notecard content */
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:file-server:cache:send", JSON_OBJECT, ["request-id", request_id, "name", name]);
|
||||
}
|
||||
}
|
||||
/* Forward cache responses to the prim-dns script. */
|
||||
else if (jsonrpc_method == "prim-dns:file-server:cache:response")
|
||||
{
|
||||
key request_id = (key) llJsonGetValue(str, ["params", "request-id"]);
|
||||
string body = llJsonGetValue(str, ["params", "body"]);
|
||||
|
||||
jsonrpc_link_notification(sender, "prim-dns:response", JSON_OBJECT, ["request-id", request_id, "status", 200, "body", body]);
|
||||
}
|
||||
/* Allow other scripts to register paths which will be ignored by the file server. */
|
||||
else if (jsonrpc_method == "prim-dns:file-server:register-path")
|
||||
{
|
||||
string path = llJsonGetValue(str, ["params", "path"]);
|
||||
|
||||
registered_paths += path;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
prim-dns.lsl
25
prim-dns.lsl
@@ -1,5 +1,5 @@
|
||||
/* The version of prim-dns. */
|
||||
string version = "2.0.0";
|
||||
string version = "2.1.0";
|
||||
|
||||
/* The name of the configuration notecard. */
|
||||
string config_notecard = "prim-dns config";
|
||||
@@ -148,12 +148,17 @@ change_setting(string setting, string value)
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a JSON-RPC notification to send to other scripts. */
|
||||
/* JSON-RPC functions */
|
||||
string jsonrpc_notification(string method, string params_type, list params)
|
||||
{
|
||||
return llList2Json(JSON_OBJECT, ["jsonrpc", "2.0", "method", method, "params", llList2Json(params_type, params)]);
|
||||
}
|
||||
|
||||
jsonrpc_link_notification(integer link, string method, string params_type, list params)
|
||||
{
|
||||
llMessageLinked(link, 0, jsonrpc_notification(method, params_type, params), NULL_KEY);
|
||||
}
|
||||
|
||||
/* The names of all the possible headers in a request. */
|
||||
list HEADER_NAMES = [
|
||||
"x-script-url",
|
||||
@@ -283,7 +288,7 @@ state read_configuration
|
||||
state_entry()
|
||||
{
|
||||
set_text("Reading configuration...");
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:read-config-start", JSON_OBJECT, []), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:read-config-start", JSON_OBJECT, []);
|
||||
|
||||
/* If the config notecard doesn't exist, abort. */
|
||||
if (llGetInventoryType(config_notecard) != INVENTORY_NOTECARD)
|
||||
@@ -338,7 +343,7 @@ state read_configuration
|
||||
state_exit()
|
||||
{
|
||||
clear_text();
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:read-config-end", JSON_OBJECT, []), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:read-config-end", JSON_OBJECT, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +354,7 @@ state startup
|
||||
{
|
||||
set_text("Waiting for startup...");
|
||||
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:startup", JSON_OBJECT, []), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:startup", JSON_OBJECT, []);
|
||||
|
||||
if (auto_start)
|
||||
{
|
||||
@@ -433,7 +438,7 @@ state request_url
|
||||
{
|
||||
temporary_url = body;
|
||||
llOwnerSay("URL request granted: " + temporary_url);
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:url-request-granted", JSON_OBJECT, ["url", temporary_url]), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:url-request-granted", JSON_OBJECT, ["url", temporary_url]);
|
||||
|
||||
/* Use the obtained temporary URL and auth string to update the permanent URL alias */
|
||||
|
||||
@@ -501,7 +506,7 @@ state request_url
|
||||
llOwnerSay("****************************************\nCOPY THIS LINE INTO THE config NOTECARD:\n\nauth = " + llJsonGetValue(body, ["auth"]) + "\n\n****************************************");
|
||||
}
|
||||
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:alias-registered", JSON_OBJECT, ["alias", endpoint]), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:alias-registered", JSON_OBJECT, ["alias", endpoint]);
|
||||
|
||||
state main;
|
||||
}
|
||||
@@ -564,7 +569,7 @@ state main
|
||||
{
|
||||
llResetTime();
|
||||
set_text("Ready!");
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:startup-complete", JSON_OBJECT, []), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:startup-complete", JSON_OBJECT, []);
|
||||
if (status_update_interval > 0)
|
||||
{
|
||||
llSetTimerEvent(status_update_interval);
|
||||
@@ -623,7 +628,7 @@ state main
|
||||
/* Pass the request data to linked prims in a JSON-RPC message. */
|
||||
http_request(key request_id, string method, string body)
|
||||
{
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:request", JSON_OBJECT, ["request-id", request_id, "method", method, "headers", get_request_headers(request_id), "body", body]), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:request", JSON_OBJECT, ["request-id", request_id, "method", method, "headers", get_request_headers(request_id), "body", body]);
|
||||
}
|
||||
|
||||
/* Process JSON-RPC messages from linked prims. */
|
||||
@@ -711,7 +716,7 @@ state shutdown
|
||||
state_entry()
|
||||
{
|
||||
set_text("Shutting down...");
|
||||
llMessageLinked(LINK_SET, 0, jsonrpc_notification("prim-dns:shutting-down", JSON_OBJECT, []), NULL_KEY);
|
||||
jsonrpc_link_notification(LINK_SET, "prim-dns:shutting-down", JSON_OBJECT, []);
|
||||
state off;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
/* JSON-RPC functions */
|
||||
string jsonrpc_notification(string method, string params_type, list params)
|
||||
{
|
||||
return llList2Json(JSON_OBJECT, ["jsonrpc", "2.0", "method", method, "params", llList2Json(params_type, params)]);
|
||||
}
|
||||
|
||||
jsonrpc_link_notification(integer link, string method, string params_type, list params)
|
||||
{
|
||||
llMessageLinked(link, 0, jsonrpc_notification(method, params_type, params), NULL_KEY);
|
||||
}
|
||||
|
||||
default
|
||||
{
|
||||
link_message(integer sender, integer num, string str, key id)
|
||||
@@ -16,8 +22,8 @@ default
|
||||
string headers = llJsonGetValue(str, ["params", "headers"]);
|
||||
string body = llJsonGetValue(str, ["params", "body"]);
|
||||
|
||||
llMessageLinked(sender, 0, jsonrpc_notification("prim-dns:set-content-type", JSON_OBJECT, ["request-id", request_id, "content-type", CONTENT_TYPE_XHTML]), NULL_KEY);
|
||||
llMessageLinked(sender, 0, jsonrpc_notification("prim-dns:response", JSON_OBJECT, ["request-id", request_id, "status", 200, "body", "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body style=\"background-color: black; color: green; font-family: monospace;\"><b>Hello, world!</b></body></html>"]), NULL_KEY);
|
||||
jsonrpc_link_notification(sender, "prim-dns:set-content-type", JSON_OBJECT, ["request-id", request_id, "content-type", CONTENT_TYPE_XHTML]);
|
||||
jsonrpc_link_notification(sender, "prim-dns:response", JSON_OBJECT, ["request-id", request_id, "status", 200, "body", "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body style=\"background-color: black; color: green; font-family: monospace;\"><b>Hello, world!</b></body></html>"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user