@@ -4,23 +4,26 @@ module;
4
4
#include < karm-base/rc.h>
5
5
#include < karm-logger/logger.h>
6
6
#include < karm-mime/url.h>
7
- #include < karm-sys/chan.h>
8
- #include < karm-sys/file.h>
9
- #include < karm-sys/lookup.h>
10
- #include < karm-sys/socket.h>
11
7
12
8
export module Karm.Http:client;
13
9
14
- import Karm.Aio;
15
- import :request;
16
- import :response;
10
+ import :transport;
17
11
18
12
namespace Karm ::Http {
19
13
20
- export struct Client {
21
- virtual ~Client () = default ;
14
+ export struct Client : public Transport {
15
+ String userAgent = " Karm-Http/" stringify$(__ck_version_value) " " s;
16
+ Rc<Transport> transport;
22
17
23
- virtual Async::Task<Rc<Response>> doAsync (Rc<Request> request) = 0;
18
+ Client (Rc<Transport> transport)
19
+ : transport(std::move(transport)) {}
20
+
21
+ Async::Task<Rc<Response>> doAsync (Rc<Request> request) override {
22
+ request->header .add (" User-Agent" , userAgent);
23
+ auto resp = co_trya$(transport->doAsync (request));
24
+ logInfo (" \" {} {}\" {} {}" , request->method , request->url , toUnderlyingType (resp->code ), resp->code );
25
+ co_return Ok (resp);
26
+ }
24
27
25
28
Async::Task<Rc<Response>> getAsync (Mime::Url url) {
26
29
auto req = makeRc<Request>();
@@ -54,173 +57,31 @@ export struct Client {
54
57
}
55
58
};
56
59
57
- // MARK: Simple Client ---------------------------------------------------------
58
-
59
- static constexpr usize BUF_SIZE = 4096 ;
60
-
61
- struct ContentBody : public Body {
62
- Buf<Byte > _resumes;
63
- usize _resumesPos = 0 ;
64
- Sys::TcpConnection _conn;
65
- usize _contentLength;
66
-
67
- ContentBody (Bytes resumes, Sys::TcpConnection conn, usize contentLength)
68
- : _resumes(resumes),
69
- _conn (std::move(conn)),
70
- _contentLength(contentLength - resumes.len()) {
71
- }
72
-
73
- Async::Task<usize> readAsync (MutBytes buf) override {
74
- if (_resumesPos < _resumes.len ()) {
75
- usize n = min (buf.len (), _resumes.len () - _resumesPos);
76
- copy (sub (_resumes, _resumesPos, n), buf);
77
- _resumesPos += n;
78
- co_return n;
79
- }
80
-
81
- if (_contentLength == 0 ) {
82
- co_return 0 ;
83
- }
84
-
85
- usize n = min (buf.len (), _contentLength);
86
- n = co_trya$(_conn.readAsync (mutSub (buf, 0 , n)));
87
- _contentLength -= n;
88
- co_return n;
89
- }
90
- };
91
-
92
- struct ChunkedBody : public Body {
93
- Buf<Byte > _buf;
94
- Sys::TcpConnection _conn;
95
-
96
- ChunkedBody (Bytes resumes, Sys::TcpConnection conn)
97
- : _buf(resumes), _conn(std::move(conn)) {}
98
- };
99
-
100
- struct SimpleClient : public Client {
101
- Async::Task<> _sendRequest (Request& request, Sys::TcpConnection& conn) {
102
- Io::StringWriter req;
103
- co_try$(request.unparse (req));
104
- co_trya$(conn.writeAsync (req.bytes ()));
105
-
106
- if (auto body = request.body )
107
- co_trya$(Aio::copyAsync (**body, conn));
108
-
109
- co_return Ok ();
110
- }
111
-
112
- Async::Task<Rc<Response>> _recvResponse (Sys::TcpConnection& conn) {
113
- Array<u8, BUF_SIZE> buf = {};
114
- Io::BufReader reader = sub (buf, 0 , co_trya$(conn.readAsync (buf)));
115
- auto response = co_try$(Response::read (reader));
116
-
117
- if (auto contentLength = response.header .contentLength ()) {
118
- response.body = makeRc<ContentBody>(reader.bytes (), std::move (conn), contentLength.unwrap ());
119
- } else if (auto transferEncoding = response.header .tryGet (" Transfer-Encoding" s)) {
120
- logWarn (" Transfer-Encoding: {} not supported" , transferEncoding);
121
- } else {
122
- // NOTE: When there is no content length, and no transfer encoding,
123
- // we read until the server closes the socket.
124
- response.body = makeRc<ContentBody>(reader.bytes (), std::move (conn), Limits<usize>::MAX);
125
- }
126
-
127
- co_return Ok (makeRc<Response>(std::move (response)));
128
- }
129
-
130
- Async::Task<Rc<Response>> doAsync (Rc<Request> request) override {
131
- auto & url = request->url ;
132
- if (url.scheme != " http" )
133
- co_return Error::invalidInput (" unsupported scheme" );
134
-
135
- logDebug (" {} {}" , request->method , url);
136
-
137
- auto ips = co_trya$(Sys::lookupAsync (url.host ));
138
- auto port = url.port .unwrapOr (80 );
139
- Sys::SocketAddr addr{first (ips), (u16)port};
140
- auto conn = co_try$(Sys::TcpConnection::connect (addr));
141
- co_trya$(_sendRequest (*request, conn));
142
- co_return co_trya$(_recvResponse (conn));
143
- }
144
- };
145
-
146
- export Rc<Client> simpleClient () {
147
- return makeRc<SimpleClient>();
148
- }
149
-
150
- // MARK: Local -----------------------------------------------------------------
151
-
152
- struct LocalClient : public Client {
153
- Res<Rc<Body>> _load (Mime::Url url) {
154
- if (try $(Sys::isFile (url)))
155
- return Ok (Body::from (try $(Sys::File::open (url))));
156
-
157
- auto dir = try $(Sys::Dir::open (url));
158
- Io::StringWriter sw;
159
- Io::Emit e{sw};
160
- e (" <html><body><h1>Index of {}</h1><ul>" , url.path );
161
- for (auto & diren : dir.entries ()) {
162
- if (diren.hidden ())
163
- continue ;
164
- e (" <li><a href=\" {}\" >{}</a></li>" , url.join (diren.name ), diren.name );
165
- }
166
- e (" </ul></body></html>" );
167
- return Ok (Body::from (sw.take ()));
168
- }
169
-
170
- Async::Task<Rc<Response>> doAsync (Rc<Request> request) override {
171
- auto response = makeRc<Response>();
172
-
173
- response->code = Code::OK;
174
-
175
- if (request->method == Method::GET)
176
- response->body = co_try$(_load (request->url ));
177
-
178
- co_return Ok (response);
179
- }
180
- };
181
-
182
- export Rc<Client> localClient () {
183
- return makeRc<LocalClient>();
184
- }
185
-
186
- // MARK: Fallback --------------------------------------------------------------
187
-
188
- struct FallbackClient : public Client {
189
- Vec<Rc<Client>> _clients;
190
-
191
- FallbackClient (Vec<Rc<Client>> clients) : _clients(std::move(clients)) {}
192
-
193
- Async::Task<Rc<Response>> doAsync (Rc<Request> request) override {
194
- for (auto & client : _clients) {
195
- auto res = co_await client->doAsync (request);
196
- if (res)
197
- co_return res.unwrap ();
198
- }
199
-
200
- co_return Error::notFound (" no client could handle the request" );
201
- }
202
- };
60
+ // MARK: Clientless ------------------------------------------------------------
203
61
204
- export Rc<Client> fallbackClient (Vec<Rc<Client>> clients) {
205
- return makeRc<FallbackClient>(std::move (clients));
62
+ export Rc<Client> defaultClient () {
63
+ return makeRc<Client>(
64
+ multiplexTransport ({
65
+ httpTransport (),
66
+ localTransport (),
67
+ })
68
+ );
206
69
}
207
70
208
- // MARK: Clientless ------------------------------------------------------------
209
-
210
71
export Async::Task<Rc<Response>> getAsync (Mime::Url url) {
211
- return simpleClient ()->getAsync (url);
72
+ return defaultClient ()->getAsync (url);
212
73
}
213
74
214
75
export Async::Task<Rc<Response>> headAsync (Mime::Url url) {
215
- return simpleClient ()->headAsync (url);
76
+ return defaultClient ()->headAsync (url);
216
77
}
217
78
218
79
export Async::Task<Rc<Response>> postAsync (Mime::Url url, Rc<Body> body) {
219
- return simpleClient ()->postAsync (url, body);
80
+ return defaultClient ()->postAsync (url, body);
220
81
}
221
82
222
83
export Async::Task<Rc<Response>> doAsync (Rc<Request> request) {
223
- return simpleClient ()->doAsync (request);
84
+ return defaultClient ()->doAsync (request);
224
85
}
225
86
226
87
} // namespace Karm::Http
0 commit comments