From 3ad212f066d01a5a58083632dfd50da5c8c0ee7a Mon Sep 17 00:00:00 2001 From: David Rose Date: Sun, 20 Oct 2002 00:17:23 +0000 Subject: [PATCH] add do_connect() --- panda/src/downloader/Sources.pp | 1 + panda/src/downloader/bioPtr.cxx | 27 --- panda/src/downloader/bioPtr.h | 1 - panda/src/downloader/bioStream.I | 90 +++++++++- panda/src/downloader/bioStream.cxx | 34 ++++ panda/src/downloader/bioStream.h | 42 +++++ panda/src/downloader/bioStreamBuf.cxx | 124 +++++++++++-- panda/src/downloader/bioStreamBuf.h | 10 +- panda/src/downloader/bioStreamPtr.I | 12 +- panda/src/downloader/bioStreamPtr.cxx | 4 +- panda/src/downloader/bioStreamPtr.h | 16 +- .../src/downloader/downloader_composite1.cxx | 1 + panda/src/downloader/httpChannel.I | 70 +++++++- panda/src/downloader/httpChannel.cxx | 125 +++++++++---- panda/src/downloader/httpChannel.h | 17 +- panda/src/downloader/socketStream.I | 20 +++ panda/src/downloader/socketStream.cxx | 165 ++++++++++++++++++ panda/src/downloader/socketStream.h | 47 +++++ 18 files changed, 706 insertions(+), 100 deletions(-) create mode 100644 panda/src/downloader/socketStream.cxx diff --git a/panda/src/downloader/Sources.pp b/panda/src/downloader/Sources.pp index 955cee2df1..2930bb52c1 100644 --- a/panda/src/downloader/Sources.pp +++ b/panda/src/downloader/Sources.pp @@ -41,6 +41,7 @@ httpChannel.cxx \ identityStream.cxx identityStreamBuf.cxx \ multiplexStream.cxx multiplexStreamBuf.cxx \ + socketStream.cxx \ urlSpec.cxx \ $[if $[HAVE_NET], downloadDb.cxx downloader.cxx] \ $[if $[HAVE_ZLIB], decompressor.cxx download_utils.cxx] diff --git a/panda/src/downloader/bioPtr.cxx b/panda/src/downloader/bioPtr.cxx index 2d709403f5..f6e08156dd 100644 --- a/panda/src/downloader/bioPtr.cxx +++ b/panda/src/downloader/bioPtr.cxx @@ -54,31 +54,4 @@ BioPtr:: } } -//////////////////////////////////////////////////////////////////// -// Function: BioPtr::connect -// Access: Public -// Description: Calls BIO_do_connect() to establish a connection on -// the previously-named server and port. Returns true -// if successful, false otherwise. -//////////////////////////////////////////////////////////////////// -bool BioPtr:: -connect() const { - nassertr(_bio != (BIO *)NULL && !_server_name.empty(), false); - if (downloader_cat.is_debug()) { - downloader_cat.debug() - << "Connecting to " << _server_name << ":" << _port << "\n"; - } - - if (BIO_do_connect(_bio) <= 0) { - downloader_cat.info() - << "Could not connect to " << _server_name << ":" << _port << "\n"; -#ifdef REPORT_SSL_ERRORS - ERR_print_errors_fp(stderr); -#endif - return false; - } - - return true; -} - #endif // HAVE_SSL diff --git a/panda/src/downloader/bioPtr.h b/panda/src/downloader/bioPtr.h index 8bf9352f5d..956596ae0a 100644 --- a/panda/src/downloader/bioPtr.h +++ b/panda/src/downloader/bioPtr.h @@ -51,7 +51,6 @@ public: INLINE void set_bio(BIO *bio); INLINE BIO *get_bio() const; - bool connect() const; INLINE const string &get_server_name() const; INLINE int get_port() const; diff --git a/panda/src/downloader/bioStream.I b/panda/src/downloader/bioStream.I index b48a8aafeb..db76b68736 100644 --- a/panda/src/downloader/bioStream.I +++ b/panda/src/downloader/bioStream.I @@ -44,7 +44,7 @@ IBioStream(BioPtr *source) : ISocketStream(&_buf) { INLINE IBioStream &IBioStream:: open(BioPtr *source) { clear(0); - _buf.open_read(source); + _buf.open(source); return *this; } @@ -56,6 +56,92 @@ open(BioPtr *source) { //////////////////////////////////////////////////////////////////// INLINE IBioStream &IBioStream:: close() { - _buf.close_read(); + _buf.close(); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: OBioStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OBioStream:: +OBioStream() : OSocketStream(&_buf) { +} + +//////////////////////////////////////////////////////////////////// +// Function: OBioStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OBioStream:: +OBioStream(BioPtr *source) : OSocketStream(&_buf) { + open(source); +} + +//////////////////////////////////////////////////////////////////// +// Function: OBioStream::open +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OBioStream &OBioStream:: +open(BioPtr *source) { + clear(0); + _buf.open(source); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: OBioStream::close +// Access: Public +// Description: Resets the BioStream to empty, but does not actually +// close the source BIO unless owns_source was true. +//////////////////////////////////////////////////////////////////// +INLINE OBioStream &OBioStream:: +close() { + _buf.close(); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE BioStream:: +BioStream() : SocketStream(&_buf) { +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE BioStream:: +BioStream(BioPtr *source) : SocketStream(&_buf) { + open(source); +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStream::open +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE BioStream &BioStream:: +open(BioPtr *source) { + clear(0); + _buf.open(source); + return *this; +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStream::close +// Access: Public +// Description: Resets the BioStream to empty, but does not actually +// close the source BIO unless owns_source was true. +//////////////////////////////////////////////////////////////////// +INLINE BioStream &BioStream:: +close() { + _buf.close(); return *this; } diff --git a/panda/src/downloader/bioStream.cxx b/panda/src/downloader/bioStream.cxx index 09bca3ac5f..25afbcc1f1 100644 --- a/panda/src/downloader/bioStream.cxx +++ b/panda/src/downloader/bioStream.cxx @@ -33,3 +33,37 @@ is_closed() { clear(); return false; } + +//////////////////////////////////////////////////////////////////// +// Function: OBioStream::is_closed +// Access: Public, Virtual +// Description: Returns true if the last write fail condition was +// triggered because the socket has genuinely closed, or +// false if we can expect to send more data along +// shortly. +//////////////////////////////////////////////////////////////////// +INLINE bool OBioStream:: +is_closed() { + if (_buf._is_closed) { + return true; + } + clear(); + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStream::is_closed +// Access: Public, Virtual +// Description: Returns true if the last eof or failure condition was +// triggered because the socket has genuinely closed, or +// false if we can expect to read or send more data +// shortly. +//////////////////////////////////////////////////////////////////// +INLINE bool BioStream:: +is_closed() { + if (_buf._is_closed) { + return true; + } + clear(); + return false; +} diff --git a/panda/src/downloader/bioStream.h b/panda/src/downloader/bioStream.h index 9409eac171..b154651fd2 100644 --- a/panda/src/downloader/bioStream.h +++ b/panda/src/downloader/bioStream.h @@ -50,6 +50,48 @@ private: BioStreamBuf _buf; }; +//////////////////////////////////////////////////////////////////// +// Class : OBioStream +// Description : An output stream object that writes data to an +// OpenSSL BIO object. This is used by the HTTPClient +// and HTTPChannel classes to provide a C++ interface +// to OpenSSL. +// +// Seeking is not supported. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS OBioStream : public OSocketStream { +public: + INLINE OBioStream(); + INLINE OBioStream(BioPtr *source); + + INLINE OBioStream &open(BioPtr *source); + INLINE OBioStream &close(); + + virtual bool is_closed(); + +private: + BioStreamBuf _buf; +}; + +//////////////////////////////////////////////////////////////////// +// Class : BioStream +// Description : A bi-directional stream object that reads and writes +// data to an OpenSSL BIO object. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS BioStream : public SocketStream { +public: + INLINE BioStream(); + INLINE BioStream(BioPtr *source); + + INLINE BioStream &open(BioPtr *source); + INLINE BioStream &close(); + + virtual bool is_closed(); + +private: + BioStreamBuf _buf; +}; + #include "bioStream.I" #endif // HAVE_SSL diff --git a/panda/src/downloader/bioStreamBuf.cxx b/panda/src/downloader/bioStreamBuf.cxx index b051e8fd6e..5a3856b20a 100644 --- a/panda/src/downloader/bioStreamBuf.cxx +++ b/panda/src/downloader/bioStreamBuf.cxx @@ -38,15 +38,21 @@ BioStreamBuf() { // In spite of the claims of the MSDN Library to the contrary, // Windows doesn't seem to provide an allocate() function, so we'll // do it by hand. - char *buf = new char[4096]; - char *ebuf = buf + 4096; - setg(buf, ebuf, ebuf); - setp(buf, ebuf); + char *buf = new char[8192]; + char *ebuf = buf + 8192; + char *mbuf = buf + 4096; + setg(buf, mbuf, mbuf); + setp(mbuf, ebuf); #else allocate(); - setg(base(), ebuf(), ebuf()); - setp(base(), ebuf()); + // Chop the buffer in half. The bottom half goes to the get buffer; + // the top half goes to the put buffer. + char *b = base(); + char *t = ebuf(); + char *m = b + (t - b) / 2; + setg(b, m, m); + setp(b, m); #endif } @@ -57,29 +63,79 @@ BioStreamBuf() { //////////////////////////////////////////////////////////////////// BioStreamBuf:: ~BioStreamBuf() { - close_read(); + close(); } //////////////////////////////////////////////////////////////////// -// Function: BioStreamBuf::open_read +// Function: BioStreamBuf::open // Access: Public // Description: //////////////////////////////////////////////////////////////////// void BioStreamBuf:: -open_read(BioPtr *source) { +open(BioPtr *source) { _source = source; } //////////////////////////////////////////////////////////////////// -// Function: BioStreamBuf::close_read +// Function: BioStreamBuf::close // Access: Public // Description: //////////////////////////////////////////////////////////////////// void BioStreamBuf:: -close_read() { +close() { + sync(); _source.clear(); } +//////////////////////////////////////////////////////////////////// +// Function: BioStreamBuf::overflow +// Access: Protected, Virtual +// Description: Called by the system ostream implementation when its +// internal buffer is filled, plus one character. +//////////////////////////////////////////////////////////////////// +int BioStreamBuf:: +overflow(int ch) { + size_t n = pptr() - pbase(); + if (n != 0) { + size_t num_wrote = write_chars(pbase(), n); + pbump(-(int)n); + if (num_wrote != n) { + return EOF; + } + } + + if (ch != EOF) { + // Store the next character back in the buffer. + *pptr() = ch; + pbump(1); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: BioStreamBuf::sync +// Access: Protected, Virtual +// Description: Called by the system iostream implementation to +// implement a flush operation. +//////////////////////////////////////////////////////////////////// +int BioStreamBuf:: +sync() { + /* + size_t n = egptr() - gptr(); + gbump(n); + */ + + size_t n = pptr() - pbase(); + size_t num_wrote = write_chars(pbase(), n); + pbump(-(int)n); + if (num_wrote != n) { + return EOF; + } + + return 0; +} + //////////////////////////////////////////////////////////////////// // Function: BioStreamBuf::underflow // Access: Protected, Virtual @@ -118,5 +174,51 @@ underflow() { return (unsigned char)*gptr(); } +//////////////////////////////////////////////////////////////////// +// Function: BioStreamBuf::write_chars +// Access: Private +// Description: Sends some characters to the dest stream. Does not +// return until all characters are sent or the socket is +// closed, even if the underlying BIO is non-blocking. +//////////////////////////////////////////////////////////////////// +size_t BioStreamBuf:: +write_chars(const char *start, size_t length) { + size_t wrote_so_far = 0; + + int write_count = BIO_write(*_source, start, length); + while (write_count != (int)(length - wrote_so_far)) { + if (write_count <= 0) { + _is_closed = !BIO_should_retry(*_source); + if (_is_closed) { + return wrote_so_far; + } + + // Block on the underlying socket before we try to write some + // more. + int fd = -1; + BIO_get_fd(*_source, &fd); + if (fd < 0) { + downloader_cat.warning() + << "socket BIO has no file descriptor.\n"; + } else { + downloader_cat.spam() + << "waiting to write to BIO.\n"; + fd_set wset; + FD_ZERO(&wset); + FD_SET(fd, &wset); + select(fd + 1, NULL, &wset, NULL, NULL); + } + + } else { + // wrote some characters. + wrote_so_far += write_count; + } + + // Try to write some more. + write_count = BIO_write(*_source, start + wrote_so_far, length - wrote_so_far); + } + + return length; +} #endif // HAVE_SSL diff --git a/panda/src/downloader/bioStreamBuf.h b/panda/src/downloader/bioStreamBuf.h index 7f605afa01..1edd4c05eb 100644 --- a/panda/src/downloader/bioStreamBuf.h +++ b/panda/src/downloader/bioStreamBuf.h @@ -38,17 +38,23 @@ public: BioStreamBuf(); virtual ~BioStreamBuf(); - void open_read(BioPtr *source); - void close_read(); + void open(BioPtr *source); + void close(); protected: + virtual int overflow(int c); + virtual int sync(void); virtual int underflow(void); private: + size_t write_chars(const char *start, size_t length); + PT(BioPtr) _source; bool _is_closed; friend class IBioStream; + friend class OBioStream; + friend class BioStream; }; #endif // HAVE_SSL diff --git a/panda/src/downloader/bioStreamPtr.I b/panda/src/downloader/bioStreamPtr.I index 180ae2be1a..ff8b158619 100644 --- a/panda/src/downloader/bioStreamPtr.I +++ b/panda/src/downloader/bioStreamPtr.I @@ -23,7 +23,7 @@ // Description: //////////////////////////////////////////////////////////////////// INLINE BioStreamPtr:: -BioStreamPtr(IBioStream *stream) : _stream(stream) { +BioStreamPtr(BioStream *stream) : _stream(stream) { } //////////////////////////////////////////////////////////////////// @@ -31,7 +31,7 @@ BioStreamPtr(IBioStream *stream) : _stream(stream) { // Access: Public // Description: //////////////////////////////////////////////////////////////////// -INLINE IBioStream &BioStreamPtr:: +INLINE BioStream &BioStreamPtr:: operator *() const { return *_stream; } @@ -41,7 +41,7 @@ operator *() const { // Access: Public // Description: //////////////////////////////////////////////////////////////////// -INLINE IBioStream *BioStreamPtr:: +INLINE BioStream *BioStreamPtr:: operator ->() const { return _stream; } @@ -52,7 +52,7 @@ operator ->() const { // Description: //////////////////////////////////////////////////////////////////// INLINE BioStreamPtr:: -operator IBioStream * () const { +operator BioStream * () const { return _stream; } @@ -62,7 +62,7 @@ operator IBioStream * () const { // Description: //////////////////////////////////////////////////////////////////// INLINE void BioStreamPtr:: -set_stream(IBioStream *stream) { +set_stream(BioStream *stream) { _stream = stream; } @@ -71,7 +71,7 @@ set_stream(IBioStream *stream) { // Access: Public // Description: //////////////////////////////////////////////////////////////////// -INLINE IBioStream *BioStreamPtr:: +INLINE BioStream *BioStreamPtr:: get_stream() const { return _stream; } diff --git a/panda/src/downloader/bioStreamPtr.cxx b/panda/src/downloader/bioStreamPtr.cxx index d68586a488..19e31f47e7 100644 --- a/panda/src/downloader/bioStreamPtr.cxx +++ b/panda/src/downloader/bioStreamPtr.cxx @@ -27,9 +27,9 @@ //////////////////////////////////////////////////////////////////// BioStreamPtr:: ~BioStreamPtr() { - if (_stream != (IBioStream *)NULL) { + if (_stream != (BioStream *)NULL) { delete _stream; - _stream = (IBioStream *)NULL; + _stream = (BioStream *)NULL; } } diff --git a/panda/src/downloader/bioStreamPtr.h b/panda/src/downloader/bioStreamPtr.h index 44946e642f..ec76616fd0 100644 --- a/panda/src/downloader/bioStreamPtr.h +++ b/panda/src/downloader/bioStreamPtr.h @@ -30,25 +30,25 @@ //////////////////////////////////////////////////////////////////// // Class : BioStreamPtr -// Description : A wrapper around an IBioStream object to make a +// Description : A wrapper around an BioStream object to make a // reference-counting pointer to it. //////////////////////////////////////////////////////////////////// class EXPCL_PANDAEXPRESS BioStreamPtr : public ReferenceCount { public: - INLINE BioStreamPtr(IBioStream *stream); + INLINE BioStreamPtr(BioStream *stream); virtual ~BioStreamPtr(); - INLINE IBioStream &operator *() const; - INLINE IBioStream *operator -> () const; - INLINE operator IBioStream * () const; + INLINE BioStream &operator *() const; + INLINE BioStream *operator -> () const; + INLINE operator BioStream * () const; - INLINE void set_stream(IBioStream *stream); - INLINE IBioStream *get_stream() const; + INLINE void set_stream(BioStream *stream); + INLINE BioStream *get_stream() const; bool connect() const; private: - IBioStream *_stream; + BioStream *_stream; }; #include "bioStreamPtr.I" diff --git a/panda/src/downloader/downloader_composite1.cxx b/panda/src/downloader/downloader_composite1.cxx index f1a24b7ed7..be4ada0327 100644 --- a/panda/src/downloader/downloader_composite1.cxx +++ b/panda/src/downloader/downloader_composite1.cxx @@ -13,5 +13,6 @@ #include "identityStreamBuf.cxx" #include "multiplexStream.cxx" #include "multiplexStreamBuf.cxx" +#include "socketStream.cxx" #include "urlSpec.cxx" diff --git a/panda/src/downloader/httpChannel.I b/panda/src/downloader/httpChannel.I index 245a763407..b63e44e986 100644 --- a/panda/src/downloader/httpChannel.I +++ b/panda/src/downloader/httpChannel.I @@ -30,6 +30,18 @@ is_valid() const { (_status_code / 100) == 2); } +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::is_connection_ready +// Access: Published +// Description: Returns true if a connection has been established to +// the named server in a previous call to connect_to() +// or begin_connect_to(), false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool HTTPChannel:: +is_connection_ready() const { + return (!_source.is_null() && _state == S_ready); +} + //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::get_url // Access: Published @@ -312,7 +324,7 @@ reset() { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: post_form(const URLSpec &url, const string &body) { - begin_request("POST", url, body, false, 0, 0); + begin_request(M_post, url, body, false, 0, 0); run(); return is_valid(); } @@ -325,7 +337,7 @@ post_form(const URLSpec &url, const string &body) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_document(const URLSpec &url) { - begin_request("GET", url, string(), false, 0, 0); + begin_request(M_get, url, string(), false, 0, 0); run(); return is_valid(); } @@ -342,7 +354,7 @@ get_document(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { - begin_request("GET", url, string(), false, first_byte, last_byte); + begin_request(M_get, url, string(), false, first_byte, last_byte); run(); return is_valid(); } @@ -358,11 +370,30 @@ get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_header(const URLSpec &url) { - begin_request("HEAD", url, string(), false, 0, 0); + begin_request(M_head, url, string(), false, 0, 0); run(); return is_valid(); } +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::connect_to +// Access: Published +// Description: Establish a direct connection to the server and port +// indicated by the URL, but do not issue any HTTP +// requests. If successful, the connection may then be +// taken to use for whatever purposes you like by +// calling get_connection(). +// +// This establishes a blocking I/O socket. Also see +// begin_connect_to(). +//////////////////////////////////////////////////////////////////// +INLINE bool HTTPChannel:: +connect_to(const URLSpec &url) { + begin_request(M_connect, url, string(), false, 0, 0); + run(); + return is_connection_ready(); +} + //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::begin_post_form // Access: Published @@ -379,7 +410,7 @@ get_header(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_post_form(const URLSpec &url, const string &body) { - begin_request("POST", url, body, true, 0, 0); + begin_request(M_post, url, body, true, 0, 0); } //////////////////////////////////////////////////////////////////// @@ -398,7 +429,7 @@ begin_post_form(const URLSpec &url, const string &body) { //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_get_document(const URLSpec &url) { - begin_request("GET", url, string(), true, 0, 0); + begin_request(M_get, url, string(), true, 0, 0); } //////////////////////////////////////////////////////////////////// @@ -415,7 +446,7 @@ begin_get_document(const URLSpec &url) { INLINE void HTTPChannel:: begin_get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { - begin_request("GET", url, string(), true, first_byte, last_byte); + begin_request(M_get, url, string(), true, first_byte, last_byte); } //////////////////////////////////////////////////////////////////// @@ -426,7 +457,30 @@ begin_get_subdocument(const URLSpec &url, size_t first_byte, //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_get_header(const URLSpec &url) { - begin_request("HEAD", url, string(), true, 0, 0); + begin_request(M_head, url, string(), true, 0, 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::begin_connect_to +// Access: Published +// Description: Begins a non-blocking request to establish a direct +// connection to the server and port indicated by the +// URL. No HTTP requests will be issued beyond what is +// necessary to establish the connection. When run() +// has finished, you may call is_connection_ready() to +// determine if the connection was successfully +// established. +// +// If successful, the connection may then be taken to +// use for whatever purposes you like by calling +// get_connection(). +// +// This establishes a nonblocking I/O socket. Also see +// connect_to(). +//////////////////////////////////////////////////////////////////// +INLINE void HTTPChannel:: +begin_connect_to(const URLSpec &url) { + begin_request(M_connect, url, string(), true, 0, 0); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/downloader/httpChannel.cxx b/panda/src/downloader/httpChannel.cxx index 655a602001..73db528356 100644 --- a/panda/src/downloader/httpChannel.cxx +++ b/panda/src/downloader/httpChannel.cxx @@ -56,6 +56,8 @@ HTTPChannel(HTTPClient *client) : _max_updates_per_second = 1.0f / _seconds_per_update; _bytes_per_update = int(_max_bytes_per_second * _seconds_per_update); _nonblocking = false; + _want_ssl = false; + _proxy_serves_document = false; _first_byte = 0; _last_byte = 0; _read_index = 0; @@ -258,7 +260,7 @@ run() { } else { _bio = new BioPtr(_proxy); } - _source = new BioStreamPtr(new IBioStream(_bio)); + _source = new BioStreamPtr(new BioStream(_bio)); if (_nonblocking) { BIO_set_nbio(*_bio, 1); } @@ -509,6 +511,33 @@ download_to_ram(Ramfile *ramfile) { return is_download_complete(); } +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::get_connection +// Access: Published +// Description: Returns the connection that was established via a +// previous call to connect_to() or begin_connect_to(), +// or NULL if the connection attempt failed or if those +// methods have not recently been called. +// +// This stream has been allocated from the free store. +// It is the user's responsibility to delete this +// pointer when finished with it. +//////////////////////////////////////////////////////////////////// +SocketStream *HTTPChannel:: +get_connection() { + if (!is_connection_ready()) { + return NULL; + } + + BioStream *stream = _source->get_stream(); + _source->set_stream(NULL); + + // We're now passing ownership of the connection to the user. + free_bio(); + + return stream; +} + //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::reached_done_state // Access: Private @@ -552,6 +581,8 @@ reached_done_state() { //////////////////////////////////////////////////////////////////// bool HTTPChannel:: run_connecting() { + _status_code = 0; + _status_string = string(); if (BIO_do_connect(*_bio) <= 0) { if (BIO_should_retry(*_bio)) { return true; @@ -577,11 +608,7 @@ run_connecting() { _state = S_proxy_ready; } else { - if (_url.get_scheme() == "https") { - _state = S_setup_ssl; - } else { - _state = S_ready; - } + _state = _want_ssl ? S_setup_ssl : S_ready; } return false; } @@ -702,11 +729,7 @@ run_proxy_reading_header() { _proxy_tunnel = true; make_request_text(string()); - if (_url.get_scheme() == "https") { - _state = S_setup_ssl; - } else { - _state = S_ready; - } + _state = _want_ssl ? S_setup_ssl : S_ready; return false; } @@ -978,7 +1001,7 @@ run_reading_header() { if ((get_status_code() / 100) == 3 && get_status_code() != 305) { // Redirect. Should we handle it automatically? - if (!get_redirect().empty() && (_method == "GET" || _method == "HEAD")) { + if (!get_redirect().empty() && (_method == M_get || _method == M_head)) { // Sure! URLSpec new_url = get_redirect(); if (!_redirect_trail.insert(new_url).second) { @@ -1049,7 +1072,7 @@ run_begin_body() { if (get_status_code() / 100 == 1 || get_status_code() == 204 || get_status_code() == 304 || - _method == "HEAD") { + _method == M_head) { // These status codes, or method HEAD, indicate we have no body. // Therefore, we have already read the (nonexistent) body. _state = S_ready; @@ -1283,8 +1306,9 @@ run_download_to_ram() { // necessary. //////////////////////////////////////////////////////////////////// void HTTPChannel:: -begin_request(const string &method, const URLSpec &url, const string &body, - bool nonblocking, size_t first_byte, size_t last_byte) { +begin_request(HTTPChannel::Method method, const URLSpec &url, + const string &body, bool nonblocking, + size_t first_byte, size_t last_byte) { reset_for_new_request(); // Changing the proxy, or the nonblocking state, is grounds for @@ -1299,19 +1323,29 @@ begin_request(const string &method, const URLSpec &url, const string &body, free_bio(); } - _method = method; set_url(url); + _method = method; _body = body; + + // An https-style request means we'll need to establish an SSL + // connection. + _want_ssl = (_url.get_scheme() == "https"); + + // If we have a proxy, we'll be asking the proxy to hand us the + // document--except when we also have https, in which case we'll be + // tunnelling through the proxy to talk to the server directly. + _proxy_serves_document = (!_proxy.empty() && !_want_ssl); + _first_byte = first_byte; _last_byte = last_byte; + make_header(); make_request_text(string()); - if (!_proxy.empty() && _url.get_scheme() == "https") { - // HTTPS over proxy requires tunnelling through the proxy to the - // server so we can handle the SSL connection directly, rather - // than asking the proxy to hand us the particular document(s) in - // question. + if (!_proxy.empty() && (_want_ssl || _method == M_connect)) { + // Maybe we need to tunnel through the proxy to connect to the + // server directly. We need this for HTTPS, or if the user + // requested a direct connection somewhere. ostringstream request; request << "CONNECT " << _url.get_server() << ":" << _url.get_port() @@ -1338,7 +1372,7 @@ begin_request(const string &method, const URLSpec &url, const string &body, _state = S_begin_body; } - _done_state = S_read_header; + _done_state = (_method == M_connect) ? S_ready : S_read_header; } //////////////////////////////////////////////////////////////////// @@ -1414,6 +1448,8 @@ bool HTTPChannel:: http_send(const string &str) { nassertr(str.length() > _sent_so_far, true); + // Use the underlying BIO to write to the server, instead of the + // BIOStream, which would insist on blocking. size_t bytes_to_send = str.length() - _sent_so_far; int write_count = BIO_write(*_bio, str.data() + _sent_so_far, bytes_to_send); @@ -1923,24 +1959,51 @@ x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) { //////////////////////////////////////////////////////////////////// void HTTPChannel:: make_header() { - string path; - if (_proxy.empty() || _url.get_scheme() == "https") { - // In either of these cases, we contact the server directly for - // the document, so we just need the server-relative path. - path = _url.get_path(); + if (_method == M_connect) { + // This method doesn't require an HTTP header at all; we'll just + // open a plain connection. (Except when we're using a proxy; but + // in that case, it's the proxy_header we'll need, not the regular + // HTTP header.) + _header = string(); + return; + } - } else { - // In this case (http-over-proxy), we ask the proxy for the - // document, so we need its full URL. + string path; + if (_proxy_serves_document) { + // If we'll be asking the proxy for the document, we need its full + // URL--but we omit the username, which is information just for us. URLSpec url_no_username = _url; url_no_username.set_username(string()); path = url_no_username.get_url(); + + } else { + // If we'll be asking the server directly for the document, we + // just want its path relative to the server. + path = _url.get_path(); } ostringstream stream; + switch (_method) { + case M_get: + stream << "GET"; + break; + + case M_head: + stream << "HEAD"; + break; + + case M_post: + stream << "POST"; + break; + + case M_connect: + stream << "CONNECT"; + break; + } + stream - << _method << " " << path << " " + << " " << path << " " << _client->get_http_version_string() << "\r\n"; if (_client->get_http_version() >= HTTPClient::HV_11) { diff --git a/panda/src/downloader/httpChannel.h b/panda/src/downloader/httpChannel.h index ddcc39930d..38ef5f4bb6 100644 --- a/panda/src/downloader/httpChannel.h +++ b/panda/src/downloader/httpChannel.h @@ -74,6 +74,7 @@ public: PUBLISHED: INLINE bool is_valid() const; + INLINE bool is_connection_ready() const; INLINE const URLSpec &get_url() const; INLINE HTTPClient::HTTPVersion get_http_version() const; INLINE const string &get_http_version_string() const; @@ -106,6 +107,7 @@ PUBLISHED: INLINE bool get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte); INLINE bool get_header(const URLSpec &url); + INLINE bool connect_to(const URLSpec &url); INLINE void begin_post_form(const URLSpec &url, const string &body); INLINE void begin_get_document(const URLSpec &url); @@ -113,16 +115,25 @@ PUBLISHED: size_t first_byte, size_t last_byte); INLINE void begin_get_header(const URLSpec &url); bool run(); + INLINE void begin_connect_to(const URLSpec &url); ISocketStream *read_body(); bool download_to_file(const Filename &filename, size_t first_byte = 0); bool download_to_ram(Ramfile *ramfile); + SocketStream *get_connection(); INLINE size_t get_bytes_downloaded() const; INLINE size_t get_bytes_requested() const; INLINE bool is_download_complete() const; private: + enum Method { + M_get, + M_head, + M_post, + M_connect + }; + bool reached_done_state(); bool run_connecting(); bool run_proxy_ready(); @@ -142,7 +153,7 @@ private: bool run_download_to_file(); bool run_download_to_ram(); - void begin_request(const string &method, const URLSpec &url, + void begin_request(Method method, const URLSpec &url, const string &body, bool nonblocking, size_t first_byte, size_t last_byte); void reset_for_new_request(); @@ -193,9 +204,11 @@ private: bool _nonblocking; URLSpec _url; - string _method; + Method _method; string _header; string _body; + bool _want_ssl; + bool _proxy_serves_document; size_t _first_byte; size_t _last_byte; diff --git a/panda/src/downloader/socketStream.I b/panda/src/downloader/socketStream.I index 7be19e2e35..d37bc3c131 100644 --- a/panda/src/downloader/socketStream.I +++ b/panda/src/downloader/socketStream.I @@ -24,4 +24,24 @@ //////////////////////////////////////////////////////////////////// INLINE ISocketStream:: ISocketStream(streambuf *buf) : istream(buf) { + _data_expected = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: OSocketStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE OSocketStream:: +OSocketStream(streambuf *buf) : ostream(buf) { +} + +//////////////////////////////////////////////////////////////////// +// Function: SocketStream::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE SocketStream:: +SocketStream(streambuf *buf) : iostream(buf) { + _data_expected = 0; } diff --git a/panda/src/downloader/socketStream.cxx b/panda/src/downloader/socketStream.cxx new file mode 100644 index 0000000000..9c48145d36 --- /dev/null +++ b/panda/src/downloader/socketStream.cxx @@ -0,0 +1,165 @@ +// Filename: socketStream.cxx +// Created by: drose (19Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "socketStream.h" +#include "datagram.h" +#include "datagramIterator.h" + +//////////////////////////////////////////////////////////////////// +// Function: ISocketStream::receive_datagram +// Access: Public +// Description: Receives a datagram over the socket by expecting a +// little-endian 16-bit byte count as a prefix. If the +// socket stream is non-blocking, may return false if +// the data is not available; otherwise, returns false +// only if the socket closes. +//////////////////////////////////////////////////////////////////// +bool ISocketStream:: +receive_datagram(Datagram &dg) { + if (_data_expected == 0) { + // Read the first two bytes: the datagram length. + while (_data_so_far.length() < 2) { + int ch = get(); + if (eof()) { + clear(); + return false; + } + _data_so_far += (char)ch; + } + + Datagram header(_data_so_far); + DatagramIterator di(header); + _data_expected = di.get_uint16(); + _data_so_far = string(); + + if (_data_expected == 0) { + // Empty datagram. + dg.clear(); + return true; + } + } + + // Read the next n bytes until the datagram is filled. + while (_data_so_far.length() < _data_expected) { + int ch = get(); + if (eof()) { + clear(); + return false; + } + _data_so_far += (char)ch; + } + + dg.clear(); + dg.append_data(_data_so_far); + + _data_expected = 0; + _data_so_far = string(); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: OSocketStream::send_datagram +// Access: Public +// Description: Transmits the indicated datagram over the socket by +// prepending it with a little-endian 16-bit byte count. +// Does not return until the data is sent or the +// connection is closed, even if the socket stream is +// non-blocking. +//////////////////////////////////////////////////////////////////// +bool OSocketStream:: +send_datagram(const Datagram &dg) { + Datagram header; + header.add_uint16(dg.get_length()); + write(header.get_data(), header.get_length()); + write(dg.get_data(), dg.get_length()); + flush(); + + return !is_closed(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SocketStream::receive_datagram +// Access: Public +// Description: Receives a datagram over the socket by expecting a +// little-endian 16-bit byte count as a prefix. If the +// socket stream is non-blocking, may return false if +// the data is not available; otherwise, returns false +// only if the socket closes. +//////////////////////////////////////////////////////////////////// +bool SocketStream:: +receive_datagram(Datagram &dg) { + if (_data_expected == 0) { + // Read the first two bytes: the datagram length. + while (_data_so_far.length() < 2) { + int ch = get(); + if (eof()) { + clear(); + return false; + } + _data_so_far += (char)ch; + } + + Datagram header(_data_so_far); + DatagramIterator di(header); + _data_expected = di.get_uint16(); + _data_so_far = string(); + + if (_data_expected == 0) { + // Empty datagram. + dg.clear(); + return true; + } + } + + // Read the next n bytes until the datagram is filled. + while (_data_so_far.length() < _data_expected) { + int ch = get(); + if (eof()) { + clear(); + return false; + } + _data_so_far += (char)ch; + } + + dg.clear(); + dg.append_data(_data_so_far); + + _data_expected = 0; + _data_so_far = string(); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: SocketStream::send_datagram +// Access: Public +// Description: Transmits the indicated datagram over the socket by +// prepending it with a little-endian 16-bit byte count. +// Does not return until the data is sent or the +// connection is closed, even if the socket stream is +// non-blocking. +//////////////////////////////////////////////////////////////////// +bool SocketStream:: +send_datagram(const Datagram &dg) { + Datagram header; + header.add_uint16(dg.get_length()); + write(header.get_data(), header.get_length()); + write(dg.get_data(), dg.get_length()); + flush(); + + return !is_closed(); +} diff --git a/panda/src/downloader/socketStream.h b/panda/src/downloader/socketStream.h index c396982aa2..1c63661d01 100644 --- a/panda/src/downloader/socketStream.h +++ b/panda/src/downloader/socketStream.h @@ -27,6 +27,8 @@ #ifdef HAVE_SSL +class Datagram; + //////////////////////////////////////////////////////////////////// // Class : ISocketStream // Description : This is a base class for istreams implemented in @@ -41,9 +43,54 @@ public: INLINE ISocketStream(streambuf *buf); PUBLISHED: + bool receive_datagram(Datagram &dg); + + virtual bool is_closed() = 0; + +private: + size_t _data_expected; + string _data_so_far; +}; + +//////////////////////////////////////////////////////////////////// +// Class : OSocketStream +// Description : A base class for ostreams that write to a (possibly +// non-blocking) socket. It adds is_closed(), which can +// be called after any write operation fails to check +// whether the socket has been closed, or whether more +// data may be sent later. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS OSocketStream : public ostream { +public: + INLINE OSocketStream(streambuf *buf); + +PUBLISHED: + bool send_datagram(const Datagram &dg); + virtual bool is_closed() = 0; }; +//////////////////////////////////////////////////////////////////// +// Class : SocketStream +// Description : A base class for iostreams that read and write to a +// (possibly non-blocking) socket. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS SocketStream : public iostream { +public: + INLINE SocketStream(streambuf *buf); + +PUBLISHED: + bool receive_datagram(Datagram &dg); + bool send_datagram(const Datagram &dg); + + virtual bool is_closed() = 0; + +private: + size_t _data_expected; + string _data_so_far; +}; + + #include "socketStream.I" #endif // HAVE_SSL