mod_esi(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

πŸ’– NAME πŸ’–

okay bestie, pull up a chair, because this is mod_esi(3) β€” the Erlang Module Definition page for mod_esi, aka the Erlang Server Interface. yep. that's the whole name. short, sweet, iconic. πŸ’…


✨ DESCRIPTION ✨

So this module defines the Erlang Server Interface (ESI) API. πŸŽ€

And here's the tea: it's a more efficient way of writing Erlang scripts for your Inets web server than writing them as common CGI scripts. like, CGI walked so ESI could run. no cap. πŸ”₯


πŸ¦‹ DATA TYPES πŸ¦‹

okay so here's the big one, the environment data. it's a union type, so let's lay it out exactly:

env() ::
          {server_software, string()} |
          {server_name, string()} |
          {gateway_interface, string()} |
          {server_protocol, string()} |
          {server_port, integer()} |
          {request_method, string()} |
          {remote_adress, inet:ip_address()} |
          {peer_cert,
           undefined | no_peercert | public_key:der_encoded()} |
          {script_name, string()} |
          {http_LowerCaseHTTPHeaderName, string()}.

Environment data associated with a request. πŸ’œ that's the vibe. it's the whole vibe of a request, wrapped up in a little tuple-shaped blanket.

🌸 Possible values 🌸

bestie, let me walk you through each one, one snack at a time:

  • {server_software, string()} β€” Indicates the inets version. it's the "what version am I running" lore drop.
  • {server_name, string()} β€” The local hostname. who are you, locally? this tells you.
  • {gateway_interface, string()} β€” Legacy string used in CGI, just ignore. literally just ignore it. it's giving vestigial tail. πŸ™ˆ
  • {server_protocol, string()} β€” HTTP version, currently "HTTP/1.1". yes it's currently that. clocked. ⭐
  • {server_port, integer()} β€” Servers port number. (the port the server is on, bestie.)
  • {request_method, "GET" | "PUT" | "DELETE" | "POST" | "PATCH"} β€” HTTP request method. pick your fighter. πŸ‘‘
  • {remote_adress, inet:ip_address()} β€” The clients ip address. (spelling is canon, don't @ me β€” it's rendered exactly as the module ships it.) πŸ“
  • {peer_cert, undefined | no_peercert | DER:binary()} β€” For TLS connections where client certificates are used this will be an ASN.1 DER-encoded X509-certificate as an Erlang binary. If client certificates are not used the value will be no_peercert, and if TLS is not used (HTTP or connection is lost due to network failure) the value will be undefined. so: cert = you got a certificate, no_peercert = no cert, undefined = no TLS at all or the connection ghosted you. πŸ’€
  • {script_name, string()} β€” Request URI. the address bar energy. 🌈
  • {http_LowerCaseHTTPHeaderName, string()} β€” example: {http_content_type, "text/html"}. so you lowercase the header name and glue http_ on the front. iykyk. πŸ’…

and then, drumroll please:

  • -opaque session_id() β€” Identifies the requesting client. that's it, that's the girl. she's opaque, you don't get to peek inside. πŸ™…β™€οΈ

πŸ”₯ FUNCTIONS πŸ”₯

deliver(SessionID, Data) -> ok | {error, Reason}
                 when
                     SessionID :: session_id(),
                     Data :: iolist(),
                     Reason :: bad_sessionID.

Sends data from an ESI script back to the client. ✨ a little packet of content, yeeted toward your user.

This function is only intended to be used from functions called by the ESI interface to deliver parts of the content to the user. like, it's an inside job. don't call it from random places, bestie, it's not that kind of function. 🫢

NOTE: If any HTTP header fields are added by the script, they must be in the first call to deliver/2, and the data in the call must be a string. Calls after the headers are complete can contain binary data to reduce copying overhead. Do not assume anything about the data type of SessionID. SessionID must be the value given as input to the ESI callback function that you implemented.

so to recap the rules of the club: headers go first, headers must be a string, after that you can go feral with binaries for the performance glow-up, and do NOT assume anything about the data type of SessionID β€” just pass through the one you were handed. periodt. πŸ’…


πŸŽ€ CALLBACKS πŸŽ€

'Function'(SessionID, Env, Input)

Called by mod_esi in response to requests. πŸ“¬

okay so the Module must be found in the code path and export Function with an arity of three. an erl_script_alias must also be set up in the configuration file for the web server, see the ESI properties documentation. (go read those docs, bestie, the config won't configure itself. 🧚)

The Module and Function that are called depend on the URL. See the ESI introductory documentation for more details. πŸ”

mod_esi:deliver/2 shall be used to generate the response to the client, and SessionID shall be passed as the first argument. that's the whole flow. you get called, you deliver, you slay. πŸ‘‘

🌈 Chunking 🌈

This function may be called several times to chunk the response data. βœ‚οΈ little bite-sized pieces, very demure.

Notice that the first chunk of data sent to the client must at least contain all HTTP header fields that the response will generate. If the first chunk does not contain the end of HTTP header, that is, "\r\n\r\n", the server assumes that no HTTP header fields will be generated. This behaviour depends on the httpd configuration, see below. ⚠️

like, if you forget the "\r\n\r\n", the server just shrugs and goes "okay, no headers then!" and that is not the plot twist you want. 🫠

πŸ’œ Parameters πŸ’œ

  • SessionID: request identifier.

Pass this to mod_esi:deliver/2 when generating a response. the sacred token. do not lose it. πŸŽ€

  • Env: environment data of the request, see env/0. all that tuple goodness from up top. ✨
  • Input: query data of a GET request or the body of a PUT or POST request.

The default behavior (legacy reasons) for delivering the body, is that the whole body is gathered and converted to a string. 🫢 But if the httpd config parameter max_client_body_chunk is set, the body will be delivered as binary chunks instead. The maximum size of the chunks is either max_client_body_chunk or decided by the client if it uses HTTP chunked encoding to send the body.

When using the chunking mechanism, this callback must return {continue, State::term()} for all calls where Input is {first, Data::binary()} or {continue, Data::binary(), State::term()}. When Input is {last, Data::binary(), State::term()} the return value will be ignored. πŸ‘€

The input State is the last returned State, in it the callback can include any data that it needs to keep track of when handling the chunks. it's your little backpack of context. carry it with you. πŸŽ’

NOTE: Note that if the body is small all data may be delivered in only one chunk and then the callback will be called with {last, Data::binary(), undefined} without getting called with {first, Data::binary()}.

so do not assume you'll always get a {first, ...} first β€” small bodies just skip straight to {last, Data::binary(), undefined} and that's totally normal, bestie. don't panic, just handle it. 😭

⭐ Setting a response status ⭐

To set the response status code, the special status response header can be sent. 🎯

For instance, to acknowledge creation of a resource and send an empty JSON response body, one could pass the following:

"status: 201 Created\r\ncontent-type: application/json\r\n\r\n{}"

look at that. status first, content-type, the \r\n\r\n separator, then {} β€” a whole empty JSON body serving absolutely nothing and eating zero crumbs. iconic. πŸ’…βœ¨


so yeah! that's mod_esi(3) β€” Erlang Server Interface. go forth, write efficient scripts, remember your headers go first, and never, ever assume anything about SessionID. πŸ’–πŸ¦‹πŸŒΈ

with love, Ericsson AB, from inets 9.7.2 β€” this was mod_esi(3), and yes she ate. πŸ’…πŸ‘‘πŸŽ€

πŸ“¦ where this page comes from