Suppress "Expect:" header for POST and PUT. No longer use CURLOPT_COPYPOSTFIELDS.
For POST and PUT, libcurl adds by default a "Expect: 100-continue" header and wait for the server to reply with a 100 response. However, since the server never does that (LL comment in the code "I'm not sure what it means") it's up to libcurl to continue anyway after a while and that part is apparently bugged, causing people not to be able to login sometimes. But suppressing the header, libcurl doesn't wait for it and we worked around this bug. The commit introduces a much improved CurlEasyRequest::setPost method that enforces the above for POST, but that also no longer uses CURLOPT_COPYPOSTFIELDS (but CURLOPT_POSTFIELDS), no longer making a copy of the body of what we are going to send to the server. Instead it uses a new object, derived from AIPostField, to keep track of this data and to dispose of it once the transaction is complete (and no sooner). Copied from llcorehttp, we now also always set a "Connection: keep-alive" and "Keep-alive: 300" header for POST. This was already done for texture downloads (HttpOpRequest), but now we do it always :p. Might need to be changed in the future, but currently those headers are ignored by the server anyway.
This commit is contained in:
@@ -701,6 +701,7 @@ CURLMcode CurlEasyHandle::remove_handle_from_multi(AICurlEasyRequest_wat& curl_e
|
||||
mActiveMultiHandle = NULL;
|
||||
CURLMcode res = check_multi_code(curl_multi_remove_handle(multi, mEasyHandle));
|
||||
removed_from_multi_handle(curl_easy_request_w);
|
||||
mPostField = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -796,11 +797,33 @@ void CurlEasyRequest::setoptString(CURLoption option, std::string const& value)
|
||||
setopt(option, value.c_str());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::setPost(char const* postdata, S32 size)
|
||||
void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, S32 size)
|
||||
{
|
||||
setopt(CURLOPT_POST, 1L);
|
||||
llassert_always(postdata->data());
|
||||
|
||||
Dout(dc::curl, "POST size is " << size << " bytes: \"" << libcwd::buf2str(postdata->data(), size) << "\".");
|
||||
setPostField(postdata); // Make sure the data stays around until we don't need it anymore.
|
||||
|
||||
setPost_raw(size, postdata->data());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::setPost_raw(S32 size, char const* data)
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
// data == NULL when we're going to read the data using CURLOPT_READFUNCTION.
|
||||
Dout(dc::curl, "POST size is " << size << " bytes.");
|
||||
}
|
||||
|
||||
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
|
||||
addHeader("Expect:");
|
||||
if (size > 0)
|
||||
{
|
||||
addHeader("Connection: keep-alive");
|
||||
addHeader("Keep-alive: 300");
|
||||
}
|
||||
setopt(CURLOPT_POSTFIELDSIZE, size);
|
||||
setopt(CURLOPT_POSTFIELDS, postdata);
|
||||
setopt(CURLOPT_POSTFIELDS, data);
|
||||
}
|
||||
|
||||
ThreadSafeCurlEasyRequest* CurlEasyRequest::get_lockobj(void)
|
||||
@@ -1133,8 +1156,23 @@ void CurlEasyRequest::finalizeRequest(std::string const& url)
|
||||
{
|
||||
llassert(!mRequestFinalized);
|
||||
mResult = CURLE_FAILED_INIT; // General error code, the final code is written here in MultiHandle::check_run_count when msg is CURLMSG_DONE.
|
||||
mRequestFinalized = true;
|
||||
lldebugs << url << llendl;
|
||||
#ifdef SHOW_ASSERT
|
||||
// Do a sanity check on the headers.
|
||||
int content_type_count = 0;
|
||||
for (curl_slist* list = mHeaders; list; list = list->next)
|
||||
{
|
||||
if (strncmp(list->data, "Content-Type:", 13) == 0)
|
||||
{
|
||||
++content_type_count;
|
||||
}
|
||||
}
|
||||
if (content_type_count > 1)
|
||||
{
|
||||
llwarns << content_type_count << " Content-Type: headers!" << llendl;
|
||||
}
|
||||
#endif
|
||||
mRequestFinalized = true;
|
||||
setopt(CURLOPT_HTTPHEADER, mHeaders);
|
||||
setoptString(CURLOPT_URL, url);
|
||||
// The following line is a bit tricky: we store a pointer to the object without increasing its reference count.
|
||||
@@ -1270,7 +1308,8 @@ void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w
|
||||
{
|
||||
if (post)
|
||||
{
|
||||
curl_easy_request_w->setoptString(CURLOPT_ENCODING, "");
|
||||
// Accept everything (send an Accept-Encoding header containing all encodings we support (zlib and gzip)).
|
||||
curl_easy_request_w->setoptString(CURLOPT_ENCODING, ""); // CURLOPT_ACCEPT_ENCODING
|
||||
}
|
||||
|
||||
mInput.reset(new LLBufferArray);
|
||||
@@ -1303,9 +1342,7 @@ void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w
|
||||
|
||||
if (!post)
|
||||
{
|
||||
curl_easy_request_w->addHeader("Connection: keep-alive");
|
||||
curl_easy_request_w->addHeader("Keep-alive: 300");
|
||||
// Add other headers.
|
||||
// Add extra headers.
|
||||
for (std::vector<std::string>::const_iterator iter = headers.begin(); iter != headers.end(); ++iter)
|
||||
{
|
||||
curl_easy_request_w->addHeader((*iter).c_str());
|
||||
|
||||
@@ -247,6 +247,21 @@ struct AICurlEasyHandleEvents {
|
||||
virtual ~AICurlEasyHandleEvents() { }
|
||||
};
|
||||
|
||||
// Pointer to data we're going to POST.
|
||||
class AIPostField : public LLThreadSafeRefCount {
|
||||
protected:
|
||||
char const* mData;
|
||||
|
||||
public:
|
||||
AIPostField(char const* data) : mData(data) { }
|
||||
char const* data(void) const { return mData; }
|
||||
};
|
||||
|
||||
// The pointer to the data that we have to POST is passed around as AIPostFieldPtr,
|
||||
// which causes it to automatically clean up when there are no pointers left
|
||||
// pointing to it.
|
||||
typedef LLPointer<AIPostField> AIPostFieldPtr;
|
||||
|
||||
#include "aicurlprivate.h"
|
||||
|
||||
// AICurlPrivate::CurlEasyRequestPtr, a boost::intrusive_ptr, is no more threadsafe than a
|
||||
|
||||
@@ -143,6 +143,7 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
CURL* mEasyHandle;
|
||||
CURLM* mActiveMultiHandle;
|
||||
char* mErrorBuffer;
|
||||
AIPostFieldPtr mPostField; // This keeps the POSTFIELD data alive for as long as the easy handle exists.
|
||||
bool mQueuedForRemoval; // Set if the easy handle is (probably) added to the multi handle, but is queued for removal.
|
||||
#ifdef SHOW_ASSERT
|
||||
public:
|
||||
@@ -190,6 +191,9 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
// Return the underlying curl easy handle.
|
||||
CURL* getEasyHandle(void) const { return mEasyHandle; }
|
||||
|
||||
// Keep POSTFIELD data alive.
|
||||
void setPostField(AIPostFieldPtr const& post_field_ptr) { mPostField = post_field_ptr; }
|
||||
|
||||
private:
|
||||
// Return, and possibly create, the curl (easy) error buffer used by the current thread.
|
||||
static char* getTLErrorBuffer(void);
|
||||
@@ -208,9 +212,13 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
// AICurlEasyRequest is deleted, then also the ThreadSafeCurlEasyRequest is deleted
|
||||
// and the CurlEasyRequest destructed.
|
||||
class CurlEasyRequest : public CurlEasyHandle {
|
||||
private:
|
||||
void setPost_raw(S32 size, char const* data);
|
||||
public:
|
||||
void setPost(S32 size) { setPost_raw(size, NULL); }
|
||||
void setPost(AIPostFieldPtr const& postdata, S32 size);
|
||||
void setPost(char const* data, S32 size) { setPost(new AIPostField(data), size); }
|
||||
void setoptString(CURLoption option, std::string const& value);
|
||||
void setPost(char const* postdata, S32 size);
|
||||
void addHeader(char const* str);
|
||||
|
||||
private:
|
||||
|
||||
@@ -443,7 +443,6 @@ static LLSD blocking_request(
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*easy_request);
|
||||
|
||||
LLHTTPBuffer http_buffer;
|
||||
std::string body_str;
|
||||
|
||||
// * Set curl handle options
|
||||
curlEasyRequest_w->setopt(CURLOPT_TIMEOUT, (long)timeout); // seconds, see warning at top of function.
|
||||
@@ -463,6 +462,9 @@ static LLSD blocking_request(
|
||||
}
|
||||
}
|
||||
|
||||
// Needs to stay alive until after the call to perform().
|
||||
std::ostringstream ostr;
|
||||
|
||||
// * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy)
|
||||
if (method == LLURLRequest::HTTP_GET)
|
||||
{
|
||||
@@ -470,24 +472,13 @@ static LLSD blocking_request(
|
||||
}
|
||||
else if (method == LLURLRequest::HTTP_POST)
|
||||
{
|
||||
curlEasyRequest_w->setopt(CURLOPT_POST, 1);
|
||||
//serialize to ostr then copy to str - need to because ostr ptr is unstable :(
|
||||
std::ostringstream ostr;
|
||||
LLSDSerialize::toXML(body, ostr);
|
||||
body_str = ostr.str();
|
||||
curlEasyRequest_w->setopt(CURLOPT_POSTFIELDS, body_str.c_str());
|
||||
//copied from PHP libs, correct?
|
||||
curlEasyRequest_w->addHeader("Content-Type: application/llsd+xml");
|
||||
|
||||
// copied from llurlrequest.cpp
|
||||
// it appears that apache2.2.3 or django in etch is busted. If
|
||||
// we do not clear the expect header, we get a 500. May be
|
||||
// limited to django/mod_wsgi.
|
||||
curlEasyRequest_w->addHeader("Expect:");
|
||||
LLSDSerialize::toXML(body, ostr);
|
||||
curlEasyRequest_w->setPost(ostr.str().c_str(), ostr.str().length());
|
||||
}
|
||||
|
||||
// * Do the action using curl, handle results
|
||||
lldebugs << "HTTP body: " << body_str << llendl;
|
||||
curlEasyRequest_w->addHeader("Accept: application/llsd+xml");
|
||||
curlEasyRequest_w->finalizeRequest(url);
|
||||
|
||||
@@ -500,7 +491,7 @@ static LLSD blocking_request(
|
||||
llwarns << "CURL REQ URL: " << url << llendl;
|
||||
llwarns << "CURL REQ METHOD TYPE: " << method << llendl;
|
||||
llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl;
|
||||
llwarns << "CURL REQ BODY: " << body_str << llendl;
|
||||
llwarns << "CURL REQ BODY: " << ostr.str() << llendl;
|
||||
llwarns << "CURL HTTP_STATUS: " << http_status << llendl;
|
||||
llwarns << "CURL ERROR: " << curlEasyRequest_w->getErrorString() << llendl;
|
||||
llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl;
|
||||
|
||||
@@ -526,6 +526,7 @@ bool LLURLRequest::configure()
|
||||
curlEasyRequest_w->setopt(CURLOPT_FOLLOWLOCATION, 1);
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
case HTTP_GET:
|
||||
curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1);
|
||||
curlEasyRequest_w->setopt(CURLOPT_FOLLOWLOCATION, 1);
|
||||
@@ -538,24 +539,15 @@ bool LLURLRequest::configure()
|
||||
case HTTP_PUT:
|
||||
// Disable the expect http 1.1 extension. POST and PUT default
|
||||
// to turning this on, and I am not too sure what it means.
|
||||
addHeader("Expect:");
|
||||
|
||||
curlEasyRequest_w->addHeader("Expect:");
|
||||
curlEasyRequest_w->setopt(CURLOPT_UPLOAD, 1);
|
||||
curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, bytes);
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
case HTTP_POST:
|
||||
// Disable the expect http 1.1 extension. POST and PUT default
|
||||
// to turning this on, and I am not too sure what it means.
|
||||
addHeader("Expect:");
|
||||
|
||||
// Disable the content type http header.
|
||||
// *FIX: what should it be?
|
||||
addHeader("Content-Type:");
|
||||
|
||||
// Set the handle for an http post
|
||||
curlEasyRequest_w->setPost(NULL, bytes);
|
||||
curlEasyRequest_w->setPost(bytes);
|
||||
|
||||
// Set Accept-Encoding to allow response compression
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
|
||||
|
||||
@@ -101,7 +101,7 @@ bool Request::post(std::string const& url, headers_t const& headers, std::string
|
||||
llassert_always(success); // AIFIXME: Maybe throw an error.
|
||||
if (!success)
|
||||
return false;
|
||||
buffered_easy_request_w->setPost(NULL, bytes);
|
||||
buffered_easy_request_w->setPost(bytes);
|
||||
buffered_easy_request_w->addHeader("Content-Type: application/octet-stream");
|
||||
buffered_easy_request_w->finalizeRequest(url);
|
||||
|
||||
@@ -129,7 +129,7 @@ bool Request::post(std::string const& url, headers_t const& headers, LLSD const&
|
||||
LLBufferStream buffer_stream(buffer_w->sChannels, buffer_w->getInput().get());
|
||||
LLSDSerialize::toXML(data, buffer_stream);
|
||||
S32 bytes = buffer_w->getInput()->countAfter(buffer_w->sChannels.out(), NULL);
|
||||
buffered_easy_request_w->setPost(NULL, bytes);
|
||||
buffered_easy_request_w->setPost(bytes);
|
||||
buffered_easy_request_w->addHeader("Content-Type: application/llsd+xml");
|
||||
buffered_easy_request_w->finalizeRequest(url);
|
||||
|
||||
|
||||
@@ -168,8 +168,6 @@ public:
|
||||
LLCurl::TransferInfo mTransferInfo;
|
||||
|
||||
std::string mURI;
|
||||
char* mRequestText;
|
||||
int mRequestTextSize;
|
||||
|
||||
std::string mProxyAddress;
|
||||
|
||||
@@ -200,7 +198,6 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
|
||||
: mCurlEasyRequestStateMachinePtr(NULL),
|
||||
mStatus(LLXMLRPCTransaction::StatusNotStarted),
|
||||
mURI(uri),
|
||||
mRequestText(0),
|
||||
mResponse(0)
|
||||
{
|
||||
init(request, useGzip);
|
||||
@@ -212,7 +209,6 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
|
||||
: mCurlEasyRequestStateMachinePtr(NULL),
|
||||
mStatus(LLXMLRPCTransaction::StatusNotStarted),
|
||||
mURI(uri),
|
||||
mRequestText(0),
|
||||
mResponse(0)
|
||||
{
|
||||
XMLRPC_REQUEST request = XMLRPC_RequestNew();
|
||||
@@ -223,8 +219,13 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
|
||||
init(request, useGzip);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Store pointer to data allocated with XMLRPC_REQUEST_ToXML and call XMLRPC_Free to free it upon destruction.
|
||||
class AIXMLRPCData : public AIPostField
|
||||
{
|
||||
public:
|
||||
AIXMLRPCData(char const* allocated_data) : AIPostField(allocated_data) { }
|
||||
/*virtual*/ ~AIXMLRPCData() { XMLRPC_Free(const_cast<char*>(mData)); mData = NULL; }
|
||||
};
|
||||
|
||||
void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip)
|
||||
{
|
||||
@@ -259,12 +260,11 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip)
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
|
||||
}
|
||||
|
||||
mRequestText = XMLRPC_REQUEST_ToXML(request, &mRequestTextSize);
|
||||
if (mRequestText)
|
||||
int requestTextSize;
|
||||
char* requestText = XMLRPC_REQUEST_ToXML(request, &requestTextSize);
|
||||
if (requestText)
|
||||
{
|
||||
Dout(dc::curl, "Writing " << mRequestTextSize << " bytes: \"" << libcwd::buf2str(mRequestText, mRequestTextSize) << "\".");;
|
||||
curlEasyRequest_w->setopt(CURLOPT_POSTFIELDSIZE, mRequestTextSize);
|
||||
curlEasyRequest_w->setoptString(CURLOPT_COPYPOSTFIELDS, mRequestText);
|
||||
curlEasyRequest_w->setPost(new AIXMLRPCData(requestText), requestTextSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -298,11 +298,6 @@ LLXMLRPCTransaction::Impl::~Impl()
|
||||
{
|
||||
XMLRPC_RequestFree(mResponse, 1);
|
||||
}
|
||||
|
||||
if (mRequestText)
|
||||
{
|
||||
XMLRPC_Free(mRequestText);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLXMLRPCTransaction::Impl::is_finished(void) const
|
||||
|
||||
Reference in New Issue
Block a user