When uploading finishes, but is not detected, the timeout should be for "reply delay", the time that the server takes before it replies, and not CurlTimeoutLowSpeedTime. This patch adds code that takes this failure into account (which happened only ONCE for me on Metropolis while flying around and using trickle (not sure if that is relevant), so it's not that likely to improvement anything in practise. Note that it is detected by an assertion when it happens, so that we can safely assume it normally never happened on SL). * Generalized PUT / POST configuration by adding CurlEasyRequest::setPut, which now also supports keep-alive (which still isn't used). * Upload content length is now stored in CurlEasyRequest::mContentLength * CurlEasyRequest::has_stalled() now return false if it was possbile that the 'upload finished' detect failed AND calls upload_finished() itself in that case, so it is no longer 'const'. * If low speed is detect exactly when the last bytes are being attempted to be sent (unlikely scenario), then the upload gets 4 more seconds after which is switches to CurlTimeoutReplyDelay. * Added EDoesAuthentication and EAllowCompressedReply to replace booleans, for readability and type-safety, as did EKeepAlive. Note that this change inverts the meaning of the compression related parameter. * Unrelated: removed an unnecessary #include "llurlrequest.h" from llxmlrpcresponder.h
292 lines
8.7 KiB
C++
292 lines
8.7 KiB
C++
/**
|
|
* @file llurlrequest.cpp
|
|
* @author Phoenix
|
|
* @date 2005-04-28
|
|
* @brief Implementation of the URLRequest class and related classes.
|
|
*
|
|
* $LicenseInfo:firstyear=2005&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, 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
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "llurlrequest.h"
|
|
|
|
#ifdef CWDEBUG
|
|
#include <libcwd/buf2str.h>
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <openssl/x509_vfy.h>
|
|
#include <openssl/ssl.h>
|
|
#include "aicurleasyrequeststatemachine.h"
|
|
#include "llioutil.h"
|
|
#include "llmemtype.h"
|
|
#include "llpumpio.h"
|
|
#include "llsd.h"
|
|
#include "llstring.h"
|
|
#include "apr_env.h"
|
|
#include "llapr.h"
|
|
#include "llscopedvolatileaprpool.h"
|
|
#include "llfasttimer.h"
|
|
#include "message.h"
|
|
|
|
/**
|
|
* String constants
|
|
*/
|
|
const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes");
|
|
|
|
/**
|
|
* class LLURLRequest
|
|
*/
|
|
|
|
// static
|
|
std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
|
|
{
|
|
static int const array_size = LLHTTPClient::REQUEST_ACTION_COUNT; // INVALID == 0
|
|
static char const* const VERBS[array_size] =
|
|
{
|
|
"(invalid)",
|
|
"HEAD",
|
|
"GET",
|
|
"PUT",
|
|
"POST",
|
|
"DELETE",
|
|
"MOVE"
|
|
};
|
|
return VERBS[action >= array_size ? LLHTTPClient::INVALID : action];
|
|
}
|
|
|
|
// This might throw AICurlNoEasyHandle.
|
|
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, std::string const& url, Injector* body,
|
|
LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool compression) :
|
|
mAction(action), mURL(url), mKeepAlive(keepalive), mIsAuth(is_auth), mNoCompression(!compression),
|
|
mBody(body), mResponder(responder), mHeaders(headers), mResponderNameCache(responder ? responder->getName() : "<uninitialized>")
|
|
{
|
|
}
|
|
|
|
void LLURLRequest::initialize_impl(void)
|
|
{
|
|
// If the header is "Pragma" with no value, the caller intends to
|
|
// force libcurl to drop the Pragma header it so gratuitously inserts.
|
|
// Before inserting the header, force libcurl to not use the proxy.
|
|
std::string pragma_value;
|
|
if (mHeaders.getValue("Pragma", pragma_value) && pragma_value.empty())
|
|
{
|
|
useProxy(false);
|
|
}
|
|
|
|
if (mAction == LLHTTPClient::HTTP_PUT || mAction == LLHTTPClient::HTTP_POST)
|
|
{
|
|
// If the Content-Type header was passed in we defer to the caller's wisdom,
|
|
// but if they did not specify a Content-Type, then ask the injector.
|
|
mHeaders.addHeader("Content-Type", mBody->contentType(), AIHTTPHeaders::keep_existing_header);
|
|
}
|
|
else if (mAction != LLHTTPClient::HTTP_HEAD)
|
|
{
|
|
// Check to see if we have already set Accept or not. If no one
|
|
// set it, set it to application/llsd+xml since that's what we
|
|
// almost always want.
|
|
mHeaders.addHeader("Accept", "application/llsd+xml", AIHTTPHeaders::keep_existing_header);
|
|
}
|
|
|
|
if (mAction == LLHTTPClient::HTTP_POST && gMessageSystem)
|
|
{
|
|
mHeaders.addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort));
|
|
}
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
|
easy_request_w->prepRequest(easy_request_w, mHeaders, mResponder);
|
|
|
|
if (mBody)
|
|
{
|
|
// This might throw AICurlNoBody.
|
|
mBodySize = mBody->get_body(easy_request_w->sChannels, easy_request_w->getInput());
|
|
}
|
|
|
|
success = configure(easy_request_w);
|
|
}
|
|
catch (AICurlNoBody const& error)
|
|
{
|
|
llwarns << "Injector::get_body() failed: " << error.what() << llendl;
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
// Continue to initialize base class.
|
|
AICurlEasyRequestStateMachine::initialize_impl();
|
|
}
|
|
else
|
|
{
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void LLURLRequest::addHeader(const char* header)
|
|
{
|
|
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
|
curlEasyRequest_w->addHeader(header);
|
|
}
|
|
|
|
// Added to mitigate the effect of libcurl looking
|
|
// for the ALL_PROXY and http_proxy env variables
|
|
// and deciding to insert a Pragma: no-cache
|
|
// header! The only usage of this method at the
|
|
// time of this writing is in llhttpclient.cpp
|
|
// in the request() method, where this method
|
|
// is called with use_proxy = FALSE
|
|
void LLURLRequest::useProxy(bool use_proxy)
|
|
{
|
|
static std::string env_proxy;
|
|
|
|
if (use_proxy && env_proxy.empty())
|
|
{
|
|
char* env_proxy_str;
|
|
LLScopedVolatileAPRPool scoped_pool;
|
|
apr_status_t status = apr_env_get(&env_proxy_str, "ALL_PROXY", scoped_pool);
|
|
if (status != APR_SUCCESS)
|
|
{
|
|
status = apr_env_get(&env_proxy_str, "http_proxy", scoped_pool);
|
|
}
|
|
if (status != APR_SUCCESS)
|
|
{
|
|
use_proxy = false;
|
|
}
|
|
else
|
|
{
|
|
// env_proxy_str is stored in the scoped_pool, so we have to make a copy.
|
|
env_proxy = env_proxy_str;
|
|
}
|
|
}
|
|
|
|
LL_DEBUGS("Proxy") << "use_proxy = " << (use_proxy?'Y':'N') << ", env_proxy = " << (!env_proxy.empty() ? env_proxy : "(null)") << LL_ENDL;
|
|
|
|
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
|
curlEasyRequest_w->setoptString(CURLOPT_PROXY, (use_proxy && !env_proxy.empty()) ? env_proxy : std::string(""));
|
|
}
|
|
|
|
#ifdef AI_UNUSED
|
|
void LLURLRequest::useProxy(const std::string &proxy)
|
|
{
|
|
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
|
curlEasyRequest_w->setoptString(CURLOPT_PROXY, proxy);
|
|
}
|
|
#endif
|
|
|
|
bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
|
|
{
|
|
bool rv = false;
|
|
{
|
|
switch(mAction)
|
|
{
|
|
case LLHTTPClient::HTTP_HEAD:
|
|
curlEasyRequest_w->setopt(CURLOPT_NOBODY, 1);
|
|
rv = true;
|
|
break;
|
|
|
|
case LLHTTPClient::HTTP_GET:
|
|
curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1);
|
|
|
|
// Set Accept-Encoding to allow response compression
|
|
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
|
|
rv = true;
|
|
break;
|
|
|
|
case LLHTTPClient::HTTP_PUT:
|
|
|
|
// Set the handle for an http put
|
|
curlEasyRequest_w->setPut(mBodySize, mKeepAlive);
|
|
|
|
// Set Accept-Encoding to allow response compression
|
|
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
|
|
rv = true;
|
|
break;
|
|
|
|
case LLHTTPClient::HTTP_POST:
|
|
|
|
// Set the handle for an http post
|
|
curlEasyRequest_w->setPost(mBodySize, mKeepAlive);
|
|
|
|
// Set Accept-Encoding to allow response compression
|
|
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
|
|
rv = true;
|
|
break;
|
|
|
|
case LLHTTPClient::HTTP_DELETE:
|
|
// Set the handle for an http post
|
|
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
rv = true;
|
|
break;
|
|
|
|
case LLHTTPClient::HTTP_MOVE:
|
|
// Set the handle for an http post
|
|
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE");
|
|
rv = true;
|
|
break;
|
|
|
|
default:
|
|
llwarns << "Unhandled URLRequest action: " << mAction << llendl;
|
|
break;
|
|
}
|
|
if(rv)
|
|
{
|
|
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, gNoVerifySSLCert ? 0L : 1L);
|
|
// Don't verify host name if this is not an authentication request,
|
|
// so urls with scrubbed host names will work (improves DNS performance).
|
|
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYHOST, (gNoVerifySSLCert || !mIsAuth) ? 0L : 2L);
|
|
curlEasyRequest_w->finalizeRequest(mURL, mResponder->getHTTPTimeoutPolicy(), this);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// Called from AIStateMachine::mainloop, but put here because we don't want to include llurlrequest.h there of course.
|
|
void print_statemachine_diagnostics(U64 total_clocks, U64 max_delta, AIEngine::queued_type::const_reference slowest_element)
|
|
{
|
|
AIStateMachine const& slowest_state_machine = slowest_element.statemachine();
|
|
LLURLRequest const* request = dynamic_cast<LLURLRequest const*>(&slowest_state_machine);
|
|
F64 const tfactor = 1000 / calc_clock_frequency();
|
|
std::ostringstream msg;
|
|
if (total_clocks > max_delta)
|
|
{
|
|
msg << "AIStateMachine::mainloop did run for " << (total_clocks * tfactor) << " ms. The slowest ";
|
|
}
|
|
else
|
|
{
|
|
msg << "AIStateMachine::mainloop: A ";
|
|
}
|
|
msg << "state machine ";
|
|
if (request)
|
|
{
|
|
msg << "(" << request->getResponderName() << ") ";
|
|
}
|
|
msg << "ran for " << (max_delta * tfactor) << " ms";
|
|
if (slowest_state_machine.getRuntime() > max_delta)
|
|
{
|
|
msg << " (" << (slowest_state_machine.getRuntime() * tfactor) << " ms in total now)";
|
|
}
|
|
msg << ".";
|
|
llwarns << msg.str() << llendl;
|
|
}
|
|
|