1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
module Httpaf_lwt_unix = Dream_httpaf__lwt_unix.Httpaf_lwt_unix
module H2_lwt_unix = Dream_h2_lwt_unix.H2_lwt_unix
module Message = Dream_pure.Message
type response = Message.response
type request = Message.request
type connection =
| Cleartext of Httpaf_lwt_unix.Client.t
| SSL of Httpaf_lwt_unix.Client.SSL.t
| H2 of H2_lwt_unix.Client.SSL.t
| WebSocket of Lwt_unix.file_descr
type 'a promise = 'a Dream_pure.Message.promise
let resolve target =
let uri = Uri.of_string target in
let host = Uri.host uri |> Option.get
and port =
match Uri.port uri with
| Some port -> port
| None ->
match Uri.scheme uri with
| Some ("https" | "wss") -> 443
| _ -> 80
in
let%lwt addresses =
Lwt_unix.getaddrinfo host (string_of_int port) [Unix.(AI_FAMILY PF_INET)] in
let address = (List.hd addresses).Unix.ai_addr in
Lwt.return address
let http1_cleartext_tcp target =
let socket = Lwt_unix.(socket PF_INET SOCK_STREAM 0) in
let%lwt address = resolve target in
let%lwt () = Lwt_unix.connect socket address in
let%lwt connection = Httpaf_lwt_unix.Client.create_connection socket in
Lwt.return (Cleartext connection)
let alpn_https_tcp ?(protocols = ["h2"; "http/1.1"]) target =
let context = Ssl.(create_context TLSv1_2 Client_context) in
Ssl.set_context_alpn_protos context protocols;
let socket = Lwt_unix.(socket PF_INET SOCK_STREAM 0) in
let%lwt address = resolve target in
let%lwt () = Lwt_unix.connect socket address in
let%lwt ssl_socket = Lwt_ssl.ssl_connect socket context in
let underlying = Lwt_ssl.ssl_socket ssl_socket |> Option.get in
begin match Ssl.get_negotiated_alpn_protocol underlying with
| Some "h2" ->
let%lwt connection =
H2_lwt_unix.Client.SSL.create_connection
~error_handler:ignore
ssl_socket
in
Lwt.return (H2 connection)
| _ ->
let%lwt connection =
Httpaf_lwt_unix.Client.SSL.create_connection ssl_socket in
Lwt.return (SSL connection)
end
let ws_cleartext_tcp target =
let socket = Lwt_unix.(socket PF_INET SOCK_STREAM 0) in
let%lwt address = resolve target in
let%lwt () = Lwt_unix.connect socket address in
Lwt.return (WebSocket socket)
let choose request =
let scheme =
Message.target request
|> Uri.of_string
|> Uri.scheme
|> Option.get
in
match scheme with
| "https" -> alpn_https_tcp ?protocols:None
| "ws" -> ws_cleartext_tcp
| _ -> http1_cleartext_tcp
let send' connection request =
match connection with
| Cleartext connection -> Http1.http connection request
| SSL connection -> Http1.https connection request
| H2 connection -> Http2.https connection request
| WebSocket connection -> Websocket.ws connection request
let no_pool ?transport request =
let connect =
match transport with
| None -> choose request
| Some `HTTP1 -> http1_cleartext_tcp
| Some `HTTPS -> alpn_https_tcp ~protocols:["http/1.1"]
| Some `HTTP2 -> alpn_https_tcp ~protocols:["h2"]
| Some `WS -> ws_cleartext_tcp
in
let%lwt connection = connect (Message.target request) in
send' connection request