Protocol-HTTP
view release on metacpan or search on metacpan
clib/src/panda/protocol/http/Request.cc view on Meta::CPAN
string Request::_generate_boundary() noexcept {
const constexpr size_t SZ = (string::MAX_SSO_CHARS / sizeof (int)) + (string::MAX_SSO_CHARS % sizeof (int) == 0 ? 0 : 1);
const constexpr char alphabet[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
const constexpr size_t alphabet_sz = sizeof (alphabet) - 1;
int dices[SZ];
string r(40, '-');
for(size_t i = 0; i <SZ; ++i) { dices[i] = std::rand(); }
const char* random_bytes = (const char*)dices;
for(size_t i = r.size() - 17; i < r.size(); ++i) {
r[i] = alphabet[*random_bytes++ % alphabet_sz];
}
return r;
}
Request::Method Request::method() const noexcept {
if (_method == Method::Unspecified) {
bool use_post = (form && form.enc_type() == EncType::Multipart && (!form.empty() || (uri && !uri->query().empty()))) // complete form
|| _form_streaming != FormStreaming::None;
return use_post ? Method::Post : Method::Get;
}
return _method;
}
static inline bool _method_has_meaning_for_body (Request::Method method) {
return method == Request::Method::Post || method == Request::Method::Put;
}
string Request::_http_header (SerializationContext& ctx) const {
//part 1: precalc pieces
auto eff_method = method();
bool body_method = _method_has_meaning_for_body(eff_method);
auto out_meth = _method_str(eff_method);
auto eff_uri = ctx.uri;
auto out_reluri = eff_uri ? eff_uri->relative() : string("/");
auto tmp_http_ver = !ctx.http_version ? 11 : ctx.http_version;
string out_content_length;
bool calc_content_length
= !ctx.chunked
&& (ctx.body->parts.size() || body_method)
&& !headers.has("Content-Length");
if (calc_content_length) out_content_length = panda::to_string(ctx.body->length());
size_t sz_host = 0;
size_t sz_host_port = 0;
if (!headers.has("Host") && eff_uri && eff_uri->host()) {
// Host field builder
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host
sz_host = eff_uri->host().length();
auto& scheme = eff_uri->scheme();
auto port = eff_uri->port();
if ((!scheme) || (scheme == "http" && port != 80) || (scheme == "https" && port != 443)) {
sz_host_port = 6;
}
}
string out_accept_encoding;
if (compression_prefs && compression_prefs != static_cast<compression::storage_t>(Compression::IDENTITY) && !headers.has("Accept-Encoding")) {
string comp_pos, comp_neg;
int index_pos = 0, index_neg = 0;
compression::for_each(compression_prefs, [&](auto value, bool negation){
const char* val = nullptr;
switch (value) {
case Compression::GZIP : val = "gzip"; break;
case Compression::BROTLI : val = "br"; break;
default: return;
}
if (negation) {
if (index_neg) { comp_neg += ", "; }
comp_neg += val;
comp_neg += ";q=0";
++index_neg;
} else {
if (index_pos) { comp_pos += ", "; }
comp_pos += val;
++index_pos;
}
});
if (index_neg) {
if (index_pos) { comp_pos += ", "; }
comp_pos += comp_neg;
}
if (comp_pos) { out_accept_encoding = comp_pos; }
}
auto out_content_encoding = _content_encoding(ctx);
size_t sz_cookies = 0;
if (cookies.size()) {
for (auto& f : cookies.fields) sz_cookies += f.name.length() + f.value.length() + 3; // 3 for ' ', '=' and ';' for each pair
}
// part 2: summarize pieces size
size_t reserved = out_meth.length();
reserved += out_reluri.length();
reserved += 5 + 6 + 2 + 1; /* http-version + trailer */
if (out_content_length) reserved += 14 + 2 + out_content_length.length() + 2;
if (sz_host) reserved += 4 + 2 + sz_host + sz_host_port + 2;
if (out_accept_encoding) reserved += 15 + 2 + out_accept_encoding.length() + 2;
if (out_content_encoding) reserved += 16 + 2 + out_content_encoding.length() + 2;
if (sz_cookies) reserved += 6 + 2 + sz_cookies + 2;
for (auto& h: ctx.handled_headers) { reserved += h.name.length() + 2 + h.value.length() + 2; }
for (auto& h: headers) {
if (ctx.handled_headers.has(h.name)) continue;
reserved += h.name.length() + 2 + h.value.length() + 2;
}
// part 3: write out pieces
string s(reserved);
s += out_meth;
s += ' ';
s += out_reluri;
s += " HTTP/";
if (tmp_http_ver == 11) s += "1.1\r\n";
else s += "1.0\r\n";
if (sz_host) {
s += "Host: " ;
s += eff_uri->host();
if (sz_host_port) { s+= ":"; s += panda::to_string(eff_uri->port()); }
clib/src/panda/protocol/http/Request.cc view on Meta::CPAN
if (ctx.handled_headers.has(h.name)) continue;
s += h.name; s += ": "; s += h.value; s+= "\r\n";
}
s += "\r\n";
//assert((sz_cookies + headers.size()) == 0);
//assert(reserved >= s.length());
return s;
}
std::vector<string> Request::to_vector () const {
SerializationContext ctx;
if (_form_streaming != FormStreaming::None && _form_streaming != FormStreaming::Started)
throw "form streaming wasn't finished";
bool form_streaming = _form_streaming == FormStreaming::Started;
/* it seems nobody supports muliptart + gzip + chunk */
ctx.compression = !form_streaming ? compression.type : Compression::Type::IDENTITY;
ctx.body = &body;
ctx.uri = uri.get();
ctx.chunked = this->chunked || form_streaming;
auto add_form_header = [&](auto& boundary) {
string ct = "multipart/form-data; boundary=";
ct += boundary;
ctx.handled_headers.add("Content-Type", ct);
};
Body form_body;
URI form_uri;
if (form) {
if (form.enc_type() == EncType::Multipart) {
if (!form.empty() || (uri && !uri->query().empty())) {
auto boundary = form_streaming ? _form_boundary : _generate_boundary();
ctx.uri = form.to_body(form_body, form_uri, uri, boundary);
ctx.body = &form_body;
add_form_header(boundary);
} else if (form_streaming) {
add_form_header(_form_boundary);
}
}
else if((form.enc_type() == EncType::UrlEncoded) && !form.empty()) {
form.to_uri(form_uri, uri);
ctx.uri = &form_uri;
}
}
else if (form_streaming) {
add_form_header(_form_boundary);
}
return _to_vector(ctx, [&]() { return _compile_prepare(ctx); }, [&]() { return _http_header(ctx); });
}
bool Request::expects_continue () const {
for (auto& val : headers.get_multi("Expect")) if (val == "100-continue") return true;
return false;
}
std::uint8_t Request::allowed_compression (bool inverse) const noexcept {
std::uint8_t result = 0;
compression::for_each(compression_prefs, [&](auto value, bool negation){
if (inverse == negation) {
result |= value;
}
});
return result;
}
static string form_trailer(const string& boundary) noexcept {
auto sz = boundary.size() + 6;
string r(sz);
r += "--";
r += boundary;
r += "--\r\n";
return r;
}
namespace tag {
using uri = std::integral_constant<int, 0>;
using form = std::integral_constant<int, 1>;
}
template<typename Tag> struct Helper;
template<> struct Helper<tag::form> {
using Field = Request::Form::value_type;
struct PatrialField {
string name;
string mime_type;
string filename;
bool complete = false;
};
struct FullField: PatrialField {
FullField(const string& name_, const string& filename_, const string& mime_type_, const string& value_):
PatrialField{name_, mime_type_, filename_, true}, value{value_}{}
string value;
};
static size_t buffer_size(const string &boundary, const Request::Form& container) noexcept {
auto fields_count = container.size();
size_t size = (
boundary.length() + 4 /* "--" prefix and "\r\n" */
+ 37 + 2 /* Content-Disposition: form-data; name="" + \r\n */
) * fields_count + 2; /* -- */
for(auto it : container) {
size += it.second.value.length();
auto& name = it.second.name;
if (name) {
size += name.length() + 14; //; filename=""
}
auto& ct = it.second.content_type;
if (ct) {
size += ct.size() + 18; //Content-Type: xxx\r\n
}
}
return size;
}
static void append_header(string& r, const string& header, const string& value) noexcept {
( run in 1.180 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )