]>
Commit | Line | Data |
---|---|---|
b32b8144 | 1 | // |
92f5a8d4 | 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) |
b32b8144 FG |
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 | ||
92f5a8d4 TL |
10 | #ifndef BOOST_BEAST_HTTP_IMPL_FILE_BODY_WIN32_HPP |
11 | #define BOOST_BEAST_HTTP_IMPL_FILE_BODY_WIN32_HPP | |
b32b8144 FG |
12 | |
13 | #if BOOST_BEAST_USE_WIN32_FILE | |
14 | ||
92f5a8d4 | 15 | #include <boost/beast/core/async_base.hpp> |
b32b8144 | 16 | #include <boost/beast/core/bind_handler.hpp> |
92f5a8d4 | 17 | #include <boost/beast/core/buffers_range.hpp> |
b32b8144 | 18 | #include <boost/beast/core/detail/clamp.hpp> |
92f5a8d4 | 19 | #include <boost/beast/core/detail/is_invocable.hpp> |
f67539c2 | 20 | #include <boost/beast/http/error.hpp> |
92f5a8d4 | 21 | #include <boost/beast/http/write.hpp> |
b32b8144 | 22 | #include <boost/beast/http/serializer.hpp> |
b32b8144 FG |
23 | #include <boost/asio/async_result.hpp> |
24 | #include <boost/asio/basic_stream_socket.hpp> | |
b32b8144 FG |
25 | #include <boost/asio/windows/overlapped_ptr.hpp> |
26 | #include <boost/make_unique.hpp> | |
27 | #include <boost/smart_ptr/make_shared_array.hpp> | |
28 | #include <boost/winapi/basic_types.hpp> | |
f67539c2 | 29 | #include <boost/winapi/error_codes.hpp> |
11fdf7f2 | 30 | #include <boost/winapi/get_last_error.hpp> |
b32b8144 FG |
31 | #include <algorithm> |
32 | #include <cstring> | |
33 | ||
34 | namespace boost { | |
35 | namespace beast { | |
36 | namespace http { | |
37 | ||
38 | namespace detail { | |
92f5a8d4 | 39 | template<class, class, bool, class, class> |
b32b8144 FG |
40 | class write_some_win32_op; |
41 | } // detail | |
42 | ||
43 | template<> | |
44 | struct basic_file_body<file_win32> | |
45 | { | |
46 | using file_type = file_win32; | |
47 | ||
48 | class writer; | |
49 | class reader; | |
50 | ||
51 | //-------------------------------------------------------------------------- | |
52 | ||
53 | class value_type | |
54 | { | |
55 | friend class writer; | |
56 | friend class reader; | |
57 | friend struct basic_file_body<file_win32>; | |
58 | ||
92f5a8d4 | 59 | template<class, class, bool, class, class> |
b32b8144 FG |
60 | friend class detail::write_some_win32_op; |
61 | template< | |
92f5a8d4 TL |
62 | class Protocol, class Executor, |
63 | bool isRequest, class Fields> | |
b32b8144 FG |
64 | friend |
65 | std::size_t | |
66 | write_some( | |
92f5a8d4 | 67 | net::basic_stream_socket<Protocol, Executor>& sock, |
b32b8144 FG |
68 | serializer<isRequest, |
69 | basic_file_body<file_win32>, Fields>& sr, | |
70 | error_code& ec); | |
71 | ||
72 | file_win32 file_; | |
73 | std::uint64_t size_ = 0; // cached file size | |
74 | std::uint64_t first_; // starting offset of the range | |
75 | std::uint64_t last_; // ending offset of the range | |
76 | ||
77 | public: | |
78 | ~value_type() = default; | |
79 | value_type() = default; | |
80 | value_type(value_type&& other) = default; | |
81 | value_type& operator=(value_type&& other) = default; | |
82 | ||
f67539c2 TL |
83 | file_win32& file() |
84 | { | |
85 | return file_; | |
86 | } | |
87 | ||
b32b8144 FG |
88 | bool |
89 | is_open() const | |
90 | { | |
91 | return file_.is_open(); | |
92 | } | |
93 | ||
94 | std::uint64_t | |
95 | size() const | |
96 | { | |
97 | return size_; | |
98 | } | |
99 | ||
100 | void | |
101 | close(); | |
102 | ||
103 | void | |
104 | open(char const* path, file_mode mode, error_code& ec); | |
105 | ||
106 | void | |
107 | reset(file_win32&& file, error_code& ec); | |
108 | }; | |
109 | ||
110 | //-------------------------------------------------------------------------- | |
111 | ||
112 | class writer | |
113 | { | |
92f5a8d4 | 114 | template<class, class, bool, class, class> |
b32b8144 FG |
115 | friend class detail::write_some_win32_op; |
116 | template< | |
92f5a8d4 TL |
117 | class Protocol, class Executor, |
118 | bool isRequest, class Fields> | |
b32b8144 FG |
119 | friend |
120 | std::size_t | |
121 | write_some( | |
92f5a8d4 | 122 | net::basic_stream_socket<Protocol, Executor>& sock, |
b32b8144 FG |
123 | serializer<isRequest, |
124 | basic_file_body<file_win32>, Fields>& sr, | |
125 | error_code& ec); | |
126 | ||
127 | value_type& body_; // The body we are reading from | |
128 | std::uint64_t pos_; // The current position in the file | |
129 | char buf_[4096]; // Small buffer for reading | |
130 | ||
131 | public: | |
132 | using const_buffers_type = | |
92f5a8d4 | 133 | net::const_buffer; |
b32b8144 FG |
134 | |
135 | template<bool isRequest, class Fields> | |
11fdf7f2 TL |
136 | writer(header<isRequest, Fields>&, value_type& b) |
137 | : body_(b) | |
f67539c2 | 138 | , pos_(body_.first_) |
b32b8144 | 139 | { |
f67539c2 | 140 | BOOST_ASSERT(body_.file_.is_open()); |
b32b8144 FG |
141 | } |
142 | ||
143 | void | |
f67539c2 | 144 | init(error_code& ec) |
b32b8144 FG |
145 | { |
146 | BOOST_ASSERT(body_.file_.is_open()); | |
f67539c2 | 147 | ec.clear(); |
b32b8144 FG |
148 | } |
149 | ||
150 | boost::optional<std::pair<const_buffers_type, bool>> | |
151 | get(error_code& ec) | |
152 | { | |
153 | std::size_t const n = (std::min)(sizeof(buf_), | |
154 | beast::detail::clamp(body_.last_ - pos_)); | |
155 | if(n == 0) | |
156 | { | |
92f5a8d4 | 157 | ec = {}; |
b32b8144 FG |
158 | return boost::none; |
159 | } | |
160 | auto const nread = body_.file_.read(buf_, n, ec); | |
161 | if(ec) | |
162 | return boost::none; | |
f67539c2 TL |
163 | if (nread == 0) |
164 | { | |
165 | ec = error::short_read; | |
166 | return boost::none; | |
167 | } | |
b32b8144 FG |
168 | BOOST_ASSERT(nread != 0); |
169 | pos_ += nread; | |
92f5a8d4 | 170 | ec = {}; |
b32b8144 FG |
171 | return {{ |
172 | {buf_, nread}, // buffer to return. | |
173 | pos_ < body_.last_}}; // `true` if there are more buffers. | |
174 | } | |
175 | }; | |
176 | ||
177 | //-------------------------------------------------------------------------- | |
178 | ||
179 | class reader | |
180 | { | |
181 | value_type& body_; | |
182 | ||
183 | public: | |
184 | template<bool isRequest, class Fields> | |
185 | explicit | |
11fdf7f2 TL |
186 | reader(header<isRequest, Fields>&, value_type& b) |
187 | : body_(b) | |
b32b8144 FG |
188 | { |
189 | } | |
190 | ||
191 | void | |
192 | init(boost::optional< | |
193 | std::uint64_t> const& content_length, | |
194 | error_code& ec) | |
195 | { | |
196 | // VFALCO We could reserve space in the file | |
197 | boost::ignore_unused(content_length); | |
198 | BOOST_ASSERT(body_.file_.is_open()); | |
92f5a8d4 | 199 | ec = {}; |
b32b8144 FG |
200 | } |
201 | ||
202 | template<class ConstBufferSequence> | |
203 | std::size_t | |
204 | put(ConstBufferSequence const& buffers, | |
205 | error_code& ec) | |
206 | { | |
207 | std::size_t nwritten = 0; | |
92f5a8d4 | 208 | for(auto buffer : beast::buffers_range_ref(buffers)) |
b32b8144 FG |
209 | { |
210 | nwritten += body_.file_.write( | |
211 | buffer.data(), buffer.size(), ec); | |
212 | if(ec) | |
213 | return nwritten; | |
214 | } | |
92f5a8d4 | 215 | ec = {}; |
b32b8144 FG |
216 | return nwritten; |
217 | } | |
218 | ||
219 | void | |
220 | finish(error_code& ec) | |
221 | { | |
92f5a8d4 | 222 | ec = {}; |
b32b8144 FG |
223 | } |
224 | }; | |
225 | ||
226 | //-------------------------------------------------------------------------- | |
227 | ||
228 | static | |
229 | std::uint64_t | |
230 | size(value_type const& body) | |
231 | { | |
232 | return body.size(); | |
233 | } | |
234 | }; | |
235 | ||
236 | //------------------------------------------------------------------------------ | |
237 | ||
238 | inline | |
239 | void | |
240 | basic_file_body<file_win32>:: | |
241 | value_type:: | |
242 | close() | |
243 | { | |
244 | error_code ignored; | |
245 | file_.close(ignored); | |
246 | } | |
247 | ||
248 | inline | |
249 | void | |
250 | basic_file_body<file_win32>:: | |
251 | value_type:: | |
252 | open(char const* path, file_mode mode, error_code& ec) | |
253 | { | |
254 | file_.open(path, mode, ec); | |
255 | if(ec) | |
256 | return; | |
257 | size_ = file_.size(ec); | |
258 | if(ec) | |
259 | { | |
260 | close(); | |
261 | return; | |
262 | } | |
263 | first_ = 0; | |
264 | last_ = size_; | |
265 | } | |
266 | ||
267 | inline | |
268 | void | |
269 | basic_file_body<file_win32>:: | |
270 | value_type:: | |
271 | reset(file_win32&& file, error_code& ec) | |
272 | { | |
273 | if(file_.is_open()) | |
274 | { | |
275 | error_code ignored; | |
276 | file_.close(ignored); | |
277 | } | |
278 | file_ = std::move(file); | |
279 | if(file_.is_open()) | |
280 | { | |
281 | size_ = file_.size(ec); | |
282 | if(ec) | |
283 | { | |
284 | close(); | |
285 | return; | |
286 | } | |
287 | first_ = 0; | |
288 | last_ = size_; | |
289 | } | |
290 | } | |
291 | ||
292 | //------------------------------------------------------------------------------ | |
293 | ||
294 | namespace detail { | |
295 | ||
296 | template<class Unsigned> | |
b32b8144 FG |
297 | boost::winapi::DWORD_ |
298 | lowPart(Unsigned n) | |
299 | { | |
300 | return static_cast< | |
301 | boost::winapi::DWORD_>( | |
302 | n & 0xffffffff); | |
303 | } | |
304 | ||
305 | template<class Unsigned> | |
b32b8144 FG |
306 | boost::winapi::DWORD_ |
307 | highPart(Unsigned n, std::true_type) | |
308 | { | |
309 | return static_cast< | |
310 | boost::winapi::DWORD_>( | |
311 | (n>>32)&0xffffffff); | |
312 | } | |
313 | ||
314 | template<class Unsigned> | |
b32b8144 FG |
315 | boost::winapi::DWORD_ |
316 | highPart(Unsigned, std::false_type) | |
317 | { | |
318 | return 0; | |
319 | } | |
320 | ||
321 | template<class Unsigned> | |
b32b8144 FG |
322 | boost::winapi::DWORD_ |
323 | highPart(Unsigned n) | |
324 | { | |
325 | return highPart(n, std::integral_constant< | |
326 | bool, (sizeof(Unsigned)>4)>{}); | |
327 | } | |
328 | ||
329 | class null_lambda | |
330 | { | |
331 | public: | |
332 | template<class ConstBufferSequence> | |
333 | void | |
334 | operator()(error_code&, | |
335 | ConstBufferSequence const&) const | |
336 | { | |
337 | BOOST_ASSERT(false); | |
338 | } | |
339 | }; | |
340 | ||
f67539c2 TL |
341 | // https://github.com/boostorg/beast/issues/1815 |
342 | // developer commentary: | |
343 | // This function mimics the behaviour of ASIO. | |
344 | // Perhaps the correct fix is to insist on the use | |
345 | // of an appropriate error_condition to detect | |
346 | // connection_reset and connection_refused? | |
347 | inline | |
348 | error_code | |
349 | make_win32_error( | |
350 | boost::winapi::DWORD_ dwError) noexcept | |
351 | { | |
352 | // from | |
353 | // https://github.com/boostorg/asio/blob/6534af41b471288091ae39f9ab801594189b6fc9/include/boost/asio/detail/impl/socket_ops.ipp#L842 | |
354 | switch(dwError) | |
355 | { | |
356 | case boost::winapi::ERROR_NETNAME_DELETED_: | |
357 | return net::error::connection_reset; | |
358 | case boost::winapi::ERROR_PORT_UNREACHABLE_: | |
359 | return net::error::connection_refused; | |
360 | case boost::winapi::WSAEMSGSIZE_: | |
361 | case boost::winapi::ERROR_MORE_DATA_: | |
362 | return {}; | |
363 | } | |
364 | return error_code( | |
365 | static_cast<int>(dwError), | |
366 | system_category()); | |
367 | } | |
368 | ||
369 | inline | |
370 | error_code | |
371 | make_win32_error( | |
372 | error_code ec) noexcept | |
373 | { | |
374 | if(ec.category() != | |
375 | system_category()) | |
376 | return ec; | |
377 | return make_win32_error( | |
378 | static_cast<boost::winapi::DWORD_>( | |
379 | ec.value())); | |
380 | } | |
381 | ||
b32b8144 FG |
382 | //------------------------------------------------------------------------------ |
383 | ||
384 | #if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR | |
385 | ||
386 | template< | |
92f5a8d4 TL |
387 | class Protocol, class Executor, |
388 | bool isRequest, class Fields, | |
389 | class Handler> | |
b32b8144 | 390 | class write_some_win32_op |
92f5a8d4 | 391 | : public beast::async_base<Handler, Executor> |
b32b8144 | 392 | { |
92f5a8d4 TL |
393 | net::basic_stream_socket< |
394 | Protocol, Executor>& sock_; | |
b32b8144 FG |
395 | serializer<isRequest, |
396 | basic_file_body<file_win32>, Fields>& sr_; | |
b32b8144 FG |
397 | bool header_ = false; |
398 | ||
399 | public: | |
92f5a8d4 | 400 | template<class Handler_> |
b32b8144 | 401 | write_some_win32_op( |
92f5a8d4 TL |
402 | Handler_&& h, |
403 | net::basic_stream_socket< | |
404 | Protocol, Executor>& s, | |
b32b8144 FG |
405 | serializer<isRequest, |
406 | basic_file_body<file_win32>,Fields>& sr) | |
92f5a8d4 TL |
407 | : async_base< |
408 | Handler, Executor>( | |
409 | std::forward<Handler_>(h), | |
410 | s.get_executor()) | |
411 | , sock_(s) | |
b32b8144 | 412 | , sr_(sr) |
b32b8144 | 413 | { |
92f5a8d4 | 414 | (*this)(); |
b32b8144 FG |
415 | } |
416 | ||
92f5a8d4 TL |
417 | void |
418 | operator()() | |
b32b8144 | 419 | { |
92f5a8d4 TL |
420 | if(! sr_.is_header_done()) |
421 | { | |
422 | header_ = true; | |
423 | sr_.split(true); | |
424 | return detail::async_write_some_impl( | |
425 | sock_, sr_, std::move(*this)); | |
426 | } | |
427 | if(sr_.get().chunked()) | |
428 | { | |
429 | return detail::async_write_some_impl( | |
430 | sock_, sr_, std::move(*this)); | |
431 | } | |
432 | auto& w = sr_.writer_impl(); | |
433 | boost::winapi::DWORD_ const nNumberOfBytesToWrite = | |
434 | static_cast<boost::winapi::DWORD_>( | |
435 | (std::min<std::uint64_t>)( | |
436 | (std::min<std::uint64_t>)(w.body_.last_ - w.pos_, sr_.limit()), | |
437 | (std::numeric_limits<boost::winapi::DWORD_>::max)())); | |
438 | net::windows::overlapped_ptr overlapped{ | |
439 | sock_.get_executor(), std::move(*this)}; | |
440 | // Note that we have moved *this, so we cannot access | |
441 | // the handler since it is now moved-from. We can still | |
442 | // access simple things like references and built-in types. | |
443 | auto& ov = *overlapped.get(); | |
444 | ov.Offset = lowPart(w.pos_); | |
445 | ov.OffsetHigh = highPart(w.pos_); | |
446 | auto const bSuccess = ::TransmitFile( | |
447 | sock_.native_handle(), | |
448 | sr_.get().body().file_.native_handle(), | |
449 | nNumberOfBytesToWrite, | |
450 | 0, | |
451 | overlapped.get(), | |
452 | nullptr, | |
453 | 0); | |
454 | auto const dwError = boost::winapi::GetLastError(); | |
455 | if(! bSuccess && dwError != | |
456 | boost::winapi::ERROR_IO_PENDING_) | |
457 | { | |
458 | // VFALCO This needs review, is 0 the right number? | |
459 | // completed immediately (with error?) | |
f67539c2 TL |
460 | overlapped.complete( |
461 | make_win32_error(dwError), 0); | |
92f5a8d4 TL |
462 | return; |
463 | } | |
464 | overlapped.release(); | |
b32b8144 FG |
465 | } |
466 | ||
b32b8144 FG |
467 | void |
468 | operator()( | |
469 | error_code ec, | |
92f5a8d4 | 470 | std::size_t bytes_transferred = 0) |
11fdf7f2 | 471 | { |
f67539c2 TL |
472 | if(ec) |
473 | { | |
474 | ec = make_win32_error(ec); | |
475 | } | |
476 | else if(! ec && ! header_) | |
92f5a8d4 | 477 | { |
92f5a8d4 TL |
478 | auto& w = sr_.writer_impl(); |
479 | w.pos_ += bytes_transferred; | |
480 | BOOST_ASSERT(w.pos_ <= w.body_.last_); | |
481 | if(w.pos_ >= w.body_.last_) | |
482 | { | |
483 | sr_.next(ec, null_lambda{}); | |
484 | BOOST_ASSERT(! ec); | |
485 | BOOST_ASSERT(sr_.is_done()); | |
486 | } | |
487 | } | |
f67539c2 | 488 | this->complete_now(ec, bytes_transferred); |
11fdf7f2 | 489 | } |
b32b8144 FG |
490 | }; |
491 | ||
92f5a8d4 | 492 | struct run_write_some_win32_op |
b32b8144 | 493 | { |
92f5a8d4 TL |
494 | template< |
495 | class Protocol, class Executor, | |
496 | bool isRequest, class Fields, | |
497 | class WriteHandler> | |
498 | void | |
499 | operator()( | |
500 | WriteHandler&& h, | |
501 | net::basic_stream_socket< | |
502 | Protocol, Executor>* s, | |
503 | serializer<isRequest, | |
504 | basic_file_body<file_win32>, Fields>* sr) | |
b32b8144 | 505 | { |
92f5a8d4 TL |
506 | // If you get an error on the following line it means |
507 | // that your handler does not meet the documented type | |
508 | // requirements for the handler. | |
509 | ||
510 | static_assert( | |
511 | beast::detail::is_invocable<WriteHandler, | |
512 | void(error_code, std::size_t)>::value, | |
513 | "WriteHandler type requirements not met"); | |
514 | ||
515 | write_some_win32_op< | |
516 | Protocol, Executor, | |
517 | isRequest, Fields, | |
518 | typename std::decay<WriteHandler>::type>( | |
519 | std::forward<WriteHandler>(h), *s, *sr); | |
b32b8144 | 520 | } |
92f5a8d4 | 521 | }; |
b32b8144 FG |
522 | |
523 | #endif | |
524 | ||
525 | } // detail | |
526 | ||
527 | //------------------------------------------------------------------------------ | |
528 | ||
92f5a8d4 TL |
529 | template< |
530 | class Protocol, class Executor, | |
531 | bool isRequest, class Fields> | |
b32b8144 FG |
532 | std::size_t |
533 | write_some( | |
92f5a8d4 TL |
534 | net::basic_stream_socket< |
535 | Protocol, Executor>& sock, | |
b32b8144 FG |
536 | serializer<isRequest, |
537 | basic_file_body<file_win32>, Fields>& sr, | |
538 | error_code& ec) | |
539 | { | |
540 | if(! sr.is_header_done()) | |
541 | { | |
542 | sr.split(true); | |
543 | auto const bytes_transferred = | |
11fdf7f2 | 544 | detail::write_some_impl(sock, sr, ec); |
b32b8144 FG |
545 | if(ec) |
546 | return bytes_transferred; | |
547 | return bytes_transferred; | |
548 | } | |
549 | if(sr.get().chunked()) | |
550 | { | |
551 | auto const bytes_transferred = | |
11fdf7f2 | 552 | detail::write_some_impl(sock, sr, ec); |
b32b8144 FG |
553 | if(ec) |
554 | return bytes_transferred; | |
555 | return bytes_transferred; | |
556 | } | |
11fdf7f2 TL |
557 | auto& w = sr.writer_impl(); |
558 | w.body_.file_.seek(w.pos_, ec); | |
b32b8144 FG |
559 | if(ec) |
560 | return 0; | |
561 | boost::winapi::DWORD_ const nNumberOfBytesToWrite = | |
562 | static_cast<boost::winapi::DWORD_>( | |
563 | (std::min<std::uint64_t>)( | |
11fdf7f2 | 564 | (std::min<std::uint64_t>)(w.body_.last_ - w.pos_, sr.limit()), |
b32b8144 FG |
565 | (std::numeric_limits<boost::winapi::DWORD_>::max)())); |
566 | auto const bSuccess = ::TransmitFile( | |
567 | sock.native_handle(), | |
11fdf7f2 | 568 | w.body_.file_.native_handle(), |
b32b8144 FG |
569 | nNumberOfBytesToWrite, |
570 | 0, | |
571 | nullptr, | |
572 | nullptr, | |
573 | 0); | |
574 | if(! bSuccess) | |
575 | { | |
f67539c2 TL |
576 | ec = detail::make_win32_error( |
577 | boost::winapi::GetLastError()); | |
b32b8144 FG |
578 | return 0; |
579 | } | |
11fdf7f2 TL |
580 | w.pos_ += nNumberOfBytesToWrite; |
581 | BOOST_ASSERT(w.pos_ <= w.body_.last_); | |
582 | if(w.pos_ < w.body_.last_) | |
b32b8144 | 583 | { |
92f5a8d4 | 584 | ec = {}; |
b32b8144 FG |
585 | } |
586 | else | |
587 | { | |
588 | sr.next(ec, detail::null_lambda{}); | |
589 | BOOST_ASSERT(! ec); | |
590 | BOOST_ASSERT(sr.is_done()); | |
591 | } | |
592 | return nNumberOfBytesToWrite; | |
593 | } | |
594 | ||
595 | #if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR | |
596 | ||
597 | template< | |
92f5a8d4 | 598 | class Protocol, class Executor, |
b32b8144 | 599 | bool isRequest, class Fields, |
20effc67 | 600 | BOOST_BEAST_ASYNC_TPARAM2 WriteHandler> |
92f5a8d4 | 601 | BOOST_BEAST_ASYNC_RESULT2(WriteHandler) |
b32b8144 | 602 | async_write_some( |
92f5a8d4 TL |
603 | net::basic_stream_socket< |
604 | Protocol, Executor>& sock, | |
b32b8144 FG |
605 | serializer<isRequest, |
606 | basic_file_body<file_win32>, Fields>& sr, | |
607 | WriteHandler&& handler) | |
92f5a8d4 TL |
608 | { |
609 | return net::async_initiate< | |
610 | WriteHandler, | |
611 | void(error_code, std::size_t)>( | |
612 | detail::run_write_some_win32_op{}, | |
613 | handler, | |
614 | &sock, | |
615 | &sr); | |
b32b8144 FG |
616 | } |
617 | ||
618 | #endif | |
619 | ||
620 | } // http | |
621 | } // beast | |
622 | } // boost | |
623 | ||
624 | #endif | |
625 | ||
626 | #endif |