]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 |