HTTP development
Prosody has a built-in HTTP server, mod_http. It also has an asynchronous HTTP client API, as well as general HTTP utility functions, in net.http.
This article goes through the basic building blocks of a HTTP-enabled module in Prosody.
Basic HTTP module
To handle HTTP requests a module adds a HTTP provider. A minimal example would be:
-- Ensure that mod_http is loaded:
:depends("http");
module
-- Now publish our HTTP 'app':
:provides("http", {
module= {
route ["GET"] = function () return "Hello world!"; end;
};
});
Each module's paths are automatically based at
http(s)://prosody/<app-name>
. The app name defaults
to the module's name with the mod_
or
mod_http_
prefix automatically removed. You can override
this by specifying a 'name' field in your provider object, but this is
not recommended.
Therefore if the above code were in mod_http_hello.lua, we would be
able to see "Hello world!" at http://prosody:5280/hello
and
https://prosody:5281/hello
.
Note that by default the unencrypted http://
port (5280)
is only accessible on localhost, and Prosody will prefer the
https://
URL by default. The precise URL used by any given
module can be altered by Prosody’s HTTP server
configuration. Taking all this into account, a module can discover
its own URL by calling module:http_url()
:
:depends("http");
module:log("info", "Hello! You can reach me at: %s", module:http_url()); module
Routes
The keys of the 'route' table specify the HTTP method to handle, and
optionally a path relative to the module's base path. If no path is
specified then it is equal to the module's base path
(e.g. /hello
).
Some examples:
= {
route -- /hello
["GET"] = function () return "Hello world!"; end;
["POST"] = function () return "Thank you for your data!"; end;
-- /hello/goodbye
["GET /goodbye"] = function () return "You say goodbye, and I say hello."; end;
}
It is also possible to set wildcard routes, these will be discussed a little later after we have looking at how handlers work. We'll also see how to handle the received data (e.g. from the POST above) next.
Handler functions
The handler function by default receives an event object, with at least two fields, 'request' and 'response'.
event.request
The request object contains various details about the request from the client, including the path, headers, and body (if any).
.request = {
event= {
headers = "example.com";
host <...>
};
= "/hello"; -- Full path, including base path
path = {
url = "/hello"; -- Same as above
path = "foo=bar"; -- For /hello?foo=bar
query };
= "POST"; -- The HTTP method used, GET/POST/HEAD/etc.
method = "raw-body";
body = <net.server connection object>;
conn = false; -- true if connection encrypted
secure }
Header names are lower-cased, and use '_' as a word separator rather than '-'. E.g. request.headers.content_length.
If the client submitted any data with its request (e.g. in the case of POST or PUT), then this is available in request.body. The data is an unprocessed string directly from the client, so you may need to look at request.headers.content_type to find out what kind of data it is and handle it accordingly. For the common type "application/x-www-form-urlencoded" (sent by browsers for form data) look at net.http's formdecode() function.
event.response
The response object is used to reply to a request. You can set the status code and headers on it, and then reply with response:send([body]).
.response = {
event= 200;
status_code = {
headers = "text/html; charset=utf-8";
content_type };
}
response:send(body) will transmit the response to the client. If a response body is specified, Content-Length header will be set automatically for you. Other headers will not be. So make sure to set content_type appropriately for the kind of data you are serving.
Return value
An alternative to calling response:send() is to simply return the data you want to send. You can either return a string, a numeric status code, a response table, an util.error object, or a promise (resolving to any of the previously mentioned types). If you return a code of 400 or above, Prosody will serve a default error page for that code.
Note that if you do not return a response, but instead choose to use response:send() (perhaps because you need to reply after completing some task) then your handler should return true to keep the connection open, and prevent other handlers from executing.
Wildcard routes
It is possible for a single handler to receive not just a single path, but all paths below it. This is done with a wildcard route:
= {
route ["GET /data/*"] = function (event, path) return "Hello from "..path.."!"; end;
["POST /data/submit"] = function (event) return "Thank you for your data!"; end;
};
You'll notice in the example above that handlers for wildcard routes receive an extra parameter. This is the part of the path that matched the wildcard. This is useful, because it already has the module's arbitrary base path stripped from it (unlike request.path).
Another thing to note is that if a non-wildcard route is given for a path, it will take priority over wildcard routes that might otherwise match the path. As above, you can choose not to handle a request and the next handler (such as a wildcard handler) answer it by returning nothing from your handler function.
Serving files
If your module needs to serve static resources, you can use
net.http.files
. It exposes a serve()
function
that returns a handler you can use in routes.
local serve = require"net.http.files".serve;
:provides("http", {
module= {
route ["GET /"] = function() return "Hello" end;
["GET /a_file.txt"] = serve(module:get_directory().."/my_file.txt");
["GET /static/*"] = serve{
="/path/to/some/stuff";
path = true; -- Allow listing files
directory_index = { "index.html" };
index_files };
};
});
It automatically handles both plain and wildcard routes. Note that
net.http.files
does not generate a file listings by itself,
but depends on another module handling that, such as
mod_http_dir_listing
from prosody-modules.