diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2016-03-14 14:38:45 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2016-03-17 12:59:35 +0300 |
commit | 0b6b57f9acaa2ec648bf582ff67851331f8e6eef (patch) | |
tree | 7ce5da6a1c37f3674762d5514b0a34bf05e38df7 /web/apache | |
parent | 637d5650b91cb1da2605e5f7049ccc8bab5591f3 (diff) |
Use serializable transaction isolation level
Diffstat (limited to 'web/apache')
-rw-r--r-- | web/apache/log | 6 | ||||
-rw-r--r-- | web/apache/request | 121 | ||||
-rw-r--r-- | web/apache/request.cxx | 147 | ||||
-rw-r--r-- | web/apache/request.ixx | 43 | ||||
-rw-r--r-- | web/apache/service | 13 | ||||
-rw-r--r-- | web/apache/service.cxx | 42 | ||||
-rw-r--r-- | web/apache/service.txx | 44 | ||||
-rw-r--r-- | web/apache/stream | 92 |
8 files changed, 316 insertions, 192 deletions
diff --git a/web/apache/log b/web/apache/log index 7318e32..291f8de 100644 --- a/web/apache/log +++ b/web/apache/log @@ -5,7 +5,7 @@ #ifndef WEB_APACHE_LOG #define WEB_APACHE_LOG -#include <httpd.h> // request_rec +#include <httpd.h> // request_rec, server_rec #include <http_log.h> #include <http_config.h> // module @@ -44,7 +44,7 @@ namespace web const char* msg) const noexcept { if (file && *file) - file = nullptr; // skip file/line placeholder from log line. + file = nullptr; // Skip file/line placeholder from log line. level = std::min (level, APLOG_TRACE8); @@ -59,7 +59,7 @@ namespace web func, msg); else - // skip function name placeholder from log line + // Skip function name placeholder from log line. // ap_log_error (file, line, diff --git a/web/apache/request b/web/apache/request index ab69dff..9540561 100644 --- a/web/apache/request +++ b/web/apache/request @@ -5,23 +5,14 @@ #ifndef WEB_APACHE_REQUEST #define WEB_APACHE_REQUEST -#include <apr_strings.h> +#include <httpd.h> // request_rec, HTTP_*, OK, M_POST -#include <httpd.h> -#include <http_core.h> -#include <util_script.h> - -#include <ios> #include <chrono> #include <memory> // unique_ptr #include <string> -#include <cassert> #include <istream> #include <ostream> -#include <utility> // move() #include <streambuf> -#include <stdexcept> -#include <exception> #include <web/module> #include <web/apache/stream> @@ -30,23 +21,63 @@ namespace web { namespace apache { + // The state of the request processing, reflecting an interaction with + // Apache API (like reading/writing content function calls), with no + // buffering taken into account. Any state different from the initial + // suppose that some irrevocable interaction with Apache API have + // happened, so request processing should be either completed, or + // reported as failed. State values are ordered in a sense that the + // higher value reflects the more advanced stage of processing, so the + // request current state value may not decrease. + // + enum class request_state + { + // Denotes the initial stage of the request handling. At this stage + // the request line and headers are already parsed by Apache. + // + initial, + + // Reading the request content. + // + reading, + + // Adding the response headers (cookies in particular). + // + headers, + + // Writing the response content. + // + writing + }; + class request: public web::request, public web::response, - public write_state + public stream_state { friend class service; - request (request_rec* rec) noexcept: rec_ (rec) {rec_->status = HTTP_OK;} + request (request_rec* rec) noexcept + : rec_ (rec) + { + rec_->status = HTTP_OK; + } + + request_state + state () const noexcept {return state_;} - // Flush of buffered content. + // Flush the buffered response content if present. The returned value + // should be passed to Apache API on request handler exit. // int flush (); - // Return true if content have been sent to the client, false otherwise. + // Prepare for the request re-processing if possible (no unbuffered + // read/write operations have been done). Throw sequence_error + // otherwise. In particular, the preparation can include the response + // content buffer cleanup, the request content buffer rewind. // - bool - get_write_state () const noexcept {return write_state_;} + void + rewind (); // Get request path. // @@ -56,7 +87,7 @@ namespace web // Get request body data stream. // virtual std::istream& - content (); + content (bool buffer = false); // Get request parameters. // @@ -70,7 +101,8 @@ namespace web // Get response status code. // - status_code status () const noexcept {return rec_->status;} + status_code + status () const noexcept {return rec_->status;} // Set response status code. // @@ -89,10 +121,11 @@ namespace web virtual void cookie (const char* name, const char* value, - const std::chrono::seconds* max_age = 0, - const char* path = 0, - const char* domain = 0, - bool secure = false); + const std::chrono::seconds* max_age = nullptr, + const char* path = nullptr, + const char* domain = nullptr, + bool secure = false, + bool buffer = true); private: // Get application/x-www-form-urlencoded form data. @@ -103,37 +136,35 @@ namespace web void parse_parameters (const char* args); + // Advance the request processing state. Noop if new state is equal to + // the current one. Throw sequence_error if the new state is less then + // the current one. Can throw invalid_request if HTTP request is + // malformed. + // + void + state (request_state); + + // stream_state members implementation. + // virtual void - set_write_state () - { - if (!write_state_) - { - // Preparing to write a response read and discard request - // body if any. - // - int r (ap_discard_request_body (rec_)); - - if (r != OK) - { - throw invalid_request (r); - } - - write_state_ = true; - } - } + set_read_state () {state (request_state::reading);} + + virtual void + set_write_state () {state (request_state::writing);} private: request_rec* rec_; - bool buffer_ {true}; - bool write_state_ {false}; - std::unique_ptr<std::streambuf> out_buf_; - std::unique_ptr<std::ostream> out_; - std::unique_ptr<std::streambuf> in_buf_; - std::unique_ptr<std::istream> in_; + request_state state_ = request_state::initial; + path_type path_; std::unique_ptr<name_values> parameters_; std::unique_ptr<name_values> cookies_; std::unique_ptr<std::string> form_data_; + std::unique_ptr<std::streambuf> in_buf_; + std::unique_ptr<std::istream> in_; + + std::unique_ptr<std::streambuf> out_buf_; + std::unique_ptr<std::ostream> out_; }; } } diff --git a/web/apache/request.cxx b/web/apache/request.cxx index 2e03190..b0b4d61 100644 --- a/web/apache/request.cxx +++ b/web/apache/request.cxx @@ -4,22 +4,30 @@ #include <web/apache/request> -#include <apr_tables.h> +#include <apr_tables.h> // apr_table_*, apr_array_header_t +#include <apr_strings.h> // apr_pstrdup() + +#include <httpd.h> // request_rec, HTTP_*, OK +#include <http_protocol.h> // ap_*() #include <strings.h> // strcasecmp() -#include <ios> -#include <ctime> +#include <ctime> // strftime(), time_t #include <chrono> #include <memory> // unique_ptr +#include <string> +#include <cassert> #include <sstream> #include <ostream> #include <istream> -#include <cstring> +#include <cstring> // str*(), size_t #include <utility> // move() -#include <stdexcept> +#include <stdexcept> // invalid_argument +#include <exception> // current_exception() #include <streambuf> +#include <butl/optional> + #include <web/mime-url-encoding> using namespace std; @@ -28,16 +36,92 @@ namespace web { namespace apache { + void request:: + state (request_state s) + { + assert (s != request_state::initial); + + if (s == state_) + return; // Noop. + + if (s < state_) + { + // Can't "unwind" irrevocable interaction with Apache API. + // + static const char* names[] = { + "initial", "reading", "headers", "writing"}; + + string str ("web::apache::request::set_state: "); + str += names[static_cast<size_t> (state_)]; + str += " to "; + str += names[static_cast<size_t> (s)]; + + throw sequence_error (move (str)); + } + + if (s == request_state::reading) + { + // Prepare request content for reading. + // + int r (ap_setup_client_block (rec_, REQUEST_CHUNKED_DECHUNK)); + + if (r != OK) + throw invalid_request (r); + } + else if (s > request_state::reading && state_ <= request_state::reading) + { + // Read request content if any, discard whatever is received. + // + int r (ap_discard_request_body (rec_)); + + if (r != OK) + throw invalid_request (r); + } + + state_ = s; + } + + void request:: + rewind () + { + // @@ Request content buffering, and response cookies buffering are not + // supported yet. When done will be possible to rewind in broader + // range of cases. + // + + if (state_ == request_state::initial || + + // Form data have been read. Lucky case, can rewind. + // + (state_ == request_state::reading && + dynamic_cast<stringbuf*> (in_buf_.get ()) != nullptr)) + { + out_.reset (); + out_buf_.reset (); + + rec_->status = HTTP_OK; + + ap_set_content_type (rec_, nullptr); + + if (in_) + in_->seekg (0); + } + else + throw sequence_error ("web::apache::request::rewind"); + } + istream& request:: - content () + content (bool buffer) { + assert (!buffer); // Request content buffering is not implemented yet. + if (!in_) { unique_ptr<streambuf> in_buf (new istreambuf (rec_, *this)); in_.reset (new istream (in_buf.get ())); in_buf_ = move (in_buf); - in_->exceptions (ios::failbit | ios::badbit); + in_->exceptions (istream::failbit | istream::badbit); // Save form data now otherwise will not be available to do later // when data already read from stream. @@ -135,17 +219,29 @@ namespace web ostream& request:: content (status_code status, const string& type, bool buffer) { - if (out_ && status == rec_->status && buffer == buffer_ && + if (out_ && + + // Same status code. + // + status == rec_->status && + + // Same buffering flag. + // + buffer == + (dynamic_cast<stringbuf*> (out_buf_.get ()) != nullptr) && + + // Same content type. + // strcasecmp (rec_->content_type ? rec_->content_type : "", type.c_str ()) == 0) { + // No change, return the existing stream. + // return *out_; } - if (get_write_state ()) - { - throw sequence_error ("::web::apache::request::content"); - } + if (state_ >= request_state::writing) + throw sequence_error ("web::apache::request::content"); if (!buffer) // Request body will be discarded prior first byte of content is @@ -161,9 +257,8 @@ namespace web out_.reset (new ostream (out_buf.get ())); out_buf_ = move (out_buf); - out_->exceptions (ios::eofbit | ios::failbit | ios::badbit); + out_->exceptions (ostream::eofbit | ostream::failbit | ostream::badbit); - buffer_ = buffer; rec_->status = status; ap_set_content_type ( @@ -182,13 +277,10 @@ namespace web // where no sense to throw but still need to signal apache a // proper status code. // - if (get_write_state () && !current_exception ()) - { - throw sequence_error ("::web::apache::request::status"); - } + if (state_ >= request_state::writing && !current_exception ()) + throw sequence_error ("web::apache::request::status"); rec_->status = status; - buffer_ = true; out_.reset (); out_buf_.reset (); ap_set_content_type (rec_, nullptr); @@ -201,14 +293,10 @@ namespace web const chrono::seconds* max_age, const char* path, const char* domain, - bool secure) + bool secure, + bool buffer) { - if (get_write_state ()) - { - // Too late to send cookie if content is already written. - // - throw sequence_error ("::web::apache::request::cookie"); - } + assert (!buffer); // Cookie buffering is not implemented yet. ostringstream s; mime_url_encode (name, s); @@ -230,20 +318,15 @@ namespace web } if (path) - { s << ";Path=" << path; - } if (domain) - { s << ";Domain=" << domain; - } if (secure) - { s << ";Secure"; - } + state (request_state::headers); apr_table_add (rec_->err_headers_out, "Set-Cookie", s.str ().c_str ()); } diff --git a/web/apache/request.ixx b/web/apache/request.ixx index 8a3b32b..821aaba 100644 --- a/web/apache/request.ixx +++ b/web/apache/request.ixx @@ -4,10 +4,12 @@ #include <strings.h> // strncasecmp() -#include <iomanip> +#include <apr_tables.h> // apr_table_* + +#include <http_protocol.h> // ap_*() + #include <sstream> -#include <cstring> -#include <cstdlib> +#include <utility> // move() namespace web { @@ -16,35 +18,34 @@ namespace web inline int request:: flush () { - if (buffer_ && out_buf_) + if (std::stringbuf* b = dynamic_cast<std::stringbuf*> (out_buf_.get ())) { - auto b (dynamic_cast<std::stringbuf*> (out_buf_.get ())); - assert (b); - + // Response content is buffered. + // std::string s (b->str ()); if (!s.empty ()) { - // Before writing response read and discard request body if any. - // - int r (ap_discard_request_body (rec_)); - - if (r == OK) + try { - set_write_state (); + state (request_state::writing); if (ap_rwrite (s.c_str (), s.length (), rec_) < 0) rec_->status = HTTP_REQUEST_TIME_OUT; } - else - rec_->status = r; + catch (const invalid_request& e) + { + rec_->status = e.status; + } } out_.reset (); out_buf_.reset (); } - return rec_->status == HTTP_OK || get_write_state () ? OK : rec_->status; + return rec_->status == HTTP_OK || state_ >= request_state::writing + ? OK + : rec_->status; } inline const std::string& request:: @@ -64,19 +65,21 @@ namespace web std::istream& istr (content ()); // Do not throw when eofbit is set (end of stream reached), and - // when failbit is set (getline() failed to extract any character). + // when failbit is set (getline() failed to extract any + // character). // - istr.exceptions (std::ios::badbit); + istr.exceptions (std::istream::badbit); std::getline (istr, *form_data_); - // Make this data the content of the input stream. + // Make this data the content of the input stream, so it's + // available for the application as well. // std::unique_ptr<std::streambuf> in_buf ( new std::stringbuf (*form_data_)); in_.reset (new std::istream (in_buf.get ())); in_buf_ = std::move (in_buf); - in_->exceptions (std::ios::failbit | std::ios::badbit); + in_->exceptions (std::istream::failbit | std::istream::badbit); } } } diff --git a/web/apache/service b/web/apache/service index 4c0d395..165ff90 100644 --- a/web/apache/service +++ b/web/apache/service @@ -5,8 +5,11 @@ #ifndef WEB_APACHE_SERVICE #define WEB_APACHE_SERVICE -#include <httpd.h> -#include <http_config.h> // module, ap_hook_*() +#include <apr_pools.h> // apr_pool_t +#include <apr_hooks.h> // APR_HOOK_* + +#include <httpd.h> // request_rec, server_rec, HTTP_*, DECLINED +#include <http_config.h> // module, cmd_parms, ap_hook_*() #include <map> #include <memory> // unique_ptr @@ -130,7 +133,8 @@ namespace web // The worker_initializer() function is called right after Apache // worker process is started. Called for every new process spawned. // - ap_hook_child_init (&worker_initializer<M>, NULL, NULL, APR_HOOK_LAST); + ap_hook_child_init ( + &worker_initializer<M>, NULL, NULL, APR_HOOK_LAST); // The request_handler () function is called for each client request. // @@ -254,7 +258,8 @@ namespace web parse_option (cmd_parms* parms, void* conf, const char* args) noexcept; const char* - add_option (context_id id, const char* name, optional<std::string> value); + add_option ( + context_id id, const char* name, optional<std::string> value); void finalize_config (server_rec*); diff --git a/web/apache/service.cxx b/web/apache/service.cxx index 2ca8cf0..da44530 100644 --- a/web/apache/service.cxx +++ b/web/apache/service.cxx @@ -4,10 +4,10 @@ #include <web/apache/service> -#include <apr_pools.h> +#include <apr_pools.h> // apr_palloc() -#include <httpd.h> -#include <http_config.h> +#include <httpd.h> // server_rec +#include <http_config.h> // command_rec, cmd_*, ap_get_module_config() #include <memory> // unique_ptr #include <string> @@ -16,7 +16,10 @@ #include <cstring> // strlen() #include <exception> +#include <butl/optional> + #include <web/module> +#include <web/apache/log> using namespace std; @@ -29,12 +32,12 @@ namespace web { assert (cmds == nullptr); - // Fill apache module directive definitions. Directives share - // common name space in apache configuration file, so to prevent name - // clash have to form directive name as a combination of module and - // option names: <module name>-<option name>. This why for option - // bar of module foo the corresponding directive will appear in apache - // configuration file as foo-bar. + // Fill apache module directive definitions. Directives share common + // name space in apache configuration file, so to prevent name clash + // have to form directive name as a combination of module and option + // names: <module name>-<option name>. This why for option bar of module + // foo the corresponding directive will appear in apache configuration + // file as foo-bar. // const option_descriptions& od (exemplar_.options ()); unique_ptr<command_rec[]> directives (new command_rec[od.size () + 1]); @@ -42,7 +45,8 @@ namespace web for (const auto& o: od) { - auto i (option_descriptions_.emplace (name_ + "-" + o.first, o.second)); + auto i ( + option_descriptions_.emplace (name_ + "-" + o.first, o.second)); assert (i.second); *d++ = @@ -73,8 +77,8 @@ namespace web create_server_context (apr_pool_t* pool, server_rec*) noexcept { // Create the object using the configuration memory pool provided by the - // Apache API. The lifetime of the object is equal to the lifetime of the - // pool. + // Apache API. The lifetime of the object is equal to the lifetime of + // the pool. // void* p (apr_palloc (pool, sizeof (context))); assert (p != nullptr); @@ -104,10 +108,10 @@ namespace web service& srv (*reinterpret_cast<service*> (parms->cmd->cmd_data)); if (srv.options_parsed_) - // Apache have started the second pass of its messy initialization cycle - // (more details at http://wiki.apache.org/httpd/ModuleLife). This time - // we are parsing for real. Cleanup the existing config, and start - // building the new one. + // Apache have started the second pass of its messy initialization + // cycle (more details at http://wiki.apache.org/httpd/ModuleLife). + // This time we are parsing for real. Cleanup the existing config, and + // start building the new one. // srv.clear_config (); @@ -214,8 +218,12 @@ namespace web // context options as a result of the merge_server_context() calls. // for (const auto& o: options_) - if (o.first->server != nullptr) // Is a directory configuration context. + { + // Is a directory configuration context. + // + if (o.first->server != nullptr) complement (o.first, o.first->server); + } options_parsed_ = true; } diff --git a/web/apache/service.txx b/web/apache/service.txx index b57befc..961c19b 100644 --- a/web/apache/service.txx +++ b/web/apache/service.txx @@ -3,9 +3,9 @@ // license : MIT; see accompanying LICENSE file #include <unistd.h> // getppid() -#include <signal.h> // kill() +#include <signal.h> // kill(), SIGTERM -#include <http_log.h> +#include <http_log.h> // APLOG_* #include <utility> // move() #include <exception> @@ -132,21 +132,31 @@ namespace web const M* e (dynamic_cast<const M*> (exemplar)); assert (e != nullptr); - M m (*e); - - if (static_cast<module&> (m).handle (rq, rq, lg)) - return rq.flush (); + for (M m (*e);;) + { + try + { + if (static_cast<module&> (m).handle (rq, rq, lg)) + return rq.flush (); - if (!rq.get_write_state ()) - return DECLINED; + if (rq.state () == request_state::initial) + return DECLINED; - lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, - "handling declined while unbuffered content " - "has been written"); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, + "handling declined being partially executed"); + break; + } + catch (const module::retry&) + { + // Retry to handle the request. + // + rq.rewind (); + } + } } catch (const invalid_request& e) { - if (!e.content.empty () && !rq.get_write_state ()) + if (!e.content.empty () && rq.state () < request_state::writing) { try { @@ -165,11 +175,12 @@ namespace web { lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); - if (*e.what () && !rq.get_write_state ()) + if (*e.what () && rq.state () < request_state::writing) { try { - rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") + rq.content ( + HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") << e.what (); return rq.flush (); @@ -184,11 +195,12 @@ namespace web { lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error"); - if (!rq.get_write_state ()) + if (rq.state () < request_state::writing) { try { - rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") + rq.content ( + HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") << "unknown error"; return rq.flush (); diff --git a/web/apache/stream b/web/apache/stream index 2ea9b7e..f05ea08 100644 --- a/web/apache/stream +++ b/web/apache/stream @@ -5,44 +5,56 @@ #ifndef WEB_APACHE_STREAM #define WEB_APACHE_STREAM -#include <httpd.h> -#include <http_protocol.h> +#include <httpd.h> // request_rec, HTTP_* +#include <http_protocol.h> // ap_*() #include <ios> // streamsize #include <vector> -#include <cstring> // memmove() +#include <cstring> // memmove(), size_t #include <streambuf> #include <algorithm> // min(), max() -#include <web/module> +#include <web/module> // invalid_request namespace web { namespace apache { - // Object of a class implementing this interface is intended for - // keeping the state of writing response to the client. + // Object of a class implementing this interface is intended for keeping + // the state of communication with the client. // - struct write_state + struct stream_state { - // Called by ostreambuf methods when some content to be written to the - // client. + // Called by istreambuf functions when content is about to be read from + // the client. Can throw invalid_request or sequence_error. // virtual void - set_write_state () = 0; + set_read_state () = 0; - // Called to check if any data have already been written to the client. - // Such checks required for some operations which are impossible to - // execute after response is partially written. + // Called by ostreambuf functions when some content is about to be + // written to the client. Can throw invalid_request or sequence_error. // - virtual bool - get_write_state () const noexcept = 0; + virtual void + set_write_state () = 0; + }; + + // Base class for ostreambuf and istreambuf. References request and + // communication state structures. + // + class rbuf: public std::streambuf + { + protected: + rbuf (request_rec* r, stream_state& s): rec_ (r), state_ (s) {} + + protected: + request_rec* rec_; + stream_state& state_; }; - class ostreambuf: public std::streambuf + class ostreambuf: public rbuf { public: - ostreambuf (request_rec* rec, write_state& ws): rec_ (rec), ws_ (ws) {} + ostreambuf (request_rec* r, stream_state& s): rbuf (r, s) {} private: virtual int_type @@ -50,7 +62,7 @@ namespace web { if (c != traits_type::eof ()) { - ws_.set_write_state (); + state_.set_write_state (); char chr (c); @@ -67,12 +79,10 @@ namespace web virtual std::streamsize xsputn (const char* s, std::streamsize num) { - ws_.set_write_state (); + state_.set_write_state (); if (ap_rwrite (s, num, rec_) < 0) - { throw invalid_request (HTTP_REQUEST_TIME_OUT); - } return num; } @@ -81,59 +91,37 @@ namespace web sync () { if (ap_rflush (rec_) < 0) - { throw invalid_request (HTTP_REQUEST_TIME_OUT); - } return 0; } - - private: - request_rec* rec_; - write_state& ws_; }; - class istreambuf: public std::streambuf + class istreambuf: public rbuf { public: - istreambuf (request_rec* rec, - write_state& ws, + istreambuf (request_rec* r, + stream_state& s, size_t bufsize = 1024, size_t putback = 1) - : rec_ (rec), - ws_ (ws), + : rbuf (r, s), bufsize_ (std::max (bufsize, (size_t)1)), putback_ (std::min (putback, bufsize_ - 1)), buf_ (bufsize_) { - if (ws_.get_write_state ()) - { - throw sequence_error ("::web::apache::istreambuf::istreambuf"); - } - char* p (buf_.data () + putback_); setg (p, p, p); - - int status (ap_setup_client_block (rec_, REQUEST_CHUNKED_DECHUNK)); - - if (status != OK) - { - throw invalid_request (status); - } } private: virtual int_type underflow () { - if (ws_.get_write_state ()) - { - throw sequence_error ("::web::apache::istreambuf::underflow"); - } - if (gptr () < egptr ()) return traits_type::to_int_type (*gptr ()); + state_.set_read_state (); + size_t pb (std::min ((size_t)(gptr () - eback ()), putback_)); std::memmove (buf_.data () + putback_ - pb, gptr () - pb, pb); @@ -141,22 +129,16 @@ namespace web int rb (ap_get_client_block (rec_, p, bufsize_ - putback_)); if (rb == 0) - { return traits_type::eof (); - } if (rb < 0) - { throw invalid_request (HTTP_REQUEST_TIME_OUT); - } setg (p - pb, p, p + rb); return traits_type::to_int_type (*gptr ()); } private: - request_rec* rec_; - write_state& ws_; size_t bufsize_; size_t putback_; std::vector<char> buf_; |