/** * 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; }