1 // 2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 // 4 // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 // 7 // Official repository: https://github.com/boostorg/beast 8 // 9 10 #include "example/doc/http_examples.hpp" 11 12 #include <boost/beast/core/flat_buffer.hpp> 13 #include <boost/beast/core/read_size.hpp> 14 #include <boost/beast/core/ostream.hpp> 15 #include <boost/beast/core/detail/clamp.hpp> 16 #include <boost/beast/core/detail/type_traits.hpp> 17 #include <boost/beast/http/chunk_encode.hpp> 18 #include <boost/beast/http/parser.hpp> 19 #include <boost/beast/http/read.hpp> 20 #include <boost/beast/http/write.hpp> 21 #include <boost/beast/_experimental/test/stream.hpp> 22 #include <boost/beast/test/yield_to.hpp> 23 #include <boost/beast/_experimental/unit_test/suite.hpp> 24 #include <sstream> 25 #include <array> 26 #include <limits> 27 #include <list> 28 #include <sstream> 29 #include <vector> 30 31 namespace boost { 32 namespace beast { 33 namespace http { 34 35 class examples_test 36 : public beast::unit_test::suite 37 , public beast::test::enable_yield_to 38 { 39 public: 40 // two threads, for some examples examples_test()41 examples_test() 42 : enable_yield_to(2) 43 { 44 } 45 46 template<bool isRequest, class Body, class Fields> 47 static 48 std::string to_string(message<isRequest,Body,Fields> const & m)49 to_string(message<isRequest, Body, Fields> const& m) 50 { 51 std::stringstream ss; 52 ss << m; 53 return ss.str(); 54 } 55 56 template<bool isRequest> 57 bool equal_body(string_view sv,string_view body)58 equal_body(string_view sv, string_view body) 59 { 60 test::stream ts{ioc_, sv}; 61 message<isRequest, string_body, fields> m; 62 multi_buffer b; 63 ts.close_remote(); 64 try 65 { 66 read(ts, b, m); 67 return m.body() == body; 68 } 69 catch(std::exception const& e) 70 { 71 log << "equal_body: " << e.what() << std::endl; 72 return false; 73 } 74 } 75 76 void doExpect100Continue()77 doExpect100Continue() 78 { 79 test::stream ts{ioc_}, tr{ioc_}; 80 ts.connect(tr); 81 yield_to( 82 [&](yield_context) 83 { 84 error_code ec; 85 flat_buffer buffer; 86 receive_expect_100_continue( 87 tr, buffer, ec); 88 BEAST_EXPECTS(! ec, ec.message()); 89 }, 90 [&](yield_context) 91 { 92 flat_buffer buffer; 93 request<string_body> req; 94 req.version(11); 95 req.method_string("POST"); 96 req.target("/"); 97 req.insert(field::user_agent, "test"); 98 req.body() = "Hello, world!"; 99 req.prepare_payload(); 100 101 error_code ec; 102 send_expect_100_continue( 103 ts, buffer, req, ec); 104 BEAST_EXPECTS(! ec, ec.message()); 105 }); 106 } 107 108 void doCgiResponse()109 doCgiResponse() 110 { 111 std::string const s = "Hello, world!"; 112 test::stream t0{ioc_, s}; 113 t0.read_size(3); 114 t0.close_remote(); 115 test::stream t1{ioc_}, t1r{ioc_}; 116 t1.connect(t1r); 117 error_code ec; 118 send_cgi_response(t0, t1, ec); 119 BEAST_EXPECTS(! ec, ec.message()); 120 BEAST_EXPECT(equal_body<false>(t1r.str(), s)); 121 } 122 123 void doRelay()124 doRelay() 125 { 126 request<string_body> req; 127 req.version(11); 128 req.method_string("POST"); 129 req.target("/"); 130 req.insert(field::user_agent, "test"); 131 req.body() = "Hello, world!"; 132 req.prepare_payload(); 133 134 test::stream ds{ioc_}, dsr{ioc_}; 135 ds.connect(dsr); 136 dsr.read_size(3); 137 test::stream us{ioc_}, usr{ioc_}; 138 us.connect(usr); 139 us.write_size(3); 140 141 error_code ec; 142 write(ds, req); 143 BEAST_EXPECTS(! ec, ec.message()); 144 ds.close(); 145 146 flat_buffer buffer; 147 relay<true>(us, dsr, buffer, ec, 148 [&](header<true, fields>& h, error_code& ev) 149 { 150 ev = {}; 151 h.erase("Content-Length"); 152 h.set("Transfer-Encoding", "chunked"); 153 }); 154 BEAST_EXPECTS(! ec, ec.message()); 155 BEAST_EXPECT(equal_body<true>( 156 usr.str(), req.body())); 157 } 158 159 void doReadStdStream()160 doReadStdStream() 161 { 162 std::string const s = 163 "HTTP/1.0 200 OK\r\n" 164 "User-Agent: test\r\n" 165 "\r\n" 166 "Hello, world!"; 167 std::istringstream is(s); 168 error_code ec; 169 flat_buffer buffer; 170 response<string_body> res; 171 read_istream(is, buffer, res, ec); 172 BEAST_EXPECTS(! ec, ec.message()); 173 BEAST_EXPECT(to_string(res) == s); 174 } 175 176 void doWriteStdStream()177 doWriteStdStream() 178 { 179 std::ostringstream os; 180 request<string_body> req; 181 req.version(11); 182 req.method(verb::get); 183 req.target("/"); 184 req.insert(field::user_agent, "test"); 185 error_code ec; 186 write_ostream(os, req, ec); 187 BEAST_EXPECTS(! ec, ec.message()); 188 BEAST_EXPECT(to_string(req) == os.str()); 189 } 190 191 void doHEAD()192 doHEAD() 193 { 194 test::stream ts{ioc_}, tr{ioc_}; 195 ts.connect(tr); 196 yield_to( 197 [&](yield_context) 198 { 199 error_code ec; 200 flat_buffer buffer; 201 do_server_head(tr, buffer, ec); 202 BEAST_EXPECTS(! ec, ec.message()); 203 }, 204 [&](yield_context) 205 { 206 error_code ec; 207 flat_buffer buffer; 208 auto res = do_head_request(ts, buffer, "/", ec); 209 BEAST_EXPECTS(! ec, ec.message()); 210 }); 211 } 212 213 struct handler 214 { 215 std::string body; 216 217 template<class Body> 218 void operator ()boost::beast::http::examples_test::handler219 operator()(request<Body>&&) 220 { 221 } 222 223 void operator ()boost::beast::http::examples_test::handler224 operator()(request<string_body>&& req) 225 { 226 body = req.body(); 227 } 228 }; 229 230 void doDeferredBody()231 doDeferredBody() 232 { 233 test::stream ts(ioc_, 234 "POST / HTTP/1.1\r\n" 235 "User-Agent: test\r\n" 236 "Content-Type: multipart/form-data\r\n" 237 "Content-Length: 13\r\n" 238 "\r\n" 239 "Hello, world!"); 240 241 handler h; 242 flat_buffer buffer; 243 do_form_request(ts, buffer, h); 244 BEAST_EXPECT(h.body == "Hello, world!"); 245 } 246 247 //-------------------------------------------------------------------------- 248 249 void doIncrementalRead()250 doIncrementalRead() 251 { 252 test::stream ts{ioc_}; 253 std::string s(2048, '*'); 254 ostream(ts.buffer()) << 255 "HTTP/1.1 200 OK\r\n" 256 "Content-Length: 2048\r\n" 257 "Server: test\r\n" 258 "\r\n" << 259 s; 260 error_code ec; 261 flat_buffer b; 262 std::stringstream ss; 263 read_and_print_body<false>(ss, ts, b, ec); 264 if(BEAST_EXPECTS(! ec, ec.message())) 265 BEAST_EXPECT(ss.str() == s); 266 } 267 268 //-------------------------------------------------------------------------- 269 270 void doExplicitChunkSerialize()271 doExplicitChunkSerialize() 272 { 273 auto const buf = 274 [](string_view s) 275 { 276 return net::const_buffer{ 277 s.data(), s.size()}; 278 }; 279 test::stream ts{ioc_}, tr{ioc_}; 280 ts.connect(tr); 281 282 response<empty_body> res{status::ok, 11}; 283 res.set(field::server, "test"); 284 res.set(field::accept, "Expires, Content-MD5"); 285 res.chunked(true); 286 287 error_code ec; 288 response_serializer<empty_body> sr{res}; 289 write_header(ts, sr, ec); 290 291 chunk_extensions exts; 292 293 net::write(ts, 294 make_chunk(buf("First")), ec); 295 296 exts.insert("quality", "1.0"); 297 net::write(ts, 298 make_chunk(buf("Hello, world!"), exts), ec); 299 300 exts.clear(); 301 exts.insert("file", "abc.txt"); 302 exts.insert("quality", "0.7"); 303 net::write(ts, 304 make_chunk(buf("The Next Chunk"), std::move(exts)), ec); 305 306 exts.clear(); 307 exts.insert("last"); 308 net::write(ts, 309 make_chunk(buf("Last one"), std::move(exts), 310 std::allocator<double>{}), ec); 311 312 fields trailers; 313 trailers.set(field::expires, "never"); 314 trailers.set(field::content_md5, "f4a5c16584f03d90"); 315 316 net::write(ts, 317 make_chunk_last( 318 trailers, 319 std::allocator<double>{} 320 ), ec); 321 BEAST_EXPECT( 322 buffers_to_string(tr.buffer().data()) == 323 "HTTP/1.1 200 OK\r\n" 324 "Server: test\r\n" 325 "Accept: Expires, Content-MD5\r\n" 326 "Transfer-Encoding: chunked\r\n" 327 "\r\n" 328 "5\r\n" 329 "First\r\n" 330 "d;quality=1.0\r\n" 331 "Hello, world!\r\n" 332 "e;file=abc.txt;quality=0.7\r\n" 333 "The Next Chunk\r\n" 334 "8;last\r\n" 335 "Last one\r\n" 336 "0\r\n" 337 "Expires: never\r\n" 338 "Content-MD5: f4a5c16584f03d90\r\n" 339 "\r\n"); 340 } 341 342 //-------------------------------------------------------------------------- 343 344 void doExplicitChunkParse()345 doExplicitChunkParse() 346 { 347 test::stream ts(ioc_, 348 "HTTP/1.1 200 OK\r\n" 349 "Server: test\r\n" 350 "Trailer: Expires, Content-MD5\r\n" 351 "Transfer-Encoding: chunked\r\n" 352 "\r\n" 353 "5\r\n" 354 "First\r\n" 355 "d;quality=1.0\r\n" 356 "Hello, world!\r\n" 357 "e;file=abc.txt;quality=0.7\r\n" 358 "The Next Chunk\r\n" 359 "8;last\r\n" 360 "Last one\r\n" 361 "0\r\n" 362 "Expires: never\r\n" 363 "Content-MD5: f4a5c16584f03d90\r\n" 364 "\r\n"); 365 366 367 error_code ec; 368 flat_buffer b; 369 std::stringstream ss; 370 print_chunked_body<false>(ss, ts, b, ec); 371 BEAST_EXPECTS(! ec, ec.message()); 372 BEAST_EXPECT(ss.str() == 373 "Chunk Body: First\n" 374 "Extension: quality = 1.0\n" 375 "Chunk Body: Hello, world!\n" 376 "Extension: file = abc.txt\n" 377 "Extension: quality = 0.7\n" 378 "Chunk Body: The Next Chunk\n" 379 "Extension: last\n" 380 "Chunk Body: Last one\n" 381 "Expires: never\n" 382 "Content-MD5: f4a5c16584f03d90\n"); 383 } 384 385 //-------------------------------------------------------------------------- 386 387 void run()388 run() 389 { 390 doExpect100Continue(); 391 doCgiResponse(); 392 doRelay(); 393 doReadStdStream(); 394 doWriteStdStream(); 395 doHEAD(); 396 doDeferredBody(); 397 doIncrementalRead(); 398 doExplicitChunkSerialize(); 399 doExplicitChunkParse(); 400 } 401 }; 402 403 BEAST_DEFINE_TESTSUITE(beast,http,examples); 404 405 } // http 406 } // beast 407 } // boost 408