Here is something that works:
:- use_module(library(http/http_server)).
:- use_module(library(http/html_write)).
:- use_module(library(dcg/high_order)).
run :-
http_server([port(8080)]).
:- http_handler(root(.), reply, []).
reply(_Request) :-
findall(row(N,C), row(N,C), Rows),
phrase(page([ head(title('Fortunes')),
body(table(\sequence(row, Rows)))
]),
Tokens),
format('Content-type: text/html~n~n'),
print_html(Tokens).
row(row(N, C)) -->
html(tr([td(N), td(C)])).
% data
row(1, 'こんにちは').
row(2, '<alert>Good bye</alert>').
And here is the output:
$ curl -D hdr http://localhost:8080/
<!DOCTYPE html>
<html>
<head>
<title>Fortunes</title>
</head>
<body>
<table>
<tr><td>1</td><td>こんにちは</td></tr>
<tr><td>2</td><td><alert>Good bye</alert></td></tr>
</table>
</body>
</html>
$ cat hdr
HTTP/1.1 200 OK
Date: Fri, 23 Jul 2021 12:30:22 GMT
Content-Type: text/html; charset=UTF-8
Connection: Keep-Alive
Content-Length: 213
Edit: may require 8.3.x. Not sure when sequence//2 was added to the library.