2017-06-04 17:25:55 -07:00
|
|
|
//
|
|
|
|
|
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
|
|
|
|
//
|
|
|
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
|
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
|
//
|
|
|
|
|
|
2017-06-14 20:26:44 -07:00
|
|
|
#include "example/doc/http_examples.hpp"
|
2017-06-04 17:25:55 -07:00
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
#include <beast/core/flat_buffer.hpp>
|
2017-06-12 01:51:52 -07:00
|
|
|
#include <beast/core/read_size.hpp>
|
2017-07-09 20:09:30 -07:00
|
|
|
#include <beast/core/ostream.hpp>
|
2017-06-04 17:25:55 -07:00
|
|
|
#include <beast/core/detail/clamp.hpp>
|
2017-07-09 20:09:30 -07:00
|
|
|
#include <beast/http/chunk_encode.hpp>
|
|
|
|
|
#include <beast/http/parser.hpp>
|
|
|
|
|
#include <beast/http/read.hpp>
|
|
|
|
|
#include <beast/http/write.hpp>
|
2017-06-04 17:25:55 -07:00
|
|
|
#include <beast/test/pipe_stream.hpp>
|
|
|
|
|
#include <beast/test/string_istream.hpp>
|
|
|
|
|
#include <beast/test/string_ostream.hpp>
|
|
|
|
|
#include <beast/test/yield_to.hpp>
|
|
|
|
|
#include <beast/unit_test/suite.hpp>
|
|
|
|
|
#include <sstream>
|
2017-06-26 13:28:55 +02:00
|
|
|
#include <array>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <list>
|
2017-06-04 17:25:55 -07:00
|
|
|
|
|
|
|
|
namespace beast {
|
|
|
|
|
namespace http {
|
|
|
|
|
|
2017-07-02 06:32:19 -07:00
|
|
|
class doc_examples_test
|
2017-06-04 17:25:55 -07:00
|
|
|
: public beast::unit_test::suite
|
|
|
|
|
, public beast::test::enable_yield_to
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
// two threads, for some examples using a pipe
|
2017-07-02 06:32:19 -07:00
|
|
|
doc_examples_test()
|
2017-06-04 17:25:55 -07:00
|
|
|
: enable_yield_to(2)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<bool isRequest>
|
|
|
|
|
bool
|
|
|
|
|
equal_body(string_view sv, string_view body)
|
|
|
|
|
{
|
|
|
|
|
test::string_istream si{
|
|
|
|
|
get_io_service(), sv.to_string()};
|
|
|
|
|
message<isRequest, string_body, fields> m;
|
|
|
|
|
multi_buffer b;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
read(si, b, m);
|
|
|
|
|
return m.body == body;
|
|
|
|
|
}
|
|
|
|
|
catch(std::exception const& e)
|
|
|
|
|
{
|
|
|
|
|
log << "equal_body: " << e.what() << std::endl;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doExpect100Continue()
|
|
|
|
|
{
|
|
|
|
|
test::pipe p{ios_};
|
|
|
|
|
yield_to(
|
|
|
|
|
[&](yield_context)
|
|
|
|
|
{
|
|
|
|
|
error_code ec;
|
|
|
|
|
flat_buffer buffer;
|
|
|
|
|
receive_expect_100_continue(
|
|
|
|
|
p.server, buffer, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
},
|
|
|
|
|
[&](yield_context)
|
|
|
|
|
{
|
|
|
|
|
flat_buffer buffer;
|
2017-06-05 19:28:17 -07:00
|
|
|
request<string_body> req;
|
2017-06-04 17:25:55 -07:00
|
|
|
req.version = 11;
|
2017-06-06 17:26:11 -07:00
|
|
|
req.method_string("POST");
|
2017-06-04 17:25:55 -07:00
|
|
|
req.target("/");
|
2017-06-06 12:49:03 -07:00
|
|
|
req.insert(field::user_agent, "test");
|
2017-06-04 17:25:55 -07:00
|
|
|
req.body = "Hello, world!";
|
2017-06-19 15:37:39 -07:00
|
|
|
req.prepare_payload();
|
2017-06-04 17:25:55 -07:00
|
|
|
|
|
|
|
|
error_code ec;
|
|
|
|
|
send_expect_100_continue(
|
|
|
|
|
p.client, buffer, req, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doCgiResponse()
|
|
|
|
|
{
|
|
|
|
|
std::string const s = "Hello, world!";
|
|
|
|
|
test::pipe child{ios_};
|
|
|
|
|
child.server.read_size(3);
|
|
|
|
|
ostream(child.server.buffer) << s;
|
|
|
|
|
child.client.close();
|
|
|
|
|
test::pipe p{ios_};
|
|
|
|
|
error_code ec;
|
|
|
|
|
send_cgi_response(child.server, p.client, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
BEAST_EXPECT(equal_body<false>(p.server.str(), s));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doRelay()
|
|
|
|
|
{
|
2017-06-05 19:28:17 -07:00
|
|
|
request<string_body> req;
|
2017-06-04 17:25:55 -07:00
|
|
|
req.version = 11;
|
2017-06-06 17:26:11 -07:00
|
|
|
req.method_string("POST");
|
2017-06-04 17:25:55 -07:00
|
|
|
req.target("/");
|
2017-06-06 12:49:03 -07:00
|
|
|
req.insert(field::user_agent, "test");
|
2017-06-04 17:25:55 -07:00
|
|
|
req.body = "Hello, world!";
|
2017-06-19 15:37:39 -07:00
|
|
|
req.prepare_payload();
|
2017-06-04 17:25:55 -07:00
|
|
|
|
|
|
|
|
test::pipe downstream{ios_};
|
|
|
|
|
downstream.server.read_size(3);
|
|
|
|
|
test::pipe upstream{ios_};
|
|
|
|
|
upstream.client.write_size(3);
|
|
|
|
|
|
|
|
|
|
error_code ec;
|
|
|
|
|
write(downstream.client, req);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
downstream.client.close();
|
|
|
|
|
|
|
|
|
|
flat_buffer buffer;
|
|
|
|
|
relay<true>(upstream.client, downstream.server, buffer, ec,
|
2017-06-18 14:57:32 -07:00
|
|
|
[&](header<true, fields>& h, error_code& ev)
|
2017-06-04 17:25:55 -07:00
|
|
|
{
|
2017-06-18 14:57:32 -07:00
|
|
|
ev = {};
|
2017-06-05 19:28:17 -07:00
|
|
|
h.erase("Content-Length");
|
2017-06-15 06:55:43 -07:00
|
|
|
h.set("Transfer-Encoding", "chunked");
|
2017-06-04 17:25:55 -07:00
|
|
|
});
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
BEAST_EXPECT(equal_body<true>(
|
|
|
|
|
upstream.server.str(), req.body));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doReadStdStream()
|
|
|
|
|
{
|
|
|
|
|
std::string const s =
|
|
|
|
|
"HTTP/1.0 200 OK\r\n"
|
|
|
|
|
"User-Agent: test\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"Hello, world!";
|
|
|
|
|
std::istringstream is(s);
|
|
|
|
|
error_code ec;
|
|
|
|
|
flat_buffer buffer;
|
|
|
|
|
response<string_body> res;
|
|
|
|
|
read_istream(is, buffer, res, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
BEAST_EXPECT(boost::lexical_cast<
|
|
|
|
|
std::string>(res) == s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doWriteStdStream()
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream os;
|
|
|
|
|
request<string_body> req;
|
|
|
|
|
req.version = 11;
|
|
|
|
|
req.method(verb::get);
|
|
|
|
|
req.target("/");
|
2017-06-06 12:49:03 -07:00
|
|
|
req.insert(field::user_agent, "test");
|
2017-06-04 17:25:55 -07:00
|
|
|
error_code ec;
|
|
|
|
|
write_ostream(os, req, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
BEAST_EXPECT(boost::lexical_cast<
|
|
|
|
|
std::string>(req) == os.str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doCustomParser()
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
string_view s{
|
|
|
|
|
"POST / HTTP/1.1\r\n"
|
|
|
|
|
"User-Agent: test\r\n"
|
|
|
|
|
"Content-Length: 13\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"Hello, world!"
|
|
|
|
|
};
|
|
|
|
|
error_code ec;
|
|
|
|
|
custom_parser<true> p;
|
|
|
|
|
p.put(boost::asio::buffer(
|
|
|
|
|
s.data(), s.size()), ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
string_view s{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
|
"Server: test\r\n"
|
|
|
|
|
"Transfer-Encoding: chunked\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"d\r\n"
|
|
|
|
|
"Hello, world!"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"0\r\n\r\n"
|
|
|
|
|
};
|
|
|
|
|
error_code ec;
|
|
|
|
|
custom_parser<false> p;
|
|
|
|
|
p.put(boost::asio::buffer(
|
|
|
|
|
s.data(), s.size()), ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doHEAD()
|
|
|
|
|
{
|
|
|
|
|
test::pipe p{ios_};
|
|
|
|
|
yield_to(
|
|
|
|
|
[&](yield_context)
|
|
|
|
|
{
|
|
|
|
|
error_code ec;
|
|
|
|
|
flat_buffer buffer;
|
|
|
|
|
do_server_head(p.server, buffer, ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
},
|
|
|
|
|
[&](yield_context)
|
|
|
|
|
{
|
|
|
|
|
error_code ec;
|
|
|
|
|
flat_buffer buffer;
|
|
|
|
|
auto res = do_head_request(p.client, buffer, "/", ec);
|
|
|
|
|
BEAST_EXPECTS(! ec, ec.message());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-05 06:44:31 -07:00
|
|
|
struct handler
|
|
|
|
|
{
|
|
|
|
|
std::string body;
|
|
|
|
|
|
|
|
|
|
template<class Body>
|
|
|
|
|
void
|
|
|
|
|
operator()(request<Body>&&)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
operator()(request<string_body>&& req)
|
|
|
|
|
{
|
|
|
|
|
body = req.body;
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-06-04 17:25:55 -07:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doDeferredBody()
|
|
|
|
|
{
|
|
|
|
|
test::pipe p{ios_};
|
|
|
|
|
ostream(p.server.buffer) <<
|
|
|
|
|
"POST / HTTP/1.1\r\n"
|
|
|
|
|
"User-Agent: test\r\n"
|
2017-06-05 06:44:31 -07:00
|
|
|
"Content-Type: multipart/form-data\r\n"
|
2017-06-04 17:25:55 -07:00
|
|
|
"Content-Length: 13\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"Hello, world!";
|
|
|
|
|
|
2017-06-05 06:44:31 -07:00
|
|
|
handler h;
|
2017-06-04 17:25:55 -07:00
|
|
|
flat_buffer buffer;
|
2017-06-05 06:44:31 -07:00
|
|
|
do_form_request(p.server, buffer, h);
|
|
|
|
|
BEAST_EXPECT(h.body == "Hello, world!");
|
2017-06-04 17:25:55 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-08 22:03:58 -07:00
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-02 06:32:19 -07:00
|
|
|
void
|
|
|
|
|
doIncrementalRead()
|
|
|
|
|
{
|
|
|
|
|
test::pipe c{ios_};
|
|
|
|
|
std::string s(2048, '*');
|
|
|
|
|
ostream(c.server.buffer) <<
|
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
|
"Content-Length: 2048\r\n"
|
|
|
|
|
"Server: test\r\n"
|
|
|
|
|
"\r\n" <<
|
|
|
|
|
s;
|
|
|
|
|
error_code ec;
|
|
|
|
|
flat_buffer b;
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
read_and_print_body<false>(ss, c.server, b, ec);
|
|
|
|
|
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
|
|
|
BEAST_EXPECT(ss.str() == s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
2017-07-09 20:09:30 -07:00
|
|
|
void
|
|
|
|
|
doExplicitChunkSerialize()
|
|
|
|
|
{
|
|
|
|
|
auto const buf =
|
|
|
|
|
[](string_view s)
|
|
|
|
|
{
|
|
|
|
|
return boost::asio::const_buffers_1{
|
|
|
|
|
s.data(), s.size()};
|
|
|
|
|
};
|
|
|
|
|
test::pipe p{ios_};
|
|
|
|
|
|
|
|
|
|
response<empty_body> res{status::ok, 11};
|
|
|
|
|
res.set(field::server, "test");
|
|
|
|
|
res.set(field::accept, "Expires, Content-MD5");
|
|
|
|
|
res.chunked(true);
|
|
|
|
|
|
|
|
|
|
error_code ec;
|
|
|
|
|
response_serializer<empty_body> sr{res};
|
|
|
|
|
write_header(p.client, sr, ec);
|
|
|
|
|
|
|
|
|
|
chunk_extensions exts;
|
|
|
|
|
|
|
|
|
|
boost::asio::write(p.client,
|
|
|
|
|
make_chunk(buf("First")), ec);
|
|
|
|
|
|
|
|
|
|
exts.insert("quality", "1.0");
|
|
|
|
|
boost::asio::write(p.client,
|
|
|
|
|
make_chunk(buf("Hello, world!"), exts), ec);
|
|
|
|
|
|
|
|
|
|
exts.clear();
|
|
|
|
|
exts.insert("file", "abc.txt");
|
|
|
|
|
exts.insert("quality", "0.7");
|
|
|
|
|
boost::asio::write(p.client,
|
|
|
|
|
make_chunk(buf("The Next Chunk"), std::move(exts)), ec);
|
|
|
|
|
|
|
|
|
|
exts.clear();
|
|
|
|
|
exts.insert("last");
|
|
|
|
|
boost::asio::write(p.client,
|
|
|
|
|
make_chunk(buf("Last one"), std::move(exts),
|
|
|
|
|
std::allocator<double>{}), ec);
|
|
|
|
|
|
|
|
|
|
fields trailers;
|
|
|
|
|
trailers.set(field::expires, "never");
|
|
|
|
|
trailers.set(field::content_md5, "f4a5c16584f03d90");
|
|
|
|
|
|
|
|
|
|
boost::asio::write(p.client,
|
|
|
|
|
make_chunk_last(
|
|
|
|
|
trailers,
|
|
|
|
|
std::allocator<double>{}
|
|
|
|
|
), ec);
|
|
|
|
|
BEAST_EXPECT(
|
|
|
|
|
boost::lexical_cast<std::string>(
|
|
|
|
|
buffers(p.server.buffer.data())) ==
|
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
|
"Server: test\r\n"
|
|
|
|
|
"Accept: Expires, Content-MD5\r\n"
|
|
|
|
|
"Transfer-Encoding: chunked\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"5\r\n"
|
|
|
|
|
"First\r\n"
|
|
|
|
|
"d;quality=1.0\r\n"
|
|
|
|
|
"Hello, world!\r\n"
|
|
|
|
|
"e;file=abc.txt;quality=0.7\r\n"
|
|
|
|
|
"The Next Chunk\r\n"
|
|
|
|
|
"8;last\r\n"
|
|
|
|
|
"Last one\r\n"
|
|
|
|
|
"0\r\n"
|
|
|
|
|
"Expires: never\r\n"
|
|
|
|
|
"Content-MD5: f4a5c16584f03d90\r\n"
|
|
|
|
|
"\r\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
doExplicitChunkParse()
|
|
|
|
|
{
|
|
|
|
|
test::pipe c{ios_};
|
|
|
|
|
ostream(c.client.buffer) <<
|
|
|
|
|
"HTTP/1.1 200 OK\r\n"
|
|
|
|
|
"Server: test\r\n"
|
|
|
|
|
"Accept: Expires, Content-MD5\r\n"
|
|
|
|
|
"Transfer-Encoding: chunked\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"5\r\n"
|
|
|
|
|
"First\r\n"
|
|
|
|
|
"d;quality=1.0\r\n"
|
|
|
|
|
"Hello, world!\r\n"
|
|
|
|
|
"e;file=abc.txt;quality=0.7\r\n"
|
|
|
|
|
"The Next Chunk\r\n"
|
|
|
|
|
"8;last\r\n"
|
|
|
|
|
"Last one\r\n"
|
|
|
|
|
"0\r\n"
|
|
|
|
|
"Expires: never\r\n"
|
|
|
|
|
"Content-MD5: f4a5c16584f03d90\r\n"
|
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
|
|
flat_buffer b;
|
|
|
|
|
response_parser<empty_body> p;
|
|
|
|
|
read_header(c.client, b, p);
|
|
|
|
|
BOOST_ASSERT(p.is_chunked());
|
|
|
|
|
//while(! p.is_done())
|
|
|
|
|
{
|
|
|
|
|
// read the chunk header?
|
|
|
|
|
// read the next chunk?
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
2017-06-04 17:25:55 -07:00
|
|
|
void
|
|
|
|
|
run()
|
|
|
|
|
{
|
|
|
|
|
doExpect100Continue();
|
|
|
|
|
doCgiResponse();
|
|
|
|
|
doRelay();
|
|
|
|
|
doReadStdStream();
|
|
|
|
|
doWriteStdStream();
|
|
|
|
|
doCustomParser();
|
|
|
|
|
doHEAD();
|
|
|
|
|
doDeferredBody();
|
2017-07-02 06:32:19 -07:00
|
|
|
doIncrementalRead();
|
2017-07-09 20:09:30 -07:00
|
|
|
doExplicitChunkSerialize();
|
|
|
|
|
doExplicitChunkParse();
|
2017-06-04 17:25:55 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-07-02 06:32:19 -07:00
|
|
|
BEAST_DEFINE_TESTSUITE(doc_examples,http,beast);
|
2017-06-04 17:25:55 -07:00
|
|
|
|
|
|
|
|
} // http
|
|
|
|
|
} // beast
|