]> git.proxmox.com Git - ceph.git/blob - ceph/src/Beast/test/http/design.cpp
bump version to 12.2.2-pve1
[ceph.git] / ceph / src / Beast / test / http / design.cpp
1 //
2 // Copyright (c) 2013-2017 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
8 #include <beast/core/flat_streambuf.hpp>
9 #include <beast/core/prepare_buffers.hpp>
10 #include <beast/http/chunk_encode.hpp>
11 #include <beast/http/read.hpp>
12 #include <beast/http/write.hpp>
13 #include <beast/http/string_body.hpp>
14 #include <beast/core/detail/clamp.hpp>
15 #include <beast/test/string_istream.hpp>
16 #include <beast/test/string_ostream.hpp>
17 #include <beast/test/yield_to.hpp>
18 #include <beast/unit_test/suite.hpp>
19 #include <boost/asio/read.hpp>
20 #include <boost/asio/write.hpp>
21
22 namespace beast {
23 namespace http {
24
25 class design_test
26 : public beast::unit_test::suite
27 , public beast::test::enable_yield_to
28 {
29 public:
30 //--------------------------------------------------------------------------
31 /*
32 Read a message with a direct Reader Body.
33 */
34 struct direct_body
35 {
36 using value_type = std::string;
37
38 class reader
39 {
40 value_type& body_;
41 std::size_t len_ = 0;
42
43 public:
44 static bool constexpr is_direct = true;
45
46 using mutable_buffers_type =
47 boost::asio::mutable_buffers_1;
48
49 template<bool isRequest, class Fields>
50 explicit
51 reader(message<isRequest, direct_body, Fields>& m)
52 : body_(m.body)
53 {
54 }
55
56 void
57 init()
58 {
59 }
60
61 void
62 init(std::uint64_t content_length)
63 {
64 if(content_length >
65 (std::numeric_limits<std::size_t>::max)())
66 throw std::length_error(
67 "Content-Length max exceeded");
68 body_.reserve(static_cast<
69 std::size_t>(content_length));
70 }
71
72 mutable_buffers_type
73 prepare(std::size_t n)
74 {
75 body_.resize(len_ + n);
76 return {&body_[len_], n};
77 }
78
79 void
80 commit(std::size_t n)
81 {
82 if(body_.size() > len_ + n)
83 body_.resize(len_ + n);
84 len_ = body_.size();
85 }
86
87 void
88 finish()
89 {
90 body_.resize(len_);
91 }
92 };
93 };
94
95 void
96 testDirectBody()
97 {
98 // Content-Length
99 {
100 test::string_istream is{ios_,
101 "GET / HTTP/1.1\r\n"
102 "Content-Length: 1\r\n"
103 "\r\n"
104 "*"
105 };
106 message<true, direct_body, fields> m;
107 flat_streambuf sb{1024};
108 read(is, sb, m);
109 BEAST_EXPECT(m.body == "*");
110 }
111
112 // end of file
113 {
114 test::string_istream is{ios_,
115 "HTTP/1.1 200 OK\r\n"
116 "\r\n" // 19 byte header
117 "*"
118 };
119 message<false, direct_body, fields> m;
120 flat_streambuf sb{20};
121 read(is, sb, m);
122 BEAST_EXPECT(m.body == "*");
123 }
124
125 // chunked
126 {
127 test::string_istream is{ios_,
128 "GET / HTTP/1.1\r\n"
129 "Transfer-Encoding: chunked\r\n"
130 "\r\n"
131 "1\r\n"
132 "*\r\n"
133 "0\r\n\r\n"
134 };
135 message<true, direct_body, fields> m;
136 flat_streambuf sb{100};
137 read(is, sb, m);
138 BEAST_EXPECT(m.body == "*");
139 }
140 }
141
142 //--------------------------------------------------------------------------
143 /*
144 Read a message with an indirect Reader Body.
145 */
146 struct indirect_body
147 {
148 using value_type = std::string;
149
150 class reader
151 {
152 value_type& body_;
153
154 public:
155 static bool constexpr is_direct = false;
156
157 using mutable_buffers_type =
158 boost::asio::null_buffers;
159
160 template<bool isRequest, class Fields>
161 explicit
162 reader(message<isRequest, indirect_body, Fields>& m)
163 : body_(m.body)
164 {
165 }
166
167 void
168 init(error_code& ec)
169 {
170 }
171
172 void
173 init(std::uint64_t content_length,
174 error_code& ec)
175 {
176 }
177
178 void
179 write(boost::string_ref const& s,
180 error_code& ec)
181 {
182 body_.append(s.data(), s.size());
183 }
184
185 void
186 finish(error_code& ec)
187 {
188 }
189 };
190 };
191
192 void
193 testIndirectBody()
194 {
195 // Content-Length
196 {
197 test::string_istream is{ios_,
198 "GET / HTTP/1.1\r\n"
199 "Content-Length: 1\r\n"
200 "\r\n"
201 "*"
202 };
203 message<true, indirect_body, fields> m;
204 flat_streambuf sb{1024};
205 read(is, sb, m);
206 BEAST_EXPECT(m.body == "*");
207 }
208
209 // end of file
210 {
211 test::string_istream is{ios_,
212 "HTTP/1.1 200 OK\r\n"
213 "\r\n" // 19 byte header
214 "*"
215 };
216 message<false, indirect_body, fields> m;
217 flat_streambuf sb{20};
218 read(is, sb, m);
219 BEAST_EXPECT(m.body == "*");
220 }
221
222
223 // chunked
224 {
225 test::string_istream is{ios_,
226 "GET / HTTP/1.1\r\n"
227 "Transfer-Encoding: chunked\r\n"
228 "\r\n"
229 "1\r\n"
230 "*\r\n"
231 "0\r\n\r\n"
232 };
233 message<true, indirect_body, fields> m;
234 flat_streambuf sb{1024};
235 read(is, sb, m);
236 BEAST_EXPECT(m.body == "*");
237 }
238 }
239
240 //--------------------------------------------------------------------------
241 /*
242 Read a message header and manually read the body.
243 */
244 void
245 testManualBody()
246 {
247 // Content-Length
248 {
249 test::string_istream is{ios_,
250 "GET / HTTP/1.1\r\n"
251 "Content-Length: 5\r\n"
252 "\r\n" // 37 byte header
253 "*****"
254 };
255 header_parser<true, fields> p;
256 flat_streambuf sb{38};
257 auto const bytes_used =
258 read_some(is, sb, p);
259 sb.consume(bytes_used);
260 BEAST_EXPECT(p.size() == 5);
261 BEAST_EXPECT(sb.size() < 5);
262 sb.commit(boost::asio::read(
263 is, sb.prepare(5 - sb.size())));
264 BEAST_EXPECT(sb.size() == 5);
265 }
266
267 // end of file
268 {
269 test::string_istream is{ios_,
270 "HTTP/1.1 200 OK\r\n"
271 "\r\n" // 19 byte header
272 "*****"
273 };
274 header_parser<false, fields> p;
275 flat_streambuf sb{20};
276 auto const bytes_used =
277 read_some(is, sb, p);
278 sb.consume(bytes_used);
279 BEAST_EXPECT(p.state() ==
280 parse_state::body_to_eof);
281 BEAST_EXPECT(sb.size() < 5);
282 sb.commit(boost::asio::read(
283 is, sb.prepare(5 - sb.size())));
284 BEAST_EXPECT(sb.size() == 5);
285 }
286 }
287
288 //--------------------------------------------------------------------------
289 /*
290 Read a header, check for Expect: 100-continue,
291 then conditionally read the body.
292 */
293 void
294 testExpect100Continue()
295 {
296 {
297 test::string_istream is{ios_,
298 "GET / HTTP/1.1\r\n"
299 "Expect: 100-continue\r\n"
300 "Content-Length: 5\r\n"
301 "\r\n"
302 "*****"
303 };
304
305 header_parser<true, fields> p;
306 flat_streambuf sb{128};
307 auto const bytes_used =
308 read_some(is, sb, p);
309 sb.consume(bytes_used);
310 BEAST_EXPECT(p.got_header());
311 BEAST_EXPECT(
312 p.get().fields["Expect"] ==
313 "100-continue");
314 message_parser<
315 true, string_body, fields> p1{
316 std::move(p)};
317 read(is, sb, p1);
318 BEAST_EXPECT(
319 p1.get().body == "*****");
320 }
321 }
322
323 //--------------------------------------------------------------------------
324 /*
325 Efficiently relay a message from one stream to another
326 */
327 template<
328 bool isRequest,
329 class SyncWriteStream,
330 class DynamicBuffer,
331 class SyncReadStream>
332 void
333 relay(
334 SyncWriteStream& out,
335 DynamicBuffer& sb,
336 SyncReadStream& in)
337 {
338 flat_streambuf buffer{4096}; // 4K limit
339 header_parser<isRequest, fields> parser;
340 error_code ec;
341 do
342 {
343 auto const state0 = parser.state();
344 auto const bytes_used =
345 read_some(in, buffer, parser, ec);
346 BEAST_EXPECTS(! ec, ec.message());
347 switch(state0)
348 {
349 case parse_state::header:
350 {
351 BEAST_EXPECT(parser.got_header());
352 write(out, parser.get());
353 break;
354 }
355
356 case parse_state::chunk_header:
357 {
358 // inspect parser.chunk_extension() here
359 if(parser.is_complete())
360 boost::asio::write(out,
361 chunk_encode_final());
362 break;
363 }
364
365 case parse_state::body:
366 case parse_state::body_to_eof:
367 case parse_state::chunk_body:
368 {
369 if(! parser.is_complete())
370 {
371 auto const body = parser.body();
372 boost::asio::write(out, chunk_encode(
373 false, boost::asio::buffer(
374 body.data(), body.size())));
375 }
376 break;
377 }
378
379 case parse_state::complete:
380 break;
381 }
382 buffer.consume(bytes_used);
383 }
384 while(! parser.is_complete());
385 }
386
387 void
388 testRelay()
389 {
390 // Content-Length
391 {
392 test::string_istream is{ios_,
393 "GET / HTTP/1.1\r\n"
394 "Content-Length: 5\r\n"
395 "\r\n" // 37 byte header
396 "*****",
397 3 // max_read
398 };
399 test::string_ostream os{ios_};
400 flat_streambuf sb{16};
401 relay<true>(os, sb, is);
402 }
403
404 // end of file
405 {
406 test::string_istream is{ios_,
407 "HTTP/1.1 200 OK\r\n"
408 "\r\n" // 19 byte header
409 "*****",
410 3 // max_read
411 };
412 test::string_ostream os{ios_};
413 flat_streambuf sb{16};
414 relay<false>(os, sb, is);
415 }
416
417 // chunked
418 {
419 test::string_istream is{ios_,
420 "GET / HTTP/1.1\r\n"
421 "Transfer-Encoding: chunked\r\n"
422 "\r\n"
423 "5;x;y=1;z=\"-\"\r\n*****\r\n"
424 "3\r\n---\r\n"
425 "1\r\n+\r\n"
426 "0\r\n\r\n",
427 2 // max_read
428 };
429 test::string_ostream os{ios_};
430 flat_streambuf sb{16};
431 relay<true>(os, sb, is);
432 }
433 }
434
435 //--------------------------------------------------------------------------
436 /*
437 Read the request header, then read the request body content using
438 a fixed-size buffer, i.e. read the body in chunks of 4k for instance.
439 The end of the body should be indicated somehow and chunk-encoding
440 should be decoded by beast.
441 */
442 template<bool isRequest,
443 class SyncReadStream, class BodyCallback>
444 void
445 doFixedRead(SyncReadStream& stream, BodyCallback const& cb)
446 {
447 flat_streambuf buffer{4096}; // 4K limit
448 header_parser<isRequest, fields> parser;
449 std::size_t bytes_used;
450 bytes_used = read_some(stream, buffer, parser);
451 BEAST_EXPECT(parser.got_header());
452 buffer.consume(bytes_used);
453 do
454 {
455 bytes_used =
456 read_some(stream, buffer, parser);
457 if(! parser.body().empty())
458 cb(parser.body());
459 buffer.consume(bytes_used);
460 }
461 while(! parser.is_complete());
462 }
463
464 struct bodyHandler
465 {
466 void
467 operator()(boost::string_ref const& body) const
468 {
469 // called for each piece of the body,
470 }
471 };
472
473 void
474 testFixedRead()
475 {
476 using boost::asio::buffer;
477 using boost::asio::buffer_cast;
478 using boost::asio::buffer_size;
479
480 // Content-Length
481 {
482 test::string_istream is{ios_,
483 "GET / HTTP/1.1\r\n"
484 "Content-Length: 1\r\n"
485 "\r\n"
486 "*"
487 };
488 doFixedRead<true>(is, bodyHandler{});
489 }
490
491 // end of file
492 {
493 test::string_istream is{ios_,
494 "HTTP/1.1 200 OK\r\n"
495 "\r\n" // 19 byte header
496 "*****"
497 };
498 doFixedRead<false>(is, bodyHandler{});
499 }
500
501 // chunked
502 {
503 test::string_istream is{ios_,
504 "GET / HTTP/1.1\r\n"
505 "Transfer-Encoding: chunked\r\n"
506 "\r\n"
507 "5;x;y=1;z=\"-\"\r\n*****\r\n"
508 "3\r\n---\r\n"
509 "1\r\n+\r\n"
510 "0\r\n\r\n",
511 2 // max_read
512 };
513 doFixedRead<true>(is, bodyHandler{});
514 }
515 }
516
517 //--------------------------------------------------------------------------
518
519 void
520 run()
521 {
522 testDirectBody();
523 testIndirectBody();
524 testManualBody();
525 testExpect100Continue();
526 testRelay();
527 testFixedRead();
528 }
529 };
530
531 BEAST_DEFINE_TESTSUITE(design,http,beast);
532
533 } // http
534 } // beast