Skip to content

Commit da0caeb

Browse files
committed
karm-http: Distinguish between the client and the transport.
1 parent 29b29e2 commit da0caeb

File tree

9 files changed

+218
-198
lines changed

9 files changed

+218
-198
lines changed

src/apps/vaev-browser/main/main.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ Async::Task<> entryPointAsync(Sys::Context& ctx) {
1313
? Mime::parseUrlOrPath(args[0], co_try$(Sys::pwd()))
1414
: "about:start"_url;
1515
Gc::Heap heap;
16-
auto client = Http::fallbackClient({
17-
Http::simpleClient(),
18-
Http::localClient(),
19-
});
16+
auto client = Http::defaultClient();
17+
client->userAgent = "Vaev-Browser/" stringify$(__ck_version_value) ""s;
2018

2119
auto dom = co_await Vaev::Driver::fetchDocumentAsync(heap, *client, url);
2220

src/clis/http-get/main.cpp

+1-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ Async::Task<> entryPointAsync(Sys::Context& ctx) {
1919
co_trya$(cmd.execAsync(ctx));
2020

2121
auto url = Mime::parseUrlOrPath(urlArg, co_try$(Sys::pwd()));
22-
auto client = Http::fallbackClient({
23-
Http::simpleClient(),
24-
Http::localClient(),
25-
});
26-
auto resp = co_trya$(client->getAsync(url));
22+
auto resp = co_trya$(Http::getAsync(url));
2723
if (not resp->body)
2824
co_return Error::invalidData("no body in response");
2925

src/clis/http-head/main.cpp

+1-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ Async::Task<> entryPointAsync(Sys::Context& ctx) {
1919
co_trya$(cmd.execAsync(ctx));
2020

2121
auto url = Mime::parseUrlOrPath(urlArg, co_try$(Sys::pwd()));
22-
auto client = Http::fallbackClient({
23-
Http::simpleClient(),
24-
Http::localClient(),
25-
});
26-
auto header = co_trya$(client->headAsync(url))->header;
22+
auto header = co_trya$(Http::headAsync(url))->header;
2723
co_try$(header.unparse(Sys::out()));
2824

2925
co_return Ok();

src/libs/karm-http/client.cpp

+25-164
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@ module;
44
#include <karm-base/rc.h>
55
#include <karm-logger/logger.h>
66
#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>
117

128
export module Karm.Http:client;
139

14-
import Karm.Aio;
15-
import :request;
16-
import :response;
10+
import :transport;
1711

1812
namespace Karm::Http {
1913

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;
2217

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+
}
2427

2528
Async::Task<Rc<Response>> getAsync(Mime::Url url) {
2629
auto req = makeRc<Request>();
@@ -54,173 +57,31 @@ export struct Client {
5457
}
5558
};
5659

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 ------------------------------------------------------------
20361

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+
);
20669
}
20770

208-
// MARK: Clientless ------------------------------------------------------------
209-
21071
export Async::Task<Rc<Response>> getAsync(Mime::Url url) {
211-
return simpleClient()->getAsync(url);
72+
return defaultClient()->getAsync(url);
21273
}
21374

21475
export Async::Task<Rc<Response>> headAsync(Mime::Url url) {
215-
return simpleClient()->headAsync(url);
76+
return defaultClient()->headAsync(url);
21677
}
21778

21879
export Async::Task<Rc<Response>> postAsync(Mime::Url url, Rc<Body> body) {
219-
return simpleClient()->postAsync(url, body);
80+
return defaultClient()->postAsync(url, body);
22081
}
22182

22283
export Async::Task<Rc<Response>> doAsync(Rc<Request> request) {
223-
return simpleClient()->doAsync(request);
84+
return defaultClient()->doAsync(request);
22485
}
22586

22687
} // namespace Karm::Http

src/libs/karm-http/mod.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export import :method;
88
export import :request;
99
export import :response;
1010
export import :server;
11+
export import :transport;

0 commit comments

Comments
 (0)