Hello Example

In this example, we demonstrate how the module "http" can be used to code a dynamic page of a web server. The prerequisites are basic HTTP and HTML knowledge and some practical experience in object-oriented programming. A tutorial on object-oriented programming in Jekejeke Prolog is found in the reference manual.

To realize a web server and run it via the module "http" a class has to be defined in the form of Prolog module text. This class can implement the Pythonesk methods initialized/1, destroyed/1, dispatch/3 and upgrade/3. If none of these methods are implemented the web server will neither send life-cycle notifications and requests will end in a 404 error.

We begin with; we will code the serving of a static image. The module "http" provides a couple of predicates to ease serving static content. The routing is still in the curtesy of the web server code that can decide which URL paths lead to what content. The following code will deliver an image to the browser client:

:- module(hello, []).
:- reexport(library(misc/http)).

:- override dispatch/4.
:- public dispatch/4.
dispatch(_, '/example04/piglet.gif', Request, Session) :- !,
dispatch_binary(library(example04/piglet), Request, Session).
dispatch(Object, Spec, Request, Session) :-
misc/http:dispatch(Object, Spec, Request, Session).

Before running the code make sure that the module has also a package directive so that short module names can be used. Further do not forget the add the module package root and the image package root to the class path. The web server can then be run as follows directly from the top-level:

?- use_module(library(misc/http)).
% 11 consults and 0 unloads in 188 ms.
Yes
?- run_http(example04/hello, 8085), write('.'), flush_output, fail; true.

The browser can now point to the web server and will receive the image for display. When on the same host as the host name suffices to "localhost". Otherwise, the IP number or host name of the web server can be looked up and remotely pointed to. The port number is the port number that was chosen for the run_http/2 command. As an example, we use:

http://localhost:8085/example04/piglet.gif

The above URL combines besides the host name "localhost" and the port number 8085 also the protocol name "http" and the path "example04/piglet.gif". The path is the same that our dispatch/4 method in the module "hello" recognized. The URL is simple in that it does not have any further components. The browser result is the image display at the client side:


Figure: Browser result of URL for static image.

We are now going to add further functionality. We want to have a dynamic HTML page that shows a parameter "name" from the request URL. We will call the dynamic HTML page "hello.jsp" and as a first step a further dispatch for this HTML page has to be added to the module "hello". We add a new clause after the image delivery and before the fallback.

:- override dispatch/4.
:- public dispatch/4.
dispatch(_, '/example04/piglet.gif', Request, Session) :- !,
dispatch_binary(library(example04/piglet), Request, Session).
dispatch(_, '/example04/hello.jsp', Request, Session) :- !,
dispatch_hello(Request, Session).
dispatch(Object, Spec, Request, Session) :-
misc/http:dispatch(Object, Spec, Request, Session).

The predicate dispatch_hello/2 has now the duty to first check whether the parameter "name" is there. The dispatch/4 predicate from the module "http" does deliver URL parameters in the third argument with the request. URL parameters can be accessed with the predicate http_parameter/3 from the module "http". This is done as follows:

:- private dispatch_hello/2.
dispatch_hello(Request, Session) :-
http_parameter(Request, name, Name), !,
catch(handle_hello(Name, Session), _, true).
dispatch_hello(_, Session) :-
dispatch_error(415, Session).

In the above code, we respond with an error 415 when the parameter is missing. The method dispatch_error/2 to deliver the response is from the module "http". If the parameter is there we use it to build some dynamic content. However, before we can deliver our dynamic content we need to get ourselves a response writer:

:- private handle_hello/2.
handle_hello(Name, Session) :-
setup_call_cleanup(
open(Session, write, Response),
send_hello(Name, Response),
close(Response)).

In the above, we do not find an ISO core standard catch/3 control construct as already used in the previous predicate to absorb an errors. Instead, we use the ISO working group suggested setup_call_cleanup/3 construct available in many Prolog systems. This way we can make sure that close/1 is called irrespective whether there was an error or not.

Incorporating data bears the danger of cross side scripting. If not enough attention is paid data will reach the client verbatim and might contain malicious script executions. To avoid such mishaps the module "http" provides a predicate html_escape/2 that will use XML entities in place of critical characters. We thus produce our dynamic content as follows:

:- private send_hello/2.
send_hello(Name, Response) :-
response_text(200, ['Content-Type'-
'text/html; charset=UTF-8'], Response),
atom_split(Title, ' ', ['Hello',Name]),
html_begin(Response, Title),
write(Response, ' <center><img src="piglet.gif">\r\n'),
write(Response, '<h1>Happy New Year 2019, '),
html_escape(Response, Name),
write(Response, '</h1></center>\r\n'),
html_end(Response).

In the above, the predicate response_text/3 will generate the HTTP response headers for us. For a realization of the predicates html_begin/2 and html_end/1 the reader is referred to the source text of the example. Reconsulting the enhanced module is a matter of stopping the web server via a top-level interrupt by the key combination Ctrl-Period.

.?- make.
% 1 consults and 0 unloads in 16 ms.
Yes
?- run_http(example04/hello, 8085), write('.'), flush_output, fail; true.

Stopping the web server via interrupt does no harm. Our realization of the catch/3 control construct is such that it cannot block interrupts so that no zombie threads should remain. Further, our realization of the setup_call_cleanup/3 control construct is such that it cleans up also for interrupt related exceptions.

As can be seen in the above, the web server is readily restarted by issuing a run_http/2 query. The URL that can now reach the dynamic page will need a "name" parameter. To have URL parameters in an URL we need to add a query part via the question mark character (?). Further the key and value needs to be separated by the equals character (=):

http://localhost:8085/example04/hello.jsp?name=Fritz

In case an URL needs multiple key value pairs these can be separated by the ampersand character (&). An URL might further contain a hash (#) to jump to some location. This is usually not part of a request. Rather information that a web server might generate for links. The browser result is the dynamic page rendered on the client side:


Figure: Browser result of URL for dynamic page.

The module "http" is a relatively new module. In this example, we tried to show some Prolog programming idioms that work for the coding of Prolog web servers based on simple GET methods. Some of the APIs of the module "http" might still change in the future, especially in respect of the debugging of service objects and new methods.

Kommentare