/
zOMGdev
/
cppcms
Обзор
Документация
Войти
/
zOMGdev
/
cppcms
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v2.0.0.beta2
src/http_response.cpp
916 строк
22 KB
Artyom Beilis
Support of HTTP/1.1 - keep alive & chunked transfer encoding
05 сен 2020, 23:37
05 сен 2020, 23:37
3db1929
Код
Авторство
О чём код?
/////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com> // // See accompanying file COPYING.TXT file for licensing details. // /////////////////////////////////////////////////////////////////////////////// #define CPPCMS_SOURCE #include "cgi_api.h" #include <cppcms/http_response.h> #include <cppcms/http_context.h> #include <cppcms/http_request.h> #include <cppcms/http_cookie.h> #include "http_protocol.h" #include "cached_settings.h" #include <cppcms/json.h> #include <cppcms/cppcms_error.h> #include <cppcms/service.h> #include <cppcms/config.h> #include <cppcms/localization.h> #include <cppcms/util.h> #include <cppcms/session_interface.h> #include <booster/log.h> #include <booster/posix_time.h> #include <iostream> #include <sstream> #include <streambuf> #include <iterator> #include <map> #include <list> #include <stdio.h> #include "response_headers.h" #ifndef CPPCMS_NO_GZIP #include <zlib.h> #endif namespace cppcms { namespace http { namespace details { template<typename T> std::string itoa(T v) { std::ostringstream ss; ss.imbue(std::locale::classic()); ss<<v; return ss.str(); } class extended_streambuf : public std::streambuf { public: virtual void close() = 0; virtual ~extended_streambuf() {} }; class copy_buf : public extended_streambuf { public: copy_buf(std::streambuf *output = 0) : out_(output) { } void open(std::streambuf *out) { out_ = out; } size_t getstr(booster::shared_ptr<std::vector<char> > &buf) { size_t n = buffer_.size() - (epptr() - pptr()); setp(0,0); if(!borrowed_buffer_) { borrowed_buffer_.reset(new std::vector<char>()); } borrowed_buffer_->swap(buffer_); buf = borrowed_buffer_; return n; } void getstr(std::string &out) { size_t n = buffer_.size() - (epptr() - pptr()); setp(0,0); if(n!=0) { out.assign(&buffer_[0],n); } else { out.clear(); } } int overflow(int c) { // we relay on a fact that std::string is continuous // storage so we can eliminate some memory copying int r=0; if(out_ && pbase()!=pptr()) { int n = pptr()-pbase(); if(out_->sputn(pbase(),n)!=n) r=-1; } if(pptr() == 0) { if(buffer_.empty()) { if(borrowed_buffer_ && borrowed_buffer_.unique() && borrowed_buffer_->size()!=0) { buffer_.swap(*borrowed_buffer_); } else { buffer_.resize(128); } } setp(&buffer_[0],&buffer_[0]+buffer_.size()); } else if(pptr()==epptr()) { size_t size = buffer_.size(); buffer_.resize(size * 2); setp(&buffer_[size],&buffer_[size]+size); } else { setp(pptr(),epptr()); } if(r==0 && c!=EOF) sputc(c); return r; } int sync() { if(overflow(EOF) < 0) return -1; if(out_) return out_->pubsync(); return 0; } void close() { overflow(EOF); out_ = 0; } private: booster::shared_ptr<std::vector<char> > borrowed_buffer_; std::vector<char> buffer_; std::streambuf *out_; }; #ifndef CPPCMS_NO_GZIP class gzip_buf : public extended_streambuf { public: gzip_buf() : opened_(false), z_stream_(z_stream()), out_(0) { } int overflow(int c) { if(pbase()==epptr()) return -1; int have = pptr() - pbase(); if(have > 0) { if(do_write(pbase(),have,Z_NO_FLUSH) < 0) return -1; pbump(-have); } if(c!=EOF) { sputc(c); } return 0; } int sync() { int have = pptr() - pbase(); if(do_write(pbase(),have,Z_SYNC_FLUSH) < 0) return -1; pbump(-have); return 0; } int do_write(char const *p,int n,int flush_flag) { if(!out_ || !opened_) { return -1; } if(n==0 && flush_flag==Z_NO_FLUSH) { return 0; } z_stream_.avail_in = n; z_stream_.next_in = (Bytef*)(p); do { z_stream_.avail_out = buffer_; z_stream_.next_out = (Bytef*)(&chunk_[0]); deflate(&z_stream_,flush_flag); int have = chunk_.size() - z_stream_.avail_out; if(out_->sputn(&chunk_[0],have)!=have) { out_ = 0; return -1; } } while(z_stream_.avail_out == 0); if(flush_flag == Z_SYNC_FLUSH && out_ && out_->pubsync() < 0) { out_ = 0; return -1; } return 0; } void close() { if(!opened_) return; do_write(pbase(),pptr()-pbase(),Z_FINISH); deflateEnd(&z_stream_); opened_ = false; z_stream_ = z_stream(); chunk_.clear(); in_chunk_.clear(); out_ = 0; } void open(extended_streambuf *out,int level,int buffer_size) { level_ = level; if(buffer_size < 256) buffer_size = 256; buffer_ = buffer_size; out_ = out; if(deflateInit2(&z_stream_, level_, Z_DEFLATED, 15 + 16, // 15 window bits+gzip = 16, 8, // memuse Z_DEFAULT_STRATEGY) != Z_OK) { std::string error = "ZLib init failed"; if(z_stream_.msg) { error+=":"; error+=z_stream_.msg; } throw booster::runtime_error(error); } in_chunk_.resize(buffer_); chunk_.resize(buffer_); setp(&in_chunk_[0],&in_chunk_[0]+buffer_); opened_ = true; } ~gzip_buf() { if(opened_) deflateEnd(&z_stream_); } private: bool opened_; std::vector<char> chunk_; std::vector<char> in_chunk_; z_stream z_stream_; extended_streambuf *out_; int level_; size_t buffer_; }; #endif class basic_device : public extended_streambuf { public: basic_device() : raw_mode_(false), final_(false), eof_send_(false), buffer_size_(0) { } void raw_mode(bool b) { raw_mode_ = b; } void open(booster::weak_ptr<cppcms::impl::cgi::connection> c,size_t n) { buffer_size_ = n; do_setp(); conn_ = c; } virtual bool do_write(cppcms::impl::cgi::connection &c,booster::aio::const_buffer const &out,bool eof,booster::system::error_code &e) = 0; int write(booster::aio::const_buffer const &out) { booster::system::error_code e; return write(out,e); } int write(booster::aio::const_buffer const &out,booster::system::error_code &e) { bool send_eof = final_ && !eof_send_; booster::shared_ptr<cppcms::impl::cgi::connection> c = conn_.lock(); if(!c) return -1; eof_send_ = send_eof; // make sure flush goes all way to write to flush the buffers if(raw_mode_ && !raw_headers_.headers_done()) { auto out_data = out.get(); booster::aio::const_buffer real_out; for(unsigned i=0;i<out_data.second;i++) { char const *ptr = out_data.first[i].ptr; size_t len = out_data.first[i].size; raw_headers_.consume(ptr,len,*c); if(len > 0) real_out += booster::aio::buffer(ptr,len); } if(raw_headers_.headers_done() || send_eof) if(do_write(*c,real_out,send_eof,e)) return 0; } else if(do_write(*c,out,send_eof,e)) { return 0; } if(e) { BOOSTER_WARNING("cppcms") << "Failed to write response:" << e.message(); conn_.reset(); return -1; } return 0; } virtual int sync() { return overflow(EOF); } virtual int overflow(int c) { char c_tmp=c; booster::aio::const_buffer out=booster::aio::buffer(pbase(),pptr()-pbase()); if(c!=EOF) out += booster::aio::buffer(&c_tmp,1); booster::system::error_code e; if(write(out)!=0) return -1; do_setp(); return 0; } virtual std::streamsize xsputn(const char* s, std::streamsize n) { if((epptr() - pptr()) >= n) { memcpy(pptr(),s,n); pbump(n); } else { booster::aio::const_buffer out=booster::aio::buffer(pbase(),pptr()-pbase()); out+=booster::aio::buffer(s,n); if(write(out)!=0) return -1; do_setp(); } return n; } void do_setp() { output_.resize(buffer_size_); if(buffer_size_ == 0) setp(0,0); else setp(&output_[0],&output_[buffer_size_-1]+1); } int flush(booster::system::error_code &e) { int r = write(booster::aio::buffer(pbase(),pptr()-pbase()),e); setp(pbase(),epptr()); return r; } virtual std::streambuf *setbuf(char *,std::streamsize size) { buffer_size_ = size; std::streamsize content_size = pptr() - pbase(); if(content_size > size) { booster::system::error_code e; if(flush(e)!=0) return 0; content_size = 0; } do_setp(); pbump(content_size); return this; } void close() { if(eof_send_) return; final_=true; booster::system::error_code e; flush(e); } protected: booster::weak_ptr<cppcms::impl::cgi::connection> conn_; cppcms::impl::cgi_headers_parser raw_headers_; bool raw_mode_; bool final_; bool eof_send_; size_t buffer_size_; std::vector<char> output_; }; class async_io_buf : public basic_device { public: async_io_buf() : full_buffering_(true) { } int full_buffering(bool buffering) { if(full_buffering_ == buffering) return 0; full_buffering_ = buffering; if(full_buffering_ == false) { if(pubsetbuf(0,buffer_size_)==0) { return -1; } } return 0; } bool full_buffering() { return full_buffering_; } std::streambuf *setbuf(char *s,std::streamsize size) { if(full_buffering_) { buffer_size_ = size; std::streamsize content_size = pptr() - pbase(); if(size_t(size) > output_.size()) output_.resize(size); do_setp(); pbump(content_size); return this; } return basic_device::setbuf(s,size); } size_t next_size(size_t in) { if(in == 0) return 64; return in * 2; } virtual int overflow(int c) { if(full_buffering_) { if(pptr() == epptr()) { size_t current_size = pptr() - pbase(); output_.resize(next_size(output_.size())); setp(&output_[0],&output_[output_.size()-1]+1); pbump(current_size); } if(c!=EOF) { *pptr() = c; pbump(1); } return 0; } else { return basic_device::overflow(c); } } virtual std::streamsize xsputn(const char* s, std::streamsize n) { if(full_buffering_) { std::streamsize reminder = epptr() - pptr(); if(reminder < n) { size_t current_size = pptr()-pbase(); size_t minimal_size = current_size + n; size_t resize_size = next_size(output_.size()); while(resize_size < minimal_size) resize_size *= 2; output_.resize(resize_size); setp(&output_[0],&output_[0]+resize_size); pbump(current_size); } memcpy(pptr(),s,n); pbump(n); return n; } else { return basic_device::xsputn(s,n); } } virtual bool do_write(cppcms::impl::cgi::connection &c,booster::aio::const_buffer const &out,bool eof,booster::system::error_code &e) { c.nonblocking_write(out,eof,e); if(e) return false; return true; } private: bool full_buffering_; }; class output_device : public basic_device { public: virtual bool do_write(cppcms::impl::cgi::connection &c,booster::aio::const_buffer const &out,bool eof,booster::system::error_code &e) { return c.write(out,eof,e); } }; } struct response::_data { cppcms::impl::response_headers headers; std::list<details::extended_streambuf *> buffers; details::async_io_buf buffered; details::copy_buf cached; #ifndef CPPCMS_NO_GZIP details::gzip_buf zbuf; #endif details::output_device output_buf; std::ostream output; booster::weak_ptr<cppcms::impl::cgi::connection> conn; int required_buffer_size; bool full_buffering; _data(cppcms::impl::cgi::connection *c) : output(0), conn(c->shared_from_this()), required_buffer_size(-1), full_buffering(true) { } }; using details::itoa; response::response(context &context) : d(new _data(&context.connection())), context_(context), stream_(0), io_mode_(asynchronous), disable_compression_(0), ostream_requested_(0), copy_to_cache_(0), finalized_(0) { set_content_header("text/html"); if(context_.service().cached_settings().service.disable_xpowered_by==false) { if(context_.service().cached_settings().service.disable_xpowered_by_version) set_header("X-Powered-By", CPPCMS_PACKAGE_NAME); else set_header("X-Powered-By", CPPCMS_PACKAGE_NAME "/" CPPCMS_PACKAGE_VERSION); } } response::~response() { } void response::set_content_header(std::string const &content_type) { if(context_.service().cached_settings().localization.disable_charset_in_content_type) { set_header("Content-Type",content_type); } else { std::string charset=std::use_facet<locale::info>(context_.locale()).encoding(); set_header("Content-Type",content_type+"; charset="+charset); } } void response::set_html_header() { set_content_header("text/html"); } void response::set_xhtml_header() { set_content_header("text/xhtml"); } void response::set_plain_text_header() { set_content_header("text/plain"); } void response::set_redirect_header(std::string const &loc,int s) { location(loc); status(s); } void response::set_cookie(cookie const &cookie) { std::ostringstream ss; ss << cookie; d->headers.add_header(std::move(ss.str())); } void response::set_header(std::string const &name,std::string const &value) { d->headers.set_header(name,value); } void response::finalize() { if(!finalized_) { out(); for(std::list<details::extended_streambuf *>::iterator p=d->buffers.begin();p!=d->buffers.end();++p) (*p)->close(); finalized_=1; } } void response::setbuf(int buffer_size) { if(buffer_size < 0) buffer_size = -1; d->required_buffer_size = buffer_size; if(ostream_requested_) { if(buffer_size < 0) { if(io_mode_ == asynchronous || io_mode_ == asynchronous_raw) buffer_size = context_.service().cached_settings().service.async_output_buffer_size; else buffer_size = context_.service().cached_settings().service.output_buffer_size; } d->buffers.back()->pubsetbuf(0,buffer_size); } } void response::full_asynchronous_buffering(bool enable) { d->buffered.full_buffering(enable); } bool response::full_asynchronous_buffering() { return d->buffered.full_buffering(); } bool response::pending_blocked_output() { booster::shared_ptr<cppcms::impl::cgi::connection> conn = d->conn.lock(); if(!conn) return false; return conn->has_pending(); } std::string response::get_header(std::string const &name) { return d->headers.get_header(name); } void response::erase_header(std::string const &name) { d->headers.erase_header(name); } bool response::need_gzip() { #ifndef CPPCMS_NO_GZIP if(disable_compression_) return false; if(io_mode_!=normal) return false; if(context_.service().cached_settings().gzip.enable==false) return false; if(strstr(context_.request().cgetenv("HTTP_ACCEPT_ENCODING"),"gzip")==0) return false; if(!get_header("Content-Encoding").empty()) // User had defined its own content encoding // he may compress data on its own... disable compression return false; std::string const content_type=get_header("Content-Type"); if(protocol::is_prefix_of("text/",content_type)) return true; return false; #else return false; #endif } response::io_mode_type response::io_mode() { return io_mode_; } void response::io_mode(response::io_mode_type mode) { if(ostream_requested_) throw cppcms_error("Can't set mode after requesting output stream"); io_mode_=mode; } void response::write_http_headers() { context_.connection().set_response_headers(d->headers); } void response::add_header(std::string const &name,std::string const &value) { d->headers.add_header(name,value); } void response::copy_to_cache() { copy_to_cache_=1; } std::string response::copied_data() { std::string tmp; if(!copy_to_cache_ || !ostream_requested_) return tmp; d->cached.getstr(tmp); return tmp; } int response::flush_async_chunk(booster::system::error_code &e) { return d->buffered.flush(e); } std::ostream &response::out() { if(ostream_requested_) return d->output; if(finalized_) throw cppcms_error("Request for output stream for finalized request is illegal"); if(io_mode_ == asynchronous || io_mode_ == asynchronous_raw) { size_t bsize = context_.service().cached_settings().service.async_output_buffer_size; if(d->required_buffer_size != -1) bsize = d->required_buffer_size; d->buffered.open(d->conn,bsize); d->output.rdbuf(&d->buffered); d->buffers.push_front(&d->buffered); if(io_mode_ == asynchronous_raw) d->buffered.raw_mode(true); } else { size_t bsize = context_.service().cached_settings().service.output_buffer_size; if(d->required_buffer_size != -1) bsize = d->required_buffer_size; d->output_buf.open(d->conn,bsize); d->output.rdbuf(&d->output_buf); d->buffers.push_front(&d->output_buf); if(io_mode_ == raw) d->output_buf.raw_mode(true); } ostream_requested_=1; #ifndef CPPCMS_NO_GZIP bool gzip = need_gzip(); if(gzip) { content_encoding("gzip"); } #endif // Now we shoulde write headers -- before comrpession if(io_mode_ != raw && io_mode_ != asynchronous_raw) { context_.session().save(); write_http_headers(); } if(copy_to_cache_) { d->cached.open(d->output.rdbuf()); d->output.rdbuf(&d->cached); d->buffers.push_front(&d->cached); } #ifndef CPPCMS_NO_GZIP if(gzip) { int level=context_.service().cached_settings().gzip.level; int buffer=context_.service().cached_settings().gzip.buffer; d->zbuf.open(d->buffers.front(),level,buffer); d->output.rdbuf(&d->zbuf); d->buffers.push_front(&d->zbuf); } #endif d->output.imbue(context_.locale()); return d->output; } bool response::some_output_was_written() { return ostream_requested_; } char const *response::status_to_string(int status) { switch(status) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Time-out"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Large"; case 415: return "Unsupported Media Type"; case 416: return "Requested range not satisfiable"; case 417: return "Expectation Failed"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Time-out"; case 505: return "HTTP Version not supported"; default: return "Unknown"; } } void response::make_error_response_html_body(int stat,std::ostream &out,std::string const &msg) { out <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n" " \"http://www.w3.org/TR/html4/loose.dtd\">\n" "<html>\n" " <head>\n" " <title>"<<stat<<" — "<< http::response::status_to_string(stat)<<"</title>\n" " </head>\n" " <body>\n" " <h1>"<<stat<<" — "<< http::response::status_to_string(stat)<<"</h1>\n"; if(!msg.empty()) { out<<" <p>"<<util::escape(msg)<<"</p>\n"; } out << " </body>\n" "</html>\n"; } void response::make_error_response(int stat,std::string const &msg) { status(stat); make_error_response_html_body(stat,out(),msg); } void response::accept_ranges(std::string const &s) { set_header("Accept-Ranges",s); } void response::age(unsigned seconds) { set_header("Age",itoa(seconds)); } void response::allow(std::string const &s) { set_header("Allow",s); } void response::cache_control(std::string const &s) { set_header("Cache-Control",s); } void response::content_encoding(std::string const &s) { set_header("Content-Encoding",s); } void response::content_language(std::string const &s) { set_header("Content-Language",s); } void response::content_length(unsigned long long len) { set_header("Content-Length",itoa(len)); } void response::content_location(std::string const &s) { set_header("Content-Location",s); } void response::content_md5(std::string const &s) { set_header("Content-MD5",s); } void response::content_range(std::string const &s) { set_header("Content-Range",s); } void response::content_type(std::string const &s) { set_header("Content-Type",s); } void response::date(time_t t) { set_header("Date",make_http_time(t)); } void response::etag(std::string const &s) { set_header("ETag",s); } void response::expires(time_t t) { set_header("Expires",make_http_time(t)); } void response::last_modified(time_t t) { set_header("Last-Modified",make_http_time(t)); } void response::location(std::string const &s) { set_header("Location",s); } void response::pragma(std::string const &s) { set_header("Pragma",s); } void response::proxy_authenticate(std::string const &s) { set_header("Proxy-Authenticate",s); } void response::retry_after(unsigned n) { set_header("Retry-After",itoa(n)); } void response::retry_after(std::string const &s) { set_header("Retry-After",s); } void response::status(int code) { status(code,status_to_string(code)); } void response::status(int code,std::string const &message) { set_header("Status",itoa(code)+" "+message); } void response::trailer(std::string const &s) { set_header("Trailer",s); } void response::vary(std::string const &s) { set_header("Vary",s); } void response::via(std::string const &s) { set_header("Via",s); } void response::warning(std::string const &s) { set_header("Warning",s); } void response::www_authenticate(std::string const &s) { set_header("WWW-Authenticate",s); } std::string response::make_http_time(time_t t) { // RFC 2616 // "Sun, 06 Nov 1994 08:49:37 GMT" std::tm tv=booster::ptime::universal_time(booster::ptime(t)); std::ostringstream ss; std::locale C=std::locale::classic(); ss.imbue(C); std::time_put<char> const &put = std::use_facet<std::time_put<char> >(C); char const format[]="%a, %d %b %Y %H:%M:%S GMT"; put.put(ss,ss,' ',&tv,format,format+sizeof(format)-1); return ss.str(); } } } // http::cppcms