HTTP Server 2.0

This commit is contained in:
Adrián Arroyo Calle
2022-08-11 20:25:19 +02:00
committed by Mark Thom
parent 91d4e91f53
commit 181be5be3f
12 changed files with 602 additions and 274 deletions

51
Cargo.lock generated
View File

@@ -411,6 +411,21 @@ dependencies = [
"new_debug_unreachable", "new_debug_unreachable",
] ]
[[package]]
name = "futures"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.21" version = "0.3.21"
@@ -418,6 +433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -426,6 +442,34 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-executor"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-macro"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
"proc-macro2 1.0.36",
"quote 1.0.17",
"syn 1.0.90",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.21"
@@ -444,10 +488,16 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab",
] ]
[[package]] [[package]]
@@ -1628,6 +1678,7 @@ dependencies = [
"ctrlc", "ctrlc",
"dirs-next", "dirs-next",
"divrem", "divrem",
"futures",
"fxhash", "fxhash",
"git-version", "git-version",
"hostname", "hostname",

View File

@@ -67,6 +67,7 @@ ryu = "1.0.9"
hyper = { version = "0.14", features = ["full"] } hyper = { version = "0.14", features = ["full"] }
hyper-tls = "0.5.0" hyper-tls = "0.5.0"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
futures = "0.3"
[dev-dependencies] [dev-dependencies]
assert_cmd = "1.0.3" assert_cmd = "1.0.3"

View File

@@ -544,6 +544,12 @@ enum SystemClauseType {
DeterministicLengthRundown, DeterministicLengthRundown,
#[strum_discriminants(strum(props(Arity = "7", Name = "$http_open")))] #[strum_discriminants(strum(props(Arity = "7", Name = "$http_open")))]
HttpOpen, HttpOpen,
#[strum_discriminants(strum(props(Arity = "2", Name = "$http_listen")))]
HttpListen,
#[strum_discriminants(strum(props(Arity = "7", Name = "$http_accept")))]
HttpAccept,
#[strum_discriminants(strum(props(Arity = "4", Name = "$http_answer")))]
HttpAnswer,
#[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))] #[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))]
PredicateDefined, PredicateDefined,
#[strum_discriminants(strum(props(Arity = "3", Name = "$strip_module")))] #[strum_discriminants(strum(props(Arity = "3", Name = "$strip_module")))]
@@ -1686,6 +1692,9 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::CallCpuNow(_) | &Instruction::CallCpuNow(_) |
&Instruction::CallDeterministicLengthRundown(_) | &Instruction::CallDeterministicLengthRundown(_) |
&Instruction::CallHttpOpen(_) | &Instruction::CallHttpOpen(_) |
&Instruction::CallHttpListen(_) |
&Instruction::CallHttpAccept(_) |
&Instruction::CallHttpAnswer(_) |
&Instruction::CallPredicateDefined(_) | &Instruction::CallPredicateDefined(_) |
&Instruction::CallStripModule(_) | &Instruction::CallStripModule(_) |
&Instruction::CallCurrentTime(_) | &Instruction::CallCurrentTime(_) |
@@ -1895,6 +1904,9 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::ExecuteCpuNow(_) | &Instruction::ExecuteCpuNow(_) |
&Instruction::ExecuteDeterministicLengthRundown(_) | &Instruction::ExecuteDeterministicLengthRundown(_) |
&Instruction::ExecuteHttpOpen(_) | &Instruction::ExecuteHttpOpen(_) |
&Instruction::ExecuteHttpListen(_) |
&Instruction::ExecuteHttpAccept(_) |
&Instruction::ExecuteHttpAnswer(_) |
&Instruction::ExecutePredicateDefined(_) | &Instruction::ExecutePredicateDefined(_) |
&Instruction::ExecuteStripModule(_) | &Instruction::ExecuteStripModule(_) |
&Instruction::ExecuteCurrentTime(_) | &Instruction::ExecuteCurrentTime(_) |

View File

@@ -1,3 +1,4 @@
use crate::http::{HttpListener, HttpResponse};
use crate::machine::loader::LiveLoadState; use crate::machine::loader::LiveLoadState;
use crate::machine::machine_indices::*; use crate::machine::machine_indices::*;
use crate::machine::streams::*; use crate::machine::streams::*;
@@ -139,7 +140,8 @@ pub enum ArenaHeaderTag {
OutputFileStream = 0b10100, OutputFileStream = 0b10100,
NamedTcpStream = 0b011100, NamedTcpStream = 0b011100,
NamedTlsStream = 0b100000, NamedTlsStream = 0b100000,
NamedHttpClientStream = 0b100001, HttpReadStream = 0b100001,
HttpWriteStream = 0b100010,
ReadlineStream = 0b110000, ReadlineStream = 0b110000,
StaticStringStream = 0b110100, StaticStringStream = 0b110100,
ByteStream = 0b111000, ByteStream = 0b111000,
@@ -147,6 +149,8 @@ pub enum ArenaHeaderTag {
StandardErrorStream = 0b11000, StandardErrorStream = 0b11000,
NullStream = 0b111100, NullStream = 0b111100,
TcpListener = 0b1000000, TcpListener = 0b1000000,
HttpListener = 0b1000001,
HttpResponse = 0b1000010,
Dropped = 0b1000100, Dropped = 0b1000100,
IndexPtrDynamicUndefined = 0b1000101, IndexPtrDynamicUndefined = 0b1000101,
IndexPtrDynamicIndex = 0b1000110, IndexPtrDynamicIndex = 0b1000110,
@@ -560,6 +564,50 @@ impl ArenaAllocated for TcpListener {
} }
} }
impl ArenaAllocated for HttpListener {
type PtrToAllocated = TypedArenaPtr<HttpListener>;
#[inline]
fn tag() -> ArenaHeaderTag {
ArenaHeaderTag::HttpListener
}
#[inline]
fn size(&self) -> usize {
mem::size_of::<Self>()
}
#[inline]
fn copy_to_arena(self, dst: *mut Self) -> Self::PtrToAllocated {
unsafe {
ptr::write(dst, self);
TypedArenaPtr::new(dst as *mut Self)
}
}
}
impl ArenaAllocated for HttpResponse {
type PtrToAllocated = TypedArenaPtr<HttpResponse>;
#[inline]
fn tag() -> ArenaHeaderTag {
ArenaHeaderTag::HttpResponse
}
#[inline]
fn size(&self) -> usize {
mem::size_of::<Self>()
}
#[inline]
fn copy_to_arena(self, dst: *mut Self) -> Self::PtrToAllocated {
unsafe {
ptr::write(dst, self);
TypedArenaPtr::new(dst as *mut Self)
}
}
}
impl ArenaAllocated for IndexPtr { impl ArenaAllocated for IndexPtr {
type PtrToAllocated = TypedArenaPtr<IndexPtr>; type PtrToAllocated = TypedArenaPtr<IndexPtr>;
@@ -647,9 +695,12 @@ unsafe fn drop_slab_in_place(value: &mut AllocSlab) {
ArenaHeaderTag::NamedTlsStream => { ArenaHeaderTag::NamedTlsStream => {
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<NamedTlsStream>>>()); ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<NamedTlsStream>>>());
} }
ArenaHeaderTag::NamedHttpClientStream => { ArenaHeaderTag::HttpReadStream => {
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<NamedHttpClientStream>>>()); ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<HttpReadStream>>>());
} }
ArenaHeaderTag::HttpWriteStream => {
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<HttpWriteStream>>>());
}
ArenaHeaderTag::ReadlineStream => { ArenaHeaderTag::ReadlineStream => {
ptr::drop_in_place(value.payload_offset::<StreamLayout<ReadlineStream>>()); ptr::drop_in_place(value.payload_offset::<StreamLayout<ReadlineStream>>());
} }
@@ -670,6 +721,12 @@ unsafe fn drop_slab_in_place(value: &mut AllocSlab) {
ArenaHeaderTag::TcpListener => { ArenaHeaderTag::TcpListener => {
ptr::drop_in_place(value.payload_offset::<TcpListener>()); ptr::drop_in_place(value.payload_offset::<TcpListener>());
} }
ArenaHeaderTag::HttpListener => {
ptr::drop_in_place(value.payload_offset::<HttpListener>());
}
ArenaHeaderTag::HttpResponse => {
ptr::drop_in_place(value.payload_offset::<HttpResponse>());
}
ArenaHeaderTag::StandardOutputStream => { ArenaHeaderTag::StandardOutputStream => {
ptr::drop_in_place(value.payload_offset::<StreamLayout<StandardOutputStream>>()); ptr::drop_in_place(value.payload_offset::<StreamLayout<StandardOutputStream>>());
} }

25
src/http.rs Normal file
View File

@@ -0,0 +1,25 @@
use std::sync::Arc;
use std::convert::Infallible;
use hyper::{Response, Request, Body};
use tokio::sync::Mutex;
use tokio::sync::mpsc::{channel, Receiver, Sender};
pub struct HttpListener {
pub incoming: Receiver<HttpRequest>
}
#[derive(Debug)]
pub struct HttpRequest {
pub request: Request<Body>,
pub response: HttpResponse,
}
pub type HttpResponse = Sender<Response<Body>>;
pub async fn serve_req(req: Request<Body>, tx: Arc<Mutex<Sender<HttpRequest>>>) -> Result<Response<Body>, Infallible> {
let (response_tx, mut rx) = channel(1);
let http_request = HttpRequest { request: req, response: response_tx };
tx.lock().await.send(http_request).await.unwrap();
Ok(rx.recv().await.unwrap())
}

View File

@@ -19,6 +19,7 @@ mod fixtures;
mod forms; mod forms;
mod heap_iter; mod heap_iter;
pub mod heap_print; pub mod heap_print;
mod http;
mod indexing; mod indexing;
#[macro_use] #[macro_use]
pub mod instructions { pub mod instructions {

View File

@@ -1,11 +1,11 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com) Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
Part of Scryer Prolog Part of Scryer Prolog
This library provides an starting point to build HTTP server based applications. This library provides an starting point to build HTTP server based applications.
It currently implements a subset of HTTP/1.0. It is recommended to put a reverse It is based on Hyper, which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
proxy like nginx in front of this server to have access to more advanced features some advanced features that Hyper provides are still not accesible.
(gzip compression, HTTPS, ...)
Usage Usage
========== ==========
@@ -41,37 +41,36 @@
Some things that are still missing: Some things that are still missing:
- Read forms in multipart format - Read forms in multipart format
- HTTP Basic Auth - HTTP Basic Auth
- Keep-Alive support
- Session handling via cookies - Session handling via cookies
- HTML Templating - HTML Templating
I place this code in the public domain. Use it in any way you want. I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(http_server, [ :- module(http_server, [
http_listen/2, http_listen/2,
http_headers/2, http_headers/2,
http_status_code/2, http_status_code/2,
http_body/2, http_body/2,
http_redirect/2, http_redirect/2,
http_query/3, http_query/3
url_decode//1
]). ]).
:- meta_predicate http_listen(?, 2). :- meta_predicate http_listen(?, :).
:- use_module(library(sockets)).
:- use_module(library(dcgs)).
:- use_module(library(format)).
:- use_module(library(error)).
:- use_module(library(charsio)). :- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(iso_ext)).
:- use_module(library(time)).
:- use_module(library(crypto)). :- use_module(library(crypto)).
:- use_module(library(error)).
:- use_module(library(format)).
:- use_module(library(iso_ext)).
:- use_module(library(lists)).
:- use_module(library(pio)).
:- use_module(library(time)).
% Module prefix workaround with meta_predicate
http_listen(Port, Module:Handlers0) :- http_listen(Port, Module:Handlers0) :-
must_be(integer, Port),
must_be(list, Handlers0),
maplist(module_qualification(Module), Handlers0, Handlers), maplist(module_qualification(Module), Handlers0, Handlers),
http_listen_(Port, Handlers). http_listen_(Port, Handlers).
@@ -79,49 +78,75 @@ module_qualification(M, H0, H) :-
H0 =.. [Method, Path, Goal], H0 =.. [Method, Path, Goal],
H =.. [Method, Path, M:Goal]. H =.. [Method, Path, M:Goal].
% Server initialization
http_listen_(Port, Handlers) :- http_listen_(Port, Handlers) :-
must_be(integer, Port), number_chars(Port, CPort),
must_be(list, Handlers), append("0.0.0.0:", CPort, Addr),
once(socket_server_open(Port, Socket)), '$http_listen'(Addr, HttpListener),!,
format("Listening at port ~d\n", [Port]), format("Listening at ~s\n", [Addr]),
accept_loop(Socket, Handlers). http_loop(HttpListener, Handlers).
% Server loop http_loop(HttpListener, Handlers) :-
accept_loop(Socket, Handlers) :- '$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
setup_call_cleanup(socket_server_accept(Socket, _Client, Stream, [type(binary)]), current_time(Time),
( phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
read_header_lines(Stream, Lines), format("~s ~w ~s\n", [TimeString, RequestMethod, RequestPath]),
[Request|Headers] = Lines, maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
( phrase(parse_queries(RequestQueries), RequestQuery),
(phrase(parse_request(_Version, Method, Path, Queries), Request), maplist(map_parse_header, Headers, HeadersKV)) -> ( (
( match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
member("content-length"-ContentLength, HeadersKV) -> (
(number_chars(ContentLengthN, ContentLength), get_bytes(Stream, ContentLengthN, Body)) HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
;true HttpResponse = http_response(_, _, _),
), (call(Handler, HttpRequest, HttpResponse) ->
current_time(Time), send_response(ResponseHandle, HttpResponse)
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString), ; '$http_answer'(ResponseHandle, 500, [], "Internal Server Error")
format("~s ~w ~s\n", [TimeString, Method, Path]), )
( )
match_handler(Handlers, Method, Path, Handler) -> ; '$http_answer'(ResponseHandle, 404, [], "Not Found")
( ),
HttpRequest = http_request(HeadersKV, binary(Body), Queries), http_loop(HttpListener, Handlers).
HttpResponse = http_response(_, _, _),
(call(Handler, HttpRequest, HttpResponse) -> send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
send_response(Stream, HttpResponse) default(StatusCode0, 200, StatusCode),
; format(Stream, "HTTP/1.0 500 Internal Server Error\r\n\r\n", []) maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
) '$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
) format(ResponseStream, "~s", [ResponseText]),
; format(Stream, "HTTP/1.0 404 Not Found\r\n\r\n", []) close(ResponseStream).
)
);( send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
format(Stream, "HTTP/1.0 400 Bad Request\r\n\r\n", []) % bad format default(StatusCode0, 200, StatusCode),
) maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
), '$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
! % Remove format(ResponseStream, "~s", [ResponseBytes]),
), close(Stream)), close(ResponseStream).
accept_loop(Socket, Handlers).
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
setup_call_cleanup(
open(Filename, read, FileStream, [type(binary)]),
(
get_n_chars(FileStream, _, FileCs),
format(ResponseStream, "~s", [FileCs])
),
close(FileStream)
),
close(ResponseStream).
default(Var, Default, Out) :-
(var(Var) -> Out = Default
; Var = Out
).
map_header_kv(T, K-V) :-
T =.. [K0, V],
atom_chars(K0, K).
map_header_kv_2(T, K-V) :-
atom_chars(K0, K),
T =.. [K0, V].
match_handler(Handlers, Method, "/", Handler) :- match_handler(Handlers, Method, "/", Handler) :-
member(H, Handlers), member(H, Handlers),
@@ -139,27 +164,6 @@ match_handler(Handlers, Method, Path, Handler) :-
var(Var), var(Var),
Var = Path. Var = Path.
% Helper and recommended predicates
http_headers(http_request(Headers, _, _), Headers).
http_headers(http_response(_, _, Headers), Headers).
http_body(http_request(_, binary(ByteBody), _), text(TextBody)) :- chars_utf8bytes(TextBody, ByteBody).
http_body(http_request(Headers, binary(ByteBody), _), form(FormBody)) :-
member("content-type"-"application/x-www-form-urlencoded", Headers),
chars_utf8bytes(TextBody, ByteBody),
phrase(parse_queries(FormBody), TextBody).
http_body(http_request(_, Body, _), Body).
http_body(http_response(_, Body, _), Body).
http_status_code(http_response(StatusCode, _, _), StatusCode).
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
% Route matching
path(Pattern) --> path(Pattern) -->
{ {
Pattern =.. Parts, Pattern =.. Parts,
@@ -183,76 +187,31 @@ path(Pattern) -->
path([]) --> []. path([]) --> [].
% Send responses string_without(Not, [Char|String]) -->
send_response(Stream, http_response(StatusCode0, file(Filename), Headers)) :- [Char],
default(StatusCode0, 200, StatusCode), {
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]), \+ member(Char, Not)
overwrite_header("connection"-"Close", Headers, Headers0), },
write_headers(Stream, Headers0), string_without(Not, String).
format(Stream, "\r\n", []),
setup_call_cleanup(
open(Filename, read, FileStream, [type(binary)]),
pipe_bytes(FileStream, Stream),
close(FileStream)
).
send_response(Stream, http_response(StatusCode0, text(TextResponse), Headers)) :- string_without(_, []) -->
default(StatusCode0, 200, StatusCode), [].
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
overwrite_header("content-type"-"text/plain", Headers, Headers0),
overwrite_header("connection"-"Close", Headers0, Headers1),
write_headers(Stream, Headers1),
format(Stream, "\r\n~s", [TextResponse]).
send_response(Stream, http_response(StatusCode0, binary(BinaryResponse), Headers)) :- http_headers(http_request(Headers, _, _), Headers).
default(StatusCode0, 200, StatusCode), http_headers(http_response(_, _, Headers), Headers).
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
overwrite_header("connection"-"Close", Headers, Headers0),
write_headers(Stream, Headers0),
format(Stream, "\r\n", []),
put_bytes(Stream, BinaryResponse).
default(Var, Default, Out) :- http_body(http_request(_, stream(StreamBody), _), bytes(BytesBody)) :- get_n_chars(StreamBody, _, BytesBody).
(var(Var) -> Out = Default http_body(http_request(_, stream(StreamBody), _), text(TextBody)) :- get_n_chars(StreamBody, _, TextBody).
; Var = Out http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
). member("content-type"-"application/x-www-form-urlencoded", Headers),
get_n_chars(StreamBody, _, TextBody),
phrase(parse_queries(FormBody), TextBody).
http_body(http_request(_, Body, _), Body).
http_body(http_response(_, Body, _), Body).
header([]) --> []. http_status_code(http_response(StatusCode, _, _), StatusCode).
header([Key-Value|Headers]) --> http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
format_("~s: ~s\r\n", [Key, Value]), http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
header(Headers).
write_headers(Stream, Headers) :-
phrase(header(Headers), Cs),
format(Stream, "~s", [Cs]).
overwrite_header(Key-Value, [], [Key-Value]).
overwrite_header(Key-Value, [Header|Headers], [Header|HeadersOut]) :-
Header = Key0-_,
Key0 \= Key,
overwrite_header(Key-Value, Headers, HeadersOut).
overwrite_header(Key-Value, [Header|Headers], [NewHeader|Headers]) :-
Header = Key-_,
NewHeader = Key-Value.
parse_request(http_version(Major, Minor), Method, Path, Queries) -->
method(Method),
" ",
parse_path(Path, Queries),
" ",
"HTTP/",
natural(Major),
".",
natural(Minor),
"\r\n".
parse_path(Path, Queries) -->
string_without("?", Path),
"?",
parse_queries(Queries).
parse_path(Path, []) -->
string_without(" ", Path).
parse_queries([Key-Value|Queries]) --> parse_queries([Key-Value|Queries]) -->
string_without("=", Key0), string_without("=", Key0),
@@ -278,94 +237,9 @@ parse_queries([Key-Value]) -->
phrase(url_decode(Value), Value0) phrase(url_decode(Value), Value0)
}. }.
map_parse_header(Header, HeaderKV) :- parse_queries([]) -->
phrase(parse_header(HeaderKV), Header).
parse_header(Key-Value) -->
string_without(":", Key0),
{
chars_lower(Key0, Key)
},
": ",
string_without("\r", Value),
"\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)
).
get_bytes(Stream, Length, Res) :- get_bytes(Stream, Length, [], Res).
get_bytes(Stream, Length, Acc, Res) :-
(Length > 0 -> (
get_byte(Stream, B),
B =\= -1,
get_bytes(Stream, Length - 1, [B|Acc], Res)
); reverse(Acc, Res)).
put_bytes(_, []).
put_bytes(Stream, [Byte|Bytes]) :-
put_byte(Stream, Byte),
put_bytes(Stream, Bytes).
pipe_bytes(StreamIn, StreamOut) :-
get_byte(StreamIn, Byte),
(
Byte =\= -1 ->
(
put_byte(StreamOut, Byte),
pipe_bytes(StreamIn, StreamOut)
)
; true).
% WARNING: This only works for ASCII chars. This code can be modified to support
% Latin1 characters also but a completely different approach is needed for other
% languages. Since HTTP internals are ASCII, this is fine for this usecase.
chars_lower(Chars, Lower) :-
maplist(char_lower, Chars, Lower).
char_lower(Char, Lower) :-
char_code(Char, Code),
((Code >= 65,Code =< 90) ->
LowerCode is Code + 32,
char_code(Lower, LowerCode)
; Char = Lower).
% Decodes a UTF-8 URL Encoded string: RFC-1738 % Decodes a UTF-8 URL Encoded string: RFC-1738
url_decode([Char|Chars]) --> url_decode([Char|Chars]) -->
[Char], [Char],

View File

@@ -4197,6 +4197,30 @@ impl Machine {
try_or_throw!(self.machine_st, self.http_open()); try_or_throw!(self.machine_st, self.http_open());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp); step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
} }
&Instruction::CallHttpListen(_) => {
try_or_throw!(self.machine_st, self.http_listen());
step_or_fail!(self, self.machine_st.p += 1);
}
&Instruction::ExecuteHttpListen(_) => {
try_or_throw!(self.machine_st, self.http_listen());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallHttpAccept(_) => {
try_or_throw!(self.machine_st, self.http_accept());
step_or_fail!(self, self.machine_st.p += 1);
}
&Instruction::ExecuteHttpAccept(_) => {
try_or_throw!(self.machine_st, self.http_accept());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallHttpAnswer(_) => {
try_or_throw!(self.machine_st, self.http_answer());
step_or_fail!(self, self.machine_st.p += 1);
}
&Instruction::ExecuteHttpAnswer(_) => {
try_or_throw!(self.machine_st, self.http_answer());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallCurrentTime(_) => { &Instruction::CallCurrentTime(_) => {
self.current_time(); self.current_time();
step_or_fail!(self, self.machine_st.p += 1); step_or_fail!(self, self.machine_st.p += 1);

View File

@@ -431,9 +431,7 @@ impl Machine {
let user_output = Stream::stdout(&mut machine_st.arena); let user_output = Stream::stdout(&mut machine_st.arena);
let user_error = Stream::stderr(&mut machine_st.arena); let user_error = Stream::stderr(&mut machine_st.arena);
let runtime = tokio::runtime::Builder::new_current_thread() let runtime = tokio::runtime::Runtime::new()
.enable_all()
.build()
.unwrap(); .unwrap();
let mut wam = Machine { let mut wam = Machine {

View File

@@ -26,6 +26,7 @@ use std::ops::{Deref, DerefMut};
use std::ptr; use std::ptr;
use native_tls::TlsStream; use native_tls::TlsStream;
use hyper::body::{Bytes, Sender};
#[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)]
#[bits = 1] #[bits = 1]
@@ -249,24 +250,51 @@ impl Write for NamedTlsStream {
} }
} }
pub struct NamedHttpClientStream { pub struct HttpReadStream {
url: Atom, url: Atom,
body_reader: Box<dyn BufRead>, body_reader: Box<dyn BufRead>,
} }
impl Debug for NamedHttpClientStream { impl Debug for HttpReadStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Http Client Stream [{}]", self.url.as_str()) write!(f, "Http Read Stream [{}]", self.url.as_str())
} }
} }
impl Read for NamedHttpClientStream { impl Read for HttpReadStream {
#[inline] #[inline]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.body_reader.read(buf) self.body_reader.read(buf)
} }
} }
pub struct HttpWriteStream {
body_writer: Sender,
}
impl Debug for HttpWriteStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Http Write Stream")
}
}
impl Write for HttpWriteStream {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let bytes = Bytes::copy_from_slice(buf);
let len = bytes.len();
match self.body_writer.try_send_data(bytes) {
Ok(()) => Ok(len),
Err(_) => Err(std::io::Error::from(ErrorKind::Interrupted))
}
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct StandardOutputStream {} pub struct StandardOutputStream {}
@@ -405,7 +433,8 @@ arena_allocated_impl_for_stream!(CharReader<InputFileStream>, InputFileStream);
arena_allocated_impl_for_stream!(OutputFileStream, OutputFileStream); arena_allocated_impl_for_stream!(OutputFileStream, OutputFileStream);
arena_allocated_impl_for_stream!(CharReader<NamedTcpStream>, NamedTcpStream); arena_allocated_impl_for_stream!(CharReader<NamedTcpStream>, NamedTcpStream);
arena_allocated_impl_for_stream!(CharReader<NamedTlsStream>, NamedTlsStream); arena_allocated_impl_for_stream!(CharReader<NamedTlsStream>, NamedTlsStream);
arena_allocated_impl_for_stream!(CharReader<NamedHttpClientStream>, NamedHttpClientStream); arena_allocated_impl_for_stream!(CharReader<HttpReadStream>, HttpReadStream);
arena_allocated_impl_for_stream!(CharReader<HttpWriteStream>, HttpWriteStream);
arena_allocated_impl_for_stream!(ReadlineStream, ReadlineStream); arena_allocated_impl_for_stream!(ReadlineStream, ReadlineStream);
arena_allocated_impl_for_stream!(StaticStringStream, StaticStringStream); arena_allocated_impl_for_stream!(StaticStringStream, StaticStringStream);
arena_allocated_impl_for_stream!(StandardOutputStream, StandardOutputStream); arena_allocated_impl_for_stream!(StandardOutputStream, StandardOutputStream);
@@ -419,7 +448,8 @@ pub enum Stream {
StaticString(TypedArenaPtr<StreamLayout<StaticStringStream>>), StaticString(TypedArenaPtr<StreamLayout<StaticStringStream>>),
NamedTcp(TypedArenaPtr<StreamLayout<CharReader<NamedTcpStream>>>), NamedTcp(TypedArenaPtr<StreamLayout<CharReader<NamedTcpStream>>>),
NamedTls(TypedArenaPtr<StreamLayout<CharReader<NamedTlsStream>>>), NamedTls(TypedArenaPtr<StreamLayout<CharReader<NamedTlsStream>>>),
NamedHttpClient(TypedArenaPtr<StreamLayout<CharReader<NamedHttpClientStream>>>), HttpRead(TypedArenaPtr<StreamLayout<CharReader<HttpReadStream>>>),
HttpWrite(TypedArenaPtr<StreamLayout<CharReader<HttpWriteStream>>>),
Null(StreamOptions), Null(StreamOptions),
Readline(TypedArenaPtr<StreamLayout<ReadlineStream>>), Readline(TypedArenaPtr<StreamLayout<ReadlineStream>>),
StandardOutput(TypedArenaPtr<StreamLayout<StandardOutputStream>>), StandardOutput(TypedArenaPtr<StreamLayout<StandardOutputStream>>),
@@ -474,7 +504,8 @@ impl Stream {
} }
ArenaHeaderTag::NamedTcpStream => Stream::NamedTcp(TypedArenaPtr::new(ptr as *mut _)), ArenaHeaderTag::NamedTcpStream => Stream::NamedTcp(TypedArenaPtr::new(ptr as *mut _)),
ArenaHeaderTag::NamedTlsStream => Stream::NamedTls(TypedArenaPtr::new(ptr as *mut _)), ArenaHeaderTag::NamedTlsStream => Stream::NamedTls(TypedArenaPtr::new(ptr as *mut _)),
ArenaHeaderTag::NamedHttpClientStream => Stream::NamedHttpClient(TypedArenaPtr::new(ptr as *mut _)), ArenaHeaderTag::HttpReadStream => Stream::HttpRead(TypedArenaPtr::new(ptr as *mut _)),
ArenaHeaderTag::HttpWriteStream => Stream::HttpWrite(TypedArenaPtr::new(ptr as *mut _)),
ArenaHeaderTag::ReadlineStream => Stream::Readline(TypedArenaPtr::new(ptr as *mut _)), ArenaHeaderTag::ReadlineStream => Stream::Readline(TypedArenaPtr::new(ptr as *mut _)),
ArenaHeaderTag::StaticStringStream => { ArenaHeaderTag::StaticStringStream => {
Stream::StaticString(TypedArenaPtr::new(ptr as *mut _)) Stream::StaticString(TypedArenaPtr::new(ptr as *mut _))
@@ -527,7 +558,8 @@ impl Stream {
Stream::StaticString(ptr) => ptr.header_ptr(), Stream::StaticString(ptr) => ptr.header_ptr(),
Stream::NamedTcp(ptr) => ptr.header_ptr(), Stream::NamedTcp(ptr) => ptr.header_ptr(),
Stream::NamedTls(ptr) => ptr.header_ptr(), Stream::NamedTls(ptr) => ptr.header_ptr(),
Stream::NamedHttpClient(ptr) => ptr.header_ptr(), Stream::HttpRead(ptr) => ptr.header_ptr(),
Stream::HttpWrite(ptr) => ptr.header_ptr(),
Stream::Null(_) => ptr::null(), Stream::Null(_) => ptr::null(),
Stream::Readline(ptr) => ptr.header_ptr(), Stream::Readline(ptr) => ptr.header_ptr(),
Stream::StandardOutput(ptr) => ptr.header_ptr(), Stream::StandardOutput(ptr) => ptr.header_ptr(),
@@ -543,7 +575,8 @@ impl Stream {
Stream::StaticString(ref ptr) => &ptr.options, Stream::StaticString(ref ptr) => &ptr.options,
Stream::NamedTcp(ref ptr) => &ptr.options, Stream::NamedTcp(ref ptr) => &ptr.options,
Stream::NamedTls(ref ptr) => &ptr.options, Stream::NamedTls(ref ptr) => &ptr.options,
Stream::NamedHttpClient(ref ptr) => &ptr.options, Stream::HttpRead(ref ptr) => &ptr.options,
Stream::HttpWrite(ref ptr) => &ptr.options,
Stream::Null(ref options) => options, Stream::Null(ref options) => options,
Stream::Readline(ref ptr) => &ptr.options, Stream::Readline(ref ptr) => &ptr.options,
Stream::StandardOutput(ref ptr) => &ptr.options, Stream::StandardOutput(ref ptr) => &ptr.options,
@@ -559,7 +592,8 @@ impl Stream {
Stream::StaticString(ref mut ptr) => &mut ptr.options, Stream::StaticString(ref mut ptr) => &mut ptr.options,
Stream::NamedTcp(ref mut ptr) => &mut ptr.options, Stream::NamedTcp(ref mut ptr) => &mut ptr.options,
Stream::NamedTls(ref mut ptr) => &mut ptr.options, Stream::NamedTls(ref mut ptr) => &mut ptr.options,
Stream::NamedHttpClient(ref mut ptr) => &mut ptr.options, Stream::HttpRead(ref mut ptr) => &mut ptr.options,
Stream::HttpWrite(ref mut ptr) => &mut ptr.options,
Stream::Null(ref mut options) => options, Stream::Null(ref mut options) => options,
Stream::Readline(ref mut ptr) => &mut ptr.options, Stream::Readline(ref mut ptr) => &mut ptr.options,
Stream::StandardOutput(ref mut ptr) => &mut ptr.options, Stream::StandardOutput(ref mut ptr) => &mut ptr.options,
@@ -576,7 +610,8 @@ impl Stream {
Stream::StaticString(ptr) => ptr.lines_read += incr_num_lines_read, Stream::StaticString(ptr) => ptr.lines_read += incr_num_lines_read,
Stream::NamedTcp(ptr) => ptr.lines_read += incr_num_lines_read, Stream::NamedTcp(ptr) => ptr.lines_read += incr_num_lines_read,
Stream::NamedTls(ptr) => ptr.lines_read += incr_num_lines_read, Stream::NamedTls(ptr) => ptr.lines_read += incr_num_lines_read,
Stream::NamedHttpClient(ptr) => ptr.lines_read += incr_num_lines_read, Stream::HttpRead(ptr) => ptr.lines_read += incr_num_lines_read,
Stream::HttpWrite(_) => {}
Stream::Null(_) => {} Stream::Null(_) => {}
Stream::Readline(ptr) => ptr.lines_read += incr_num_lines_read, Stream::Readline(ptr) => ptr.lines_read += incr_num_lines_read,
Stream::StandardOutput(ptr) => ptr.lines_read += incr_num_lines_read, Stream::StandardOutput(ptr) => ptr.lines_read += incr_num_lines_read,
@@ -593,7 +628,8 @@ impl Stream {
Stream::StaticString(ptr) => ptr.lines_read = value, Stream::StaticString(ptr) => ptr.lines_read = value,
Stream::NamedTcp(ptr) => ptr.lines_read = value, Stream::NamedTcp(ptr) => ptr.lines_read = value,
Stream::NamedTls(ptr) => ptr.lines_read = value, Stream::NamedTls(ptr) => ptr.lines_read = value,
Stream::NamedHttpClient(ptr) => ptr.lines_read = value, Stream::HttpRead(ptr) => ptr.lines_read = value,
Stream::HttpWrite(_) => {}
Stream::Null(_) => {} Stream::Null(_) => {}
Stream::Readline(ptr) => ptr.lines_read = value, Stream::Readline(ptr) => ptr.lines_read = value,
Stream::StandardOutput(ptr) => ptr.lines_read = value, Stream::StandardOutput(ptr) => ptr.lines_read = value,
@@ -610,7 +646,8 @@ impl Stream {
Stream::StaticString(ptr) => ptr.lines_read, Stream::StaticString(ptr) => ptr.lines_read,
Stream::NamedTcp(ptr) => ptr.lines_read, Stream::NamedTcp(ptr) => ptr.lines_read,
Stream::NamedTls(ptr) => ptr.lines_read, Stream::NamedTls(ptr) => ptr.lines_read,
Stream::NamedHttpClient(ptr) => ptr.lines_read, Stream::HttpRead(ptr) => ptr.lines_read,
Stream::HttpWrite(_) => 0,
Stream::Null(_) => 0, Stream::Null(_) => 0,
Stream::Readline(ptr) => ptr.lines_read, Stream::Readline(ptr) => ptr.lines_read,
Stream::StandardOutput(ptr) => ptr.lines_read, Stream::StandardOutput(ptr) => ptr.lines_read,
@@ -625,13 +662,14 @@ impl CharRead for Stream {
Stream::InputFile(file) => (*file).peek_char(), Stream::InputFile(file) => (*file).peek_char(),
Stream::NamedTcp(tcp_stream) => (*tcp_stream).peek_char(), Stream::NamedTcp(tcp_stream) => (*tcp_stream).peek_char(),
Stream::NamedTls(tls_stream) => (*tls_stream).peek_char(), Stream::NamedTls(tls_stream) => (*tls_stream).peek_char(),
Stream::NamedHttpClient(http_stream) => (*http_stream).peek_char(), Stream::HttpRead(http_stream) => (*http_stream).peek_char(),
Stream::Readline(rl_stream) => (*rl_stream).peek_char(), Stream::Readline(rl_stream) => (*rl_stream).peek_char(),
Stream::StaticString(src) => (*src).peek_char(), Stream::StaticString(src) => (*src).peek_char(),
Stream::Byte(cursor) => (*cursor).peek_char(), Stream::Byte(cursor) => (*cursor).peek_char(),
Stream::OutputFile(_) | Stream::OutputFile(_) |
Stream::StandardError(_) | Stream::StandardError(_) |
Stream::StandardOutput(_) | Stream::StandardOutput(_) |
Stream::HttpWrite(_) |
Stream::Null(_) => Some(Err(std::io::Error::new( Stream::Null(_) => Some(Err(std::io::Error::new(
ErrorKind::PermissionDenied, ErrorKind::PermissionDenied,
StreamError::ReadFromOutputStream, StreamError::ReadFromOutputStream,
@@ -644,13 +682,14 @@ impl CharRead for Stream {
Stream::InputFile(file) => (*file).read_char(), Stream::InputFile(file) => (*file).read_char(),
Stream::NamedTcp(tcp_stream) => (*tcp_stream).read_char(), Stream::NamedTcp(tcp_stream) => (*tcp_stream).read_char(),
Stream::NamedTls(tls_stream) => (*tls_stream).read_char(), Stream::NamedTls(tls_stream) => (*tls_stream).read_char(),
Stream::NamedHttpClient(http_stream) => (*http_stream).read_char(), Stream::HttpRead(http_stream) => (*http_stream).read_char(),
Stream::Readline(rl_stream) => (*rl_stream).read_char(), Stream::Readline(rl_stream) => (*rl_stream).read_char(),
Stream::StaticString(src) => (*src).read_char(), Stream::StaticString(src) => (*src).read_char(),
Stream::Byte(cursor) => (*cursor).read_char(), Stream::Byte(cursor) => (*cursor).read_char(),
Stream::OutputFile(_) | Stream::OutputFile(_) |
Stream::StandardError(_) | Stream::StandardError(_) |
Stream::StandardOutput(_) | Stream::StandardOutput(_) |
Stream::HttpWrite(_) |
Stream::Null(_) => Some(Err(std::io::Error::new( Stream::Null(_) => Some(Err(std::io::Error::new(
ErrorKind::PermissionDenied, ErrorKind::PermissionDenied,
StreamError::ReadFromOutputStream, StreamError::ReadFromOutputStream,
@@ -663,13 +702,14 @@ impl CharRead for Stream {
Stream::InputFile(file) => file.put_back_char(c), Stream::InputFile(file) => file.put_back_char(c),
Stream::NamedTcp(tcp_stream) => tcp_stream.put_back_char(c), Stream::NamedTcp(tcp_stream) => tcp_stream.put_back_char(c),
Stream::NamedTls(tls_stream) => tls_stream.put_back_char(c), Stream::NamedTls(tls_stream) => tls_stream.put_back_char(c),
Stream::NamedHttpClient(http_stream) => http_stream.put_back_char(c), Stream::HttpRead(http_stream) => http_stream.put_back_char(c),
Stream::Readline(rl_stream) => rl_stream.put_back_char(c), Stream::Readline(rl_stream) => rl_stream.put_back_char(c),
Stream::StaticString(src) => src.put_back_char(c), Stream::StaticString(src) => src.put_back_char(c),
Stream::Byte(cursor) => cursor.put_back_char(c), Stream::Byte(cursor) => cursor.put_back_char(c),
Stream::OutputFile(_) | Stream::OutputFile(_) |
Stream::StandardError(_) | Stream::StandardError(_) |
Stream::StandardOutput(_) | Stream::StandardOutput(_) |
Stream::HttpWrite(_) |
Stream::Null(_) => {} Stream::Null(_) => {}
} }
} }
@@ -679,13 +719,14 @@ impl CharRead for Stream {
Stream::InputFile(ref mut file) => file.consume(nread), Stream::InputFile(ref mut file) => file.consume(nread),
Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.consume(nread), Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.consume(nread),
Stream::NamedTls(ref mut tls_stream) => tls_stream.consume(nread), Stream::NamedTls(ref mut tls_stream) => tls_stream.consume(nread),
Stream::NamedHttpClient(ref mut http_stream) => http_stream.consume(nread), Stream::HttpRead(ref mut http_stream) => http_stream.consume(nread),
Stream::Readline(ref mut rl_stream) => rl_stream.consume(nread), Stream::Readline(ref mut rl_stream) => rl_stream.consume(nread),
Stream::StaticString(ref mut src) => src.consume(nread), Stream::StaticString(ref mut src) => src.consume(nread),
Stream::Byte(ref mut cursor) => cursor.consume(nread), Stream::Byte(ref mut cursor) => cursor.consume(nread),
Stream::OutputFile(_) | Stream::OutputFile(_) |
Stream::StandardError(_) | Stream::StandardError(_) |
Stream::StandardOutput(_) | Stream::StandardOutput(_) |
Stream::HttpWrite(_) |
Stream::Null(_) => {} Stream::Null(_) => {}
} }
} }
@@ -698,13 +739,14 @@ impl Read for Stream {
Stream::InputFile(file) => (*file).read(buf), Stream::InputFile(file) => (*file).read(buf),
Stream::NamedTcp(tcp_stream) => (*tcp_stream).read(buf), Stream::NamedTcp(tcp_stream) => (*tcp_stream).read(buf),
Stream::NamedTls(tls_stream) => (*tls_stream).read(buf), Stream::NamedTls(tls_stream) => (*tls_stream).read(buf),
Stream::NamedHttpClient(http_stream) => (*http_stream).read(buf), Stream::HttpRead(http_stream) => (*http_stream).read(buf),
Stream::Readline(rl_stream) => (*rl_stream).read(buf), Stream::Readline(rl_stream) => (*rl_stream).read(buf),
Stream::StaticString(src) => (*src).read(buf), Stream::StaticString(src) => (*src).read(buf),
Stream::Byte(cursor) => (*cursor).read(buf), Stream::Byte(cursor) => (*cursor).read(buf),
Stream::OutputFile(_) Stream::OutputFile(_)
| Stream::StandardError(_) | Stream::StandardError(_)
| Stream::StandardOutput(_) | Stream::StandardOutput(_)
| Stream::HttpWrite(_)
| Stream::Null(_) => Err(std::io::Error::new( | Stream::Null(_) => Err(std::io::Error::new(
ErrorKind::PermissionDenied, ErrorKind::PermissionDenied,
StreamError::ReadFromOutputStream, StreamError::ReadFromOutputStream,
@@ -724,7 +766,8 @@ impl Write for Stream {
Stream::Byte(ref mut cursor) => cursor.get_mut().write(buf), Stream::Byte(ref mut cursor) => cursor.get_mut().write(buf),
Stream::StandardOutput(stream) => stream.write(buf), Stream::StandardOutput(stream) => stream.write(buf),
Stream::StandardError(stream) => stream.write(buf), Stream::StandardError(stream) => stream.write(buf),
Stream::NamedHttpClient(_) | Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf),
Stream::HttpRead(_) |
Stream::StaticString(_) | Stream::StaticString(_) |
Stream::Readline(_) | Stream::Readline(_) |
Stream::InputFile(..) | Stream::InputFile(..) |
@@ -743,7 +786,8 @@ impl Write for Stream {
Stream::Byte(ref mut cursor) => cursor.stream.get_mut().flush(), Stream::Byte(ref mut cursor) => cursor.stream.get_mut().flush(),
Stream::StandardError(stream) => stream.stream.flush(), Stream::StandardError(stream) => stream.stream.flush(),
Stream::StandardOutput(stream) => stream.stream.flush(), Stream::StandardOutput(stream) => stream.stream.flush(),
Stream::NamedHttpClient(_) | Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(),
Stream::HttpRead(_) |
Stream::StaticString(_) | Stream::StaticString(_) |
Stream::Readline(_) | Stream::Readline(_) |
Stream::InputFile(_) | Stream::InputFile(_) |
@@ -868,7 +912,8 @@ impl Stream {
Stream::StaticString(stream) => stream.past_end_of_stream, Stream::StaticString(stream) => stream.past_end_of_stream,
Stream::NamedTcp(stream) => stream.past_end_of_stream, Stream::NamedTcp(stream) => stream.past_end_of_stream,
Stream::NamedTls(stream) => stream.past_end_of_stream, Stream::NamedTls(stream) => stream.past_end_of_stream,
Stream::NamedHttpClient(stream) => stream.past_end_of_stream, Stream::HttpRead(stream) => stream.past_end_of_stream,
Stream::HttpWrite(stream) => stream.past_end_of_stream,
Stream::Null(_) => false, Stream::Null(_) => false,
Stream::Readline(stream) => stream.past_end_of_stream, Stream::Readline(stream) => stream.past_end_of_stream,
Stream::StandardOutput(stream) => stream.past_end_of_stream, Stream::StandardOutput(stream) => stream.past_end_of_stream,
@@ -890,7 +935,8 @@ impl Stream {
Stream::StaticString(stream) => stream.past_end_of_stream = value, Stream::StaticString(stream) => stream.past_end_of_stream = value,
Stream::NamedTcp(stream) => stream.past_end_of_stream = value, Stream::NamedTcp(stream) => stream.past_end_of_stream = value,
Stream::NamedTls(stream) => stream.past_end_of_stream = value, Stream::NamedTls(stream) => stream.past_end_of_stream = value,
Stream::NamedHttpClient(stream) => stream.past_end_of_stream = value, Stream::HttpRead(stream) => stream.past_end_of_stream = value,
Stream::HttpWrite(stream) => stream.past_end_of_stream = value,
Stream::Null(_) => {} Stream::Null(_) => {}
Stream::Readline(stream) => stream.past_end_of_stream = value, Stream::Readline(stream) => stream.past_end_of_stream = value,
Stream::StandardOutput(stream) => stream.past_end_of_stream = value, Stream::StandardOutput(stream) => stream.past_end_of_stream = value,
@@ -956,11 +1002,11 @@ impl Stream {
Stream::Byte(_) Stream::Byte(_)
| Stream::Readline(_) | Stream::Readline(_)
| Stream::StaticString(_) | Stream::StaticString(_)
| Stream::NamedHttpClient(_) | Stream::HttpRead(_)
| Stream::InputFile(..) => atom!("read"), | Stream::InputFile(..) => atom!("read"),
Stream::NamedTcp(..) | Stream::NamedTls(..) => atom!("read_append"), Stream::NamedTcp(..) | Stream::NamedTls(..) => atom!("read_append"),
Stream::OutputFile(file) if file.is_append => atom!("append"), Stream::OutputFile(file) if file.is_append => atom!("append"),
Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) => atom!("write"), Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::HttpWrite(_) => atom!("write"),
Stream::Null(_) => atom!(""), Stream::Null(_) => atom!(""),
} }
} }
@@ -1020,8 +1066,8 @@ impl Stream {
http_stream: Box<dyn BufRead>, http_stream: Box<dyn BufRead>,
arena: &mut Arena, arena: &mut Arena,
) -> Self { ) -> Self {
Stream::NamedHttpClient(arena_alloc!( Stream::HttpRead(arena_alloc!(
StreamLayout::new(CharReader::new(NamedHttpClientStream { StreamLayout::new(CharReader::new(HttpReadStream {
url, url,
body_reader: http_stream body_reader: http_stream
})), })),
@@ -1029,6 +1075,19 @@ impl Stream {
)) ))
} }
#[inline]
pub(crate) fn from_http_sender(
body_writer: Sender,
arena: &mut Arena,
) -> Self {
Stream::HttpWrite(arena_alloc!(
StreamLayout::new(CharReader::new(HttpWriteStream {
body_writer
})),
arena
))
}
#[inline] #[inline]
pub(crate) fn from_file_as_output( pub(crate) fn from_file_as_output(
file_name: Atom, file_name: Atom,
@@ -1065,7 +1124,7 @@ impl Stream {
Stream::NamedTls(ref mut tls_stream) => { Stream::NamedTls(ref mut tls_stream) => {
tls_stream.inner_mut().tls_stream.shutdown() tls_stream.inner_mut().tls_stream.shutdown()
} }
Stream::NamedHttpClient(ref mut http_stream) => { Stream::HttpRead(ref mut http_stream) => {
unsafe { unsafe {
http_stream.set_tag(ArenaHeaderTag::Dropped); http_stream.set_tag(ArenaHeaderTag::Dropped);
std::ptr::drop_in_place(&mut http_stream.inner_mut().body_reader as *mut _); std::ptr::drop_in_place(&mut http_stream.inner_mut().body_reader as *mut _);
@@ -1073,6 +1132,14 @@ impl Stream {
Ok(()) Ok(())
} }
Stream::HttpWrite(ref mut http_stream) => {
unsafe {
http_stream.set_tag(ArenaHeaderTag::Dropped);
std::ptr::drop_in_place(&mut http_stream.inner_mut().body_writer as *mut _);
}
Ok(())
}
Stream::InputFile(mut file_stream) => { Stream::InputFile(mut file_stream) => {
// close the stream by dropping the inner File. // close the stream by dropping the inner File.
unsafe { unsafe {
@@ -1109,7 +1176,7 @@ impl Stream {
match self { match self {
Stream::NamedTcp(..) Stream::NamedTcp(..)
| Stream::NamedTls(..) | Stream::NamedTls(..)
| Stream::NamedHttpClient(..) | Stream::HttpRead(..)
| Stream::Byte(_) | Stream::Byte(_)
| Stream::Readline(_) | Stream::Readline(_)
| Stream::StaticString(_) | Stream::StaticString(_)
@@ -1124,7 +1191,8 @@ impl Stream {
Stream::StandardError(_) Stream::StandardError(_)
| Stream::StandardOutput(_) | Stream::StandardOutput(_)
| Stream::NamedTcp(..) | Stream::NamedTcp(..)
| Stream::NamedTls(..) | Stream::NamedTls(..)
| Stream::HttpWrite(..)
| Stream::Byte(_) | Stream::Byte(_)
| Stream::OutputFile(..) => true, | Stream::OutputFile(..) => true,
_ => false, _ => false,

View File

@@ -8,6 +8,7 @@ use crate::atom_table::*;
use crate::forms::*; use crate::forms::*;
use crate::heap_iter::*; use crate::heap_iter::*;
use crate::heap_print::*; use crate::heap_print::*;
use crate::http::{self, HttpListener, HttpResponse};
use crate::instructions::*; use crate::instructions::*;
use crate::machine; use crate::machine;
use crate::machine::{Machine, VERIFY_ATTR_INTERRUPT_LOC, get_structure_index}; use crate::machine::{Machine, VERIFY_ATTR_INTERRUPT_LOC, get_structure_index};
@@ -37,19 +38,20 @@ use ref_thread_local::{RefThreadLocal, ref_thread_local};
use std::cell::Cell; use std::cell::Cell;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::convert::TryFrom; use std::convert::{TryFrom, Infallible};
use std::env; use std::env;
use std::fs; use std::fs;
use std::hash::{BuildHasher, BuildHasherDefault}; use std::hash::{BuildHasher, BuildHasherDefault};
use std::io::{ErrorKind, Read, Write}; use std::io::{ErrorKind, Read, Write};
use std::iter::{once, FromIterator}; use std::iter::{once, FromIterator};
use std::mem; use std::mem;
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream, SocketAddr, ToSocketAddrs};
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::ops::Sub; use std::ops::Sub;
use std::process; use std::process;
use std::rc::Rc; use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use chrono::{offset::Local, DateTime}; use chrono::{offset::Local, DateTime};
use cpu_time::ProcessTime; use cpu_time::ProcessTime;
@@ -79,10 +81,13 @@ use base64;
use roxmltree; use roxmltree;
use select; use select;
use hyper::{Body, Client, HeaderMap, Method, Request, Uri}; use hyper::{Body, Server, Client, HeaderMap, Method, Request, Response, Uri};
use hyper::header::{HeaderName, HeaderValue}; use hyper::header::{HeaderName, HeaderValue};
use hyper::body::Buf; use hyper::body::Buf;
use hyper::service::{make_service_fn, service_fn};
use hyper_tls::HttpsConnector; use hyper_tls::HttpsConnector;
use tokio::sync::Mutex;
use tokio::sync::mpsc::channel;
ref_thread_local! { ref_thread_local! {
pub(crate) static managed RANDOM_STATE: RandState<'static> = RandState::new(); pub(crate) static managed RANDOM_STATE: RandState<'static> = RandState::new();
@@ -3901,6 +3906,203 @@ impl Machine {
Ok(()) Ok(())
} }
#[inline(always)]
pub(crate) fn http_listen(&mut self) -> CallResult {
let address_sink = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[1]));
if let Some(address_str) = self.machine_st.value_to_str_like(address_sink) {
let address_string = address_str.as_str();
let addr: SocketAddr = match address_string.to_socket_addrs().ok().and_then(|mut s| s.next()) {
Some(addr) => addr,
_ => {
self.machine_st.fail = true;
return Ok(());
}
};
let (tx, rx) = channel(1);
let tx = Arc::new(Mutex::new(tx));
let _guard = self.runtime.enter();
let server = match Server::try_bind(&addr) {
Ok(server) => server,
Err(_) => {
return Err(self.machine_st.open_permission_error(address_sink, atom!("http_listen"), 2));
}
};
self.runtime.spawn(async move {
let make_svc = make_service_fn(move |_conn| {
let tx = tx.clone();
async move { Ok::<_, Infallible>(service_fn(move |req| http::serve_req(req, tx.clone()))) }
});
let server = server.serve(make_svc);
if let Err(_) = server.await {
eprintln!("server error");
}
});
let http_listener = HttpListener { incoming: rx };
let http_listener = arena_alloc!(http_listener, &mut self.machine_st.arena);
let addr = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[2]));
self.machine_st.bind(addr.as_var().unwrap(), typed_arena_ptr_as_cell!(http_listener));
}
Ok(())
}
#[inline(always)]
pub(crate) fn http_accept(&mut self) -> CallResult {
let culprit = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[1]));
let method = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[2]));
let path = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[3]));
let query = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[5]));
let stream_addr = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[6]));
let handle_addr = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[7]));
read_heap_cell!(culprit,
(HeapCellValueTag::Cons, cons_ptr) => {
match_untyped_arena_ptr!(cons_ptr,
(ArenaHeaderTag::HttpListener, http_listener) => {
match http_listener.incoming.blocking_recv() {
Some(request) => {
let method_atom = match *request.request.method() {
Method::GET => atom!("get"),
Method::POST => atom!("post"),
Method::PUT => atom!("put"),
Method::DELETE => atom!("delete"),
Method::PATCH => atom!("patch"),
Method::HEAD => atom!("head"),
_ => unreachable!(),
};
let path_atom = self.machine_st.atom_tbl.build_with(request.request.uri().path());
let path_cell = atom_as_cstr_cell!(path_atom);
let headers: Vec<HeapCellValue> = request.request.headers().iter().map(|(header_name, header_value)| {
let h = self.machine_st.heap.len();
let header_term = functor!(
self.machine_st.atom_tbl.build_with(header_name.as_str()),
[cell(string_as_cstr_cell!(self.machine_st.atom_tbl.build_with(header_value.to_str().unwrap())))]
);
self.machine_st.heap.extend(header_term.into_iter());
str_loc_as_cell!(h)
}).collect();
let headers_list = iter_to_heap_list(&mut self.machine_st.heap, headers.into_iter());
let query_str = request.request.uri().query().unwrap_or("");
let query_atom = self.machine_st.atom_tbl.build_with(query_str);
let query_cell = string_as_cstr_cell!(query_atom);
let hyper_req = request.request;
let buf = self.runtime.block_on(async {hyper::body::aggregate(hyper_req).await.unwrap()});
let reader = buf.reader();
let mut stream = Stream::from_http_stream(
path_atom,
Box::new(reader),
&mut self.machine_st.arena
);
*stream.options_mut() = StreamOptions::default();
stream.options_mut().set_stream_type(StreamType::Binary);
self.indices.streams.insert(stream);
let stream = stream_as_cell!(stream);
let handle = arena_alloc!(request.response, &mut self.machine_st.arena);
self.machine_st.bind(method.as_var().unwrap(), atom_as_cell!(method_atom));
self.machine_st.bind(path.as_var().unwrap(), path_cell);
unify!(self.machine_st, heap_loc_as_cell!(headers_list), self.machine_st.registers[4]);
self.machine_st.bind(query.as_var().unwrap(), query_cell);
self.machine_st.bind(stream_addr.as_var().unwrap(), stream);
self.machine_st.bind(handle_addr.as_var().unwrap(), typed_arena_ptr_as_cell!(handle));
}
None => {
self.machine_st.fail = true;
}
}
}
_ => {
unreachable!();
}
);
}
_ => {
unreachable!();
}
);
Ok(())
}
#[inline(always)]
pub(crate) fn http_answer(&mut self) -> CallResult {
let culprit = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[1]));
let status_code = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[2]));
let status_code: u16 = match Number::try_from(status_code) {
Ok(Number::Fixnum(n)) => n.get_num() as u16,
Ok(Number::Integer(n)) => match n.to_u16() {
Some(u) => u,
_ => {
self.machine_st.fail = true;
return Ok(());
}
}
_ => unreachable!()
};
let stub_gen = || functor_stub(atom!("http_listen"), 2);
let headers = match self.machine_st.try_from_list(self.machine_st.registers[3], stub_gen) {
Ok(addrs) => {
let mut header_map = HeaderMap::new();
for heap_cell in addrs{
read_heap_cell!(heap_cell,
(HeapCellValueTag::Str, s) => {
let name = cell_as_atom_cell!(self.machine_st.heap[s]).get_name();
let value = self.machine_st.value_to_str_like(self.machine_st.heap[s + 1]).unwrap();
header_map.insert(HeaderName::from_str(name.as_str()).unwrap(), HeaderValue::from_str(value.as_str()).unwrap());
}
_ => {
unreachable!()
}
)
}
header_map
},
Err(e) => return Err(e)
};
let stream_addr = self.machine_st.store(self.machine_st.deref(self.machine_st.registers[4]));
read_heap_cell!(culprit,
(HeapCellValueTag::Cons, cons_ptr) => {
match_untyped_arena_ptr!(cons_ptr,
(ArenaHeaderTag::HttpResponse, http_response) => {
let mut response = Response::builder()
.status(status_code);
*response.headers_mut().unwrap() = headers;
let (sender, body) = Body::channel();
let response = response.body(body).unwrap();
http_response.blocking_send(response).unwrap();
let mut stream = Stream::from_http_sender(
sender,
&mut self.machine_st.arena
);
*stream.options_mut() = StreamOptions::default();
stream.options_mut().set_stream_type(StreamType::Binary);
self.indices.streams.insert(stream);
let stream = stream_as_cell!(stream);
self.machine_st.bind(stream_addr.as_var().unwrap(), stream);
}
_ => {
unreachable!();
}
);
}
_ => {
unreachable!();
}
);
Ok(())
}
#[inline(always)] #[inline(always)]
pub(crate) fn current_time(&mut self) { pub(crate) fn current_time(&mut self) {
let timestamp = self.systemtime_to_timestamp(SystemTime::now()); let timestamp = self.systemtime_to_timestamp(SystemTime::now());

View File

@@ -272,6 +272,20 @@ macro_rules! match_untyped_arena_ptr_pat_body {
#[allow(unused_braces)] #[allow(unused_braces)]
$code $code
}}; }};
($ptr:ident, HttpListener, $listener:ident, $code:expr) => {{
let payload_ptr = unsafe { std::mem::transmute::<_, *mut HttpListener>($ptr.payload_offset()) };
#[allow(unused_mut)]
let mut $listener = TypedArenaPtr::new(payload_ptr);
#[allow(unused_braces)]
$code
}};
($ptr:ident, HttpResponse, $listener:ident, $code:expr) => {{
let payload_ptr = unsafe { std::mem::transmute::<_, *mut HttpResponse>($ptr.payload_offset()) };
#[allow(unused_mut)]
let mut $listener = TypedArenaPtr::new(payload_ptr);
#[allow(unused_braces)]
$code
}};
($ptr:ident, IndexPtr, $ip:ident, $code:expr) => {{ ($ptr:ident, IndexPtr, $ip:ident, $code:expr) => {{
#[allow(unused_mut)] #[allow(unused_mut)]
let mut $ip = TypedArenaPtr::new(unsafe { std::mem::transmute::<_, *mut IndexPtr>($ptr.get_ptr()) }); let mut $ip = TypedArenaPtr::new(unsafe { std::mem::transmute::<_, *mut IndexPtr>($ptr.get_ptr()) });
@@ -291,7 +305,8 @@ macro_rules! match_untyped_arena_ptr_pat {
| ArenaHeaderTag::OutputFileStream | ArenaHeaderTag::OutputFileStream
| ArenaHeaderTag::NamedTcpStream | ArenaHeaderTag::NamedTcpStream
| ArenaHeaderTag::NamedTlsStream | ArenaHeaderTag::NamedTlsStream
| ArenaHeaderTag::NamedHttpClientStream | ArenaHeaderTag::HttpReadStream
| ArenaHeaderTag::HttpWriteStream
| ArenaHeaderTag::ReadlineStream | ArenaHeaderTag::ReadlineStream
| ArenaHeaderTag::StaticStringStream | ArenaHeaderTag::StaticStringStream
| ArenaHeaderTag::ByteStream | ArenaHeaderTag::ByteStream