3 The HTTP server libraries
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • SWI-Prolog HTTP support
        • The HTTP server libraries
          • Creating an HTTP reply
          • library(http/http_dispatch): Dispatch requests in the HTTP server
          • library(http/http_dirindex): HTTP directory listings
          • library(http/http_files): Serve plain files from a hierarchy
          • library(http/http_session): HTTP Session management
          • library(http/http_cors): Enable CORS: Cross-Origin Resource Sharing
          • library(http/http_authenticate): Authenticate HTTP connections using 401 headers
          • library(http/http_digest): HTTP Digest authentication
          • library(http/http_dyn_workers): Dynamically schedule HTTP workers.
          • Custom Error Pages
          • library(http/http_openid): OpenID consumer and server library
          • Get parameters from HTML forms
          • Request format
          • Running the server
          • The wrapper library
          • library(http/http_host): Obtain public server location
          • library(http/http_log): HTTP Logging module
          • library(http/http_server_health): HTTP Server health statistics
          • Debugging HTTP servers
          • library(http/http_header): Handling HTTP headers
          • The library(http/html_write) library
            • html//1
            • page//2
            • page//1
            • html_begin//1
            • html_end//1
            • Emitting HTML documents
            • Repositioning HTML for CSS and javascript links
            • Adding rules for html//1
            • Generating layout
            • Examples for using the HTML write library
            • Remarks on the library(http/html_write) library
          • library(http/js_write): Utilities for including JavaScript
          • library(http/http_path): Abstract specification of HTTP server locations
          • library(http/html_head): Automatic inclusion of CSS and scripts links
          • library(http/http_pwp): Serve PWP pages through the HTTP server
          • library(http/htmx): Support htmx.org

3.21 The library(http/html_write) library

Producing output for the web in the form of an HTML document is a requirement for many Prolog programs. Just using format/2 is not satisfactory as it leads to poorly readable programs generating poor HTML. This library is based on using DCG rules.

The library(http/html_write) structures the generation of HTML from a program. It is an extensible library, providing a DCG framework for generating legal HTML under (Prolog) program control. It is especially useful for the generation of structured pages (e.g. tables) from Prolog data structures.

The normal way to use this library is through the DCG html//1. This non-terminal provides the central translation from a structured term with embedded calls to additional translation rules to a list of atoms that can then be printed using print_html/[1,2].

html(:Spec)//
The DCG non-terminal html//1 is the main predicate of this library. It translates the specification for an HTML page into a list of atoms that can be written to a stream using print_html/[1,2]. The expansion rules of this predicate may be extended by defining the multifile DCG html_write:expand//1. Spec is either a single specification or a list of single specifications. Using nested lists is not allowed to avoid ambiguity caused by the atom []

  • Atomic data
    Atomic data is quoted using html_quoted//1.

  • Fmt - Args
    Fmt and Args are used as format-specification and argument list to format/3. The result is quoted and added to the output list.

  • \List
    Escape sequence to add atoms directly to the output list. This can be used to embed external HTML code or emit script output. List is a list of the following terms:

    • Fmt - Args
      Fmt and Args are used as format-specification and argument list to format/3. The result is added to the output list.
    • Atomic
      Atomic values are added directly to the output list.

  • \Term
    Invoke the non-terminal Term in the calling module. This is the common mechanism to realise abstraction and modularisation in generating HTML.

  • Module:Term
    Invoke the non-terminal <Module>:<Term>. This is similar to \Term but allows for invoking grammar rules in external packages.

  • &(Entity)
    Emit &<Entity>; or &#<Entity>; if Entity is an integer. SWI-Prolog atoms and strings are represented as Unicode. Explicit use of this construct is rarely needed because code-points that are not supported by the output encoding are automatically converted into character-entities.

  • Tag(Content)
    Emit HTML element Tag using Content and no attributes. Content is handed to html//1. See section 3.21.4 for details on the automatically generated layout.

  • Tag(Attributes, Content)
    Emit HTML element Tag using Attributes and Content. Attributes is either a single attribute of a list of attributes. Each attributes is of the format Name(Value) or Name=Value. Value is the atomic attribute value but allows for a limited functional notation:

    • A + B
      Concatenation of A and B
    • Format-Arguments
      Use format/3 and emit the result as quoted value.
    • encode(Atom)
      Use uri_encoded/3 to create a valid URL query component.
    • location_by_id(ID)
      HTTP location of the HTTP handler with given ID. See http_location_by_id/2.
    • #(ID)
      Abbreviated for for location_by_id(ID).
    • A + List
      List is handled as a URL‘search’component. The list members are terms of the format Name = Value or Name(Value). Values are encoded as in the encode option described above.
    • List
      Emit SGML multi-valued attributes (e.g., NAMES). Each value in list is separated by a space. This is particularly useful for setting multiple class attributes on an element. For example:
              ...
              span(class([c1,c2]), ...),

    The example below generates a URL that references the predicate set_lang/1 in the application with given parameters. The http_handler/3 declaration binds /setlang to the predicate set_lang/1 for which we provide a very simple implementation. The code between ... is part of an HTML page showing the English flag which, when pressed, calls set_lang(Request) where Request contains the search parameter lang = en. Note that the HTTP location (path) /setlang can be moved without affecting this code.

    :- http_handler('/setlang', set_lang, []).
    
    set_lang(Request) :-
            http_parameters(Request,
                            [ lang(Lang, [])
                            ]),
            http_session_retractall(lang(_)),
            http_session_assert(lang(Lang)),
            reply_html_page(title('Switched language'),
                            p(['Switch language to ', Lang])).
    
    
            ...
            html(a(href(location_by_id(set_lang) + [lang(en)]),
                   img(src('/www/images/flags/en.png')))),
            ...

page(:HeadContent, :BodyContent)//
The DCG non-terminal page//2 generated a complete page, including the SGML DOCTYPE declaration. HeadContent are elements to be placed in the head element and BodyContent are elements to be placed in the body element.

To achieve common style (background, page header and footer), it is possible to define DCG non-terminals head//1 and/or body//1. Non-terminal page//1 checks for the definition of these non-terminals in the module it is called from as well as in the user module. If no definition is found, it creates a head with only the HeadContent (note that the title is obligatory) and a body with bgcolor set to white and the provided BodyContent.

Note that further customisation is easily achieved using html//1 directly as page//2 is (besides handling the hooks) defined as:

page(Head, Body) -->
        html([ \['<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 4.0//EN">\n'],
               html([ head(Head),
                      body(bgcolor(white), Body)
                    ])
             ]).
page(:Contents)//
This version of the page/[1,2] only gives you the SGML DOCTYPE and the HTML element. Contents is used to generate both the head and body of the page.
html_begin(+Begin)//
Just open the given element. Begin is either an atom or a compound term, In the latter case the arguments are used as arguments to the begin-tag. Some examples:
        html_begin(table)
        html_begin(table(border(2), align(center)))

This predicate provides an alternative to using the \Command syntax in the html//1 specification. The following two fragments are the same. The preferred solution depends on your preferences as well as whether the specification is generated or entered by the programmer.

table(Rows) -->
        html(table([border(1), align(center), width('80%')],
                   [ \table_header,
                     \table_rows(Rows)
                   ])).

% or

table(Rows) -->
        html_begin(table(border(1), align(center), width('80%'))),
        table_header,
        table_rows,
        html_end(table).
html_end(+End)//
End an element. See html_begin/1 for details.

3.21.1 Emitting HTML documents

The non-terminal html//1 translates a specification into a list of atoms and layout instructions. Currently the layout instructions are terms of the format nl(N), requesting at least N newlines. Multiple consecutive nl(1) terms are combined to an atom containing the maximum of the requested number of newline characters.

To simplify handing the data to a client or storing it into a file, the following predicates are available from this library:

reply_html_page(:Head, :Body)
Same as reply_html_page(default, Head, Body).
reply_html_page(+Style, :Head, :Body)
Writes an HTML page preceded by an HTTP header as required by library(http_wrapper) (CGI-style). Here is a simple typical example:
reply(Request) :-
        reply_html_page(title('Welcome'),
                        [ h1('Welcome'),
                          p('Welcome to our ...')
                        ]).

The header and footer of the page can be hooked using the grammar-rules user:head//2 and user:body//2. The first argument passed to these hooks is the Style argument of reply_html_page/3 and the second is the 2nd (for head//2) or 3rd (for body//2) argument of reply_html_page/3. These hooks can be used to restyle the page, typically by embedding the real body content in a div. E.g., the following code provides a menu on top of each page of that is identified using the style myapp.

:- multifile
        user:body//2.

user:body(myapp, Body) -->
        html(body([ div(id(top), \application_menu),
                    div(id(content), Body)
                  ])).

Redefining the head can be used to pull in scripts, but typically html_requires//1 provides a more modular approach for pulling scripts and CSS-files.

reply_html_partial(+HTML)
Reply with partial HTML document. The reply only contains the element from HTML, i.e., this predicate does not add a DOCTYPE header, html, head or body. It is intended for JavaScript handlers that request a partial document and insert that somewhere into the existing page DOM. See reply_html_page/3 to reply with a complete (valid) HTML page.
print_html(+List)
Print the token list to the Prolog current output stream.
print_html(+Stream, +List)
Print the token list to the specified output stream
html_print_length(+List, -Length)
When calling html_print/[1,2] on List, Length characters will be produced. Knowing the length is needed to provide the Content-length field of an HTTP reply-header.

3.21.2 Repositioning HTML for CSS and javascript links

Modern HTML commonly uses CSS and Javascript. This requires <link> elements in the HTML <head> element or <script> elements in the <body>. Unfortunately this seriously harms re-using HTML DCG rules as components as each of these components may rely on their own style sheets or JavaScript code. We added a‘mailing’system to reposition and collect fragments of HTML. This is implemented by html_post//2, html_receive//1 and html_receive//2.

[det]html_post(+Id, :HTML)//
Reposition HTML to the receiving Id. The html_post//2 call processes HTML using html//1. Embedded \-commands are executed by mailman/1 from print_html/1 or html_print_length/2. These commands are called in the calling context of the html_post//2 call.

A typical usage scenario is to get required CSS links in the document head in a reusable fashion. First, we define css//1 as:

css(URL) -->
        html_post(css,
                  link([ type('text/css'),
                         rel('stylesheet'),
                         href(URL)
                       ])).

Next we insert the unique CSS links, in the pagehead using the following call to reply_html_page/2:

        reply_html_page([ title(...),
                          \html_receive(css)
                        ],
                        ...)
[det]html_receive(+Id)//
Receive posted HTML tokens. Unique sequences of tokens posted with html_post//2 are inserted at the location where html_receive//1 appears.
See also
- The local predicate sorted_html//1 handles the output of html_receive//1.
- html_receive//2 allows for post-processing the posted material.
[det]html_receive(+Id, :Handler)//
This extended version of html_receive//1 causes Handler to be called to process all messages posted to the channel at the time output is generated. Handler is called as below, where PostedTerms is a list of Module:Term created from calls to html_post//2. Module is the context module of html_post and Term is the unmodified term. Members in PostedTerms are in the order posted and may contain duplicates.
  phrase(Handler, PostedTerms, HtmlTerms, Rest)

Typically, Handler collects the posted terms, creating a term suitable for html//1 and finally calls html//1.

The library predefines the receiver channel head at the end of the head element for all pages that write the html head through this library. The following code can be used anywhere inside an HTML generating rule to demand a javascript in the header:

js_script(URL) -->
        html_post(head, script([ src(URL),
                                 type('text/javascript')
                               ], [])).

This mechanism is also exploited to add XML namespace (xmlns) declarations to the (outer) html element using xhml_ns//2:

xhtml_ns(+Id, +Value)//
Demand an xmlns:id=Value in the outer html tag. This uses the html_post/2 mechanism to post to the xmlns channel. Rdfa (http://www.w3.org/2006/07/SWD/RDFa/syntax/), embedding RDF in (x)html provides a typical usage scenario where we want to publish the required namespaces in the header. We can define:
rdf_ns(Id) -->
        { rdf_global_id(Id:'', Value) },
        xhtml_ns(Id, Value).

After which we can use rdf_ns//1 as a normal rule in html//1 to publish namespaces from library(semweb/rdf_db). Note that this macro only has effect if the dialect is set to xhtml. In html mode it is silently ignored.

The required xmlns receiver is installed by html_begin//1 using the html tag and thus is present in any document that opens the outer html environment through this library.

3.21.3 Adding rules for html//1

In some cases it is practical to extend the translations imposed by html//1. We used this technique to define translation rules for the output of the SWI-Prolog library(sgml) package.

The html//1 non-terminal first calls the multifile ruleset html_write:expand//1.

html_write:expand(+Spec)//
Hook to add additional translation rules for html//1.
html_quoted(+Atom)//
Emit the text in Atom, inserting entity-references for the SGML special characters <&>.
html_quoted_attribute(+Atom)//
Emit the text in Atom suitable for use as an SGML attribute, inserting entity-references for the SGML special characters <&>".

3.21.4 Generating layout

Though not strictly necessary, the library attempts to generate reasonable layout in SGML output. It does this only by inserting newlines before and after tags. It does this on the basis of the multifile predicate html_write:layout/3

html_write:layout(+Tag, -Open, -Close)
Specify the layout conventions for the element Tag, which is a lowercase atom. Open is a term Pre-Post. It defines that the element should have at least Pre newline characters before and Post after the tag. The Close specification is similar, but in addition allows for the atom -, requesting the output generator to omit the close-tag altogether or empty, telling the library that the element has declared empty content. In this case the close-tag is not emitted either, but in addition html//1 interprets Arg in Tag(Arg) as a list of attributes rather than the content.

A tag that does not appear in this table is emitted without additional layout. See also print_html/[1,2]. Please consult the library source for examples.

3.21.5 Examples for using the HTML write library

In the following example we will generate a table of Prolog predicates we find from the SWI-Prolog help system based on a keyword. The primary database is defined by the predicate predicate/5 We will make hyperlinks for the predicates pointing to their documentation.

:- use_module(library(http/html_write)).
:- use_module(library(pldoc/man_index)).
:- use_module(library(uri)).

html_apropos(Kwd) :-
    findall(Pred, apropos_predicate(Kwd, Pred), Matches),
    phrase(apropos_page(Kwd, Matches), Tokens),
    print_html(Tokens).

%       emit page with title, header and table of matches

apropos_page(Kwd, Matches) -->
    page([ title(['Predicates for ', Kwd])
         ],
         [ h2(align(center),
              ['Predicates for ', Kwd]),
           table([ align(center),
                   border(1),
                   width('80%')
                 ],
                 [ tr([ th('Predicate'),
                        th('Summary')
                      ])
                 | \apropos_rows(Matches)
                 ])
         ]).

%       emit the rows for the body of the table.

apropos_rows([]) -->
    [].
apropos_rows([pred(Name, Arity, Summary)|T]) -->
    html([ tr([ td(\predref(Name/Arity)),
                td(em(Summary))
              ])
         ]),
    apropos_rows(T).

%!  predref(Name/Arity)//
%
%   Emit Name/Arity as a hyperlink to
%
%           /cgi-bin/plman?name=Name&arity=Arity

predref(Name/Arity) -->
    { uri_edit([search([name=Name,arity=Arity])],
               '/cgi-bin/plman', Href)
    },
    html(a(href(Href), [Name, /, Arity])).

%       Find predicates from a keyword.

apropos_predicate(Pattern, pred(Name, Arity, Summary)) :-
    man_object_property(Name/Arity, summary(Summary)),
    (   sub_atom_icasechk(Name, _, Pattern)
    ->  true
    ;   sub_atom_icasechk(Summary, _, Pattern)
    ).

3.21.6 Remarks on the library(http/html_write) library

This library is the result of various attempts to reach at a more satisfactory and Prolog-minded way to produce HTML text from a program. We have been using Prolog for the generation of web pages in a number of projects. Just using format/2 never was not a real option, generating error-prone HTML from clumsy syntax. We started with a layer on top of format/2, keeping track of the current nesting and thus always capable of properly closing the environment.

DCG based translation however, naturally exploits Prolog's term-rewriting primitives. If generation fails for whatever reason it is easy to produce an alternative document (for example holding an error message).

In a future version we will probably define a goal_expansion/2 to do compile-time optimisation of the library. Quotation of known text and invocation of sub-rules using the \RuleSet and <Module>:<RuleSet> operators are costly operations in the analysis that can be done at compile-time.