-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* http tunnels with CONNECT * response state * refactor and add tests * client: not just 200, 2xx
- Loading branch information
1 parent
c728225
commit 8ceb388
Showing
9 changed files
with
329 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
module Arg = Stdlib.Arg | ||
open Httpun | ||
|
||
let http_handler ~on_eof response response_body = | ||
match response with | ||
| { Response.status = `OK; _ } as response -> | ||
Format.eprintf "response: %a@." Response.pp_hum response; | ||
let rec on_read bs ~off ~len = | ||
Bigstringaf.substring ~off ~len bs |> print_string; | ||
flush stdout; | ||
Body.Reader.schedule_read response_body ~on_read ~on_eof | ||
in | ||
Body.Reader.schedule_read response_body ~on_read ~on_eof | ||
| response -> | ||
Format.fprintf Format.err_formatter "%a\n%!" Response.pp_hum response; | ||
Stdlib.exit 124 | ||
|
||
let proxy_handler _env ~sw ~headers flow ~on_eof response _response_body = | ||
Format.eprintf "CONNECT response: %a@." Response.pp_hum response; | ||
match response with | ||
| { Response.status = `OK; _ } as response -> | ||
(* This means we can now communicate via any protocol on the socket since | ||
the server approved the tunnel. | ||
We'll be boring and use HTTP/1.1 again. *) | ||
let connection = Httpun_eio.Client.create_connection ~sw flow in | ||
let exit_cond = Eio.Condition.create () in | ||
Eio.Fiber.fork ~sw (fun () -> | ||
let response_handler = | ||
http_handler ~on_eof:(fun () -> | ||
Stdlib.Format.eprintf "http eof@."; | ||
Eio.Condition.broadcast exit_cond; | ||
on_eof ()) | ||
in | ||
let request_body = | ||
Httpun_eio.Client.request | ||
~flush_headers_immediately:true | ||
~error_handler:Httpun_examples.Client.error_handler | ||
~response_handler | ||
connection | ||
(Request.create ~headers `GET "/") | ||
in | ||
Body.Writer.close request_body); | ||
Eio.Condition.await_no_mutex exit_cond; | ||
Httpun_eio.Client.shutdown connection |> Eio.Promise.await | ||
| _response -> Stdlib.exit 124 | ||
|
||
let main port proxy_host = | ||
let real_host = "example.com:80" in | ||
Eio_main.run (fun _env -> | ||
Eio.Switch.run (fun sw -> | ||
let fd = Unix.socket ~cloexec:true Unix.PF_INET Unix.SOCK_STREAM 0 in | ||
let addrs = | ||
Eio_unix.run_in_systhread (fun () -> | ||
Unix.getaddrinfo | ||
proxy_host | ||
(Int.to_string port) | ||
[ Unix.(AI_FAMILY PF_INET) ]) | ||
in | ||
Eio_unix.run_in_systhread (fun () -> | ||
Unix.connect fd (List.hd addrs).ai_addr); | ||
let socket = Eio_unix.Net.import_socket_stream ~sw ~close_unix:true fd in | ||
let headers = Headers.of_list [ "host", real_host ] in | ||
let connection = Httpun_eio.Client.create_connection ~sw socket in | ||
|
||
let exit_cond = Eio.Condition.create () in | ||
Eio.Fiber.fork ~sw (fun ()-> | ||
let response_handler = | ||
fun response response_body -> | ||
Eio.Fiber.fork ~sw @@ fun () -> | ||
proxy_handler _env ~sw socket ~headers ~on_eof:(fun () -> | ||
Stdlib.Format.eprintf "(connect) eof@."; | ||
Eio.Condition.broadcast exit_cond) | ||
response | ||
response_body | ||
in | ||
let request_body = | ||
Httpun_eio.Client.request | ||
~flush_headers_immediately:true | ||
~error_handler:Httpun_examples.Client.error_handler | ||
~response_handler | ||
connection | ||
(Request.create ~headers `CONNECT real_host) | ||
in | ||
Body.Writer.close request_body; | ||
Eio.Condition.await_no_mutex exit_cond; | ||
|
||
Httpun_eio.Client.shutdown connection |> Eio.Promise.await))) | ||
|
||
let () = | ||
let host = ref None in | ||
let port = ref 80 in | ||
Arg.parse | ||
[ "-p", Set_int port, " Port number (80 by default)" ] | ||
(fun host_argument -> host := Some host_argument) | ||
"lwt_get.exe [-p N] HOST"; | ||
let host = | ||
match !host with | ||
| None -> failwith "No hostname provided" | ||
| Some host -> host | ||
in | ||
main !port host |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
(* curl -v -p -x http://localhost:8080 http://example.com *) | ||
open Base | ||
module Arg = Stdlib.Arg | ||
open Httpun_eio | ||
open Httpun | ||
|
||
let error_handler (_ : Eio.Net.Sockaddr.stream) = | ||
Httpun_examples.Server.error_handler | ||
|
||
let request_handler | ||
env | ||
~sw | ||
~u | ||
flow | ||
(_ : Eio.Net.Sockaddr.stream) | ||
{ Gluten.reqd; _ } | ||
= | ||
match Reqd.request reqd with | ||
| { Request.meth = `CONNECT; headers; _ } -> | ||
Stdlib.Format.eprintf "x: %a@." Request.pp_hum (Reqd.request reqd); | ||
let host, port = | ||
let host_and_port = Headers.get_exn headers "host" in | ||
let[@ocaml.warning "-8"] [ host; port ] = | ||
String.split_on_chars ~on:[ ':' ] host_and_port | ||
in | ||
host, port | ||
in | ||
let () = | ||
(* todo: try/with *) | ||
let p, u' = Eio.Promise.create () in | ||
Eio.Fiber.fork ~sw (fun () -> | ||
Eio.Net.with_tcp_connect | ||
(Eio.Stdenv.net env) | ||
~host | ||
~service:port | ||
(fun upstream -> | ||
Eio.Promise.resolve u' (); | ||
Stdlib.Format.eprintf | ||
"connected to upstream %s (port %s)@." | ||
host | ||
port; | ||
Eio.Fiber.both | ||
(fun () -> Eio.Flow.copy flow upstream) | ||
(fun () -> Eio.Flow.copy upstream flow); | ||
Eio.Promise.resolve_ok u ())); | ||
Eio.Promise.await p | ||
in | ||
Reqd.respond_with_string reqd (Response.create `OK) "" | ||
| _ -> | ||
let headers = Headers.of_list [ "connection", "close" ] in | ||
Reqd.respond_with_string | ||
reqd | ||
(Response.create ~headers `Method_not_allowed) | ||
"" | ||
|
||
let log_connection_error ex = | ||
Eio.traceln "Uncaught exception handling client: %a" Fmt.exn ex | ||
|
||
let main port = | ||
Eio_main.run (fun env -> | ||
let listen_address = `Tcp (Eio.Net.Ipaddr.V4.loopback, port) in | ||
let network = Eio.Stdenv.net env in | ||
let handler ~u = | ||
fun ~sw client_addr socket -> | ||
let request_handler = request_handler env ~sw ~u socket in | ||
Server.create_connection_handler | ||
~request_handler | ||
~error_handler | ||
~sw | ||
client_addr | ||
socket | ||
in | ||
Eio.Switch.run (fun sw -> | ||
let socket = | ||
Eio.Net.listen | ||
~reuse_addr:true | ||
~reuse_port:false | ||
~backlog:5 | ||
~sw | ||
network | ||
listen_address | ||
in | ||
Stdio.printf "Listening on port %i and echoing POST requests.\n" port; | ||
Stdio.printf "To send a POST request, try one of the following\n\n"; | ||
Stdio.printf | ||
" echo \"Testing echo POST\" | dune exec examples/async/async_post.exe\n"; | ||
Stdio.printf | ||
" echo \"Testing echo POST\" | dune exec examples/lwt/lwt_post.exe\n"; | ||
Stdio.printf | ||
" echo \"Testing echo POST\" | curl -XPOST --data @- \ | ||
http://localhost:%d\n\n\ | ||
%!" | ||
port; | ||
let domain_mgr = Eio.Stdenv.domain_mgr env in | ||
let p, _ = Eio.Promise.create () in | ||
for _i = 1 to Stdlib.Domain.recommended_domain_count () do | ||
Eio.Fiber.fork_daemon ~sw (fun () -> | ||
Eio.Domain_manager.run domain_mgr (fun () -> | ||
Eio.Switch.run (fun sw -> | ||
while true do | ||
Eio.Net.accept_fork | ||
socket | ||
~sw | ||
~on_error:log_connection_error | ||
(fun client_sock client_addr -> | ||
let p, u = Eio.Promise.create () in | ||
handler ~sw ~u client_addr client_sock; | ||
Eio.Promise.await_exn p) | ||
done; | ||
`Stop_daemon))) | ||
done; | ||
Eio.Promise.await p)) | ||
|
||
let () = | ||
let port = ref 8080 in | ||
Arg.parse | ||
[ "-p", Arg.Set_int port, " Listening port number (8080 by default)" ] | ||
ignore | ||
"Echoes POST requests. Runs forever."; | ||
main !port |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.