From 36142718f6371478af1ddf336fe6eb8ba96f64a2 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 24 Dec 2017 14:25:09 -0700 Subject: [PATCH] ircd::http: Massage additional header related into response interface/stack. --- include/ircd/http.h | 26 +++---- include/ircd/resource.h | 3 +- ircd/http.cc | 153 +++++++++++++++++++++++----------------- ircd/resource.cc | 48 +++++++++---- 4 files changed, 138 insertions(+), 92 deletions(-) diff --git a/include/ircd/http.h b/include/ircd/http.h index e48cff301..a055a9aaa 100644 --- a/include/ircd/http.h +++ b/include/ircd/http.h @@ -32,6 +32,7 @@ namespace ircd::http struct error; struct line; struct query; + struct header; struct headers; struct content; struct request; @@ -39,6 +40,14 @@ namespace ircd::http string_view status(const enum code &); enum code status(const string_view &); + + void writeline(stream_buffer &); + void writeline(stream_buffer &, const stream_buffer::closure &); + + void write(stream_buffer &out, const header &); + void write(stream_buffer &out, const vector_view &); + size_t serialized(const vector_view &); + std::string strung(const vector_view &); } // @@ -92,8 +101,10 @@ struct ircd::http::error { enum code code; std::string content; + std::string headers; - error(const enum code &, std::string content = {}); + error(const enum code &, std::string content = {}, std::string headers = {}); + error(const enum code &, std::string content, const vector_view &); }; /// Represents a single \r\n delimited line used in HTTP. @@ -108,17 +119,11 @@ struct ircd::http::line { struct request; struct response; - struct header; using string_view::string_view; line(parse::capstan &); }; -namespace ircd::http -{ - using header = line::header; -} - /// Represents a 'request line' or the first line a client sends to a server. /// /// This is a dual-use class. For HTTP clients, one may simply connect the @@ -199,7 +204,7 @@ struct ircd::http::query::string /// components of the std::pair. Those receiving headers can pass the ctor an /// ircd::http::line which will construct the pair using the formal grammars. /// -struct ircd::http::line::header +struct ircd::http::header :std::pair { bool operator<(const string_view &s) const { return iless(first, s); } @@ -219,7 +224,6 @@ struct ircd::http::line::header struct ircd::http::headers :string_view { - using header = line::header; using closure = std::function; headers(parse::capstan &, const closure & = {}); @@ -255,7 +259,6 @@ struct ircd::http::request struct content; using proffer = std::function; - using header = line::header; // send request(stream_buffer &, @@ -316,14 +319,13 @@ struct ircd::http::response using write_closure = std::function &)>; using proffer = std::function; - using header = line::header; // send response(stream_buffer &, const code & = code::OK, const size_t &content_length = 0, const string_view &content_type = {}, - const string_view &cache_control = {}, + const string_view &headers = {}, const vector_view & = {}, const bool &termination = true); diff --git a/include/ircd/resource.h b/include/ircd/resource.h index 7d548523c..638e109e6 100644 --- a/include/ircd/resource.h +++ b/include/ircd/resource.h @@ -130,7 +130,8 @@ struct ircd::resource::request::object struct ircd::resource::response { - response(client &, const string_view &str, const string_view &content_type, const http::code & = http::OK); + response(client &, const string_view &str, const string_view &content_type, const http::code &, const vector_view &); + response(client &, const string_view &str, const string_view &content_type, const http::code & = http::OK, const string_view &headers = {}); 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::members & = {}, const http::code & = http::OK); diff --git a/ircd/http.cc b/ircd/http.cc index 4b3ea75c5..76b1a32df 100644 --- a/ircd/http.cc +++ b/ircd/http.cc @@ -49,10 +49,6 @@ namespace ircd::http struct parser extern const parser; extern const std::unordered_map reason; - - size_t serialized(const vector_view &headers); - void writeline(stream_buffer &, const stream_buffer::closure &); - void writeline(stream_buffer &); } BOOST_FUSION_ADAPT_STRUCT @@ -64,9 +60,9 @@ BOOST_FUSION_ADAPT_STRUCT BOOST_FUSION_ADAPT_STRUCT ( - ircd::http::line::header, - ( decltype(ircd::http::line::header::first), first ) - ( decltype(ircd::http::line::header::second), second ) + ircd::http::header, + ( decltype(ircd::http::header::first), first ) + ( decltype(ircd::http::header::second), second ) ) BOOST_FUSION_ADAPT_STRUCT @@ -164,7 +160,7 @@ struct ircd::http::grammar rule head_key { raw[+(char_ - (illegal | ws | colon))] ,"head key" }; rule head_val { string ,"head value" }; - rule header { head_key >> *ws >> colon >> *ws >> head_val ,"header" }; + rule header { head_key >> *ws >> colon >> *ws >> head_val ,"header" }; rule headers { (header % (*ws >> CRLF)) ,"headers" }; rule<> query_terminator { equal | question | ampersand | pound ,"query terminator" }; @@ -314,18 +310,7 @@ ircd::http::request::request(stream_buffer &out, }; }); - 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 - }; - }); - } + write(out, headers); if(termination) writeline(out); @@ -383,8 +368,8 @@ ircd::http::response::response(stream_buffer &out, const code &code, const size_t &content_length, const string_view &content_type, - const string_view &cache_control, - const vector_view &headers, + const string_view &headers_string, + const vector_view &headers_vector, const bool &termination) { writeline(out, [&code](const mutable_buffer &out) -> size_t @@ -414,25 +399,7 @@ ircd::http::response::response(stream_buffer &out, }; }); - if((code >= 200 && code < 300) || (code >= 403 && code <= 405) || (code >= 300 && code < 400)) - writeline(out, [&cache_control](const mutable_buffer &out) -> size_t - { - return fmt::sprintf - { - out, "Cache-Control: %s", cache_control?: "no-cache" - }; - }); - - const bool has_transfer_encoding - { - std::any_of(std::begin(headers), std::end(headers), [] - (const auto &header) - { - return iequals(header.first, "transfer-encoding"s); - }) - }; - - if((content_length && code != NO_CONTENT) || has_transfer_encoding) + if(code != NO_CONTENT && content_type && content_length) writeline(out, [&content_type](const mutable_buffer &out) -> size_t { return fmt::sprintf @@ -441,7 +408,7 @@ ircd::http::response::response(stream_buffer &out, }; }); - if(code != NO_CONTENT && !has_transfer_encoding) + if(code != NO_CONTENT && content_length != std::numeric_limits::max()) writeline(out, [&content_length](const mutable_buffer &out) -> size_t { return fmt::sprintf @@ -450,18 +417,14 @@ ircd::http::response::response(stream_buffer &out, }; }); - for(const auto &header : headers) - { - assert(!header.first.empty()); - assert(!header.second.empty()); - writeline(out, [&header](const mutable_buffer &out) -> size_t + if(!headers_string.empty()) + out([&headers_string](const mutable_buffer &out) { - return fmt::sprintf - { - out, "%s: %s", header.first, header.second - }; + return copy(out, headers_string); }); - } + + if(!headers_vector.empty()) + write(out, headers_vector); if(termination) writeline(out); @@ -477,7 +440,7 @@ ircd::http::response::chunked::chunked(const code &code, user_headers.size() + 1 }; - line::header headers[num_headers] + header headers[num_headers] { { "Transfer-Encoding", "chunked" } }; @@ -647,9 +610,9 @@ ircd::http::headers::headers(parse::capstan &pc, :string_view{[&pc, &c] () -> string_view { - line::header h{pc}; + header h{pc}; const char *const &started{h.first.data()}, *stopped{started}; - for(; !h.first.empty(); stopped = h.second.data() + h.second.size(), h = line::header{pc}) + for(; !h.first.empty(); stopped = h.second.data() + h.second.size(), h = header{pc}) if(c) c(h); @@ -658,7 +621,7 @@ ircd::http::headers::headers(parse::capstan &pc, { } -ircd::http::line::header::header(const line &line) +ircd::http::header::header(const line &line) try { static const auto grammar @@ -846,6 +809,65 @@ ircd::http::parser::content_length(const string_view &str) return ret; } +std::string +ircd::http::strung(const vector_view &headers) +{ + std::string ret(serialized(headers), char{}); + stream_buffer out{ret}; + write(out, headers); + assert(out.consumed() <= ret.size()); + ret.resize(out.consumed()); + assert(out.consumed() == ret.size()); + return ret; +} + +/// Indicates the buffer size required to write these headers. This size +/// may include room for a terminating null character which may be written +/// by write(headers). Only use write(headers) to know the actually written +/// string size (without null) not this. +size_t +ircd::http::serialized(const vector_view &headers) +{ + // Because the write(header) functions use fmt::sprintf we have to + // indicate an extra space for a null string terminator to not overlof + static const size_t initial{1}; + + return std::accumulate(std::begin(headers), std::end(headers), initial, [] + (auto &ret, const auto &pair) + { + // key : SP value CRLF + return ret += pair.first.size() + 1 + 1 + pair.second.size() + 2; + }); +} + +void +ircd::http::write(stream_buffer &out, + const vector_view &headers) +{ + for(const auto &header : headers) + write(out, header); +} + +void +ircd::http::write(stream_buffer &out, + const header &header) +{ + if(header.second.empty()) + return; + + assert(!header.first.empty()); + if(unlikely(header.first.empty())) + return; + + writeline(out, [&header](const mutable_buffer &out) -> size_t + { + return fmt::sprintf + { + out, "%s: %s", header.first, header.second + }; + }); +} + /// Close over the user's closure to append a newline. void ircd::http::writeline(stream_buffer &write, @@ -875,22 +897,23 @@ ircd::http::writeline(stream_buffer &write) }); } -size_t -ircd::http::serialized(const vector_view &headers) +ircd::http::error::error(const enum code &code, + std::string content, + const vector_view &headers) +:error +{ + code, std::move(content), strung(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, - std::string content) + std::string content, + std::string headers) :ircd::error{generate_skip} ,code{code} ,content{std::move(content)} +,headers{std::move(headers)} { snprintf(buf, sizeof(buf), "%d %s", int(code), status(code).c_str()); } diff --git a/ircd/resource.cc b/ircd/resource.cc index ece8f771b..b235388ff 100644 --- a/ircd/resource.cc +++ b/ircd/resource.cc @@ -62,15 +62,9 @@ try } catch(const http::error &e) { - log::debug("client[%s] HTTP %s in %ld$us %s", - string(remote(client)), - e.what(), - client.request_timer.at().count(), - e.content); - resource::response { - client, e.content, "text/html; charset=utf8", e.code + client, e.content, "text/html; charset=utf8", e.code, e.headers }; switch(e.code) @@ -593,16 +587,41 @@ ircd::resource::response::response(client &client, ircd::resource::response::response(client &client, const string_view &content, const string_view &content_type, - const http::code &code) + const http::code &code, + const vector_view &headers) +{ + char buf[serialized(headers)]; + const mutable_buffer mb{buf, sizeof(buf)}; + stream_buffer sb{mb}; + write(sb, headers); + response + { + client, content, content_type, code, sb.completed() + }; +} + +ircd::resource::response::response(client &client, + const string_view &content, + const string_view &content_type, + const http::code &code, + const string_view &headers) { const auto request_time { client.request_timer.at().count() }; - char rtime[64]; const auto rtime_len + const fmt::bsprintf<64> rtime { - snprintf(rtime, sizeof(rtime), "%zdus", request_time) + "%zdus", request_time + }; + + const string_view cache_control + { + (code >= 200 && code < 300) || + (code >= 403 && code <= 405) || + (code >= 300 && code < 400)? "no-cache": + "" }; char head_buf[2048]; @@ -613,11 +632,12 @@ ircd::resource::response::response(client &client, code, content.size(), content_type, - string_view{}, // cache_control + headers, { - { "Access-Control-Allow-Origin", "*" }, //TODO: XXX - { "X-IRCd-Request-Timer", string_view{rtime, size_t(rtime_len)} } - } + { "Access-Control-Allow-Origin", "*" }, //TODO: XXX + { "Cache-Control", cache_control }, + { "X-IRCd-Request-Timer", rtime, }, + }, }; const ilist vector