Beispiel #1
0
HttpStatus HttpOpRequest::cancel()
{
	mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);

	addAsReply();

	return HttpStatus();
}
Beispiel #2
0
void HttpOpRequest::stageFromActive(HttpService * service)
{
	if (mReplyLength)
	{
		// If non-zero, we received and processed a Content-Range
		// 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);
		}
	}
	
	if (mCurlHeaders)
	{
		// We take these headers out of the request now as they were
		// allocated originally in this thread and the notifier doesn't
		// need them.  This eliminates one source of heap moving across
		// threads.

		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}

	// Also not needed on the other side
	delete [] mCurlTemp;
	mCurlTemp = NULL;
	mCurlTempLen = 0;
	
	addAsReply();
}
Beispiel #3
0
// Immediately search for the request on various queues
// and cancel operations if found.  Return the status of
// the search and cancel as the status of this request.
// The canceled request will return a canceled status to
// its handler.
void HttpOpCancel::stageFromRequest(HttpService * service)
{
	if (! service->cancel(mHandle))
	{
		mStatus = HttpStatus(HttpStatus::LLCORE, HE_HANDLE_NOT_FOUND);
	}
	
	addAsReply();
}
Beispiel #4
0
HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
								   HttpRequest::priority_t priority,
								   const std::string & url,
								   HttpOptions * options,
								   HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, NULL, options, headers);
	mReqMethod = HOR_GET;
	
	return HttpStatus();
}
Beispiel #5
0
HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value)
{
	HttpStatus status;

	if (HttpService::sOptionDesc[opt].mIsLong)
	{
		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
	}
	if (! HttpService::sOptionDesc[opt].mIsDynamic)
	{
		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
	}

	mReqOption = opt;
	mReqClass = pclass;
	mReqDoSet = true;
	mReqStrValue = value;
	
	return status;
}
Beispiel #6
0
HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
								   HttpRequest::priority_t priority,
								   const std::string & url,
								   BufferArray * body,
								   HttpOptions * options,
								   HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, body, options, headers);
	mReqMethod = HOR_PUT;
	
	return HttpStatus();
}
Beispiel #7
0
bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op)
{
	// Retry or finalize
	if (! op->mStatus)
	{
		// *DEBUG:  For "[curl:bugs] #1420" tests.  This will interfere
		// with unit tests due to allocation retention by logging code.
		// But you won't be checking this in enabled.
#if 0
		if (op->mStatus == HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT))
		{
			LL_WARNS(LOG_CORE) << "HTTP request " << static_cast<HttpHandle>(op)
							   << " timed out."
							   << LL_ENDL;
		}
#endif
		
		// If this failed, we might want to retry.
		if (op->mPolicyRetries < op->mPolicyRetryLimit && op->mStatus.isRetryable())
		{
			// Okay, worth a retry.
			retryOp(op);
			return true;				// still active/ready
		}
	}

	// This op is done, finalize it delivering it to the reply queue...
	if (! op->mStatus)
	{
		LL_WARNS(LOG_CORE) << "HTTP request " << static_cast<HttpHandle>(op)
						   << " failed after " << op->mPolicyRetries
						   << " retries.  Reason:  " << op->mStatus.toString()
						   << " (" << op->mStatus.toTerseString() << ")"
						   << LL_ENDL;
	}
	else if (op->mPolicyRetries)
	{
		LL_DEBUGS(LOG_CORE) << "HTTP request " << static_cast<HttpHandle>(op)
							<< " succeeded on retry " << op->mPolicyRetries << "."
							<< LL_ENDL;
	}

	op->stageFromActive(mService);
	op->release();
	return false;						// not active
}
Beispiel #8
0
HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
											HttpRequest::priority_t priority,
											const std::string & url,
											size_t offset,
											size_t len,
											HttpOptions * options,
											HttpHeaders * headers)
{
	setupCommon(policy_id, priority, url, NULL, options, headers);
	mReqMethod = HOR_GET;
	mReqOffset = offset;
	mReqLength = len;
	if (offset || len)
	{
		mProcFlags |= PF_SCAN_RANGE_HEADER;
	}
	
	return HttpStatus();
}
Beispiel #9
0
size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
	static const char status_line[] = "HTTP/";
	static const size_t status_line_len = sizeof(status_line) - 1;
	static const char con_ran_line[] = "content-range";
	static const char con_retry_line[] = "retry-after";
	
	HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));

	const size_t hdr_size(size * nmemb);
	const char * hdr_data(static_cast<const char *>(data));		// Not null terminated
	bool is_header(true);
	
	if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len))
	{
		// One of possibly several status lines.  Reset what we know and start over
		// taking results from the last header stanza we receive.
		op->mReplyOffset = 0;
		op->mReplyLength = 0;
		op->mReplyFullLength = 0;
		op->mReplyRetryAfter = 0;
		op->mStatus = HttpStatus();
		if (op->mReplyHeaders)
		{
			op->mReplyHeaders->clear();
		}
		is_header = false;
	}

	// Nothing in here wants a final CR/LF combination.  Remove
	// it as much as possible.
	size_t wanted_hdr_size(hdr_size);
	if (wanted_hdr_size && '\n' == hdr_data[wanted_hdr_size - 1])
	{
		if (--wanted_hdr_size && '\r' == hdr_data[wanted_hdr_size - 1])
		{
			--wanted_hdr_size;
		}
	}

	// Copy and normalize header fragments for the following
	// stages.  Would like to modify the data in-place but that
	// may not be allowed and we need one byte extra for NUL.
	// At the end of this we will have:
	//
	// If ':' present in header:
	//   1.  name points to text to left of colon which
	//       will be ascii lower-cased and left and right
	//       trimmed of whitespace.
	//   2.  value points to text to right of colon which
	//       will be left trimmed of whitespace.
	// Otherwise:
	//   1.  name points to header which will be left
	//       trimmed of whitespace.
	//   2.  value is NULL
	// Any non-NULL pointer may point to a zero-length string.
	//
	if (wanted_hdr_size >= op->mCurlTempLen)
	{
		delete [] op->mCurlTemp;
		op->mCurlTempLen = 2 * wanted_hdr_size + 1;
		op->mCurlTemp = new char [op->mCurlTempLen];
	}
	memcpy(op->mCurlTemp, hdr_data, wanted_hdr_size);
	op->mCurlTemp[wanted_hdr_size] = '\0';
	char * name(op->mCurlTemp);
	char * value(strchr(name, ':'));
	if (value)
	{
		*value++ = '\0';
		os_strlower(name);
		name = os_strtrim(name);
		value = os_strltrim(value);
	}
	else
	{
		// Doesn't look well-formed, do minimal normalization on it
		name = os_strltrim(name);
	}

	// Normalized, now reject headers with empty names.
	if (! *name)
	{
		// No use continuing
		return hdr_size;
	}
	
	// Save header if caller wants them in the response
	if (is_header && op->mProcFlags & PF_SAVE_HEADERS)
	{
		// Save headers in response
		if (! op->mReplyHeaders)
		{
			op->mReplyHeaders = new HttpHeaders;
		}
		op->mReplyHeaders->append(name, value ? value : "");
	}

	// From this point, header-specific processors are free to
	// modify the header value.
	
	// Detect and parse 'Content-Range' headers
	if (is_header
		&& op->mProcFlags & PF_SCAN_RANGE_HEADER
		&& value && *value
		&& ! strcmp(name, con_ran_line))
	{
		unsigned int first(0), last(0), length(0);
		int status;

		if (! (status = parse_content_range_header(value, &first, &last, &length)))
		{
			// Success, record the fragment position
			op->mReplyOffset = first;
			op->mReplyLength = last - first + 1;
			op->mReplyFullLength = length;
		}
		else if (-1 == status)
		{
			// Response is badly formed and shouldn't be accepted
			op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
		}
		else
		{
			// Ignore the unparsable.
			LL_INFOS_ONCE(LOG_CORE) << "Problem parsing odd Content-Range header:  '"
									<< std::string(hdr_data, wanted_hdr_size)
									<< "'.  Ignoring."
									<< LL_ENDL;
		}
	}

	// Detect and parse 'Retry-After' headers
	if (is_header
		&& op->mProcFlags & PF_USE_RETRY_AFTER
		&& value && *value
		&& ! strcmp(name, con_retry_line))
	{
		int time(0);
		if (! parse_retry_after_header(value, &time))
		{
			op->mReplyRetryAfter = time;
		}
	}

	return hdr_size;
}
Beispiel #10
0
// Sets all libcurl options and data for a request.
//
// Used both for initial requests and to 'reload' for
// a retry, generally with a different CURL handle.
// Junk may be left around from a failed request and that
// needs to be cleaned out.
//
// *TODO:  Move this to _httplibcurl where it belongs.
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
{
	CURLcode code;
	
	// Scrub transport and result data for retried op case
	mCurlActive = false;
	mCurlHandle = NULL;
	mCurlService = NULL;
	if (mCurlHeaders)
	{
		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}
	mCurlBodyPos = 0;

	if (mReplyBody)
	{
		mReplyBody->release();
		mReplyBody = NULL;
	}
	mReplyOffset = 0;
	mReplyLength = 0;
	mReplyFullLength = 0;
	if (mReplyHeaders)
	{
		mReplyHeaders->release();
		mReplyHeaders = NULL;
	}
	mReplyConType.clear();
	
	// *FIXME:  better error handling later
	HttpStatus status;

	// Get global and class policy options
	HttpPolicyGlobal & gpolicy(service->getPolicy().getGlobalOptions());
	HttpPolicyClass & cpolicy(service->getPolicy().getClassOptions(mReqPolicy));
	
	mCurlHandle = service->getTransport().getHandle();
	if (! mCurlHandle)
	{
		// We're in trouble.  We'll continue but it won't go well.
		LL_WARNS(LOG_CORE) << "Failed to allocate libcurl easy handle.  Continuing."
						   << LL_ENDL;
		return HttpStatus(HttpStatus::LLCORE, HE_BAD_ALLOC);
	}

	code = curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
	check_curl_easy_code(code, CURLOPT_IPRESOLVE);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
	check_curl_easy_code(code, CURLOPT_NOSIGNAL);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
	check_curl_easy_code(code, CURLOPT_NOPROGRESS);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
	check_curl_easy_code(code, CURLOPT_URL);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
	check_curl_easy_code(code, CURLOPT_PRIVATE);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
	check_curl_easy_code(code, CURLOPT_ENCODING);

	// 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.
	code = curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15);
	check_curl_easy_code(code, CURLOPT_DNS_CACHE_TIMEOUT);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
	check_curl_easy_code(code, CURLOPT_AUTOREFERER);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
	check_curl_easy_code(code, CURLOPT_FOLLOWLOCATION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
	check_curl_easy_code(code, CURLOPT_MAXREDIRS);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
	check_curl_easy_code(code, CURLOPT_WRITEFUNCTION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
	check_curl_easy_code(code, CURLOPT_WRITEDATA);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
	check_curl_easy_code(code, CURLOPT_READFUNCTION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
	check_curl_easy_code(code, CURLOPT_READDATA);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
	check_curl_easy_code(code, CURLOPT_SSL_VERIFYPEER);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
	check_curl_easy_code(code, CURLOPT_SSL_VERIFYHOST);

	if (gpolicy.mUseLLProxy)
	{
		// Use the viewer-based thread-safe API which has a
		// fast/safe check for proxy enable.  Would like to
		// encapsulate this someway...
		LLProxy::getInstance()->applyProxySettings(mCurlHandle);
	}
	else if (gpolicy.mHttpProxy.size())
	{
		// *TODO:  This is fine for now but get fuller socks5/
		// authentication thing going later....
		code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, gpolicy.mHttpProxy.c_str());
		check_curl_easy_code(code, CURLOPT_PROXY);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
		check_curl_easy_code(code, CURLOPT_PROXYTYPE);
	}
	if (gpolicy.mCAPath.size())
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, gpolicy.mCAPath.c_str());
		check_curl_easy_code(code, CURLOPT_CAPATH);
	}
	if (gpolicy.mCAFile.size())
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, gpolicy.mCAFile.c_str());
		check_curl_easy_code(code, CURLOPT_CAINFO);
	}
	
	switch (mReqMethod)
	{
	case HOR_GET:
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
		check_curl_easy_code(code, CURLOPT_HTTPGET);
		mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
		mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		break;
		
	case HOR_POST:
		{
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
			check_curl_easy_code(code, CURLOPT_POST);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
			check_curl_easy_code(code, CURLOPT_ENCODING);
			long data_size(0);
			if (mReqBody)
			{
				data_size = mReqBody->size();
			}
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
			check_curl_easy_code(code, CURLOPT_POSTFIELDS);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
			check_curl_easy_code(code, CURLOPT_POSTFIELDSIZE);
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		}
		break;
		
	case HOR_PUT:
		{
			code = curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
			check_curl_easy_code(code, CURLOPT_UPLOAD);
			long data_size(0);
			if (mReqBody)
			{
				data_size = mReqBody->size();
			}
			code = curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
			check_curl_easy_code(code, CURLOPT_INFILESIZE);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
			check_curl_easy_code(code, CURLOPT_POSTFIELDS);
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
			// *TODO: Should this be 'Keep-Alive' ?
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		}
		break;
		
	default:
		LL_ERRS(LOG_CORE) << "Invalid HTTP method in request:  "
						  << int(mReqMethod)  << ".  Can't recover."
						  << LL_ENDL;
		break;
	}

	// Tracing
	if (mTracing >= HTTP_TRACE_CURL_HEADERS)
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
		check_curl_easy_code(code, CURLOPT_VERBOSE);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
		check_curl_easy_code(code, CURLOPT_DEBUGDATA);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
		check_curl_easy_code(code, CURLOPT_DEBUGFUNCTION);
	}
	
	// There's a CURLOPT for this now...
	if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod)
	{
		static const char * const fmt1("Range: bytes=%lu-%lu");
		static const char * const fmt2("Range: bytes=%lu-");

		char range_line[64];

#if LL_WINDOWS
		_snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1,
					(mReqLength ? fmt1 : fmt2),
					(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#else
		if ( mReqLength )
		{
			snprintf(range_line, sizeof(range_line),
					 fmt1,
					 (unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
		}
		else
		{
			snprintf(range_line, sizeof(range_line),
					 fmt2,
					 (unsigned long) mReqOffset);
		}
#endif // LL_WINDOWS
		range_line[sizeof(range_line) - 1] = '\0';
		mCurlHeaders = curl_slist_append(mCurlHeaders, range_line);
	}

	mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:");

	// Request options
	long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT);
	long xfer_timeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT);
	if (mReqOptions)
 	{
		timeout = mReqOptions->getTimeout();
		timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
		xfer_timeout = mReqOptions->getTransferTimeout();
		xfer_timeout = llclamp(xfer_timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
	}
	if (xfer_timeout == 0L)
	{
		xfer_timeout = timeout;
	}
	if (cpolicy.mPipelining > 1L)
	{
		// Pipelining affects both connection and transfer timeout values.
		// Requests that are added to a pipeling immediately have completed
		// their connection so the connection delay tends to be less than
		// the non-pipelined value.  Transfers are the opposite.  Transfer
		// timeout starts once the connection is established and completion
		// can be delayed due to the pipelined requests ahead.  So, it's
		// a handwave but bump the transfer timeout up by the pipelining
		// depth to give some room.
		//
		// BUG-7698, BUG-7688, BUG-7694 (others).  Scylla and Charybdis
		// situation.  Operating against a CDN having service issues may
		// lead to requests stalling for an arbitrarily long time with only
		// the CURLOPT_TIMEOUT value leading to a closed connection.  Sadly
		// for pipelining, libcurl (7.39.0 and earlier, at minimum) starts
		// the clock on this value as soon as a request is started down
		// the wire.  We want a short value to recover and retry from the
		// CDN.  We need a long value to safely deal with a succession of
		// piled-up pipelined requests.
		//
		// *TODO:  Find a better scheme than timeouts to guarantee liveness.
		// Progress on the connection is what we really want, not timeouts.
		// But we don't have access to that and the request progress indicators
		// (various libcurl callbacks) have the same problem TIMEOUT does.
		//
		// xfer_timeout *= cpolicy.mPipelining;
		xfer_timeout *= 2L;
	}
	// *DEBUG:  Enable following override for timeout handling and "[curl:bugs] #1420" tests
	// xfer_timeout = 1L;
	code = curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout);
	check_curl_easy_code(code, CURLOPT_TIMEOUT);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
	check_curl_easy_code(code, CURLOPT_CONNECTTIMEOUT);

	// Request headers
	if (mReqHeaders)
	{
		// Caller's headers last to override
		mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
	}
	code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
	check_curl_easy_code(code, CURLOPT_HTTPHEADER);

	if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_USE_RETRY_AFTER))
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
		check_curl_easy_code(code, CURLOPT_HEADERFUNCTION);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
		check_curl_easy_code(code, CURLOPT_HEADERDATA);
	}
	
	if (status)
	{
		mCurlService = service;
	}
	return status;
}
Beispiel #11
0
// *NOTE:  cancelRequest logic parallels completeRequest logic.
// Keep them synchronized as necessary.
bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status)
{
	HttpOpRequest * op(NULL);
	curl_easy_getinfo(handle, CURLINFO_PRIVATE, &op);

	if (handle != op->mCurlHandle || ! op->mCurlActive)
	{
		LL_WARNS(LOG_CORE) << "libcurl handle and HttpOpRequest handle in disagreement or inactive request."
						   << "  Handle:  " << static_cast<HttpHandle>(handle)
						   << LL_ENDL;
		return false;
	}

	active_set_t::iterator it(mActiveOps.find(op));
	if (mActiveOps.end() == it)
	{
		LL_WARNS(LOG_CORE) << "libcurl completion for request not on active list.  Continuing."
						   << "  Handle:  " << static_cast<HttpHandle>(handle)
						   << LL_ENDL;
		return false;
	}

	// Deactivate request
	mActiveOps.erase(it);
	--mActiveHandles[op->mReqPolicy];
	op->mCurlActive = false;

	// Set final status of request if it hasn't failed by other mechanisms yet
	if (op->mStatus)
	{
		op->mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, status);
	}
	if (op->mStatus)
	{
		int http_status(HTTP_OK);

		curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &http_status);
		if (http_status >= 100 && http_status <= 999)
		{
			char * cont_type(NULL);
			curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &cont_type);
			if (cont_type)
			{
				op->mReplyConType = cont_type;
			}
			op->mStatus = HttpStatus(http_status);
		}
		else
		{
			LL_WARNS(LOG_CORE) << "Invalid HTTP response code ("
							   << http_status << ") received from server."
							   << LL_ENDL;
			op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INVALID_HTTP_STATUS);
		}
	}

	// Detach from multi and recycle handle
	curl_multi_remove_handle(multi_handle, handle);
	mHandleCache.freeHandle(op->mCurlHandle);
	op->mCurlHandle = NULL;

	// Tracing
	if (op->mTracing > HTTP_TRACE_OFF)
	{
		LL_INFOS(LOG_CORE) << "TRACE, RequestComplete, Handle:  "
						   << static_cast<HttpHandle>(op)
						   << ", Status:  " << op->mStatus.toTerseString()
						   << LL_ENDL;
	}

	// Dispatch to next stage
	HttpPolicy & policy(mService->getPolicy());
	bool still_active(policy.stageAfterCompletion(op));

	return still_active;
}
Beispiel #12
0
/*
 * HttpStatus.cpp
 *
 *  Created on: Apr 10, 2014
 *      Author: enrico
 */
#include <utility>

#include "HttpStatus.h"

const HttpStatus HttpStatus::status_100_Continue = HttpStatus(100, "Continue");
const HttpStatus HttpStatus::status_101_Switching_Protocols = HttpStatus(101, "Switching Protocols");
const HttpStatus HttpStatus::status_200_OK = HttpStatus(200, "OK");
const HttpStatus HttpStatus::status_201_Created = HttpStatus(201, "Created");
const HttpStatus HttpStatus::status_202_Accepted = HttpStatus(202, "Accepted");
const HttpStatus HttpStatus::status_203_Non_Authoritative_Information = HttpStatus(203, "Non-Authoritative Information");
const HttpStatus HttpStatus::status_204_No_Content = HttpStatus(204, "No Content");
const HttpStatus HttpStatus::status_205_Reset_Content = HttpStatus(205, "Reset Content");
const HttpStatus HttpStatus::status_206_Partial_Content = HttpStatus(206, "Partial Content");
const HttpStatus HttpStatus::status_300_Multiple_Choices = HttpStatus(300, "Multiple Choices");
const HttpStatus HttpStatus::status_301_Moved_Permanently = HttpStatus(301, "Moved Permanently");
const HttpStatus HttpStatus::status_302_Found = HttpStatus(302, "Found");
const HttpStatus HttpStatus::status_303_See_Other = HttpStatus(303, "See Other");
const HttpStatus HttpStatus::status_304_Not_Modified = HttpStatus(304, "Not Modified");
const HttpStatus HttpStatus::status_305_Use_Proxy = HttpStatus(305, "Use Proxy");
const HttpStatus HttpStatus::status_307_Temporary_Redirect = HttpStatus(307, "Temporary Redirect");
const HttpStatus HttpStatus::status_400_Bad_Request = HttpStatus(400, "Bad Request");
const HttpStatus HttpStatus::status_401_Unauthorized = HttpStatus(401, "Unauthorized");
const HttpStatus HttpStatus::status_402_Payment_Required = HttpStatus(402, "Payment Required");
const HttpStatus HttpStatus::status_403_Forbidden = HttpStatus(403, "Forbidden");
const HttpStatus HttpStatus::status_404_Not_Found = HttpStatus(404, "Not Found");
Beispiel #13
0
// Sets all libcurl options and data for a request.
//
// Used both for initial requests and to 'reload' for
// a retry, generally with a different CURL handle.
// Junk may be left around from a failed request and that
// needs to be cleaned out.
//
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
{
	CURLcode code;
	
	// Scrub transport and result data for retried op case
	mCurlActive = false;
	mCurlHandle = NULL;
	mCurlService = NULL;
	if (mCurlHeaders)
	{
		curl_slist_free_all(mCurlHeaders);
		mCurlHeaders = NULL;
	}
	mCurlBodyPos = 0;

	if (mReplyBody)
	{
		mReplyBody->release();
		mReplyBody = NULL;
	}
	mReplyOffset = 0;
	mReplyLength = 0;
	mReplyFullLength = 0;
	if (mReplyHeaders)
	{
		mReplyHeaders->release();
		mReplyHeaders = NULL;
	}
	mReplyConType.clear();
	
	// *FIXME:  better error handling later
	HttpStatus status;

	// Get global policy options
	HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
	
	mCurlHandle = LLCurl::createStandardCurlHandle();
	if (! mCurlHandle)
	{
		// We're in trouble.  We'll continue but it won't go well.
		LL_WARNS("CoreHttp") << "Failed to allocate libcurl easy handle.  Continuing."
							 << LL_ENDL;
		return HttpStatus(HttpStatus::LLCORE, HE_BAD_ALLOC);
	}
	code = curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
	check_curl_easy_code(code, CURLOPT_IPRESOLVE);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
	check_curl_easy_code(code, CURLOPT_NOSIGNAL);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
	check_curl_easy_code(code, CURLOPT_NOPROGRESS);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
	check_curl_easy_code(code, CURLOPT_URL);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
	check_curl_easy_code(code, CURLOPT_PRIVATE);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
	check_curl_easy_code(code, CURLOPT_ENCODING);

	// 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.
	code = curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15);
	check_curl_easy_code(code, CURLOPT_DNS_CACHE_TIMEOUT);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
	check_curl_easy_code(code, CURLOPT_AUTOREFERER);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
	check_curl_easy_code(code, CURLOPT_FOLLOWLOCATION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
	check_curl_easy_code(code, CURLOPT_MAXREDIRS);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
	check_curl_easy_code(code, CURLOPT_WRITEFUNCTION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
	check_curl_easy_code(code, CURLOPT_WRITEDATA);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
	check_curl_easy_code(code, CURLOPT_READFUNCTION);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
	check_curl_easy_code(code, CURLOPT_READDATA);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
	check_curl_easy_code(code, CURLOPT_SSL_VERIFYPEER);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
	check_curl_easy_code(code, CURLOPT_SSL_VERIFYHOST);

	if (policy.mUseLLProxy)
	{
		// Use the viewer-based thread-safe API which has a
		// fast/safe check for proxy enable.  Would like to
		// encapsulate this someway...
		LLProxy::getInstance()->applyProxySettings(mCurlHandle);
	}
	else if (policy.mHttpProxy.size())
	{
		// *TODO:  This is fine for now but get fuller socks5/
		// authentication thing going later....
		code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str());
		check_curl_easy_code(code, CURLOPT_PROXY);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
		check_curl_easy_code(code, CURLOPT_PROXYTYPE);
	}
	if (policy.mCAPath.size())
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str());
		check_curl_easy_code(code, CURLOPT_CAPATH);
	}
	if (policy.mCAFile.size())
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str());
		check_curl_easy_code(code, CURLOPT_CAINFO);
	}
	
	switch (mReqMethod)
	{
	case HOR_GET:
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
		check_curl_easy_code(code, CURLOPT_HTTPGET);
		mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
		mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		break;
		
	case HOR_POST:
		{
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
			check_curl_easy_code(code, CURLOPT_POST);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
			check_curl_easy_code(code, CURLOPT_ENCODING);
			long data_size(0);
			if (mReqBody)
			{
				data_size = mReqBody->size();
			}
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
			check_curl_easy_code(code, CURLOPT_POSTFIELDS);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
			check_curl_easy_code(code, CURLOPT_POSTFIELDSIZE);
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		}
		break;
		
	case HOR_PUT:
		{
			code = curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
			check_curl_easy_code(code, CURLOPT_UPLOAD);
			long data_size(0);
			if (mReqBody)
			{
				data_size = mReqBody->size();
			}
			code = curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
			check_curl_easy_code(code, CURLOPT_INFILESIZE);
			code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
			check_curl_easy_code(code, CURLOPT_POSTFIELDS);
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
			mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
		}
		break;
		
	default:
		LL_ERRS("CoreHttp") << "Invalid HTTP method in request:  "
							<< int(mReqMethod)  << ".  Can't recover."
							<< LL_ENDL;
		break;
	}

	// Tracing
	if (mTracing >= HTTP_TRACE_CURL_HEADERS)
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
		check_curl_easy_code(code, CURLOPT_VERBOSE);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
		check_curl_easy_code(code, CURLOPT_DEBUGDATA);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
		check_curl_easy_code(code, CURLOPT_DEBUGFUNCTION);
	}
	
	// There's a CURLOPT for this now...
	if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod)
	{
		static const char * const fmt1("Range: bytes=%lu-%lu");
		static const char * const fmt2("Range: bytes=%lu-");

		char range_line[64];

#if LL_WINDOWS
		_snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1,
					(mReqLength ? fmt1 : fmt2),
					(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#else
		snprintf(range_line, sizeof(range_line),
				 (mReqLength ? fmt1 : fmt2),
				 (unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#endif // LL_WINDOWS
		range_line[sizeof(range_line) - 1] = '\0';
		mCurlHeaders = curl_slist_append(mCurlHeaders, range_line);
	}

	mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:");

	// Request options
	long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT);
	long xfer_timeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT);
	if (mReqOptions)
 	{
		timeout = mReqOptions->getTimeout();
		timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
		xfer_timeout = mReqOptions->getTransferTimeout();
		xfer_timeout = llclamp(xfer_timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
	}
	if (xfer_timeout == 0L)
	{
		xfer_timeout = timeout;
	}
	code = curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout);
	check_curl_easy_code(code, CURLOPT_TIMEOUT);
	code = curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
	check_curl_easy_code(code, CURLOPT_CONNECTTIMEOUT);

	// Request headers
	if (mReqHeaders)
	{
		// Caller's headers last to override
		mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
	}
	code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
	check_curl_easy_code(code, CURLOPT_HTTPHEADER);

	if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_USE_RETRY_AFTER))
	{
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
		check_curl_easy_code(code, CURLOPT_HEADERFUNCTION);
		code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
		check_curl_easy_code(code, CURLOPT_HEADERDATA);
	}
	
	if (status)
	{
		mCurlService = service;
	}
	return status;
}