historical/toontown-classic.git/panda/include/httpChannel.I
2024-01-16 11:20:27 -06:00

813 lines
27 KiB
Text

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file httpChannel.I
* @author drose
* @date 2002-09-24
*/
/**
* Returns the HTTPClient object that owns this channel.
*/
INLINE HTTPClient *HTTPChannel::
get_client() const {
return _client;
}
/**
* 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()));
}
/**
* 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);
}
/**
* 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();
}
/**
* 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;
}
/**
* 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;
}
/**
* Returns the HTTP version number returned by the server, formatted as a
* string, e.g. "HTTP/1.1".
*/
INLINE const std::string &HTTPChannel::
get_http_version_string() const {
return _http_version_string;
}
/**
* 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;
}
/**
* 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 std::string &HTTPChannel::
get_www_realm() const {
return _www_realm;
}
/**
* 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 std::string &HTTPChannel::
get_proxy_realm() const {
return _proxy_realm;
}
/**
* 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 successful 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;
}
/**
* If the document automatically followed one or more redirects, this will
* return the number of redirects that were automatically followed. Use
* get_redirect_step() to retrieve each URL in sequence.
*/
INLINE int HTTPChannel::
get_num_redirect_steps() const {
return _redirect_trail.size();
}
/**
* Use in conjunction with get_num_redirect_steps() 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_step(int n) const {
nassertr(n >= 0 && n < (int)_redirect_trail.size(), _redirect_trail[0]);
return _redirect_trail[n];
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* Specifies the maximum number of bytes in a received (but unwanted) body
* that will be skipped past, in order to reset to a new request.
*
* That is, if this HTTPChannel requests a file via get_document(), but does
* not call download_to_ram(), download_to_file(), or open_read_body(), and
* instead immediately requests a new file, then the HTTPChannel has a choice
* whether to skip past the unwanted document, or to close the connection and
* open a new one. If the number of bytes to skip is more than this
* threshold, the connection will be closed; otherwise, the data will simply
* be read and discarded.
*/
INLINE void HTTPChannel::
set_skip_body_size(size_t skip_body_size) {
_skip_body_size = skip_body_size;
}
/**
* Returns the maximum number of bytes in a received (but unwanted) body that
* will be skipped past, in order to reset to a new request. See
* set_skip_body_size().
*/
INLINE size_t HTTPChannel::
get_skip_body_size() const {
return _skip_body_size;
}
/**
* Specifies the amount of time, in seconds, in which a previously-established
* connection is allowed to remain open and unused. If a previous connection
* has remained unused for at least this number of seconds, it will be closed
* and a new connection will be opened; otherwise, the same connection will be
* reused for the next request (for this particular HTTPChannel).
*/
INLINE void HTTPChannel::
set_idle_timeout(double idle_timeout) {
_idle_timeout = idle_timeout;
}
/**
* Returns the amount of time, in seconds, in which an previously-established
* connection is allowed to remain open and unused. See set_idle_timeout().
*/
INLINE double HTTPChannel::
get_idle_timeout() const {
return _idle_timeout;
}
/**
* 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;
}
/**
* Returns whether the nonblocking downloads will be bandwidth-limited. See
* set_download_throttle().
*/
INLINE bool HTTPChannel::
get_download_throttle() const {
return _download_throttle;
}
/**
* 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);
}
/**
* 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;
}
/**
* 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);
}
/**
* 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;
}
/**
* Specifies the Content-Type header, useful for applications that require
* different types of content, such as JSON.
*/
INLINE void HTTPChannel::
set_content_type(std::string content_type) {
_content_type = content_type;
}
/**
* Returns the value of the Content-Type header.
*/
INLINE std::string HTTPChannel::
get_content_type() const {
return _content_type;
}
/**
* 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;
}
/**
* 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;
}
/**
* Returns the first byte of the file requested by the request. This will
* normally be 0 to indicate that the file is being requested from the
* beginning, but if the file was requested via a get_subdocument() call, this
* will contain the first_byte parameter from that call.
*/
INLINE size_t HTTPChannel::
get_first_byte_requested() const {
return _first_byte_requested;
}
/**
* Returns the last byte of the file requested by the request. This will
* normally be 0 to indicate that the file is being requested to its last
* byte, but if the file was requested via a get_subdocument() call, this will
* contain the last_byte parameter from that call.
*/
INLINE size_t HTTPChannel::
get_last_byte_requested() const {
return _last_byte_requested;
}
/**
* Returns the first byte of the file (that will be) delivered by the server
* in response to the current request. Normally, this is the same as
* get_first_byte_requested(), but some servers will ignore a subdocument
* request and always return the whole file, in which case this value will be
* 0, regardless of what was requested to get_subdocument().
*/
INLINE size_t HTTPChannel::
get_first_byte_delivered() const {
return _first_byte_delivered;
}
/**
* Returns the last byte of the file (that will be) delivered by the server in
* response to the current request. Normally, this is the same as
* get_last_byte_requested(), but some servers will ignore a subdocument
* request and always return the whole file, in which case this value will be
* 0, regardless of what was requested to get_subdocument().
*/
INLINE size_t HTTPChannel::
get_last_byte_delivered() const {
return _last_byte_delivered;
}
/**
* 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();
}
/**
* 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);
}
/**
* Resets the extra headers that were previously added via calls to
* send_extra_header().
*/
INLINE void HTTPChannel::
clear_extra_headers() {
_send_extra_headers = std::string();
}
/**
* 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 std::string &key, const std::string &value) {
_send_extra_headers += key;
_send_extra_headers += ": ";
_send_extra_headers += value;
_send_extra_headers += "\r\n";
}
/**
* 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, std::string(), false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* 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, std::string(), false, first_byte, last_byte);
while (run()) {
}
return is_valid();
}
/**
* 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, std::string(), false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* Posts form data to a particular URL and retrieves the response.
*/
INLINE bool HTTPChannel::
post_form(const DocumentSpec &url, const std::string &body) {
begin_request(HTTPEnum::M_post, url, body, false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* 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 std::string &body) {
begin_request(HTTPEnum::M_put, url, body, false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* Requests the server to remove the indicated URL.
*/
INLINE bool HTTPChannel::
delete_document(const DocumentSpec &url) {
begin_request(HTTPEnum::M_delete, url, std::string(), false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* 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, std::string(), false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* 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, std::string(), false, 0, 0);
while (run()) {
}
return is_connection_ready();
}
/**
* 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, std::string(), false, 0, 0);
while (run()) {
}
return is_valid();
}
/**
* 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, std::string(), true, 0, 0);
}
/**
* 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, std::string(), true, first_byte, last_byte);
}
/**
* 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, std::string(), true, 0, 0);
}
/**
* 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 std::string &body) {
begin_request(HTTPEnum::M_post, url, body, true, 0, 0);
}
/**
* 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, std::string(), true, 0, 0);
}
/**
* 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;
}
/**
* 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;
}
/**
* 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 successfully 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));
}
/**
*
*/
INLINE HTTPChannel::StatusEntry::
StatusEntry() {
_status_code = SC_incomplete;
}