-:- module(http_server, [http_listen/2, sample_handler/1]).
+:- module(http_server, [http_listen/2, sample_handler/2, sample_body_handler/2]).
:- use_module(library(sockets)).
:- use_module(library(dcgs)).
:- use_module(library(error)).
:- use_module(library(charsio)).
:- use_module(library(lists)).
+:- use_module(library(iso_ext)).
% TODO
-% - Parse request Headers
-% - Output response Headers
+% - Parse body
% - Improve route matching
% - Long-running socket_server_accept
% - Cookies?
% - HTTP Error Codes
% - Redirections
% - Improve code quality
+% - Binary
+% - Comments
http_listen(Port, Handlers) :-
must_be(integer, Port),
must_be(list, Handlers),
once(socket_server_open(Port, Socket)),
- socket_server_accept(Socket, Client, Stream, [type(text)]),
+ socket_server_accept(Socket, Client, Stream, [type(binary)]),
read_header_lines(Stream, Lines),
[Request|Headers] = Lines,
+ % get_bytes(Stream, RequestBody), % Doesn't work
(
- phrase(parse_request(Version, Method, Path), Request) -> (
- call_handler(Path, Headers, Handlers, Response),
- http_response(StatusCode, Body) = Response,
- format(Stream, "HTTP/1.0 ~d\r\n\r\n~s", [StatusCode, Body])
+ (phrase(parse_request(Version, Method, Path), Request), maplist(map_parse_header, Headers, HeadersKV)) -> (
+ call_handler(Path, HeadersKV, RequestBody, Handlers, Response),
+ http_response(StatusCode, ResponseBody, RequestHeaders) = Response,
+ format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
+ forall(member(RequestHeaderKey-RequestHeaderValue, RequestHeaders), format(Stream, "~s: ~s\r\n", [RequestHeaderKey, RequestHeaderValue])),
+ format(Stream, "\r\n~s", [ResponseBody])
);(
format(Stream, "HTTP/1.0 400 Bad Request\r\n\r\n", []) % bad format
)
),
close(Stream).
-call_handler(Path, Headers, Handlers, Response) :-
- member(http_route(Dcg, Handler), Handlers),
+call_handler(Path, Headers, _, Handlers, Response) :-
+ member(get(Dcg, Handler), Handlers),
phrase(Dcg, Path),!,
- call(Handler, Response).
+ Request = http_request(Headers, _),
+ call(Handler, Request, Response).
+
+call_handler(Path, Headers, Body, Handlers, Response) :-
+ member(post(Dcg, Handler), Handlers),
+ phrase(Dcg, Path),!,
+ Request = http_request(Headers, Body),
+ call(Handler, Request, Response).
call_handler(_, _, _, http_response(404, "Not found")).
natural(Minor),
"\r\n".
+map_parse_header(Header, HeaderKV) :-
+ phrase(parse_header(HeaderKV), Header).
+
+parse_header(Key-Value) -->
+ string_without(":", Key),
+ ": ",
+ string_without("\r", Value),
+ "\r\n".
+
method(options) --> "OPTIONS".
method(get) --> "GET".
method(head) --> "HEAD".
read_header_lines(Stream, Rest)
).
-sample_handler(Response) :-
- Response = http_response(200, "Hello Prolog friends!").
\ No newline at end of file
+
+get_bytes(Stream, Res) :- get_bytes(Stream, [], Res).
+get_bytes(Stream, Acc, Res) :-
+ get_byte(Stream, B),
+ (B =:= -1 ->
+ reverse(Acc, Res)
+ ; get_bytes(Stream, [B|Acc], Res)).
+
+sample_handler(http_request(Headers, _), Response) :-
+ member("User-Agent"-UserAgent, Headers),
+ Response = http_response(200, UserAgent, ["Content-Type"-"text/plain"]).
+
+sample_body_handler(http_request(Headers, Body), http_response(200, Body, ["Content-Type"-"application/json"])).
\ No newline at end of file