mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 03:05:29 -05:00
wip: bugfixes
This commit is contained in:
parent
e3047a7b6a
commit
ec3dec6b72
14 changed files with 158 additions and 159 deletions
|
|
@ -1,4 +1,4 @@
|
|||
module S = Tiny_httpd
|
||||
open Tiny_httpd_core
|
||||
module Log = Tiny_httpd.Log
|
||||
|
||||
let now_ = Unix.gettimeofday
|
||||
|
|
@ -34,7 +34,7 @@ let alice_text =
|
|||
sides of the well, and noticed that they were filled with cupboards......"
|
||||
|
||||
(* util: a little middleware collecting statistics *)
|
||||
let middleware_stat () : S.Middleware.t * (unit -> string) =
|
||||
let middleware_stat () : Server.Middleware.t * (unit -> string) =
|
||||
let n_req = ref 0 in
|
||||
let total_time_ = ref 0. in
|
||||
let parse_time_ = ref 0. in
|
||||
|
|
@ -43,7 +43,7 @@ let middleware_stat () : S.Middleware.t * (unit -> string) =
|
|||
|
||||
let m h req ~resp =
|
||||
incr n_req;
|
||||
let t1 = S.Request.start_time req in
|
||||
let t1 = Request.start_time req in
|
||||
let t2 = now_ () in
|
||||
h req ~resp:(fun response ->
|
||||
let t3 = now_ () in
|
||||
|
|
@ -92,23 +92,23 @@ let () =
|
|||
(fun _ -> raise (Arg.Bad ""))
|
||||
"echo [option]*";
|
||||
|
||||
let server = S.create ~port:!port_ ~max_connections:!j () in
|
||||
let server = Tiny_httpd.create ~port:!port_ ~max_connections:!j () in
|
||||
|
||||
Tiny_httpd_camlzip.setup ~compress_above:1024 ~buf_size:(16 * 1024) server;
|
||||
let m_stats, get_stats = middleware_stat () in
|
||||
S.add_middleware server ~stage:(`Stage 1) m_stats;
|
||||
Server.add_middleware server ~stage:(`Stage 1) m_stats;
|
||||
|
||||
(* say hello *)
|
||||
S.add_route_handler ~meth:`GET server
|
||||
S.Route.(exact "hello" @/ string @/ return)
|
||||
(fun name _req -> S.Response.make_string (Ok ("hello " ^ name ^ "!\n")));
|
||||
Server.add_route_handler ~meth:`GET server
|
||||
Route.(exact "hello" @/ string @/ return)
|
||||
(fun name _req -> Response.make_string (Ok ("hello " ^ name ^ "!\n")));
|
||||
|
||||
(* compressed file access *)
|
||||
S.add_route_handler ~meth:`GET server
|
||||
S.Route.(exact "zcat" @/ string_urlencoded @/ return)
|
||||
Server.add_route_handler ~meth:`GET server
|
||||
Route.(exact "zcat" @/ string_urlencoded @/ return)
|
||||
(fun path _req ->
|
||||
let ic = open_in path in
|
||||
let str = S.Byte_stream.of_chan ic in
|
||||
let str = IO.Input.of_in_channel ic in
|
||||
let mime_type =
|
||||
try
|
||||
let p = Unix.open_process_in (Printf.sprintf "file -i -b %S" path) in
|
||||
|
|
@ -121,42 +121,42 @@ let () =
|
|||
[]
|
||||
with _ -> []
|
||||
in
|
||||
S.Response.make_stream ~headers:mime_type (Ok str));
|
||||
Response.make_stream ~headers:mime_type (Ok str));
|
||||
|
||||
(* echo request *)
|
||||
S.add_route_handler server
|
||||
S.Route.(exact "echo" @/ return)
|
||||
Server.add_route_handler server
|
||||
Route.(exact "echo" @/ return)
|
||||
(fun req ->
|
||||
let q =
|
||||
S.Request.query req
|
||||
Request.query req
|
||||
|> List.map (fun (k, v) -> Printf.sprintf "%S = %S" k v)
|
||||
|> String.concat ";"
|
||||
in
|
||||
S.Response.make_string
|
||||
(Ok (Format.asprintf "echo:@ %a@ (query: %s)@." S.Request.pp req q)));
|
||||
Response.make_string
|
||||
(Ok (Format.asprintf "echo:@ %a@ (query: %s)@." Request.pp req q)));
|
||||
|
||||
(* file upload *)
|
||||
S.add_route_handler_stream ~meth:`PUT server
|
||||
S.Route.(exact "upload" @/ string @/ return)
|
||||
Server.add_route_handler_stream ~meth:`PUT server
|
||||
Route.(exact "upload" @/ string @/ return)
|
||||
(fun path req ->
|
||||
Log.debug (fun k ->
|
||||
k "start upload %S, headers:\n%s\n\n%!" path
|
||||
(Format.asprintf "%a" S.Headers.pp (S.Request.headers req)));
|
||||
(Format.asprintf "%a" Headers.pp (Request.headers req)));
|
||||
try
|
||||
let oc = open_out @@ "/tmp/" ^ path in
|
||||
S.Byte_stream.to_chan oc req.S.Request.body;
|
||||
IO.Input.to_chan oc req.Request.body;
|
||||
flush oc;
|
||||
S.Response.make_string (Ok "uploaded file")
|
||||
Response.make_string (Ok "uploaded file")
|
||||
with e ->
|
||||
S.Response.fail ~code:500 "couldn't upload file: %s"
|
||||
Response.fail ~code:500 "couldn't upload file: %s"
|
||||
(Printexc.to_string e));
|
||||
|
||||
(* protected by login *)
|
||||
S.add_route_handler server
|
||||
S.Route.(exact "protected" @/ return)
|
||||
Server.add_route_handler server
|
||||
Route.(exact "protected" @/ return)
|
||||
(fun req ->
|
||||
let ok =
|
||||
match S.Request.get_header req "authorization" with
|
||||
match Request.get_header req "authorization" with
|
||||
| Some v ->
|
||||
Log.debug (fun k -> k "authenticate with %S" v);
|
||||
v = "Basic " ^ base64 "user:foobar"
|
||||
|
|
@ -167,40 +167,40 @@ let () =
|
|||
let s =
|
||||
"<p>hello, this is super secret!</p><a href=\"/logout\">log out</a>"
|
||||
in
|
||||
S.Response.make_string (Ok s)
|
||||
Response.make_string (Ok s)
|
||||
) else (
|
||||
let headers =
|
||||
S.Headers.(empty |> set "www-authenticate" "basic realm=\"echo\"")
|
||||
Headers.(empty |> set "www-authenticate" "basic realm=\"echo\"")
|
||||
in
|
||||
S.Response.fail ~code:401 ~headers "invalid"
|
||||
Response.fail ~code:401 ~headers "invalid"
|
||||
));
|
||||
|
||||
(* logout *)
|
||||
S.add_route_handler server
|
||||
S.Route.(exact "logout" @/ return)
|
||||
(fun _req -> S.Response.fail ~code:401 "logged out");
|
||||
Server.add_route_handler server
|
||||
Route.(exact "logout" @/ return)
|
||||
(fun _req -> Response.fail ~code:401 "logged out");
|
||||
|
||||
(* stats *)
|
||||
S.add_route_handler server
|
||||
S.Route.(exact "stats" @/ return)
|
||||
Server.add_route_handler server
|
||||
Route.(exact "stats" @/ return)
|
||||
(fun _req ->
|
||||
let stats = get_stats () in
|
||||
S.Response.make_string @@ Ok stats);
|
||||
Response.make_string @@ Ok stats);
|
||||
|
||||
S.add_route_handler server
|
||||
S.Route.(exact "alice" @/ return)
|
||||
(fun _req -> S.Response.make_string (Ok alice_text));
|
||||
Server.add_route_handler server
|
||||
Route.(exact "alice" @/ return)
|
||||
(fun _req -> Response.make_string (Ok alice_text));
|
||||
|
||||
(* VFS *)
|
||||
Tiny_httpd_dir.add_vfs server
|
||||
Tiny_httpd.Dir.add_vfs server
|
||||
~config:
|
||||
(Tiny_httpd_dir.config ~download:true
|
||||
~dir_behavior:Tiny_httpd_dir.Index_or_lists ())
|
||||
(Tiny_httpd.Dir.config ~download:true
|
||||
~dir_behavior:Tiny_httpd.Dir.Index_or_lists ())
|
||||
~vfs:Vfs.vfs ~prefix:"vfs";
|
||||
|
||||
(* main page *)
|
||||
S.add_route_handler server
|
||||
S.Route.(return)
|
||||
Server.add_route_handler server
|
||||
Route.(return)
|
||||
(fun _req ->
|
||||
let open Tiny_httpd_html in
|
||||
let h =
|
||||
|
|
@ -272,9 +272,10 @@ let () =
|
|||
]
|
||||
in
|
||||
let s = to_string_top h in
|
||||
S.Response.make_string ~headers:[ "content-type", "text/html" ] @@ Ok s);
|
||||
Response.make_string ~headers:[ "content-type", "text/html" ] @@ Ok s);
|
||||
|
||||
Printf.printf "listening on http://%s:%d\n%!" (S.addr server) (S.port server);
|
||||
match S.run server with
|
||||
Printf.printf "listening on http://%s:%d\n%!" (Server.addr server)
|
||||
(Server.port server);
|
||||
match Server.run server with
|
||||
| Ok () -> ()
|
||||
| Error e -> raise e
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
module S = Tiny_httpd
|
||||
module Log = Tiny_httpd.Log
|
||||
module IO = Tiny_httpd_io
|
||||
open Tiny_httpd_core
|
||||
|
||||
let setup_logging ~debug () =
|
||||
Logs.set_reporter @@ Logs.format_reporter ();
|
||||
|
|
@ -13,8 +12,7 @@ let setup_logging ~debug () =
|
|||
|
||||
let handle_ws _client_addr ic oc =
|
||||
Log.info (fun k ->
|
||||
k "new client connection from %s"
|
||||
(Tiny_httpd_util.show_sockaddr _client_addr));
|
||||
k "new client connection from %s" (Util.show_sockaddr _client_addr));
|
||||
|
||||
let (_ : Thread.t) =
|
||||
Thread.create
|
||||
|
|
@ -58,7 +56,7 @@ let () =
|
|||
|
||||
let server = S.create ~port:!port_ ~max_connections:!j () in
|
||||
Tiny_httpd_ws.add_route_handler server
|
||||
S.Route.(exact "echo" @/ return)
|
||||
Route.(exact "echo" @/ return)
|
||||
handle_ws;
|
||||
|
||||
Printf.printf "listening on http://%s:%d\n%!" (S.addr server) (S.port server);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
(* serves some streams of events *)
|
||||
|
||||
module S = Tiny_httpd
|
||||
module Log = Tiny_httpd_log
|
||||
open Tiny_httpd_core
|
||||
|
||||
let port = ref 8080
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ let () =
|
|||
])
|
||||
(fun _ -> ())
|
||||
"sse_clock [opt*]";
|
||||
let server = S.create ~port:!port () in
|
||||
let server = Tiny_httpd.create ~port:!port () in
|
||||
|
||||
let extra_headers =
|
||||
[
|
||||
|
|
@ -24,9 +23,9 @@ let () =
|
|||
in
|
||||
|
||||
(* tick/tock goes the clock *)
|
||||
S.add_route_server_sent_handler server
|
||||
S.Route.(exact "clock" @/ return)
|
||||
(fun _req (module EV : S.SERVER_SENT_GENERATOR) ->
|
||||
Server.add_route_server_sent_handler server
|
||||
Route.(exact "clock" @/ return)
|
||||
(fun _req (module EV : Server.SERVER_SENT_GENERATOR) ->
|
||||
Log.debug (fun k -> k "new SSE connection");
|
||||
EV.set_headers extra_headers;
|
||||
let tick = ref true in
|
||||
|
|
@ -47,26 +46,26 @@ let () =
|
|||
done);
|
||||
|
||||
(* just count *)
|
||||
S.add_route_server_sent_handler server
|
||||
S.Route.(exact "count" @/ return)
|
||||
(fun _req (module EV : S.SERVER_SENT_GENERATOR) ->
|
||||
Server.add_route_server_sent_handler server
|
||||
Route.(exact "count" @/ return)
|
||||
(fun _req (module EV : Server.SERVER_SENT_GENERATOR) ->
|
||||
let n = ref 0 in
|
||||
while true do
|
||||
EV.send_event ~data:(string_of_int !n) ();
|
||||
incr n;
|
||||
Unix.sleepf 0.1
|
||||
done);
|
||||
S.add_route_server_sent_handler server
|
||||
S.Route.(exact "count" @/ int @/ return)
|
||||
(fun n _req (module EV : S.SERVER_SENT_GENERATOR) ->
|
||||
Server.add_route_server_sent_handler server
|
||||
Route.(exact "count" @/ int @/ return)
|
||||
(fun n _req (module EV : Server.SERVER_SENT_GENERATOR) ->
|
||||
for i = 0 to n do
|
||||
EV.send_event ~data:(string_of_int i) ();
|
||||
Unix.sleepf 0.1
|
||||
done;
|
||||
EV.close ());
|
||||
|
||||
Printf.printf "listening on http://localhost:%d/\n%!" (S.port server);
|
||||
match S.run server with
|
||||
Printf.printf "listening on http://localhost:%d/\n%!" (Server.port server);
|
||||
match Server.run server with
|
||||
| Ok () -> ()
|
||||
| Error e ->
|
||||
Printf.eprintf "error: %s\n%!" (Printexc.to_string e);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
module H = Tiny_httpd
|
||||
open Tiny_httpd_core
|
||||
|
||||
let serve_zeroes server : unit =
|
||||
H.add_route_handler server H.(Route.(exact "zeroes" @/ int @/ return))
|
||||
Server.add_route_handler server Route.(exact "zeroes" @/ int @/ return)
|
||||
@@ fun n _req ->
|
||||
(* stream [n] zeroes *)
|
||||
let write (oc : H.IO.Output.t) : unit =
|
||||
|
|
@ -11,7 +12,7 @@ let serve_zeroes server : unit =
|
|||
done
|
||||
in
|
||||
let writer = H.IO.Writer.make ~write () in
|
||||
H.Response.make_writer @@ Ok writer
|
||||
Response.make_writer @@ Ok writer
|
||||
|
||||
let serve_file server : unit =
|
||||
H.add_route_handler server H.(Route.(exact "file" @/ string @/ return))
|
||||
|
|
@ -32,9 +33,9 @@ let serve_file server : unit =
|
|||
in
|
||||
|
||||
let writer = H.IO.Writer.make ~write () in
|
||||
H.Response.make_writer @@ Ok writer
|
||||
Response.make_writer @@ Ok writer
|
||||
) else
|
||||
H.Response.fail ~code:404 "file not found"
|
||||
Response.fail ~code:404 "file not found"
|
||||
|
||||
let () =
|
||||
let port = ref 8085 in
|
||||
|
|
@ -43,7 +44,7 @@ let () =
|
|||
Printf.printf "listen on http://localhost:%d/\n%!" !port;
|
||||
serve_file server;
|
||||
serve_zeroes server;
|
||||
H.add_route_handler server H.Route.return (fun _req ->
|
||||
H.add_route_handler server Route.return (fun _req ->
|
||||
let body =
|
||||
H.Html.(
|
||||
div []
|
||||
|
|
@ -58,5 +59,5 @@ let () =
|
|||
])
|
||||
|> H.Html.to_string_top
|
||||
in
|
||||
H.Response.make_string @@ Ok body);
|
||||
Response.make_string @@ Ok body);
|
||||
H.run_exn server
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ module Buf = Buf
|
|||
include Server
|
||||
module Util = Util
|
||||
module Dir = Tiny_httpd_unix.Dir
|
||||
|
||||
module type VFS = Tiny_httpd_unix.Dir.VFS
|
||||
|
||||
module Html = Tiny_httpd_html
|
||||
module IO = Tiny_httpd_core.IO
|
||||
module Pool = Tiny_httpd_core.Pool
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ module Pool = Tiny_httpd_core.Pool
|
|||
|
||||
module Dir = Tiny_httpd_unix.Dir
|
||||
|
||||
module type VFS = Tiny_httpd_unix.Dir.VFS
|
||||
|
||||
(** {2 HTML combinators} *)
|
||||
|
||||
module Html = Tiny_httpd_html
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ let is_url s =
|
|||
is_prefix "http://" s || is_prefix "https://" s
|
||||
|
||||
let emit oc (l : entry list) : unit =
|
||||
fpf oc "let embedded_fs = Tiny_httpd_dir.Embedded_fs.create ~mtime:%f ()\n"
|
||||
fpf oc "let embedded_fs = Tiny_httpd.Dir.Embedded_fs.create ~mtime:%f ()\n"
|
||||
now_;
|
||||
|
||||
let add_vfs ~mtime vfs_path content =
|
||||
fpf oc
|
||||
"let () = Tiny_httpd_dir.Embedded_fs.add_file embedded_fs \n\
|
||||
"let () = Tiny_httpd.Dir.Embedded_fs.add_file embedded_fs \n\
|
||||
\ ~mtime:%h ~path:%S\n\
|
||||
\ %S\n"
|
||||
mtime vfs_path content
|
||||
|
|
@ -99,7 +99,7 @@ let emit oc (l : entry list) : unit =
|
|||
in
|
||||
List.iter add_entry l;
|
||||
|
||||
fpf oc "let vfs = Tiny_httpd_dir.Embedded_fs.to_vfs embedded_fs\n";
|
||||
fpf oc "let vfs = Tiny_httpd.Dir.Embedded_fs.to_vfs embedded_fs\n";
|
||||
()
|
||||
|
||||
let help =
|
||||
|
|
|
|||
|
|
@ -295,56 +295,51 @@ module Input = struct
|
|||
| () -> Some (Buf.contents_and_clear buf)
|
||||
| exception End_of_file -> None
|
||||
|
||||
(** new stream with maximum size [max_size].
|
||||
@param close_rec if true, closing this will also close the input stream *)
|
||||
let limit_size_to ~close_rec ~max_size ~(bytes : bytes) (arg : t) : t =
|
||||
let remaining_size = ref max_size in
|
||||
let reading_exactly_ ~skip_on_close ~close_rec ~size (arg : t) : t =
|
||||
let remaining_size = ref size in
|
||||
|
||||
object
|
||||
inherit Iostream.In_buf.t_from_refill ~bytes ()
|
||||
method close () = if close_rec then close arg
|
||||
method close () =
|
||||
if !remaining_size > 0 && skip_on_close then skip arg !remaining_size;
|
||||
if close_rec then close arg
|
||||
|
||||
method private refill slice =
|
||||
if slice.len = 0 then
|
||||
if !remaining_size > 0 then (
|
||||
let sub = fill_buf arg in
|
||||
let len = min sub.len !remaining_size in
|
||||
method fill_buf () =
|
||||
if !remaining_size > 0 then
|
||||
fill_buf arg
|
||||
else
|
||||
Slice.empty
|
||||
|
||||
Bytes.blit sub.bytes sub.off slice.bytes 0 len;
|
||||
slice.off <- 0;
|
||||
slice.len <- len;
|
||||
Slice.consume sub len
|
||||
)
|
||||
method input bs i len =
|
||||
if !remaining_size > 0 then (
|
||||
let slice = fill_buf arg in
|
||||
let n = min len (min slice.len !remaining_size) in
|
||||
Bytes.blit slice.bytes slice.off bs i n;
|
||||
remaining_size := !remaining_size - n;
|
||||
Slice.consume slice n;
|
||||
n
|
||||
) else
|
||||
0
|
||||
|
||||
method consume n =
|
||||
if n > !remaining_size then
|
||||
invalid_arg "reading_exactly: consuming too much";
|
||||
remaining_size := !remaining_size - n;
|
||||
consume arg n
|
||||
end
|
||||
|
||||
(** new stream with maximum size [max_size].
|
||||
@param close_rec if true, closing this will also close the input stream *)
|
||||
let limit_size_to ~close_rec ~max_size (arg : t) : t =
|
||||
reading_exactly_ ~size:max_size ~skip_on_close:false ~close_rec arg
|
||||
|
||||
(** New stream that consumes exactly [size] bytes from the input.
|
||||
If fewer bytes are read before [close] is called, we read and discard
|
||||
the remaining quota of bytes before [close] returns.
|
||||
@param close_rec if true, closing this will also close the input stream *)
|
||||
let reading_exactly ~close_rec ~size ~(bytes : bytes) (arg : t) : t =
|
||||
let remaining_size = ref size in
|
||||
let reading_exactly ~close_rec ~size (arg : t) : t =
|
||||
reading_exactly_ ~size ~close_rec ~skip_on_close:true arg
|
||||
|
||||
object
|
||||
inherit Iostream.In_buf.t_from_refill ~bytes ()
|
||||
|
||||
method close () =
|
||||
if !remaining_size > 0 then skip arg !remaining_size;
|
||||
if close_rec then close arg
|
||||
|
||||
method private refill slice =
|
||||
if slice.len = 0 then
|
||||
if !remaining_size > 0 then (
|
||||
let sub = fill_buf arg in
|
||||
let len = min sub.len !remaining_size in
|
||||
|
||||
Bytes.blit sub.bytes sub.off slice.bytes 0 len;
|
||||
slice.off <- 0;
|
||||
slice.len <- len;
|
||||
Slice.consume sub len
|
||||
)
|
||||
end
|
||||
|
||||
let read_chunked ~(bytes : bytes) ~fail (bs : #t) : t =
|
||||
let read_chunked ~(bytes : bytes) ~fail (ic : #t) : t =
|
||||
let first = ref true in
|
||||
|
||||
(* small buffer to read the chunk sizes *)
|
||||
|
|
@ -353,11 +348,11 @@ module Input = struct
|
|||
if !first then
|
||||
first := false
|
||||
else (
|
||||
let line = read_line_using ~buf:line_buf bs in
|
||||
let line = read_line_using ~buf:line_buf ic in
|
||||
if String.trim line <> "" then
|
||||
raise (fail "expected crlf between chunks")
|
||||
);
|
||||
let line = read_line_using ~buf:line_buf bs in
|
||||
let line = read_line_using ~buf:line_buf ic in
|
||||
(* parse chunk length, ignore extensions *)
|
||||
let chunk_size =
|
||||
if String.trim line = "" then
|
||||
|
|
@ -380,7 +375,10 @@ module Input = struct
|
|||
inherit t_from_refill ~bytes ()
|
||||
|
||||
method private refill (slice : Slice.t) : unit =
|
||||
if !chunk_size = 0 && not !eof then chunk_size := read_next_chunk_len ();
|
||||
if !chunk_size = 0 && not !eof then (
|
||||
chunk_size := read_next_chunk_len ();
|
||||
if !chunk_size = 0 then eof := true (* stream is finished *)
|
||||
);
|
||||
slice.off <- 0;
|
||||
slice.len <- 0;
|
||||
if !chunk_size > 0 then (
|
||||
|
|
@ -388,12 +386,10 @@ module Input = struct
|
|||
let to_read = min !chunk_size (Bytes.length slice.bytes) in
|
||||
read_exactly_
|
||||
~too_short:(fun () -> raise (fail "chunk is too short"))
|
||||
bs slice.bytes to_read;
|
||||
ic slice.bytes to_read;
|
||||
slice.len <- to_read;
|
||||
chunk_size := !chunk_size - to_read
|
||||
) else
|
||||
(* stream is finished *)
|
||||
eof := true
|
||||
)
|
||||
|
||||
method close () = eof := true (* do not close underlying stream *)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -71,18 +71,17 @@ let read_stream_chunked_ ~bytes (bs : #IO.Input.t) : IO.Input.t =
|
|||
Log.debug (fun k -> k "body: start reading chunked stream...");
|
||||
IO.Input.read_chunked ~bytes ~fail:(fun s -> Bad_req (400, s)) bs
|
||||
|
||||
let limit_body_size_ ~max_size ~bytes (bs : #IO.Input.t) : IO.Input.t =
|
||||
let limit_body_size_ ~max_size (bs : #IO.Input.t) : IO.Input.t =
|
||||
Log.debug (fun k -> k "limit size of body to max-size=%d" max_size);
|
||||
IO.Input.limit_size_to ~max_size ~close_rec:false ~bytes bs
|
||||
IO.Input.limit_size_to ~max_size ~close_rec:false bs
|
||||
|
||||
let limit_body_size ~max_size ?(bytes = Bytes.create 4096) (req : IO.Input.t t)
|
||||
: IO.Input.t t =
|
||||
{ req with body = limit_body_size_ ~max_size ~bytes req.body }
|
||||
let limit_body_size ~max_size (req : IO.Input.t t) : IO.Input.t t =
|
||||
{ req with body = limit_body_size_ ~max_size req.body }
|
||||
|
||||
(** read exactly [size] bytes from the stream *)
|
||||
let read_exactly ~size ~bytes (bs : #IO.Input.t) : IO.Input.t =
|
||||
let read_exactly ~size (bs : #IO.Input.t) : IO.Input.t =
|
||||
Log.debug (fun k -> k "body: must read exactly %d bytes" size);
|
||||
IO.Input.reading_exactly bs ~close_rec:false ~size ~bytes
|
||||
IO.Input.reading_exactly bs ~close_rec:false ~size
|
||||
|
||||
(* parse request, but not body (yet) *)
|
||||
let parse_req_start ~client_addr ~get_time_s ~buf (bs : IO.Input.t) :
|
||||
|
|
@ -160,18 +159,15 @@ let parse_body_ ~tr_stream ~bytes (req : IO.Input.t t) :
|
|||
in
|
||||
let body =
|
||||
match get_header ~f:String.trim req "Transfer-Encoding" with
|
||||
| None ->
|
||||
let bytes = Bytes.create 4096 in
|
||||
read_exactly ~size ~bytes @@ tr_stream req.body
|
||||
| None -> read_exactly ~size @@ tr_stream req.body
|
||||
| Some "chunked" ->
|
||||
(* body sent by chunks *)
|
||||
let bs : IO.Input.t =
|
||||
read_stream_chunked_ ~bytes @@ tr_stream req.body
|
||||
in
|
||||
if size > 0 then (
|
||||
let bytes = Bytes.create 4096 in
|
||||
limit_body_size_ ~max_size:size ~bytes bs
|
||||
) else
|
||||
if size > 0 then
|
||||
limit_body_size_ ~max_size:size bs
|
||||
else
|
||||
bs
|
||||
| Some s -> bad_reqf 500 "cannot handle transfer encoding: %s" s
|
||||
in
|
||||
|
|
|
|||
|
|
@ -107,11 +107,9 @@ val start_time : _ t -> float
|
|||
(** time stamp (from {!Unix.gettimeofday}) after parsing the first line of the request
|
||||
@since 0.11 *)
|
||||
|
||||
val limit_body_size :
|
||||
max_size:int -> ?bytes:bytes -> IO.Input.t t -> IO.Input.t t
|
||||
val limit_body_size : max_size:int -> IO.Input.t t -> IO.Input.t t
|
||||
(** Limit the body size to [max_size] bytes, or return
|
||||
a [413] error.
|
||||
@param bytes intermediate buffer
|
||||
@since 0.3
|
||||
*)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
(tests
|
||||
(names t_util t_buf t_server)
|
||||
(package tiny_httpd)
|
||||
(libraries tiny_httpd qcheck-core qcheck-core.runner test_util))
|
||||
(libraries tiny_httpd.core qcheck-core qcheck-core.runner test_util))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
open Test_util
|
||||
open Tiny_httpd_buf
|
||||
open Tiny_httpd_core.Buf
|
||||
|
||||
let spf = Printf.sprintf
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
open Test_util
|
||||
open Tiny_httpd_server
|
||||
open Tiny_httpd_core
|
||||
|
||||
let () =
|
||||
let q =
|
||||
|
|
@ -9,9 +9,13 @@ let () =
|
|||
\r\n\
|
||||
salutationsSOMEJUNK"
|
||||
in
|
||||
let str = Tiny_httpd.Byte_stream.of_string q in
|
||||
let str = IO.Input.of_string q in
|
||||
let client_addr = Unix.(ADDR_INET (inet_addr_loopback, 1024)) in
|
||||
let r = Request.Internal_.parse_req_start ~client_addr ~get_time_s:(fun _ -> 0.) str in
|
||||
let r =
|
||||
Request.Private_.parse_req_start_exn ~client_addr ~buf:(Buf.create ())
|
||||
~get_time_s:(fun _ -> 0.)
|
||||
str
|
||||
in
|
||||
match r with
|
||||
| None -> failwith "should parse"
|
||||
| Some req ->
|
||||
|
|
@ -19,6 +23,6 @@ let () =
|
|||
assert_eq (Some "coucou") (Headers.get "host" req.Request.headers);
|
||||
assert_eq (Some "11") (Headers.get "content-length" req.Request.headers);
|
||||
assert_eq "hello" req.Request.path;
|
||||
let req = Request.Internal_.parse_body req str |> Request.read_body_full in
|
||||
let req = Request.Private_.parse_body req str |> Request.read_body_full in
|
||||
assert_eq ~to_string:(fun s -> s) "salutations" req.Request.body;
|
||||
()
|
||||
|
|
|
|||
|
|
@ -1,33 +1,34 @@
|
|||
open Test_util
|
||||
open Tiny_httpd_util
|
||||
open Tiny_httpd_core
|
||||
module U = Util
|
||||
|
||||
let () = assert_eq "hello%20world" (percent_encode "hello world")
|
||||
let () = assert_eq "%23%25^%24%40^%40" (percent_encode "#%^$@^@")
|
||||
let () = assert_eq "hello%20world" (U.percent_encode "hello world")
|
||||
let () = assert_eq "%23%25^%24%40^%40" (U.percent_encode "#%^$@^@")
|
||||
|
||||
let () =
|
||||
assert_eq "a%20ohm%2B5235%25%26%40%23%20---%20_"
|
||||
(percent_encode "a ohm+5235%&@# --- _")
|
||||
(U.percent_encode "a ohm+5235%&@# --- _")
|
||||
|
||||
let () = assert_eq (Some "?") (percent_decode @@ percent_encode "?")
|
||||
let () = assert_eq (Some "?") (U.percent_decode @@ U.percent_encode "?")
|
||||
|
||||
let () =
|
||||
add_qcheck
|
||||
@@ QCheck.Test.make ~count:1_000 ~long_factor:20 Q.string (fun s ->
|
||||
String.iter (fun c -> Q.assume @@ is_ascii_char c) s;
|
||||
match percent_decode (percent_encode s) with
|
||||
match U.percent_decode (U.percent_encode s) with
|
||||
| Some s' -> s = s'
|
||||
| None -> Q.Test.fail_report "invalid percent encoding")
|
||||
|
||||
let () = assert_eq [ "a"; "b" ] (split_on_slash "/a/b")
|
||||
let () = assert_eq [ "coucou"; "lol" ] (split_on_slash "/coucou/lol")
|
||||
let () = assert_eq [ "a"; "b"; "c" ] (split_on_slash "/a/b//c/")
|
||||
let () = assert_eq [ "a"; "b" ] (split_on_slash "//a/b/")
|
||||
let () = assert_eq [ "a" ] (split_on_slash "/a//")
|
||||
let () = assert_eq [] (split_on_slash "/")
|
||||
let () = assert_eq [] (split_on_slash "//")
|
||||
let () = assert_eq [ "a"; "b" ] (U.split_on_slash "/a/b")
|
||||
let () = assert_eq [ "coucou"; "lol" ] (U.split_on_slash "/coucou/lol")
|
||||
let () = assert_eq [ "a"; "b"; "c" ] (U.split_on_slash "/a/b//c/")
|
||||
let () = assert_eq [ "a"; "b" ] (U.split_on_slash "//a/b/")
|
||||
let () = assert_eq [ "a" ] (U.split_on_slash "/a//")
|
||||
let () = assert_eq [] (U.split_on_slash "/")
|
||||
let () = assert_eq [] (U.split_on_slash "//")
|
||||
|
||||
let () =
|
||||
assert_eq ~cmp:eq_sorted (Ok [ "a", "b"; "c", "d" ]) (parse_query "a=b&c=d")
|
||||
assert_eq ~cmp:eq_sorted (Ok [ "a", "b"; "c", "d" ]) (U.parse_query "a=b&c=d")
|
||||
|
||||
let () =
|
||||
add_qcheck
|
||||
|
|
@ -43,9 +44,9 @@ let () =
|
|||
let s =
|
||||
String.concat "&"
|
||||
(List.map
|
||||
(fun (x, y) -> percent_encode x ^ "=" ^ percent_encode y)
|
||||
(fun (x, y) -> U.percent_encode x ^ "=" ^ U.percent_encode y)
|
||||
l)
|
||||
in
|
||||
eq_sorted (Ok l) (parse_query s))
|
||||
eq_sorted (Ok l) (U.parse_query s))
|
||||
|
||||
let () = run_qcheck_and_exit ()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue