be nicer about closing sockets

This commit is contained in:
David Rose 2002-10-25 00:42:09 +00:00
parent 4680e81f12
commit 6c31d3a9cf
5 changed files with 117 additions and 62 deletions

View File

@ -73,6 +73,7 @@ ChunkedStreamBuf::
void ChunkedStreamBuf::
open_read(BioStreamPtr *source, HTTPChannel *doc) {
_source = source;
nassertv(!_source.is_null());
_chunk_remaining = 0;
_done = false;
_doc = doc;
@ -140,6 +141,7 @@ underflow() {
////////////////////////////////////////////////////////////////////
size_t ChunkedStreamBuf::
read_chars(char *start, size_t length) {
nassertr(!_source.is_null(), 0);
if (_done) {
return 0;
}
@ -185,7 +187,7 @@ read_chars(char *start, size_t length) {
// Last chunk; we're done.
_done = true;
if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) {
_doc->_state = HTTPChannel::S_read_body;
_doc->finished_body(true);
}
return 0;
}

View File

@ -26,8 +26,8 @@
////////////////////////////////////////////////////////////////////
INLINE bool HTTPChannel::
is_valid() const {
return (!_source.is_null() && _state != S_failure &&
(_status_code / 100) == 2);
return (_state != S_failure && (_status_code / 100) == 2 &&
(has_no_body() || !_source.is_null()));
}
////////////////////////////////////////////////////////////////////
@ -372,7 +372,7 @@ get_file_size() const {
INLINE void HTTPChannel::
reset() {
reset_for_new_request();
free_bio();
reset_to_new();
}
////////////////////////////////////////////////////////////////////

View File

@ -91,7 +91,7 @@ HTTPChannel(HTTPClient *client) :
////////////////////////////////////////////////////////////////////
HTTPChannel::
~HTTPChannel() {
free_bio();
close_connection();
reset_download_to();
}
@ -149,10 +149,26 @@ will_close_connection() const {
return true;
}
// Assume the serve will keep it open.
// Assume the server will keep it open.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::has_no_body
// Access: Public
// Description: Returns true if the nature of the request is such
// that there is no associated body to read, false if
// there should be a body (even if that body might be
// empty).
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
has_no_body() const {
return (get_status_code() / 100 == 1 ||
get_status_code() == 204 ||
get_status_code() == 304 ||
_method == M_head);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::open_read_file
// Access: Public, Virtual
@ -379,7 +395,7 @@ run() {
////////////////////////////////////////////////////////////////////
ISocketStream *HTTPChannel::
read_body() {
if (_state != S_read_header && _state != S_begin_body) {
if ((_state != S_read_header && _state != S_begin_body) || _source.is_null()) {
return NULL;
}
@ -548,7 +564,7 @@ get_connection() {
_source->set_stream(NULL);
// We're now passing ownership of the connection to the user.
free_bio();
reset_to_new();
return stream;
}
@ -1047,6 +1063,12 @@ run_reading_header() {
_state = S_read_header;
if (has_no_body() && will_close_connection()) {
// If the server said it will close the connection, we should
// close it too.
close_connection();
}
// Handle automatic retries and redirects.
int last_status = _last_status_code;
_last_status_code = get_status_code();
@ -1144,19 +1166,15 @@ run_read_header() {
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
run_begin_body() {
if (!get_persistent_connection() || will_close_connection()) {
if (will_close_connection()) {
// If the socket will close anyway, no point in skipping past the
// previous body; just reset.
free_bio();
reset_to_new();
return false;
}
if (get_status_code() / 100 == 1 ||
get_status_code() == 204 ||
get_status_code() == 304 ||
_method == M_head) {
// These status codes, or method HEAD, indicate we have no body.
// Therefore, we have already read the (nonexistent) body.
if (has_no_body()) {
// We have already "read" the nonexistent body.
_state = S_ready;
} else if (_file_size > 8192) {
@ -1169,7 +1187,7 @@ run_begin_body() {
<< "Dropping connection rather than skipping past " << _file_size
<< " bytes.\n";
}
free_bio();
reset_to_new();
} else {
nassertr(_body_stream == NULL, false);
@ -1179,7 +1197,7 @@ run_begin_body() {
downloader_cat.debug()
<< "Unable to skip body.\n";
}
free_bio();
reset_to_new();
} else {
_state = S_reading_body;
@ -1203,17 +1221,17 @@ run_begin_body() {
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
run_reading_body() {
if (!get_persistent_connection() || will_close_connection()) {
if (will_close_connection()) {
// If the socket will close anyway, no point in skipping past the
// previous body; just reset.
free_bio();
reset_to_new();
return false;
}
// Skip the body we've already started.
if (_body_stream == NULL) {
// Whoops, we're not in skip-body mode. Better reset.
free_bio();
reset_to_new();
return false;
}
@ -1255,10 +1273,10 @@ run_reading_body() {
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
run_read_body() {
if (!get_persistent_connection() || will_close_connection()) {
if (will_close_connection()) {
// If the socket will close anyway, no point in skipping past the
// previous body; just reset.
free_bio();
reset_to_new();
return false;
}
// Skip the trailer following the recently-read body.
@ -1286,10 +1304,10 @@ run_read_body() {
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
run_read_trailer() {
if (!get_persistent_connection() || will_close_connection()) {
if (will_close_connection()) {
// If the socket will close anyway, no point in skipping past the
// previous body; just reset.
free_bio();
reset_to_new();
return false;
}
@ -1397,12 +1415,12 @@ begin_request(HTTPChannel::Method method, const URLSpec &url,
// dropping the old connection, if any.
if (_proxy != _client->get_proxy()) {
_proxy = _client->get_proxy();
free_bio();
reset_to_new();
}
if (_nonblocking != nonblocking) {
_nonblocking = nonblocking;
free_bio();
reset_to_new();
}
set_url(url);
@ -1446,7 +1464,7 @@ begin_request(HTTPChannel::Method method, const URLSpec &url,
// Also, reset from whatever previous request might still be pending.
if (_state == S_failure || (_state < S_read_header && _state != S_ready)) {
free_bio();
reset_to_new();
} else if (_state == S_read_header) {
// Roll one step forwards to start skipping past the previous
@ -1473,6 +1491,32 @@ reset_for_new_request() {
_bytes_requested = 0;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::finished_body
// Access: Private
// Description: This is called by the body reading
// classes--ChunkedStreamBuf and IdentityStreamBuf--when
// they have finished reading the body. It advances the
// state appropriately.
//
// has_trailer should be set true if the body type has
// an associated trailer which should be read or
// skipped, or false if there is no trailer.
////////////////////////////////////////////////////////////////////
void HTTPChannel::
finished_body(bool has_trailer) {
if (will_close_connection()) {
reset_to_new();
} else {
if (has_trailer) {
_state = HTTPChannel::S_read_body;
} else {
_state = HTTPChannel::S_read_trailer;
}
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::http_getline
// Access: Private
@ -1546,7 +1590,7 @@ http_send(const string &str) {
downloader_cat.debug()
<< "Lost connection to server unexpectedly during write.\n";
}
free_bio();
reset_to_new();
return false;
}
@ -1589,7 +1633,7 @@ parse_http_response(const string &line) {
} else {
// Maybe we were just in some bad state. Drop the connection
// and try again, once.
free_bio();
reset_to_new();
_response_type = RT_non_http;
}
return false;
@ -1733,7 +1777,7 @@ parse_content_range(const string &content_range) {
// Access: Private
// Description: Checks whether the connection to the server has been
// closed after a failed read. If it has, issues a
// warning and calls free_bio().
// warning and calls reset_to_new().
////////////////////////////////////////////////////////////////////
void HTTPChannel::
check_socket() {
@ -1743,7 +1787,7 @@ check_socket() {
downloader_cat.debug()
<< "Lost connection to server unexpectedly during read.\n";
}
free_bio();
reset_to_new();
}
}
@ -2182,7 +2226,7 @@ set_url(const URLSpec &url) {
if (url.get_scheme() != _url.get_scheme() ||
(_proxy.empty() && (url.get_server() != _url.get_server() ||
url.get_port() != _url.get_port()))) {
free_bio();
reset_to_new();
}
_url = url;
}
@ -2443,28 +2487,6 @@ parse_authentication_schemes(HTTPChannel::AuthenticationSchemes &schemes,
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::free_bio
// Access: Private
// Description: Frees the BIO and its IBioStream object, if
// allocated. This will close the connection if it is
// open.
////////////////////////////////////////////////////////////////////
void HTTPChannel::
free_bio() {
if (_body_stream != (ISocketStream *)NULL) {
delete _body_stream;
_body_stream = (ISocketStream *)NULL;
}
_source.clear();
_bio.clear();
_working_getline = string();
_sent_so_far = 0;
_proxy_tunnel = false;
_read_index++;
_state = S_new;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::reset_download_to
// Access: Private
@ -2480,6 +2502,37 @@ reset_download_to() {
_download_dest = DD_none;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::reset_to_new
// Access: Private
// Description: Closes the connection and resets the state to S_new.
////////////////////////////////////////////////////////////////////
void HTTPChannel::
reset_to_new() {
close_connection();
_state = S_new;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::close_connection
// Access: Private
// Description: Closes the connection but leaves the _state
// unchanged.
////////////////////////////////////////////////////////////////////
void HTTPChannel::
close_connection() {
if (_body_stream != (ISocketStream *)NULL) {
delete _body_stream;
_body_stream = (ISocketStream *)NULL;
}
_source.clear();
_bio.clear();
_working_getline = string();
_sent_so_far = 0;
_proxy_tunnel = false;
_read_index++;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::get_basic_authorization
// Access: Private

View File

@ -71,6 +71,7 @@ public:
virtual istream *open_read_file() const;
bool will_close_connection() const;
bool has_no_body() const;
PUBLISHED:
INLINE bool is_valid() const;
@ -164,6 +165,8 @@ private:
size_t first_byte, size_t last_byte);
void reset_for_new_request();
void finished_body(bool has_trailer);
bool http_getline(string &str);
bool http_send(const string &str);
bool parse_http_response(const string &line);
@ -194,8 +197,9 @@ private:
static void show_send(const string &message);
#endif
void free_bio();
void reset_download_to();
void reset_to_new();
void close_connection();
HTTPClient *_client;
URLSpec _proxy;

View File

@ -146,9 +146,7 @@ read_chars(char *start, size_t length) {
if ((*_source)->is_closed()) {
// socket closed; we're done.
if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) {
// An IdentityStreamBuf doesn't have a trailer, so we've already
// "read" it.
_doc->_state = HTTPChannel::S_read_trailer;
_doc->finished_body(false);
}
}
return 0;
@ -177,9 +175,7 @@ read_chars(char *start, size_t length) {
if (_bytes_remaining == 0) {
// We're done.
if (_doc != (HTTPChannel *)NULL && _read_index == _doc->_read_index) {
// An IdentityStreamBuf doesn't have a trailer, so we've already
// "read" it.
_doc->_state = HTTPChannel::S_read_trailer;
_doc->finished_body(false);
}
}
}