view release on metacpan or search on metacpan
- update docs
- add MANIFEST.SKIP
1.1.0 08.04.2021
- C++ codes moved to its own library with cmake
- do not automatically add "Content-Length: 0" header to response for 304 code unless provided by user
- treat protocol-relative urls as path
1.0.11 25.11.2020
- improve makefile
- change constant names
1.0.10 30.10.2020
- bugfix: chunked data streaming correctly supports compression
- bugfix: gzip uncompression
- multipart/form-data: allow to define filename and content/type
1.0.9 25.06.2020
- $request->method_str now returns stringified effective method
- fix compilation issues on *BSD
- fix tests on *BSD
1.0.8 22.06.2020
- version bump
1.0.7 22.06.2020
- fix tests: make them timezone- and randomizer- neutral
clib/README.md view on Meta::CPAN
# Protocol-HTTP
Protocol-HTTP - very fast HTTP protocol incremental parser and serializer
Features: cookies, transparent (un)compression with `Gzip` or `Brotli`, transparent chunked transfer encoding for body streaming, respecting request's preferences when making response.
The module is a protocol implementation, by itself it does not perform any I/O activity. For HTTP library see [UniEvent-HTTP](https://github.com/CrazyPandaLimited/UniEvent-HTTP).
Currenly supported HTTP versions are 1.0 and 1.1
# Synopsis
```cpp
using namespace panda::protocol::http;
RequestSP request = Request::Builder()
clib/src/panda/protocol/http/Request.cc view on Meta::CPAN
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 {
clib/src/panda/protocol/http/Request.cc view on Meta::CPAN
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;
}
clib/src/panda/protocol/http/Request.cc view on Meta::CPAN
r += it.first;
r += "\"";
r += "\r\n";
r += "\r\n";
r += it.second;
r += "\r\n";
}
};
void Request::form_file_finalize(string& out) noexcept {
if (_form_streaming == FormStreaming::File) {
if (compressor) {
out += compressor->flush();
compressor.reset();
}
out += "\r\n"; /* finalize file */
}
}
Request::wrapped_chunk Request::form_finish() {
if (_form_streaming == FormStreaming::None) throw "form streaming was not started";
if (_form_streaming == FormStreaming::Done) throw "form streaming already complete";
string data;
form_file_finalize(data);
data += form_trailer(_form_boundary);
_form_streaming = FormStreaming::Done;
return final_chunk(data);
}
Request::wrapped_chunk Request::form_field(const string& name, const string& content, const string& filename, const string& mime_type) {
using H = Helper<tag::form>;
if (_form_streaming == FormStreaming::None) throw "form streaming was not started";
if (_form_streaming == FormStreaming::Done) throw "form streaming already complete";
string data;
form_file_finalize(data);
H::append(data, H::FullField{name, filename, mime_type, content}, _form_boundary);
return make_chunk(data, compression::CompressorPtr{}); // we don't compress
}
Request::wrapped_chunk Request::form_file(const string& name, const string filename, const string& mime_type) {
using H = Helper<tag::form>;
if (_form_streaming == FormStreaming::None) throw "form streaming was not started";
if (_form_streaming == FormStreaming::Done) throw "form streaming already complete";
string data;
form_file_finalize(data);
_form_streaming = FormStreaming::File;
H::append(data, H::PatrialField{name, mime_type, filename, false}, _form_boundary);
data += "\r\n";
return make_chunk(data, compression::CompressorPtr{}); // we don't compress
}
Request::wrapped_chunk Request::form_data(const string& content) {
if (_form_streaming != FormStreaming::File) throw "form file streaming was not started";
return make_chunk(content);
}
template<typename Tag, typename Container>
void _serialize(Body& body, const string &boundary, const Container& container) {
using H = Helper<Tag>;
string r(H::buffer_size(boundary, container));
for(auto it : container) { H::append(r, it, boundary); }
r += form_trailer(boundary);
body.parts.emplace_back(r);
clib/src/panda/protocol/http/Request.h view on Meta::CPAN
}
Method method () const noexcept;
Method method_raw () const noexcept { return _method; }
void method_raw (Method value) noexcept { _method = value; }
std::uint8_t allowed_compression (bool inverse = false) const noexcept;
void form_stream () {
if (_form_streaming == FormStreaming::None) {
_form_streaming = FormStreaming::Started;
form._enc_type = EncType::Multipart;
_form_boundary = _generate_boundary();
}
else if (_form_streaming != FormStreaming::Started) {
throw "invalid state for form streaming";
}
}
bool form_streaming () noexcept { return _form_streaming == FormStreaming::Started; }
wrapped_chunk form_finish ();
wrapped_chunk form_field (const string& name, const string& content, const string& filename = "", const string& mime_type = "");
wrapped_chunk form_file (const string& name, const string filename = "", const string& mime_type = "application/octet-stream");
wrapped_chunk form_data (const string& data);
protected:
struct SerializationContext: Message::SerializationContext {
const URI* uri;
};
clib/src/panda/protocol/http/Request.h view on Meta::CPAN
string form_trailer (const string& boundary) const noexcept {
auto sz = boundary.size() + 6;
string r(sz);
r += "--";
r += boundary;
r += "--\r\n";
return r;
}
Method _method = Method::Unspecified;
FormStreaming _form_streaming = FormStreaming::None;
string _form_boundary;
template<typename... PrefN>
void _allow_compression (Compression::Type p, PrefN... prefn) {
compression::pack(this->compression_prefs, p);
return _allow_compression(prefn...);
}
void _allow_compression () {}
void form_file_finalize (string& out) noexcept;
clib/tests/compile/form.cc view on Meta::CPAN
template<typename String, typename Container>
string merge(String s, Container c) {
for(auto& it:c) {
s += string(it);
}
return s;
}
// content can be tested with http://ptsv2.com + netcat
TEST("multipart/form-data (streaming)") {
auto req = Request::Builder().form_stream().build();
SECTION("emtpy form") {
auto data = req->to_string();
data = merge(data, req->form_finish());
CHECK(canonize(data).first ==
"POST / HTTP/1.1\r\n"
"Content-Type: multipart/form-data; boundary=-----------------------XXXXXXXXXXXXXXXXX\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"2e\r\n"
clib/tests/compile/form.cc view on Meta::CPAN
"\r\n"
"[pdf]"
"\r\n\r\n"
"2e\r\n"
"-------------------------XXXXXXXXXXXXXXXXX--\r\n"
"\r\n"
"0\r\n\r\n"
);
}
SECTION("start streaming file") {
auto data = req->to_string();
data = merge(data, req->form_file("key", "cv.pdf", "application/pdf"));
data = merge(data, req->form_data("[0123456789]"));
data = merge(data, req->form_finish());
//std::cout << "zzz:\n" << data << "zzz\n";
CHECK(canonize(data).first ==
"POST / HTTP/1.1\r\n"
"Content-Type: multipart/form-data; boundary=-----------------------XXXXXXXXXXXXXXXXX\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
clib/tests/compile/form.cc view on Meta::CPAN
"[0123456789]"
"\r\n"
"30\r\n"
"\r\n"
"-------------------------XXXXXXXXXXXXXXXXX--\r\n"
"\r\n"
"0\r\n\r\n"
);
}
SECTION("start streaming file, then embed field") {
//auto req = Request::Builder().uri("http://ptsv2.com/t/27ibp-1600433748/post").form_stream().build();
auto data = req->to_string();
data = merge(data, req->form_file("key", "cv.pdf", "application/pdf"));
data = merge(data, req->form_data("[0123456789]"));
data = merge(data, req->form_field("key2", "[pdf]"));
data = merge(data, req->form_finish());
//std::cout << "zzz:\n" << data << "zzz\n";
CHECK(canonize(data).first ==
"POST / HTTP/1.1\r\n"
"Content-Type: multipart/form-data; boundary=-----------------------XXXXXXXXXXXXXXXXX\r\n"
clib/tests/compile/form.cc view on Meta::CPAN
"Content-Disposition: form-data; name=\"key2\"\r\n"
"\r\n"
"[pdf]\r\n\r\n"
"30\r\n\r\n"
"-------------------------XXXXXXXXXXXXXXXXX--\r\n"
"\r\n"
"0\r\n\r\n"
);
}
SECTION("start streaming file, then embed field, gzip compression is ignored") {
//auto req = Request::Builder().uri("/").form_stream()/* .compress(Compression::Type::GZIP) */ .build();
auto req = Request::Builder().uri("/").form_stream().compress(Compression::Type::GZIP).build();
auto data = req->to_string();
data = merge(data, req->form_file("key", "cv.pdf", "application/pdf"));
data = merge(data, req->form_data("[0123456789]"));
data = merge(data, req->form_field("key2", "[pdf]"));
data = merge(data, req->form_finish());
CHECK(canonize(data).first ==
"POST / HTTP/1.1\r\n"
"Content-Type: multipart/form-data; boundary=-----------------------XXXXXXXXXXXXXXXXX\r\n"
lib/Protocol/HTTP.pod view on Meta::CPAN
$jar->collect($response); # after response is received
=head1 DESCRIPTION
The C<Protocol::HTTP> is a port of C<panda::protocol::http> library, an RFC-compiant very fast HTTP protocol implementation, written in C++
with XS-adapters in perl. The module has dual Perl/C++ interface (see L<XS::Manifesto>) so futher XS-bindings can be written on the top of
C<Protocol::HTTP>.
The following features are supported in the C<Protocol::HTTP>: cookies, transparent (un)compression with C<Gzip> or C<Brotli>,
transparent chunked transfer encoding for body streaming, respecting request's preferences when making response.
The module is a protocol implementation, by itself it does not perform any I/O activity.
Currenly supported HTTP versions are 1.0 and 1.1
=head1 REFERENCE
L<Protocol::HTTP::Request>