// Filename: httpChannel.I
// Created by:  drose (24Sep02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::is_valid
//       Access: Published
//  Description: Returns true if the last-requested document was
//               successfully retrieved and is ready to be read, false
//               otherwise.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
is_valid() const {
  return (_state != S_failure && (get_status_code() / 100) == 2 &&
          (_server_response_has_no_body || !_source.is_null()));
}

////////////////////////////////////////////////////////////////////
//     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
//  Description: Returns the URL that was used to retrieve the
//               most recent document: whatever URL was last passed to
//               get_document() or get_header().  If a redirect has
//               transparently occurred, this will return the new,
//               redirected URL (the actual URL at which the document
//               was located).
////////////////////////////////////////////////////////////////////
INLINE const URLSpec &HTTPChannel::
get_url() const {
  return _document_spec.get_url();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_document_spec
//       Access: Published
//  Description: Returns the DocumentSpec associated with the most
//               recent document.  This includes its actual URL
//               (following redirects) along with the identity tag and
//               last-modified date, if supplied by the server.
//
//               This structure may be saved and used to retrieve the
//               same version of the document later, or to
//               conditionally retrieve a newer version if it is
//               available.
////////////////////////////////////////////////////////////////////
INLINE const DocumentSpec &HTTPChannel::
get_document_spec() const {
  return _document_spec;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_http_version
//       Access: Published
//  Description: Returns the HTTP version number returned by the
//               server, as one of the HTTPClient enumerated types,
//               e.g. HTTPClient::HV_11.
////////////////////////////////////////////////////////////////////
INLINE HTTPEnum::HTTPVersion HTTPChannel::
get_http_version() const {
  return _http_version;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_http_version_string
//       Access: Published
//  Description: Returns the HTTP version number returned by the
//               server, formatted as a string, e.g. "HTTP/1.1".
////////////////////////////////////////////////////////////////////
INLINE const string &HTTPChannel::
get_http_version_string() const {
  return _http_version_string;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_status_code
//       Access: Published
//  Description: Returns the HTML return code from the document
//               retrieval request.  This will be in the 200 range if
//               the document is successfully retrieved, or some other
//               value in the case of an error.
//
//               Some proxy errors during an https-over-proxy request
//               would return the same status code as a different
//               error that occurred on the host server.  To
//               differentiate these cases, status codes that are
//               returned by the proxy during the CONNECT phase
//               (except code 407) are incremented by 1000.
////////////////////////////////////////////////////////////////////
INLINE int HTTPChannel::
get_status_code() const {
  return _status_entry._status_code;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_www_realm
//       Access: Published
//  Description: If the document failed to connect because of a 401
//               (Authorization required), this method will return the
//               "realm" returned by the server in which the requested
//               document must be authenticated.  This string may be
//               presented to the user to request an associated
//               username and password (which then should be stored in
//               HTTPClient::set_username()).
////////////////////////////////////////////////////////////////////
INLINE const string &HTTPChannel::
get_www_realm() const {
  return _www_realm;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_proxy_realm
//       Access: Published
//  Description: If the document failed to connect because of a 407
//               (Proxy authorization required), this method will
//               return the "realm" returned by the proxy.  This
//               string may be presented to the user to request an
//               associated username and password (which then should
//               be stored in HTTPClient::set_username()).
////////////////////////////////////////////////////////////////////
INLINE const string &HTTPChannel::
get_proxy_realm() const {
  return _proxy_realm;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_redirect
//       Access: Published
//  Description: If the document failed with a redirect code (300
//               series), this will generally contain the new URL the
//               server wants us to try.  In many cases, the client
//               will automatically follow redirects; if these are
//               succesful the client will return a successful code
//               and get_redirect() will return empty, but get_url()
//               will return the new, redirected URL.
////////////////////////////////////////////////////////////////////
INLINE const URLSpec &HTTPChannel::
get_redirect() const {
  return _redirect;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_num_redirect_trail
//       Access: Published
//  Description: If the document automatically followed one or more
//               redirects, this will return the number of redirects
//               that were automatically followed.  Use
//               get_redirect_trail() to retrieve each URL in
//               sequence.
////////////////////////////////////////////////////////////////////
INLINE int HTTPChannel::
get_num_redirect_trail() const {
  return _redirect_trail.size();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_redirect_trail
//       Access: Published
//  Description: Use in conjunction with get_num_redirect_trail() to
//               extract the chain of URL's that the channel was
//               automatically redirected through to arrive at the
//               final document.
////////////////////////////////////////////////////////////////////
INLINE const URLSpec &HTTPChannel::
get_redirect_trail(int n) const {
  nassertr(n >= 0 && n < (int)_redirect_trail.size(), _redirect_trail[0]);
  return _redirect_trail[n];
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_persistent_connection
//       Access: Published
//  Description: Indicates whether the HTTPChannel should try to keep
//               the connection to the server open and reuse that
//               connection for multiple documents, or whether it
//               should close the connection and open a new one for
//               each request.  Set this true to keep the connections
//               around when possible, false to recycle them.
//
//               It makes most sense to set this false when the
//               HTTPChannel will be used only once to retrieve a
//               single document, true when you will be using the same
//               HTTPChannel object to retrieve multiple documents.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_persistent_connection(bool persistent_connection) {
  _persistent_connection = persistent_connection;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_persistent_connection
//       Access: Published
//  Description: Returns whether the HTTPChannel should try to keep
//               the connection to the server open and reuse that
//               connection for multiple documents, or whether it
//               should close the connection and open a new one for
//               each request.  See set_persistent_connection().
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_persistent_connection() const {
  return _persistent_connection;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_allow_proxy
//       Access: Published
//  Description: If this is true (the normal case), the HTTPClient
//               will be consulted for information about the proxy to
//               be used for each connection via this HTTPChannel.  If
//               this has been set to false by the user, then all
//               connections will be made directly, regardless of the
//               proxy settings indicated on the HTTPClient.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_allow_proxy(bool allow_proxy) {
  _allow_proxy = allow_proxy;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_allow_proxy
//       Access: Published
//  Description: If this is true (the normal case), the HTTPClient
//               will be consulted for information about the proxy to
//               be used for each connection via this HTTPChannel.  If
//               this has been set to false by the user, then all
//               connections will be made directly, regardless of the
//               proxy settings indicated on the HTTPClient.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_allow_proxy() const {
  return _allow_proxy;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_proxy_tunnel
//       Access: Published
//  Description: Normally, a proxy is itself asked for ordinary URL's,
//               and the proxy decides whether to hand the client a
//               cached version of the document or to contact the
//               server for a fresh version.  The proxy may also
//               modify the headers and transfer encoding on the way.
//
//               If this is set to true, then instead of asking for
//               URL's from the proxy, we will ask the proxy to open a
//               connection to the server (for instance, on port 80);
//               if the proxy honors this request, then we contact the
//               server directly through this connection to retrieve
//               the document.  If the proxy does not honor the
//               connect request, then the retrieve operation fails.
//
//               SSL connections (e.g. https), and connections through
//               a Socks proxy, are always tunneled, regardless of the
//               setting of this flag.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_proxy_tunnel(bool proxy_tunnel) {
  _proxy_tunnel = proxy_tunnel;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_proxy_tunnel
//       Access: Published
//  Description: Returns true if connections always tunnel through a
//               proxy, or false (the normal case) if we allow the
//               proxy to serve up documents.  See set_proxy_tunnel().
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_proxy_tunnel() const {
  return _proxy_tunnel;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_connect_timeout
//       Access: Published
//  Description: Sets the maximum length of time, in seconds, that the
//               channel will wait before giving up on establishing a
//               TCP connection.
//
//               At present, this is used only for the nonblocking
//               interfaces (e.g. begin_get_document(),
//               begin_connect_to()), but it is used whether
//               set_blocking_connect() is true or false.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_connect_timeout(double connect_timeout) {
  _connect_timeout = connect_timeout;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_connect_timeout
//       Access: Published
//  Description: Returns the length of time, in seconds, to wait for a
//               new nonblocking socket to connect.  See
//               set_connect_timeout().
////////////////////////////////////////////////////////////////////
INLINE double HTTPChannel::
get_connect_timeout() const {
  return _connect_timeout;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_blocking_connect
//       Access: Published
//  Description: If this flag is true, a socket connect will block
//               even for nonblocking I/O calls like
//               begin_get_document(), begin_connect_to(), etc.  If
//               false, a socket connect will not block for
//               nonblocking I/O calls, but will block for blocking
//               I/O calls (get_document(), connect_to(), etc.).

//               Setting this true is useful when you want to use
//               non-blocking I/O once you have established the
//               connection, but you don't want to bother with polling
//               for the initial connection.  It's also useful when
//               you don't particularly care about non-blocking I/O,
//               but you need to respect timeouts like connect_timeout
//               and http_timeout.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_blocking_connect(bool blocking_connect) {
  _blocking_connect = blocking_connect;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_blocking_connect
//       Access: Published
//  Description: If this flag is true, a socket connect will block
//               even for nonblocking I/O calls like
//               begin_get_document(), begin_connect_to(), etc.  If
//               false, a socket connect will not block for
//               nonblocking I/O calls, but will block for blocking
//               I/O calls (get_document(), connect_to(), etc.).
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_blocking_connect() const {
  return _blocking_connect;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_http_timeout
//       Access: Published
//  Description: Sets the maximum length of time, in seconds, that the
//               channel will wait for the HTTP server to finish
//               sending its response to our request.
//
//               The timer starts counting after the TCP connection
//               has been established (see set_connect_timeout(),
//               above) and the request has been sent.
//
//               At present, this is used only for the nonblocking
//               interfaces (e.g. begin_get_document(),
//               begin_connect_to()), but it is used whether
//               set_blocking_connect() is true or false.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_http_timeout(double http_timeout) {
  _http_timeout = http_timeout;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_http_timeout
//       Access: Published
//  Description: Returns the length of time, in seconds, to wait for 
//               the HTTP server to respond to our request.  See
//               set_http_timeout().
////////////////////////////////////////////////////////////////////
INLINE double HTTPChannel::
get_http_timeout() const {
  return _http_timeout;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_download_throttle
//       Access: Published
//  Description: Specifies whether nonblocking downloads (via
//               download_to_file() or download_to_ram()) will be
//               limited so as not to use all available bandwidth.
//
//               If this is true, when a download has been started on
//               this channel it will be invoked no more frequently
//               than get_max_updates_per_second(), and the total
//               bandwidth used by the download will be no more than
//               get_max_bytes_per_second().  If this is false,
//               downloads will proceed as fast as the server can send
//               the data.
//
//               This only has effect on the nonblocking I/O methods
//               like begin_get_document(), etc.  The blocking methods
//               like get_document() always use as much CPU and
//               bandwidth as they can get.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_download_throttle(bool download_throttle) {
  _download_throttle = download_throttle;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_download_throttle
//       Access: Published
//  Description: Returns whether the nonblocking downloads will be
//               bandwidth-limited.  See set_download_throttle().
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_download_throttle() const {
  return _download_throttle;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_max_bytes_per_second
//       Access: Published
//  Description: When bandwidth throttling is in effect (see
//               set_download_throttle()), this specifies the maximum
//               number of bytes per second that may be consumed by
//               this channel.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_max_bytes_per_second(double max_bytes_per_second) {
  _max_bytes_per_second = max_bytes_per_second;
  _bytes_per_update = int(_max_bytes_per_second * _seconds_per_update);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_max_bytes_per_second
//       Access: Published
//  Description: Returns the maximum number of bytes per second that
//               may be consumed by this channel when
//               get_download_throttle() is true.
////////////////////////////////////////////////////////////////////
INLINE double HTTPChannel::
get_max_bytes_per_second() const {
  return _max_bytes_per_second;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_max_updates_per_second
//       Access: Published
//  Description: When bandwidth throttling is in effect (see
//               set_download_throttle()), this specifies the maximum
//               number of times per second that run() will attempt to
//               do any downloading at all.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_max_updates_per_second(double max_updates_per_second) {
  nassertv(max_updates_per_second != 0.0f);
  _max_updates_per_second = max_updates_per_second;
  _seconds_per_update = 1.0f / _max_updates_per_second;
  _bytes_per_update = int(_max_bytes_per_second * _seconds_per_update);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_max_updates_per_second
//       Access: Published
//  Description: Returns the maximum number of times per second that
//               run() will do anything at all, when
//               get_download_throttle() is true.
////////////////////////////////////////////////////////////////////
INLINE double HTTPChannel::
get_max_updates_per_second() const {
  return _max_updates_per_second;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::set_expected_file_size
//       Access: Published
//  Description: This may be called immediately after a call to
//               get_document() or some related function to specify
//               the expected size of the document we are retrieving,
//               if we happen to know.  This is used as the return
//               value to get_file_size() only in the case that the
//               server does not tell us the actual file size.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
set_expected_file_size(size_t file_size) {
  _expected_file_size = file_size;
  _got_expected_file_size = true;
}


////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::is_file_size_known
//       Access: Published
//  Description: Returns true if the size of the file we are currently
//               retrieving was told us by the server and thus is
//               reliably known, or false if the size reported by
//               get_file_size() represents an educated guess
//               (possibly as set by set_expected_file_size(), or as
//               inferred from a chunked transfer encoding in
//               progress).
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
is_file_size_known() const {
  return _got_file_size;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::reset
//       Access: Published
//  Description: Stops whatever file transaction is currently in
//               progress, closes the connection, and resets to begin
//               anew.  You shouldn't ever need to call this, since
//               the channel should be able to reset itself cleanly
//               between requests, but it is provided in case you are
//               an especially nervous type.
//
//               Don't call this after every request unless you set
//               set_persistent_connection() to false, since calling
//               reset() rudely closes the connection regardless of
//               whether we have told the server we intend to keep it
//               open or not.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
reset() {
  reset_for_new_request();
  reset_to_new();
  _status_list.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::preserve_status
//       Access: Published
//  Description: Preserves the previous status code (presumably a
//               failure) from the previous connection attempt.  If
//               the subsequent connection attempt also fails, the
//               returned status code will be the better of the
//               previous code and the current code.
//
//               This can be called to daisy-chain subsequent attempts
//               to download the same document from different servers.
//               After all servers have been attempted, the final
//               status code will reflect the attempt that most nearly
//               succeeded.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
preserve_status() {
  _status_list.push_back(_status_entry);
}


////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::clear_extra_headers
//       Access: Published
//  Description: Resets the extra headers that were previously added
//               via calls to send_extra_header().
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
clear_extra_headers() {
  _send_extra_headers = string();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::send_extra_header
//       Access: Published
//  Description: Specifies an additional key: value pair that is added
//               into the header sent to the server with the next
//               request.  This is passed along with no interpretation
//               by the HTTPChannel code.  You may call this
//               repeatedly to append multiple headers.
//
//               This is persistent for one request only; it must be
//               set again for each new request.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
send_extra_header(const string &key, const string &value) {
  _send_extra_headers += key;
  _send_extra_headers += ": ";
  _send_extra_headers += value;
  _send_extra_headers += "\r\n";
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_document
//       Access: Published
//  Description: Opens the named document for reading, if available.
//               Returns true if successful, false otherwise.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_document(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_get, url, string(), false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_subdocument
//       Access: Published
//  Description: Retrieves only the specified byte range of the
//               indicated document.  If last_byte is 0, it stands for
//               the last byte of the document.  When a subdocument is
//               requested, get_file_size() and get_bytes_downloaded()
//               will report the number of bytes of the subdocument,
//               not of the complete document.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_subdocument(const DocumentSpec &url, size_t first_byte, size_t last_byte) {
  begin_request(HTTPEnum::M_get, url, string(), false, first_byte, last_byte);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_header
//       Access: Published
//  Description: Like get_document(), except only the header
//               associated with the document is retrieved.  This may
//               be used to test for existence of the document; it
//               might also return the size of the document (if the
//               server gives us this information).
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_header(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_head, url, string(), false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::post_form
//       Access: Published
//  Description: Posts form data to a particular URL and retrieves the
//               response.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
post_form(const DocumentSpec &url, const string &body) {
  begin_request(HTTPEnum::M_post, url, body, false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::put_document
//       Access: Published
//  Description: Uploads the indicated body to the server to replace
//               the indicated URL, if the server allows this.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
put_document(const DocumentSpec &url, const string &body) {
  begin_request(HTTPEnum::M_put, url, body, false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::delete_document
//       Access: Published
//  Description: Requests the server to remove the indicated URL.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
delete_document(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_delete, url, string(), false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_trace
//       Access: Published
//  Description: Sends a TRACE message to the server, which should
//               return back the same message as the server received
//               it, allowing inspection of proxy hops, etc.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_trace(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_trace, 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 DocumentSpec &url) {
  begin_request(HTTPEnum::M_connect, url, string(), false, 0, 0);
  run();
  return is_connection_ready();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_options
//       Access: Published
//  Description: Sends an OPTIONS message to the server, which should
//               query the available options, possibly in relation to
//               a specified URL.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
get_options(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_options, url, string(), false, 0, 0);
  run();
  return is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::begin_get_document
//       Access: Published
//  Description: Begins a non-blocking request to retrieve a given
//               document.  This method will return immediately, even
//               before a connection to the server has necessarily
//               been established; you must then call run() from time
//               to time until the return value of run() is false.
//               Then you may check is_valid() and get_status_code()
//               to determine the status of your request.
//
//               If a previous request had been pending, that request
//               is discarded.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
begin_get_document(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_get, url, string(), true, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::begin_get_subdocument
//       Access: Published
//  Description: Begins a non-blocking request to retrieve only the
//               specified byte range of the indicated document.  If
//               last_byte is 0, it stands for the last byte of the
//               document.  When a subdocument is requested,
//               get_file_size() and get_bytes_downloaded() will
//               report the number of bytes of the subdocument, not of
//               the complete document.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
begin_get_subdocument(const DocumentSpec &url, size_t first_byte, 
                      size_t last_byte) {
  begin_request(HTTPEnum::M_get, url, string(), true, first_byte, last_byte);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::begin_get_header
//       Access: Published
//  Description: Begins a non-blocking request to retrieve a given
//               header.  See begin_get_document() and get_header().
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
begin_get_header(const DocumentSpec &url) {
  begin_request(HTTPEnum::M_head, url, string(), true, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::begin_post_form
//       Access: Published
//  Description: Posts form data to a particular URL and retrieves the
//               response, all using non-blocking I/O.  See
//               begin_get_document() and post_form().
//
//               It is important to note that you *must* call run()
//               repeatedly after calling this method until run()
//               returns false, and you may not call any other
//               document posting or retrieving methods using the
//               HTTPChannel object in the interim, or your form data
//               may not get posted.
////////////////////////////////////////////////////////////////////
INLINE void HTTPChannel::
begin_post_form(const DocumentSpec &url, const string &body) {
  begin_request(HTTPEnum::M_post, url, body, 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 DocumentSpec &url) {
  begin_request(HTTPEnum::M_connect, url, string(), true, 0, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_bytes_downloaded
//       Access: Published
//  Description: Returns the number of bytes downloaded during the
//               last (or current) download_to_file() or
//               download_to_ram operation().  This can be used in
//               conjunction with get_file_size() to report the
//               percent complete (but be careful, since
//               get_file_size() may return 0 if the server has not
//               told us the size of the file).
////////////////////////////////////////////////////////////////////
INLINE size_t HTTPChannel::
get_bytes_downloaded() const {
  return _bytes_downloaded;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::get_bytes_requested
//       Access: Published
//  Description: When download throttling is in effect
//               (set_download_throttle() has been set to true) and
//               non-blocking I/O methods (like begin_get_document())
//               are used, this returns the number of bytes
//               "requested" from the server so far: that is, the
//               theoretical maximum value for get_bytes_downloaded(),
//               if the server has been keeping up with our demand.
//
//               If this number is less than get_bytes_downloaded(),
//               then the server has not been supplying bytes fast
//               enough to meet our own download throttle rate.
//
//               When download throttling is not in effect, or when
//               the blocking I/O methods (like get_document(), etc.)
//               are used, this returns 0.
////////////////////////////////////////////////////////////////////
INLINE size_t HTTPChannel::
get_bytes_requested() const {
  return _bytes_requested;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::is_download_complete
//       Access: Published
//  Description: Returns true when a download_to() or
//               download_to_ram() has executed and the file has been
//               fully downloaded.  If this still returns false after
//               processing has completed, there was an error in
//               transmission.
//
//               Note that simply testing is_download_complete() does
//               not prove that the requested document was succesfully
//               retrieved--you might have just downloaded the "404
//               not found" stub (for instance) that a server would
//               provide in response to some error condition.  You
//               should also check is_valid() to prove that the file
//               you expected has been successfully retrieved.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
is_download_complete() const {
  return (_download_dest != DD_none &&
          (_state == S_read_body || _state == S_read_trailer));
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPChannel::StatusEntry::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
INLINE HTTPChannel::StatusEntry::
StatusEntry() {
  _status_code = SC_incomplete;
}
