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:
module:depends("http");
 
-- Now publish our HTTP 'app':
module:provides("http", {
    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():

module:depends("http");
module:log("info", "Hello! You can reach me at: %s", module:http_url());

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).

event.request = {
    headers = {
        host = "example.com";
        <...>
    };
    path = "/hello"; -- Full path, including base path
    url = {
        path = "/hello"; -- Same as above
        query = "foo=bar"; -- For /hello?foo=bar
    };
    method = "POST"; -- The HTTP method used, GET/POST/HEAD/etc.
    body = "raw-body";
    conn = <net.server connection object>;
    secure = false; -- true if connection encrypted
}

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]).

event.response = {
    status_code = 200;
    headers = {
        content_type = "text/html; charset=utf-8";
    };
}

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;
module:provides("http", {
    route = {
        ["GET /"] = function() return "Hello" end;
        ["GET /a_file.txt"] = serve(module:get_directory().."/my_file.txt");
        ["GET /static/*"] = serve{
          path ="/path/to/some/stuff";
          directory_index = true; -- Allow listing files
          index_files = { "index.html" };
        };
    };
});

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.