feat: allow socket activation

This enables passing in an existing socket e.g. from systemd,[1] instead
of creating a new socket when running the server.

[1] https://github.com/juergenhoetzel/ocaml-systemd
This commit is contained in:
Yawar Amin 2021-09-25 01:41:32 -04:00
parent 9961bd1b29
commit ce61ac2afd
2 changed files with 22 additions and 8 deletions

View file

@ -809,6 +809,8 @@ type t = {
port: int; port: int;
sock: Unix.file_descr option;
sem_max_connections: Sem_.t; sem_max_connections: Sem_.t;
(* semaphore to restrict the number of active concurrent connections *) (* semaphore to restrict the number of active concurrent connections *)
@ -947,10 +949,10 @@ let create
?(masksigpipe=true) ?(masksigpipe=true)
?(max_connections=32) ?(max_connections=32)
?(new_thread=(fun f -> ignore (Thread.create f () : Thread.t))) ?(new_thread=(fun f -> ignore (Thread.create f () : Thread.t)))
?(addr="127.0.0.1") ?(port=8080) () : t = ?(addr="127.0.0.1") ?(port=8080) ?sock () : t =
let handler _req = Response.fail ~code:404 "no top handler" in let handler _req = Response.fail ~code:404 "no top handler" in
let max_connections = max 4 max_connections in let max_connections = max 4 max_connections in
{ new_thread; addr; port; masksigpipe; handler; { new_thread; addr; port; sock; masksigpipe; handler;
running= true; sem_max_connections=Sem_.create max_connections; running= true; sem_max_connections=Sem_.create max_connections;
path_handlers=[]; path_handlers=[];
cb_encode_resp=[]; cb_decode_req=[]; cb_encode_resp=[]; cb_decode_req=[];
@ -1065,16 +1067,24 @@ let run (self:t) : (unit,_) result =
if self.masksigpipe then ( if self.masksigpipe then (
ignore (Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigpipe] : _ list); ignore (Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigpipe] : _ list);
); );
let sock = let sock, should_bind = match self.sock with
Unix.socket (if is_ipv6 self then Unix.PF_INET6 else Unix.PF_INET) | Some s ->
Unix.SOCK_STREAM 0 s, false (* Because we're getting a socket from systemd *)
| None ->
Unix.socket
(if is_ipv6 self then Unix.PF_INET6 else Unix.PF_INET)
Unix.SOCK_STREAM
0,
true (* Because we're creating the socket ourselves *)
in in
Unix.clear_nonblock sock; Unix.clear_nonblock sock;
Unix.setsockopt sock Unix.SO_REUSEADDR true; Unix.setsockopt sock Unix.SO_REUSEADDR true;
Unix.setsockopt_optint sock Unix.SO_LINGER None; Unix.setsockopt_optint sock Unix.SO_LINGER None;
let inet_addr = Unix.inet_addr_of_string self.addr in let inet_addr = Unix.inet_addr_of_string self.addr in
begin if should_bind then
Unix.bind sock (Unix.ADDR_INET (inet_addr, self.port)); Unix.bind sock (Unix.ADDR_INET (inet_addr, self.port));
Unix.listen sock (2 * self.sem_max_connections.Sem_.n); Unix.listen sock (2 * self.sem_max_connections.Sem_.n)
end;
while self.running do while self.running do
(* limit concurrency *) (* limit concurrency *)
Sem_.acquire 1 self.sem_max_connections; Sem_.acquire 1 self.sem_max_connections;

View file

@ -437,6 +437,7 @@ val create :
?new_thread:((unit -> unit) -> unit) -> ?new_thread:((unit -> unit) -> unit) ->
?addr:string -> ?addr:string ->
?port:int -> ?port:int ->
?sock:Unix.file_descr ->
unit -> unit ->
t t
(** Create a new webserver. (** Create a new webserver.
@ -455,7 +456,10 @@ val create :
@param max_connections maximum number of simultaneous connections. @param max_connections maximum number of simultaneous connections.
@param addr address (IPv4 or IPv6) to listen on. Default ["127.0.0.1"]. @param addr address (IPv4 or IPv6) to listen on. Default ["127.0.0.1"].
@param port to listen on. Default [8080]. @param port to listen on. Default [8080].
*) @param sock an existing socket given to the server to listen on, e.g. by
systemd on Linux (or launchd on macOS). If passed in, this socket will be
used instead of the [addr] and [port]. If not passed in, those will be
used. *)
val addr : t -> string val addr : t -> string
(** Address on which the server listens. *) (** Address on which the server listens. *)