0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-01 19:22:53 +01:00

ircd::http: Update HTTP send-sides to write to stream_buffer.

This commit is contained in:
Jason Volk 2017-12-22 17:40:44 -07:00
parent 8178d96f64
commit 51ebd9c346
5 changed files with 251 additions and 179 deletions

View file

@ -254,20 +254,23 @@ struct ircd::http::request
struct head; struct head;
struct content; struct content;
using write_closure = std::function<void (const ilist<const_buffer> &)>;
using proffer = std::function<void (const head &)>; using proffer = std::function<void (const head &)>;
using header = line::header; using header = line::header;
request(const string_view &host = {}, // send
request(stream_buffer &,
const string_view &host,
const string_view &method = "GET", const string_view &method = "GET",
const string_view &path = "/", const string_view &path = "/",
const string_view &query = {}, const string_view &query = {},
const string_view &content = {}, const size_t &content_length = 0,
const write_closure & = nullptr, const string_view &content_type = {},
const vector_view<const header> & = {}); const vector_view<const header> & = {},
const bool &termination = true);
// recv
request(parse::capstan &, request(parse::capstan &,
content *const & = nullptr, content *const &,
const proffer & = nullptr, const proffer & = nullptr,
const headers::closure & = {}); const headers::closure & = {});
}; };
@ -315,13 +318,18 @@ struct ircd::http::response
using proffer = std::function<void (const head &)>; using proffer = std::function<void (const head &)>;
using header = line::header; using header = line::header;
response(const code &, // send
const string_view &content, response(stream_buffer &,
const write_closure &, const code & = code::OK,
const vector_view<const header> & = {}); const size_t &content_length = 0,
const string_view &content_type = {},
const string_view &cache_control = {},
const vector_view<const header> & = {},
const bool &termination = true);
// recv
response(parse::capstan &, response(parse::capstan &,
content *const & = nullptr, content *const &,
const proffer & = nullptr, const proffer & = nullptr,
const headers::closure & = {}); const headers::closure & = {});

View file

@ -128,7 +128,7 @@ struct ircd::resource::request::object
struct ircd::resource::response struct ircd::resource::response
{ {
response(client &, const string_view &str, const string_view &ct = "text/plain; charset=utf8", const http::code & = http::OK); response(client &, const string_view &str, const string_view &content_type, const http::code & = http::OK);
response(client &, const json::object &str, const http::code & = http::OK); response(client &, const json::object &str, const http::code & = http::OK);
response(client &, const json::array &str, const http::code & = http::OK); response(client &, const json::array &str, const http::code & = http::OK);
response(client &, const json::members & = {}, const http::code & = http::OK); response(client &, const json::members & = {}, const http::code & = http::OK);

View file

@ -48,20 +48,25 @@ namespace ircd::http
template<class it, class top = unused_type> struct grammar; template<class it, class top = unused_type> struct grammar;
struct parser extern const parser; struct parser extern const parser;
size_t printed_size(const vector_view<const line::header> &headers);
size_t print(char *const &buf, const size_t &max, const vector_view<const line::header> &headers);
extern const std::unordered_map<ircd::http::code, ircd::string_view> reason; extern const std::unordered_map<ircd::http::code, ircd::string_view> reason;
size_t serialized(const vector_view<const line::header> &headers);
void writeline(stream_buffer &, const stream_buffer::closure &);
void writeline(stream_buffer &);
} }
BOOST_FUSION_ADAPT_STRUCT BOOST_FUSION_ADAPT_STRUCT
( (
ircd::http::line::request, ircd::http::query,
( decltype(ircd::http::line::request::method), method ) ( decltype(ircd::http::query::first), first )
( decltype(ircd::http::line::request::path), path ) ( decltype(ircd::http::query::second), second )
( decltype(ircd::http::line::request::query), query ) )
( decltype(ircd::http::line::request::fragment), fragment )
( decltype(ircd::http::line::request::version), version ) BOOST_FUSION_ADAPT_STRUCT
(
ircd::http::line::header,
( decltype(ircd::http::line::header::first), first )
( decltype(ircd::http::line::header::second), second )
) )
BOOST_FUSION_ADAPT_STRUCT BOOST_FUSION_ADAPT_STRUCT
@ -74,16 +79,12 @@ BOOST_FUSION_ADAPT_STRUCT
BOOST_FUSION_ADAPT_STRUCT BOOST_FUSION_ADAPT_STRUCT
( (
ircd::http::line::header, ircd::http::line::request,
( decltype(ircd::http::line::header::first), first ) ( decltype(ircd::http::line::request::method), method )
( decltype(ircd::http::line::header::second), second ) ( decltype(ircd::http::line::request::path), path )
) ( decltype(ircd::http::line::request::query), query )
( decltype(ircd::http::line::request::fragment), fragment )
BOOST_FUSION_ADAPT_STRUCT ( decltype(ircd::http::line::request::version), version )
(
ircd::http::query,
( decltype(ircd::http::query::first), first )
( decltype(ircd::http::query::second), second )
) )
const decltype(ircd::http::reason) ircd::http::reason const decltype(ircd::http::reason) ircd::http::reason
@ -236,35 +237,6 @@ struct ircd::http::parser
} }
const ircd::http::parser; const ircd::http::parser;
size_t
ircd::http::print(char *const &buf,
const size_t &max,
const vector_view<const line::header> &headers)
{
size_t ret(0);
for(auto it(std::begin(headers)); it != std::end(headers); ++it)
{
ret += fmt::snprintf(buf + ret, max - ret, "%s: %s",
it->first,
it->second);
ret += snprintf(buf + ret, max - ret, "\r\n");
}
return ret;
}
size_t
ircd::http::printed_size(const vector_view<const line::header> &headers)
{
return std::accumulate(std::begin(headers), std::end(headers), size_t(1), []
(auto &ret, const auto &pair)
{
// key : SP value CRLF
return ret += pair.first.size() + 1 + 1 + pair.second.size() + 2;
});
}
ircd::http::request::request(parse::capstan &pc, ircd::http::request::request(parse::capstan &pc,
content *const &c, content *const &c,
const proffer &proffer, const proffer &proffer,
@ -286,64 +258,77 @@ ircd::http::request::request(parse::capstan &pc,
*c = content{pc, h}; *c = content{pc, h};
} }
ircd::http::request::request(const string_view &host, /// Compose a request. This prints an HTTP head into the buffer. No real IO is
/// done here. After composing into the buffer, the user can then drive the
/// socket by sending the header and the content as specified.
///
/// If termination is false, no extra CRLF is printed to the buffer allowing
/// additional headers not specified to be appended later.
ircd::http::request::request(stream_buffer &out,
const string_view &host,
const string_view &method, const string_view &method,
const string_view &path, const string_view &path,
const string_view &query, const string_view &query,
const string_view &content, const size_t &content_length,
const write_closure &closure, const string_view &content_type,
const vector_view<const header> &headers) const vector_view<const header> &headers,
const bool &termination)
{
writeline(out, [&method, &path, &query](const mutable_buffer &out) -> size_t
{ {
assert(!method.empty());
assert(!path.empty()); assert(!path.empty());
assert(!method.empty());
static const auto &version return fmt::sprintf
{ {
"HTTP/1.1" out, "%s /%s%s%s HTTP/1.1",
}; method,
path,
char request_line[2048]; const auto request_line_len
{
snprintf(request_line, sizeof(request_line), "%s /%s%s%s %s\r\n",
method.c_str(),
path.c_str(),
query.empty()? "" : "?", query.empty()? "" : "?",
query.empty()? "" : query.c_str(), query.empty()? "" : query
version)
}; };
});
char host_line[128] {"Host: "}; const auto host_line_len writeline(out, [&host](const mutable_buffer &out) -> size_t
{ {
6 + snprintf(host_line + 6, std::min(sizeof(host_line) - 6, host.size() + 3), "%s\r\n", assert(!host.empty());
host.c_str()) return fmt::sprintf
};
char content_len[64]; const auto content_len_len
{ {
snprintf(content_len, sizeof(content_len), "Content-Length: %zu\r\n", out, "Host: %s", host
content.size())
}; };
});
char user_headers[printed_size(headers) + 2]; auto user_headers_len if(content_length)
writeline(out, [&content_type](const mutable_buffer &out) -> size_t
{ {
print(user_headers, sizeof(user_headers), headers) return fmt::sprintf
};
assert(user_headers_len + 3 == sizeof(user_headers));
user_headers[user_headers_len++] = '\r';
user_headers[user_headers_len++] = '\n';
//user_headers[user_headers_len++] = '\0';
const ilist<const_buffer> vector
{ {
{ request_line, size_t(request_line_len) }, out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8"
{ host_line, size_t(host_line_len) },
{ content_len, size_t(content_len_len) },
{ user_headers, size_t(user_headers_len) },
{ content.data(), content.size() },
}; };
});
closure(vector); writeline(out, [&content_length](const mutable_buffer &out) -> size_t
{
return fmt::sprintf
{
out, "Content-Length: %zu", content_length
};
});
for(const auto &header : headers)
{
assert(!header.first.empty());
assert(!header.second.empty());
writeline(out, [&header](const mutable_buffer &out) -> size_t
{
return fmt::sprintf
{
out, "%s: %s", header.first, header.second
};
});
}
if(termination)
writeline(out);
} }
ircd::http::request::head::head(parse::capstan &pc, ircd::http::request::head::head(parse::capstan &pc,
@ -394,84 +379,92 @@ ircd::http::response::response(parse::capstan &pc,
*c = content{pc, h}; *c = content{pc, h};
} }
ircd::http::response::response(const code &code, ircd::http::response::response(stream_buffer &out,
const string_view &content, const code &code,
const write_closure &closure, const size_t &content_length,
const vector_view<const header> &headers) const string_view &content_type,
const string_view &cache_control,
const vector_view<const header> &headers,
const bool &termination)
{ {
char status_line[128]; const auto status_line_len writeline(out, [&code](const mutable_buffer &out) -> size_t
{ {
snprintf(status_line, sizeof(status_line), "HTTP/1.1 %u %s\r\n", return fmt::sprintf
uint(code), {
status(code).data()) out, "HTTP/1.1 %u %s", uint(code), status(code)
}; };
});
char server_line[128]; const auto server_line_len if(code >= 200 && code < 300)
writeline(out, [&code](const mutable_buffer &out) -> size_t
{ {
code >= 200 && code < 300? return fmt::sprintf
snprintf(server_line, sizeof(server_line), "Server: %s (IRCd %s)\r\n", {
BRANDING_NAME, out, "Server: %s (IRCd %s)", BRANDING_NAME, BRANDING_VERSION
BRANDING_VERSION):
0
}; };
});
char date_line[128], date_buf[96]; const auto date_line_len if(code < 400)
writeline(out, [](const mutable_buffer &out) -> size_t
{ {
code < 400? char date_buf[96];
snprintf(data(date_line), size(date_line), "Date: %s\r\n", return fmt::sprintf
timef(date_buf, ircd::localtime).c_str()): {
0 out, "Date: %s", timef(date_buf, ircd::localtime)
}; };
});
char cache_line[64]; const auto cache_line_len if((code >= 200 && code < 300) || (code >= 403 && code <= 405) || (code >= 300 && code < 400))
writeline(out, [&cache_control](const mutable_buffer &out) -> size_t
{ {
//TODO: real cache control subsystem return fmt::sprintf
(code >= 200 && code < 300) || (code >= 403 && code <= 405) || (code >= 300 && code < 400)? {
snprintf(cache_line, sizeof(cache_line), "Cache-Control: %s\r\n", out, "Cache-Control: %s", cache_control?: "no-cache"
"no-cache"):
0
}; };
});
const bool has_transfer_encoding const bool has_transfer_encoding
{ {
std::any_of(std::begin(headers), std::end(headers), [] std::any_of(std::begin(headers), std::end(headers), []
(const auto &header) (const auto &header)
{ {
return header == "Transfer-Encoding"; return iequals(header.first, "transfer-encoding"s);
}) })
}; };
char content_len[64]; const auto content_len_len if((content_length && code != NO_CONTENT) || has_transfer_encoding)
writeline(out, [&content_type](const mutable_buffer &out) -> size_t
{ {
code != NO_CONTENT && !has_transfer_encoding? return fmt::sprintf
snprintf(content_len, sizeof(content_len), "Content-Length: %zu\r\n",
content.size()):
0
};
const auto user_headers_bufsize
{ {
printed_size(headers) out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8"
}; };
});
char user_headers[user_headers_bufsize]; const auto user_headers_len if(code != NO_CONTENT && !has_transfer_encoding)
writeline(out, [&content_length](const mutable_buffer &out) -> size_t
{ {
print(user_headers, sizeof(user_headers), headers) return fmt::sprintf
};
const ilist<const_buffer> iov
{ {
{ status_line, size_t(status_line_len) }, out, "Content-Length: %zu", content_length
{ server_line, size_t(server_line_len) },
{ date_line, size_t(date_line_len) },
{ cache_line, size_t(cache_line_len) },
{ user_headers, size_t(user_headers_len) },
{ content_len, size_t(content_len_len) },
{ "\r\n", 2 },
{ content.data(), content.size() },
}; };
});
closure(iov); for(const auto &header : headers)
{
assert(!header.first.empty());
assert(!header.second.empty());
writeline(out, [&header](const mutable_buffer &out) -> size_t
{
return fmt::sprintf
{
out, "%s: %s", header.first, header.second
};
});
}
if(termination)
writeline(out);
} }
ircd::http::response::chunked::chunked(const code &code, ircd::http::response::chunked::chunked(const code &code,
@ -491,10 +484,14 @@ ircd::http::response::chunked::chunked(const code &code,
std::copy(begin(user_headers), end(user_headers), headers + 1); std::copy(begin(user_headers), end(user_headers), headers + 1);
//TODO: bitrot
assert(0);
/*
response response
{ {
code, {}, closure, { headers, headers + num_headers } code, {}, closure, { headers, headers + num_headers }
}; };
*/
} }
ircd::http::response::chunked::~chunked() ircd::http::response::chunked::~chunked()
@ -849,13 +846,53 @@ ircd::http::parser::content_length(const string_view &str)
return ret; return ret;
} }
/// Close over the user's closure to append a newline.
void
ircd::http::writeline(stream_buffer &write,
const stream_buffer::closure &closure)
{
// A new stream_buffer is implicit constructed out of the mutable_buffer
// otherwise presented to this closure as its write window.
write([&closure](stream_buffer write)
{
const auto newline{[](const mutable_buffer &out)
{
return copy(out, "\r\n"_sv);
}};
write(closure);
write(newline);
return write.consumed();
});
}
void
ircd::http::writeline(stream_buffer &write)
{
writeline(write, [](const mutable_buffer &out)
{
return 0;
});
}
size_t
ircd::http::serialized(const vector_view<const line::header> &headers)
{
return std::accumulate(std::begin(headers), std::end(headers), size_t{0}, []
(auto &ret, const auto &pair)
{
// key : SP value CRLF
return ret += pair.first.size() + 1 + 1 + pair.second.size() + 2;
});
}
ircd::http::error::error(const enum code &code, ircd::http::error::error(const enum code &code,
std::string content) std::string content)
:ircd::error{generate_skip} :ircd::error{generate_skip}
,code{code} ,code{code}
,content{std::move(content)} ,content{std::move(content)}
{ {
snprintf(buf, sizeof(buf), "%d %s", int(code), status(code).data()); snprintf(buf, sizeof(buf), "%d %s", int(code), status(code).c_str());
} }
ircd::http::code ircd::http::code

View file

@ -598,11 +598,10 @@ const
std::min(addl_headers.size(), size_t(64UL)) std::min(addl_headers.size(), size_t(64UL))
}; };
size_t headers{2 + addl_headers_size}; size_t headers{1};
http::line::header header[headers + 1] http::line::header header[headers + addl_headers_size]
{ {
{ "User-Agent", BRANDING_NAME " (IRCd " BRANDING_VERSION ")" }, { "User-Agent", BRANDING_NAME " (IRCd " BRANDING_VERSION ")" },
{ "Content-Type", "application/json" },
}; };
for(size_t i(0); i < addl_headers_size; ++i) for(size_t i(0); i < addl_headers_size; ++i)
@ -615,16 +614,32 @@ const
"Authorization", generate_authorization(x_matrix) "Authorization", generate_authorization(x_matrix)
}; };
static const auto content_type
{
"application/json; charset=utf-8"
};
const unique_buffer<mutable_buffer> head_buf{8192};
stream_buffer head{head_buf};
http::request http::request
{ {
head,
destination, destination,
method, method,
path, path,
query, query,
content, content.size(),
write_closure(server), content_type,
{ header, headers } { header, headers }
}; };
const ilist<const_buffer> vector
{
head.completed(),
content
};
write_closure(server)(vector);
} }
namespace ircd::m::name namespace ircd::m::name

View file

@ -496,7 +496,7 @@ ircd::resource::response::response(client &client,
} }
ircd::resource::response::response(client &client, ircd::resource::response::response(client &client,
const string_view &str, const string_view &content,
const string_view &content_type, const string_view &content_type,
const http::code &code) const http::code &code)
{ {
@ -507,20 +507,32 @@ ircd::resource::response::response(client &client,
char rtime[64]; const auto rtime_len char rtime[64]; const auto rtime_len
{ {
snprintf(rtime, sizeof(rtime), "%zdus", snprintf(rtime, sizeof(rtime), "%zdus", request_time)
request_time)
}; };
char head_buf[2048];
stream_buffer head{head_buf};
http::response http::response
{ {
code, str, write_closure(client), head,
code,
content.size(),
content_type,
string_view{}, // cache_control
{ {
{ "Content-Type", content_type },
{ "Access-Control-Allow-Origin", "*" }, //TODO: XXX { "Access-Control-Allow-Origin", "*" }, //TODO: XXX
{ "X-IRCd-Request-Timer", string_view{rtime, size_t(rtime_len)} } { "X-IRCd-Request-Timer", string_view{rtime, size_t(rtime_len)} }
} }
}; };
const ilist<const_buffer> vector
{
head.completed(),
content
};
write_closure(client)(vector);
log::debug("client[%s] HTTP %d %s in %ld$us; response in %ld$us (%s) content-length: %zu", log::debug("client[%s] HTTP %d %s in %ld$us; response in %ld$us (%s) content-length: %zu",
string(remote(client)), string(remote(client)),
int(code), int(code),
@ -528,5 +540,5 @@ ircd::resource::response::response(client &client,
request_time, request_time,
(client.request_timer.at<microseconds>().count() - request_time), (client.request_timer.at<microseconds>().count() - request_time),
content_type, content_type,
str.size()); content.size());
} }