diff options
Diffstat (limited to 'indra/llcorehttp')
| -rwxr-xr-x | indra/llcorehttp/_httpinternal.h | 3 | ||||
| -rwxr-xr-x | indra/llcorehttp/_httpoprequest.cpp | 61 | ||||
| -rwxr-xr-x | indra/llcorehttp/_httpoprequest.h | 1 | ||||
| -rwxr-xr-x | indra/llcorehttp/_httppolicy.cpp | 28 | ||||
| -rwxr-xr-x | indra/llcorehttp/httpcommon.cpp | 34 | ||||
| -rwxr-xr-x | indra/llcorehttp/httpcommon.h | 8 | ||||
| -rwxr-xr-x | indra/llcorehttp/httpresponse.h | 9 | ||||
| -rwxr-xr-x | indra/llcorehttp/tests/test_httprequest.hpp | 196 | ||||
| -rwxr-xr-x | indra/llcorehttp/tests/test_llcorehttp_peer.py | 95 | 
9 files changed, 342 insertions, 93 deletions
| diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index 14f744a9f1..008e4fd95c 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -146,9 +146,6 @@ const int HTTP_SERVICE_LOOP_SLEEP_NORMAL_MS = 2;  // Block allocation size (a tuning parameter) is found  // in bufferarray.h. -// Compatibility controls -const bool HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX = true; -  }  // end namespace LLCore  #endif	// _LLCORE_HTTP_INTERNAL_H_ diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index 51a8eaf998..207ed8e1e4 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -186,9 +186,11 @@ void HttpOpRequest::stageFromActive(HttpService * service)  	if (mReplyLength)  	{  		// If non-zero, we received and processed a Content-Range -		// header with the response.  Verify that what it says -		// is consistent with the received data. -		if (mReplyLength != mReplyBody->size()) +		// header with the response.  If there is received data +		// (and there may not be due to protocol violations, +		// HEAD requests, etc., see BUG-2295) Verify that what it +		// says is consistent with the received data. +		if (mReplyBody && mReplyBody->size() && mReplyLength != mReplyBody->size())  		{  			// Not as expected, fail the request  			mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); @@ -339,7 +341,6 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,  	}  } -  // Sets all libcurl options and data for a request.  //  // Used both for initial requests and to 'reload' for @@ -381,41 +382,15 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)  	// Get policy options  	HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); -	mCurlHandle = curl_easy_init(); -	curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); -	curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); -	curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); -	curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); -	curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); -	curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); +	mCurlHandle = LLCurl::createStandardCurlHandle(); -	if (HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX) -	{ -		// The Linksys WRT54G V5 router has an issue with frequent -		// DNS lookups from LAN machines.  If they happen too often, -		// like for every HTTP request, the router gets annoyed after -		// about 700 or so requests and starts issuing TCP RSTs to -		// new connections.  Reuse the DNS lookups for even a few -		// seconds and no RSTs. -		curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); -	} -	else -	{ -		// *TODO:  Revisit this old DNS timeout setting - may no longer be valid -		// I don't think this is valid anymore, the Multi shared DNS -		// cache is working well.  For the case of naked easy handles, -		// consider using a shared DNS object. -		curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); -	} -	curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1); -	curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1); -	curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);  	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback); -	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this); -	curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback); +	curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION,  readCallback);	  	curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this); -	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); -	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); +	curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this); +	curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); +	curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); +	curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);	  	const std::string * opt_value(NULL);  	long opt_long(0L); @@ -694,7 +669,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  	std::string safe_line;  	std::string tag;  	bool logit(false); -	len = (std::min)(len, size_t(256));					// Keep things reasonable in all cases +	const size_t log_len((std::min)(len, size_t(256)));		// Keep things reasonable in all cases  	switch (info)  	{ @@ -702,7 +677,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)  		{  			tag = "TEXT"; -			escape_libcurl_debug_data(buffer, len, true, safe_line); +			escape_libcurl_debug_data(buffer, log_len, true, safe_line);  			logit = true;  		}  		break; @@ -711,7 +686,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)  		{  			tag = "HEADERIN"; -			escape_libcurl_debug_data(buffer, len, true, safe_line); +			escape_libcurl_debug_data(buffer, log_len, true, safe_line);  			logit = true;  		}  		break; @@ -720,7 +695,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  		if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)  		{  			tag = "HEADEROUT"; -			escape_libcurl_debug_data(buffer, 2 * len, true, safe_line);		// Goes out as one line +			escape_libcurl_debug_data(buffer, log_len, true, safe_line);	// Goes out as one line unlike header_in  			logit = true;  		}  		break; @@ -732,7 +707,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  			logit = true;  			if (op->mTracing >= HTTP_TRACE_CURL_BODIES)  			{ -				escape_libcurl_debug_data(buffer, len, false, safe_line); +				escape_libcurl_debug_data(buffer, log_len, false, safe_line);  			}  			else  			{ @@ -750,7 +725,7 @@ int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffe  			logit = true;  			if (op->mTracing >= HTTP_TRACE_CURL_BODIES)  			{ -				escape_libcurl_debug_data(buffer, len, false, safe_line); +				escape_libcurl_debug_data(buffer, log_len, false, safe_line);  			}  			else  			{ diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 7b65d17783..74a349b0bf 100755 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -60,7 +60,6 @@ class HttpOptions;  /// the information needed to make a working request which can  /// then be enqueued to a request queue.  /// -  class HttpOpRequest : public HttpOperation  {  public: diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 76c1e22431..014bd37e2e 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -319,33 +319,13 @@ bool HttpPolicy::cancel(HttpHandle handle)  bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op)  { -	static const HttpStatus cant_connect(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); -	static const HttpStatus cant_res_proxy(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_PROXY); -	static const HttpStatus cant_res_host(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_HOST); -	static const HttpStatus send_error(HttpStatus::EXT_CURL_EASY, CURLE_SEND_ERROR); -	static const HttpStatus recv_error(HttpStatus::EXT_CURL_EASY, CURLE_RECV_ERROR); -	static const HttpStatus upload_failed(HttpStatus::EXT_CURL_EASY, CURLE_UPLOAD_FAILED); -	static const HttpStatus op_timedout(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); -	static const HttpStatus post_error(HttpStatus::EXT_CURL_EASY, CURLE_HTTP_POST_ERROR); -  	// Retry or finalize  	if (! op->mStatus)  	{ -		// If this failed, we might want to retry.  Have to inspect -		// the status a little more deeply for those reasons worth retrying... -		if (op->mPolicyRetries < op->mPolicyRetryLimit && -			((op->mStatus.isHttpStatus() && op->mStatus.mType >= 499 && op->mStatus.mType <= 599) || -			 cant_connect == op->mStatus || -			 cant_res_proxy == op->mStatus || -			 cant_res_host == op->mStatus || -			 send_error == op->mStatus || -			 recv_error == op->mStatus || -			 upload_failed == op->mStatus || -			 op_timedout == op->mStatus || -			 post_error == op->mStatus)) +		// If this failed, we might want to retry. +		if (op->mPolicyRetries < op->mPolicyRetryLimit && op->mStatus.isRetryable())  		{ -			// Okay, worth a retry.  We include 499 in this test as -			// it's the old 'who knows?' error from many grid services... +			// Okay, worth a retry.  			retryOp(op);  			return true;				// still active/ready  		} diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp index f2fcbf77a3..0738760763 100755 --- a/indra/llcorehttp/httpcommon.cpp +++ b/indra/llcorehttp/httpcommon.cpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -116,6 +116,7 @@ std::string HttpStatus::toString() const  			{ 415, "Unsupported Media Type" },  			{ 416, "Requested range not satisfiable" },  			{ 417, "Expectation Failed" }, +			{ 499, "Linden Catch-All" },  			{ 500, "Internal Server Error" },  			{ 501, "Not Implemented" },  			{ 502, "Bad Gateway" }, @@ -174,6 +175,37 @@ std::string HttpStatus::toString() const  	}  	return std::string("Unknown error");  } + + +// Pass true on statuses that might actually be cleared by a +// retry.  Library failures, calling problems, etc. aren't +// going to be fixed by squirting bits all over the Net. +bool HttpStatus::isRetryable() const +{ +	static const HttpStatus cant_connect(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); +	static const HttpStatus cant_res_proxy(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_PROXY); +	static const HttpStatus cant_res_host(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_HOST); +	static const HttpStatus send_error(HttpStatus::EXT_CURL_EASY, CURLE_SEND_ERROR); +	static const HttpStatus recv_error(HttpStatus::EXT_CURL_EASY, CURLE_RECV_ERROR); +	static const HttpStatus upload_failed(HttpStatus::EXT_CURL_EASY, CURLE_UPLOAD_FAILED); +	static const HttpStatus op_timedout(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); +	static const HttpStatus post_error(HttpStatus::EXT_CURL_EASY, CURLE_HTTP_POST_ERROR); +	static const HttpStatus partial_file(HttpStatus::EXT_CURL_EASY, CURLE_PARTIAL_FILE); +	static const HttpStatus inv_cont_range(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); + +	return ((isHttpStatus() && mType >= 499 && mType <= 599) ||	// Include special 499 in retryables +			*this == cant_connect ||	// Connection reset/endpoint problems +			*this == cant_res_proxy ||	// DNS problems +			*this == cant_res_host ||	// DNS problems +			*this == send_error ||		// General socket problems  +			*this == recv_error ||		// General socket problems  +			*this == upload_failed ||	// Transport problem +			*this == op_timedout ||		// Timer expired +			*this == post_error ||		// Transport problem +			*this == partial_file ||	// Data inconsistency in response +			*this == inv_cont_range);	// Short data read disagrees with content-range +} +  } // end namespace LLCore diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h index c0d4ec5aad..41fb5164cf 100755 --- a/indra/llcorehttp/httpcommon.h +++ b/indra/llcorehttp/httpcommon.h @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -303,6 +303,12 @@ struct HttpStatus  	{  		return 	mType >= type_enum_t(100) && mType <= type_enum_t(999);  	} + +	/// Returns true if the status is one that will be retried +	/// internally.  Provided for external consumption for cases +	/// where that logic needs to be replicated.  Only applies +	/// to failed statuses, successful statuses will return false. +	bool isRetryable() const;  }; // end struct HttpStatus diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h index 4a481db6ac..f19b521fbf 100755 --- a/indra/llcorehttp/httpresponse.h +++ b/indra/llcorehttp/httpresponse.h @@ -48,8 +48,9 @@ class HttpHeaders;  /// individual pieces of the response.  ///  /// Typical usage will have the caller interrogate the object -/// and return from the handler callback.  Instances are refcounted -/// and callers can bump the count and retain the object as needed. +/// during the handler callback and then simply returning. +/// But instances are refcounted and and callers can add a +/// reference and hold onto the object after the callback.  ///  /// Threading:  Not intrinsically thread-safe.  /// @@ -119,6 +120,10 @@ public:  	/// caller is going to have to make assumptions on receipt of  	/// a 206 status.  The @full value may also be zero in cases of  	/// parsing problems or a wild-carded length response. +	/// +	/// These values will not necessarily agree with the data in +	/// the body itself (if present).  The BufferArray object +	/// is authoritative for actual data length.  	void getRange(unsigned int * offset, unsigned int * length, unsigned int * full) const  		{  			*offset = mReplyOffset; diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index e5488cf941..ff84b04070 100755 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -4,7 +4,7 @@   *   * $LicenseInfo:firstyear=2012&license=viewerlgpl$   * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, Linden Research, Inc.   *   * This library is free software; you can redistribute it and/or   * modify it under the terms of the GNU Lesser General Public @@ -2650,6 +2650,200 @@ void HttpRequestTestObjectType::test<21>()  	}  } +// BUG-2295 Tests - Content-Range header received but no body +template <> template <> +void HttpRequestTestObjectType::test<22>() +{ +	ScopedCurlInit ready; + +	std::string url_base(get_base_url()); +	// std::cerr << "Base:  "  << url_base << std::endl; +	 +	set_test_name("BUG-2295"); + +	// Handler can be stack-allocated *if* there are no dangling +	// references to it after completion of this method. +	// Create before memory record as the string copy will bump numbers. +	TestHandler2 handler(this, "handler"); +		 +	// record the total amount of dynamically allocated memory +	mMemTotal = GetMemTotal(); +	mHandlerCalls = 0; + +	HttpOptions * options = NULL; +	HttpRequest * req = NULL; + +	try +	{ +		// options set +		options = new HttpOptions(); +		options->setRetries(1);			// Partial_File is retryable and can timeout in here + +		// Get singletons created +		HttpRequest::createService(); +		 +		// Start threading early so that thread memory is invariant +		// over the test. +		HttpRequest::startThread(); + +		// create a new ref counted object with an implicit reference +		req = new HttpRequest(); +		ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + +		// ====================================== +		// Issue bug2295 GETs that will get a 206 +		// ====================================== +		mStatus = HttpStatus(206); +		static const int test_count(3); +		for (int i(0); i < test_count; ++i) +		{ +			char buffer[128]; +			sprintf(buffer, "/bug2295/%d/", i); +			HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, +														 0U, +														 url_base + buffer, +														 0, +														 25, +														 options, +														 NULL, +														 &handler); +			ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); +		} +		 +		// Run the notification pump. +		int count(0); +		int limit(30); +		while (count++ < limit && mHandlerCalls < test_count) +		{ +			req->update(1000000); +			usleep(100000); +		} +		ensure("Request executed in reasonable time - ms1", count < limit); +		ensure("One handler invocation for each request - ms1", mHandlerCalls == test_count); + +		// ====================================== +		// Issue bug2295 GETs that will get a libcurl 18 (PARTIAL_FILE) +		// ====================================== +		mHandlerCalls = 0; +		mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_PARTIAL_FILE); +		static const int test2_count(1); +		for (int i(0); i < test2_count; ++i) +		{ +			char buffer[128]; +			sprintf(buffer, "/bug2295/00000012/%d/", i); +			HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, +														 0U, +														 url_base + buffer, +														 0, +														 25, +														 options, +														 NULL, +														 &handler); +			ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); +		} +		 +		// Run the notification pump. +		count = 0; +		limit = 30; +		while (count++ < limit && mHandlerCalls < test2_count) +		{ +			req->update(1000000); +			usleep(100000); +		} +		ensure("Request executed in reasonable time - ms2", count < limit); +		ensure("One handler invocation for each request - ms2", mHandlerCalls == test2_count); + +		// ====================================== +		// Issue bug2295 GETs that will get an llcorehttp HE_INV_CONTENT_RANGE_HDR status +		// ====================================== +		mHandlerCalls = 0; +		mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); +		static const int test3_count(1); +		for (int i(0); i < test3_count; ++i) +		{ +			char buffer[128]; +			sprintf(buffer, "/bug2295/inv_cont_range/%d/", i); +			HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, +														 0U, +														 url_base + buffer, +														 0, +														 25, +														 options, +														 NULL, +														 &handler); +			ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); +		} +		 +		// Run the notification pump. +		count = 0; +		limit = 30; +		while (count++ < limit && mHandlerCalls < test3_count) +		{ +			req->update(1000000); +			usleep(100000); +		} +		ensure("Request executed in reasonable time - ms3", count < limit); +		ensure("One handler invocation for each request - ms3", mHandlerCalls == test3_count); + +		// ====================================== +		// Okay, request a shutdown of the servicing thread +		// ====================================== +		mStatus = HttpStatus(); +		mHandlerCalls = 0; +		HttpHandle handle = req->requestStopThread(&handler); +		ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); +	 +		// Run the notification pump again +		count = 0; +		limit = 20; +		while (count++ < limit && mHandlerCalls < 1) +		{ +			req->update(1000000); +			usleep(100000); +		} +		ensure("Shutdown request executed in reasonable time", count < limit); +		ensure("Shutdown handler invocation", mHandlerCalls == 1); + +		// See that we actually shutdown the thread +		count = 0; +		limit = 10; +		while (count++ < limit && ! HttpService::isStopped()) +		{ +			usleep(100000); +		} +		ensure("Thread actually stopped running", HttpService::isStopped()); + +		// release options +		if (options) +		{ +			options->release(); +			options = NULL; +		} +		 +		// release the request object +		delete req; +		req = NULL; + +		// Shut down service +		HttpRequest::destroyService(); +	 +#if defined(WIN32) +		// Can only do this memory test on Windows.  On other platforms, +		// the LL logging system holds on to memory and produces what looks +		// like memory leaks... +	 +		// printf("Old mem:  %d, New mem:  %d\n", mMemTotal, GetMemTotal()); +		ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif +	} +	catch (...) +	{ +		stop_thread(req); +		delete req; +		HttpRequest::destroyService(); +		throw; +	} +}  }  // end namespace tut diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 75a3c39ef2..8796ae57c7 100755 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -9,7 +9,7 @@  $LicenseInfo:firstyear=2008&license=viewerlgpl$  Second Life Viewer Source Code -Copyright (C) 2012, Linden Research, Inc. +Copyright (C) 2012-2013, Linden Research, Inc.  This library is free software; you can redistribute it and/or  modify it under the terms of the GNU Lesser General Public @@ -35,6 +35,10 @@ import time  import select  import getopt  from threading import Thread +try: +    from cStringIO import StringIO +except ImportError: +    from StringIO import StringIO  from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler  from SocketServer import ThreadingMixIn @@ -47,6 +51,27 @@ from testrunner import freeport, run, debug, VERBOSE  class TestHTTPRequestHandler(BaseHTTPRequestHandler):      """This subclass of BaseHTTPRequestHandler is to receive and echo      LLSD-flavored messages sent by the C++ LLHTTPClient. + +    Target URLs are fairly free-form and are assembled by  +    concatinating fragments.  Currently defined fragments +    are: +    - '/reflect/'       Request headers are bounced back to caller +                        after prefixing with 'X-Reflect-' +    - '/fail/'          Body of request can contain LLSD with  +                        'reason' string and 'status' integer +                        which will become response header. +    - '/bug2295/'       206 response, no data in body: +    -- '/bug2295/0/'       "Content-Range: bytes 0-75/2983" +    -- '/bug2295/1/'       "Content-Range: bytes 0-75/*" +    -- '/bug2295/2/'       "Content-Range: bytes 0-75/2983", +                           "Content-Length: 0" +    -- '/bug2295/00000018/0/'  Generates PARTIAL_FILE (18) error in libcurl. +                           "Content-Range: bytes 0-75/2983", +                           "Content-Length: 76" +    -- '/bug2295/inv_cont_range/0/'  Generates HE_INVALID_CONTENT_RANGE error in llcorehttp. + +    Some combinations make no sense, there's no effort to protect +    you from that.      """      def read(self):          # The following logic is adapted from the library module @@ -107,22 +132,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):          if "/sleep/" in self.path:              time.sleep(30) -        if "fail" not in self.path: -            data = data.copy()          # we're going to modify -            # Ensure there's a "reply" key in data, even if there wasn't before -            data["reply"] = data.get("reply", llsd.LLSD("success")) -            response = llsd.format_xml(data) -            debug("success: %s", response) -            self.send_response(200) -            if "/reflect/" in self.path: -                self.reflect_headers() -            self.send_header("Content-type", "application/llsd+xml") -            self.send_header("Content-Length", str(len(response))) -            self.send_header("X-LL-Special", "Mememememe"); -            self.end_headers() -            if withdata: -                self.wfile.write(response) -        else:                           # fail requested +        if "fail" in self.path:              status = data.get("status", 500)              # self.responses maps an int status to a (short, long) pair of              # strings. We want the longer string. That's why we pass a string @@ -138,6 +148,57 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):              if "/reflect/" in self.path:                  self.reflect_headers()              self.end_headers() +        elif "/bug2295/" in self.path: +            # Test for https://jira.secondlife.com/browse/BUG-2295 +            # +            # Client can receive a header indicating data should +            # appear in the body without actually getting the body. +            # Library needs to defend against this case. +            # +            body = None +            if "/bug2295/0/" in self.path: +                self.send_response(206) +                self.send_header("Content-Range", "bytes 0-75/2983") +            elif "/bug2295/1/" in self.path: +                self.send_response(206) +                self.send_header("Content-Range", "bytes 0-75/*") +            elif "/bug2295/2/" in self.path: +                self.send_response(206) +                self.send_header("Content-Range", "bytes 0-75/2983") +                self.send_header("Content-Length", "0") +            elif "/bug2295/00000012/0/" in self.path: +                self.send_response(206) +                self.send_header("Content-Range", "bytes 0-75/2983") +                self.send_header("Content-Length", "76") +            elif "/bug2295/inv_cont_range/0/" in self.path: +                self.send_response(206) +                self.send_header("Content-Range", "bytes 0-75/2983") +                body = "Some text, but not enough." +            else: +                # Unknown request +                self.send_response(400) +            if "/reflect/" in self.path: +                self.reflect_headers() +            self.send_header("Content-type", "text/plain") +            self.end_headers() +            if body: +                self.wfile.write(body) +        else: +            # Normal response path +            data = data.copy()          # we're going to modify +            # Ensure there's a "reply" key in data, even if there wasn't before +            data["reply"] = data.get("reply", llsd.LLSD("success")) +            response = llsd.format_xml(data) +            debug("success: %s", response) +            self.send_response(200) +            if "/reflect/" in self.path: +                self.reflect_headers() +            self.send_header("Content-type", "application/llsd+xml") +            self.send_header("Content-Length", str(len(response))) +            self.send_header("X-LL-Special", "Mememememe"); +            self.end_headers() +            if withdata: +                self.wfile.write(response)      def reflect_headers(self):          for name in self.headers.keys(): | 
