From dbc193ab2a478489067b6871949ce7657f945d48 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Wed, 9 Dec 2020 23:40:00 +0100 Subject: [PATCH] WIP http_server --- src/lib/http/http_server.pl | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/lib/http/http_server.pl diff --git a/src/lib/http/http_server.pl b/src/lib/http/http_server.pl new file mode 100644 index 00000000..c1889cff --- /dev/null +++ b/src/lib/http/http_server.pl @@ -0,0 +1,98 @@ +:- module(http_server, [http_listen/2, sample_handler/1]). + +:- use_module(library(sockets)). +:- use_module(library(dcgs)). +:- use_module(library(format)). +:- use_module(library(error)). +:- use_module(library(charsio)). +:- use_module(library(lists)). + +% TODO +% - Parse request Headers +% - Output response Headers +% - Improve route matching +% - Long-running socket_server_accept +% - Cookies? +% - HTTP Error Codes +% - Redirections +% - Improve code quality + +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)]), + read_header_lines(Stream, Lines), + [Request|Headers] = Lines, + ( + 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]) + );( + 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), + phrase(Dcg, Path),!, + call(Handler, Response). + +call_handler(_, _, _, http_response(404, "Not found")). + +parse_request(http_version(Major, Minor), Method, Path) --> + method(Method), + " ", + string_without(" ", Path), + " ", + "HTTP/", + natural(Major), + ".", + natural(Minor), + "\r\n". + +method(options) --> "OPTIONS". +method(get) --> "GET". +method(head) --> "HEAD". +method(post) --> "POST". +method(put) --> "PUT". +method(delete) --> "DELETE". + +string_without(Not, [Char|String]) --> + [Char], + { + \+ member(Char, Not) + }, + string_without(Not, String). + +string_without(_, []) --> + []. + +natural(Nat) --> + natural_(NatChars), + { + number_chars(Nat, NatChars) + }. + +natural_([Nat|Nats]) --> + [Nat], + { + char_type(Nat, decimal_digit) + }, + natural_(Nats). + +natural_([]) --> + []. + +read_header_lines(Stream, Hs) :- + read_line_to_chars(Stream, Cs, []), + ( Cs == "" -> Hs = [] + ; Cs == "\r\n" -> Hs = [] + ; Hs = [Cs|Rest], + read_header_lines(Stream, Rest) + ). + +sample_handler(Response) :- + Response = http_response(200, "Hello Prolog friends!"). \ No newline at end of file -- 2.54.0