Skip to content

Commit 7928279

Browse files
author
Bryce Nichols
committed
A library for calling the nginx http api (ngx_http_api_module)
0 parents  commit 7928279

File tree

6 files changed

+476
-0
lines changed

6 files changed

+476
-0
lines changed

dune-project

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(lang dune 2.0)
2+
(name nginx_http_api)
3+
(implicit_transitive_deps false)
4+

lib/dune

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(library
2+
(name nginx_http_api)
3+
(modules nginx nginx_t nginx_j)
4+
(libraries
5+
atdgen-runtime
6+
curl
7+
devkit
8+
devkit.core
9+
extlib
10+
extunix
11+
lwt
12+
lwt.unix
13+
pcre
14+
timedesc
15+
yojson)
16+
(preprocess
17+
(pps lwt_ppx tyxml-ppx ppx_deriving.std ppx_fields_conv)))
18+
19+
(rule
20+
(targets nginx_t.mli nginx_t.ml)
21+
(deps nginx.atd)
22+
(action
23+
(run atdgen -t %{deps})))
24+
25+
(rule
26+
(targets nginx_j.mli nginx_j.ml)
27+
(deps nginx.atd)
28+
(action
29+
(run atdgen -j -j-std -j-defaults %{deps})))

lib/nginx.atd

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
type counts = (string * int) list <json repr="object">
2+
3+
type datetime = string wrap <ocaml t="Timedesc.t" wrap="Timedesc.of_iso8601_exn" unwrap="Timedesc.to_iso8601_milli">
4+
5+
type nginx = {
6+
version : string;
7+
build : string;
8+
address : string;
9+
generation : int;
10+
load_timestamp : string;
11+
timestamp : string;
12+
pid : int;
13+
ppid : int;
14+
}
15+
16+
type error = {
17+
status : int;
18+
text : string;
19+
code : string;
20+
}
21+
22+
type error_obj = {
23+
error : error;
24+
request_id : string;
25+
href : string;
26+
}
27+
28+
type verify_failures = {
29+
no_cert: int;
30+
expired_cert: int;
31+
revoked_cert: int;
32+
other: int;
33+
}
34+
35+
type ssl = {
36+
handshakes: int;
37+
handshakes_failed : int;
38+
session_reuses : int;
39+
no_common_protocol : int;
40+
no_common_cipher : int;
41+
handshake_timeout : int;
42+
peer_rejected_cert : int;
43+
verify_failures : verify_failures;
44+
}
45+
46+
type peer_state =
47+
[ Up <json name="up">
48+
| Draining <json name= "draining">
49+
| Down <json name= "down">
50+
| Unavail <json name= "unavail">
51+
| Checking <json name= "checking">
52+
| Unhealthy <json name= "unhealthy"> ]
53+
54+
type sessions =
55+
{ _2xx <json name="2xx"> : int; _4xx <json name="4xx"> : int; _5xx <json name="5xx"> : int; total: int; }
56+
57+
type responses =
58+
{ _1xx <json name="1xx">: int; _2xx <json name="2xx"> : int; _3xx <json name="3xx"> : int; _4xx <json name="4xx"> : int; _5xx <json name="5xx"> : int; codes: counts; total: int;
59+
}
60+
61+
type health_checks = {
62+
checks : int;
63+
fails : int;
64+
unhealthy : int;
65+
~last_passed <ocaml default="false"> : bool; (* TODO see why this is missing at times? *)
66+
}
67+
68+
type peer = {
69+
id : int;
70+
server : string;
71+
(* service: string; *)
72+
name : string;
73+
backup : bool;
74+
weight: int;
75+
state : peer_state;
76+
active : int;
77+
(*
78+
ssl: ssl;
79+
max_conns: int;
80+
*)
81+
requests: int;
82+
responses: responses;
83+
sent: int;
84+
received: int;
85+
fails: int;
86+
unavail: int;
87+
health_checks : health_checks;
88+
downtime : int;
89+
(* downstart : string; (* make into datetime? ISO8601 *) *)
90+
(* selected : string; (* make into datetime *) *)
91+
(* header_time : int; (* what units? *) *)
92+
(* response_time : int; (* apparently not in this version? *) *)
93+
}
94+
95+
type queue = {
96+
size : int;
97+
max_size : int;
98+
overflows : int;
99+
}
100+
101+
type upstream = {
102+
peers: peer list;
103+
keepalive : int;
104+
zombies : int;
105+
zone : string;
106+
(* queue : queue; *)
107+
}
108+
109+
type upstream_collection = (string * upstream) list <json repr="object">
110+
111+
type upstream_server_new = {
112+
server : string;
113+
?service : string option;
114+
weight : int;
115+
max_conns : int;
116+
max_fails: int;
117+
fail_timeout : string;
118+
slow_start : string;
119+
route : string;
120+
backup : bool;
121+
down : bool;
122+
?drain : bool option;
123+
?parent : string option;
124+
?host : string option;
125+
}
126+
127+
type upstream_server = {
128+
id : int;
129+
server : string;
130+
?service : string option;
131+
weight : int;
132+
max_conns : int;
133+
max_fails: int;
134+
fail_timeout : string;
135+
slow_start : string;
136+
route : string;
137+
backup : bool;
138+
down : bool;
139+
?drain : bool option;
140+
?parent : string option;
141+
?host : string option;
142+
}
143+
144+
type upstream_server_list = upstream_server list
145+
146+
type upstream_server_collection = (string * upstream_server) list <json repr="object">
147+
148+
type http_keyval_shared_memory_zone_get = (string * string) list <json repr="object">
149+
150+
type http_keyval_shared_memory_zone_post = (string * abstract) list <json repr="object">
151+
152+
type stream_server_zone = {
153+
processing : int;
154+
connections : int;
155+
sessions : sessions;
156+
discarded : int;
157+
received : int;
158+
sent : int;
159+
ssl : ssl;
160+
}
161+
162+
type endpoints = string list
163+

lib/nginx.ml

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
open Devkit
2+
open Result
3+
4+
let default_api_path = [ "api"; "8" ]
5+
6+
type url = {
7+
base : string;
8+
path : string list;
9+
}
10+
11+
type nginx = {
12+
base_url : string;
13+
additional_headers : string list;
14+
api_path : string list;
15+
}
16+
17+
let string_of_url url = Printf.sprintf "%s/%s" url.base (String.concat "/" url.path)
18+
19+
let endpoints_cache = Hashtbl.create 100
20+
21+
let pp_endpoints_cache () = Hashtbl.iter (fun k _v -> Printf.printf "%s\n" k) endpoints_cache
22+
23+
let add_endpoint url = Hashtbl.replace endpoints_cache url None
24+
25+
let update_endpoint url terminal_val = Hashtbl.replace endpoints_cache url (Some terminal_val)
26+
27+
let create ?(additional_headers = []) ?(api_path = default_api_path) base_url =
28+
{ base_url; additional_headers; api_path }
29+
30+
type resp_error =
31+
| MethodDisabled
32+
| UnknownVersion
33+
| UpstreamNotFound
34+
| UpstreamStatic
35+
| Other of int
36+
37+
type nginx_response =
38+
| Upstream of Nginx_t.upstream
39+
| Endpoints of Nginx_t.endpoints
40+
| ErrorObj of Nginx_t.error_obj
41+
| Nginx of Nginx_t.nginx
42+
| Peer_state of Nginx_t.peer_state
43+
| Ssl of Nginx_t.ssl
44+
| Success
45+
| Upstreams of Nginx_t.upstream list
46+
| UpstreamServer of Nginx_t.upstream_server
47+
48+
type 't r = ('t, string) result Lwt.t
49+
50+
module ReqErr : Map.OrderedType = struct
51+
type t = string * int
52+
let compare (fn1, code1) (fn2, code2) =
53+
match String.compare fn1 fn2 with
54+
| 0 -> Int.compare code1 code2
55+
| c -> c
56+
end
57+
58+
module ErrsMap = Map.Make (ReqErr)
59+
60+
let _errs_map = ErrsMap.empty
61+
62+
let return_generic_response url ?body code parse str =
63+
let body_str =
64+
match body with
65+
| None -> ""
66+
| Some (`Raw (_ct, str)) -> str
67+
| _ -> ""
68+
in
69+
if code >= 200 && code < 300 then Lwt.return (Ok (parse str))
70+
else Lwt.return (Error (Printf.sprintf "unsuccessful request %s: [%s] (HTTP code: %d): %s" url body_str code str))
71+
72+
let do_api8 verb ng path ?body parse_resp =
73+
let nm = Printf.sprintf "%s %s" (Web.string_of_http_action verb) (String.concat ":" path) in
74+
let url = string_of_url { base = ng.base_url; path = ng.api_path @ path } in
75+
let body = Option.map (fun v -> `Raw ("application/json", v)) body in
76+
match%lwt Web.http_request_lwt' ~headers:ng.additional_headers ?body verb url with
77+
| `Ok (code, str) ->
78+
(try return_generic_response url ?body code parse_resp str
79+
with exn -> Lwt.return (Error (Printf.sprintf "error\n calling %s - %s" nm (Printexc.to_string exn))))
80+
| `Error _ as e -> Lwt.fail_with (Printf.sprintf "error calling %s - %s" nm (Web.show_result e))
81+
82+
let do_api8_del_req ng path parse_resp = do_api8 `DELETE ng path parse_resp
83+
84+
let do_api8_get_req ng path parse_resp = do_api8 `GET ng path parse_resp
85+
86+
let do_api8_patch_req ng path body parse_resp = do_api8 `PATCH ng path ~body parse_resp
87+
88+
let do_api8_post_req ng path body parse_resp = do_api8 `POST ng path ~body parse_resp
89+
90+
let endpoints ng path = do_api8_get_req ng path (fun str -> Nginx_j.endpoints_of_string str)
91+
92+
let endpoints_cache_size () = Hashtbl.length endpoints_cache
93+
94+
let upd_all_endpoints ng =
95+
let rec upd_aux path =
96+
match%lwt endpoints ng path with
97+
| Ok l ->
98+
update_endpoint (string_of_url { base = ng.base_url; path = [ "api"; "8" ] @ path }) false;
99+
Lwt_list.iter_s upd_aux
100+
(List.map
101+
(fun i ->
102+
let np = path @ [ i ] in
103+
add_endpoint (string_of_url { base = ng.base_url; path = [ "api"; "8" ] @ np });
104+
np)
105+
l)
106+
| _ -> Lwt.return_unit
107+
in
108+
let%lwt res = upd_aux [] in
109+
Lwt.return (Ok res)
110+
111+
let nginx ng = do_api8_get_req ng [ "nginx" ] (fun str -> Nginx_j.nginx_of_string str)
112+
113+
module Upstream = struct
114+
type t = Nginx_t.upstream
115+
type upstream_id = string
116+
117+
let upstream_id (str : string) = str
118+
119+
(* val list : nginx -> t list r *)
120+
121+
(** Fetch a list of all upstreams configured *)
122+
let list ng =
123+
do_api8_get_req ng [ "http"; "upstreams" ] (fun resp -> List.map snd (Nginx_j.upstream_collection_of_string resp))
124+
125+
(* val get : nginx -> upstream_id -> t r *)
126+
127+
(** Fetch state and configuration of the given upstream *)
128+
let get ng upstr_id =
129+
do_api8_get_req ng [ "http"; "upstreams"; upstr_id ] (fun (str : string) -> Nginx_j.upstream_of_string str)
130+
131+
(* val reset : nginx -> upstream_id -> unit r *)
132+
133+
(** Reset statistics of the given upstream *)
134+
let reset ng upstr_id = do_api8_del_req ng [ "http"; "upstreams"; upstr_id ] (Fun.const ())
135+
136+
module Server = struct
137+
type t = Nginx_t.upstream_server
138+
type server_id = int
139+
140+
let server_id id : int = id
141+
142+
(* val list : nginx -> upstream_id -> t list r *)
143+
144+
(** Fetch a list of all upstreams configured *)
145+
let list ng upstr_id =
146+
do_api8_get_req ng [ "http"; "upstreams"; upstr_id; "servers" ] Nginx_j.upstream_server_list_of_string
147+
148+
(* val add : nginx -> upstream_id -> t -> unit r *)
149+
150+
(** Add a server to upstream server_list *)
151+
let add ng upstr_id srv =
152+
do_api8_post_req ng
153+
[ "http"; "upstreams"; upstr_id; "servers" ]
154+
(Nginx_j.string_of_upstream_server_new srv)
155+
Nginx_j.upstream_server_of_string
156+
157+
(* val get : nginx -> upstream_id -> server_id -> t r *)
158+
159+
(** Fetch status and configuration of the given server *)
160+
let get ng upstr_id srv =
161+
do_api8_get_req ng
162+
[ "http"; "upstreams"; upstr_id; "servers"; string_of_int srv ]
163+
(fun str -> Nginx_j.upstream_server_of_string str)
164+
165+
(* val remove : nginx -> upstream_id -> server_id -> unit r *)
166+
167+
(** Remove the server from the upstream group *)
168+
let remove ng upstr_id srv_id =
169+
do_api8_del_req ng [ "http"; "upstreams"; upstr_id; "servers"; string_of_int srv_id ] (Fun.const ())
170+
171+
let update ng upstr_id srv_id conf =
172+
do_api8_patch_req ng
173+
[ "https"; "upstreams"; upstr_id; "servers"; string_of_int srv_id ]
174+
(Yojson.Basic.to_string conf) (Fun.const ())
175+
end
176+
end

0 commit comments

Comments
 (0)