|
|
@@ -1,6 +1,5 @@
|
|
|
//
|
|
|
// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
|
|
|
-// Copyright (c) 2008-2015 the Urho3D project.
|
|
|
//
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
@@ -24,276 +23,416 @@
|
|
|
#include "../Precompiled.h"
|
|
|
|
|
|
#include "../Core/Profiler.h"
|
|
|
+#include "../Container/HashMap.h"
|
|
|
+#include "../IO/BufferQueue.h"
|
|
|
#include "../IO/Log.h"
|
|
|
#include "../Web/WebRequest.h"
|
|
|
|
|
|
-#include "../DebugNew.h"
|
|
|
-
|
|
|
-// !!! WARNING! Use HttpRequest from Network if you don't want your interface
|
|
|
-// to change. This file mimics HttpRequest for now, but will be
|
|
|
-// changing, and it is here as a placeholder only!
|
|
|
-
|
|
|
#ifdef EMSCRIPTEN
|
|
|
|
|
|
+#include "../DebugNew.h"
|
|
|
// Add code to use an XMLHttpRequest or ActiveX XMLHttpRequest here.
|
|
|
|
|
|
#else
|
|
|
|
|
|
-#include <Civetweb/include/civetweb.h>
|
|
|
+#include "../Web/WebInternalConfig.h"
|
|
|
+#include <asio.hpp>
|
|
|
+#include <functional>
|
|
|
+#include <curl/curl.h>
|
|
|
|
|
|
+#include "../DebugNew.h"
|
|
|
|
|
|
namespace Atomic
|
|
|
{
|
|
|
|
|
|
-static const unsigned ERROR_BUFFER_SIZE = 256;
|
|
|
-static const unsigned READ_BUFFER_SIZE = 65536; // Must be a power of two
|
|
|
-
|
|
|
-WebRequest::WebRequest(const String& url, const String& verb, const Vector<String>& headers, const String& postData) :
|
|
|
- url_(url.Trimmed()),
|
|
|
- verb_(!verb.Empty() ? verb : "GET"),
|
|
|
- headers_(headers),
|
|
|
- postData_(postData),
|
|
|
- state_(HTTP_INITIALIZING),
|
|
|
- httpReadBuffer_(new unsigned char[READ_BUFFER_SIZE]),
|
|
|
- readBuffer_(new unsigned char[READ_BUFFER_SIZE]),
|
|
|
- readPosition_(0),
|
|
|
- writePosition_(0)
|
|
|
-{
|
|
|
- // Size of response is unknown, so just set maximum value. The position will also be changed
|
|
|
- // to maximum value once the request is done, signaling end for Deserializer::IsEof().
|
|
|
- size_ = M_MAX_UNSIGNED;
|
|
|
-
|
|
|
- LOGERROR("DO NOT USE WebRequest YET! Use the Network subsystem's HttpRequest. The WebRequest interface is under development and will change soon!");
|
|
|
- LOGDEBUG("HTTP " + verb_ + " request to URL " + url_);
|
|
|
-
|
|
|
- // Start the worker thread to actually create the connection and read the response data.
|
|
|
- Run();
|
|
|
-}
|
|
|
-
|
|
|
-WebRequest::~WebRequest()
|
|
|
-{
|
|
|
- Stop();
|
|
|
-}
|
|
|
-
|
|
|
-void WebRequest::ThreadFunction()
|
|
|
+struct WebRequestInternalState
|
|
|
{
|
|
|
- String protocol = "http";
|
|
|
- String host;
|
|
|
- String path = "/";
|
|
|
- int port = 80;
|
|
|
-
|
|
|
- unsigned protocolEnd = url_.Find("://");
|
|
|
- if (protocolEnd != String::NPOS)
|
|
|
+ /// The WebRequest external state.
|
|
|
+ WebRequest& es;
|
|
|
+ /// The WebRequest external state to force it to stay around.
|
|
|
+ SharedPtr<WebRequest> es_hold;
|
|
|
+ /// The work queue.
|
|
|
+ asio::io_service* service;
|
|
|
+ /// URL.
|
|
|
+ String url;
|
|
|
+ /// Verb.
|
|
|
+ String verb;
|
|
|
+ /// Response headers.
|
|
|
+ HashMap<StringHash, Pair<String, String>> responseHeaders;
|
|
|
+ /// Upload stream.
|
|
|
+ SharedPtr<Object> upload;
|
|
|
+ /// Download stream.
|
|
|
+ SharedPtr<Object> download;
|
|
|
+ /// Request Headers.
|
|
|
+ curl_slist* headers = NULL;
|
|
|
+ /// Connection state.
|
|
|
+ WebRequestState state;
|
|
|
+ /// cURL multi handle.
|
|
|
+ CURLM* curlm;
|
|
|
+ /// cURL easy handle.
|
|
|
+ CURL* curl;
|
|
|
+ /// A flag to know if the request has contents (has data to upload).
|
|
|
+ curl_off_t requestContentSize;
|
|
|
+ /// A flag to know if the operation has been aborted.
|
|
|
+ bool isAborted;
|
|
|
+ /// A flag to know if the easy handle has been added to the Web class's multi handle.
|
|
|
+ bool isAddedToMulti;
|
|
|
+ /// Error string. Empty if no error.
|
|
|
+ char error[CURL_ERROR_SIZE];
|
|
|
+
|
|
|
+ WebRequestInternalState(WebRequest &es_) :
|
|
|
+ es(es_)
|
|
|
{
|
|
|
- protocol = url_.Substring(0, protocolEnd);
|
|
|
- host = url_.Substring(protocolEnd + 3);
|
|
|
+ LOGDEBUG("Create WebRequestInternalState");
|
|
|
}
|
|
|
- else
|
|
|
- host = url_;
|
|
|
|
|
|
- unsigned pathStart = host.Find('/');
|
|
|
- if (pathStart != String::NPOS)
|
|
|
+ ~WebRequestInternalState()
|
|
|
{
|
|
|
- path = host.Substring(pathStart);
|
|
|
- host = host.Substring(0, pathStart);
|
|
|
+ LOGDEBUG("Destroy WebRequestInternalState");
|
|
|
}
|
|
|
|
|
|
- unsigned portStart = host.Find(':');
|
|
|
- if (portStart != String::NPOS)
|
|
|
+ static int onProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
|
|
{
|
|
|
- port = ToInt(host.Substring(portStart + 1));
|
|
|
- host = host.Substring(0, portStart);
|
|
|
- }
|
|
|
-
|
|
|
- char errorBuffer[ERROR_BUFFER_SIZE];
|
|
|
- memset(errorBuffer, 0, sizeof(errorBuffer));
|
|
|
+ WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(clientp));
|
|
|
+ if (is_->isAborted)
|
|
|
+ {
|
|
|
+ // This should probably be CURL_XFERINFO_ABORT, but that doesn't
|
|
|
+ // exist. It probably would be the same numeric value, if it did.
|
|
|
+ // The docs say that it just has to be a nonzero to abort.
|
|
|
+ return CURL_READFUNC_ABORT;
|
|
|
+ }
|
|
|
|
|
|
- String headersStr;
|
|
|
- for (unsigned i = 0; i < headers_.Size(); ++i)
|
|
|
- {
|
|
|
- // Trim and only add non-empty header strings
|
|
|
- String header = headers_[i].Trimmed();
|
|
|
- if (header.Length())
|
|
|
- headersStr += header + "\r\n";
|
|
|
+ VariantMap eventData;
|
|
|
+ eventData.Insert(MakePair(StringHash("down_total"), Variant((double)dltotal)));
|
|
|
+ eventData.Insert(MakePair(StringHash("down_loaded"), Variant((double)dlnow)));
|
|
|
+ eventData.Insert(MakePair(StringHash("up_total"), Variant((double)ultotal)));
|
|
|
+ eventData.Insert(MakePair(StringHash("up_loaded"), Variant((double)ulnow)));
|
|
|
+ is_->es.SendEvent("progress", eventData);
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
- // Initiate the connection. This may block due to DNS query
|
|
|
- /// \todo SSL mode will not actually work unless Civetweb's SSL mode is initialized with an external SSL DLL
|
|
|
- mg_connection* connection = 0;
|
|
|
- if (postData_.Empty())
|
|
|
- {
|
|
|
- connection = mg_download(host.CString(), port, protocol.Compare("https", false) ? 0 : 1, errorBuffer, sizeof(errorBuffer),
|
|
|
- "%s %s HTTP/1.1\r\n"
|
|
|
- "Host: %s\r\n"
|
|
|
- "Connection: close\r\n"
|
|
|
- "%s"
|
|
|
- "\r\n", verb_.CString(), path.CString(), host.CString(), headersStr.CString());
|
|
|
- }
|
|
|
- else
|
|
|
+ static size_t onHeader(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
|
{
|
|
|
- connection = mg_download(host.CString(), port, protocol.Compare("https", false) ? 0 : 1, errorBuffer, sizeof(errorBuffer),
|
|
|
- "%s %s HTTP/1.1\r\n"
|
|
|
- "Host: %s\r\n"
|
|
|
- "Connection: close\r\n"
|
|
|
- "%s"
|
|
|
- "Content-Length: %d\r\n"
|
|
|
- "\r\n"
|
|
|
- "%s", verb_.CString(), path.CString(), host.CString(), headersStr.CString(), postData_.Length(), postData_.CString());
|
|
|
+ WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(userdata));
|
|
|
+ if (is_->isAborted)
|
|
|
+ {
|
|
|
+ is_->state = HTTP_CLOSED;
|
|
|
+ // This should probably be CURL_HEADERFUNC_ABORT, but that doesn't
|
|
|
+ // exist. It probably would be the same numeric value, if it did.
|
|
|
+ // The docs say that it just has to be a number of bytes that is
|
|
|
+ // not "size * nmemb" to abort.
|
|
|
+ return CURL_READFUNC_ABORT;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the size in bytes.
|
|
|
+ size_t real_size(size * nmemb);
|
|
|
+
|
|
|
+ // Check for some known values.
|
|
|
+ if (real_size == 2 && ptr[0] == '\r' && ptr[1] == '\n')
|
|
|
+ {
|
|
|
+ return real_size;
|
|
|
+ }
|
|
|
+ if (real_size > 5 && !strncmp(ptr, "HTTP/", 5))
|
|
|
+ {
|
|
|
+ return real_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the header key and value, and add them to the map.
|
|
|
+ unsigned int key_end = 0;
|
|
|
+ unsigned int value_begin = 2;
|
|
|
+ while (value_begin < real_size)
|
|
|
+ {
|
|
|
+ if (ptr[key_end] == ':' && ptr[key_end + 1] == ' ')
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ++key_end;
|
|
|
+ ++value_begin;
|
|
|
+ }
|
|
|
+ if (value_begin == real_size)
|
|
|
+ {
|
|
|
+ String key(ptr, (unsigned int)real_size);
|
|
|
+ is_->responseHeaders.InsertNew(key.ToUpper(), MakePair(key, String()));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ String key(ptr, (unsigned int)key_end);
|
|
|
+ is_->responseHeaders.InsertNew(key.ToUpper(), MakePair(key, String(ptr + value_begin, (unsigned int)real_size - value_begin - 2)));
|
|
|
+ }
|
|
|
+
|
|
|
+ return real_size;
|
|
|
}
|
|
|
|
|
|
+ static size_t onWrite(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
|
{
|
|
|
- MutexLock lock(mutex_);
|
|
|
- state_ = connection ? HTTP_OPEN : HTTP_ERROR;
|
|
|
-
|
|
|
- // If no connection could be made, store the error and exit
|
|
|
- if (state_ == HTTP_ERROR)
|
|
|
+ WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(userdata));
|
|
|
+ is_->state = HTTP_OPEN;
|
|
|
+ if (is_->isAborted)
|
|
|
{
|
|
|
- error_ = String(&errorBuffer[0]);
|
|
|
- return;
|
|
|
+ is_->state = HTTP_CLOSED;
|
|
|
+ // This should probably be CURL_WRITEFUNC_ABORT, but that doesn't
|
|
|
+ // exist. It probably would be the same numeric value, if it did.
|
|
|
+ // The docs say that it just has to be a number of bytes that is
|
|
|
+ // not "size * nmemb" to abort.
|
|
|
+ return CURL_READFUNC_ABORT;
|
|
|
}
|
|
|
+
|
|
|
+ // Find the size in bytes.
|
|
|
+ size_t real_size(size * nmemb);
|
|
|
+
|
|
|
+ // Write the date into the download buffer queue.
|
|
|
+ Serializer* download(dynamic_cast<Serializer*>(is_->download.Get()));
|
|
|
+ download->Write(ptr, (unsigned int)real_size);
|
|
|
+
|
|
|
+ // Emit a "download_chunk" event.
|
|
|
+ VariantMap eventData;
|
|
|
+ eventData.Insert(MakePair(StringHash("download"), Variant(is_->download)));
|
|
|
+ eventData.Insert(MakePair(StringHash("size"), Variant((unsigned int)real_size)));
|
|
|
+ is_->es.SendEvent("download_chunk", eventData);
|
|
|
+
|
|
|
+ return real_size;
|
|
|
}
|
|
|
|
|
|
- // Loop while should run, read data from the connection, copy to the main thread buffer if there is space
|
|
|
- while (shouldRun_)
|
|
|
+ static size_t onRead(char *buffer, size_t size, size_t nitems, void *instream)
|
|
|
{
|
|
|
- // Read less than full buffer to be able to distinguish between full and empty ring buffer. Reading may block
|
|
|
- int bytesRead = mg_read(connection, httpReadBuffer_.Get(), READ_BUFFER_SIZE / 4);
|
|
|
- if (bytesRead <= 0)
|
|
|
- break;
|
|
|
+ WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(instream));
|
|
|
+ is_->state = HTTP_OPEN;
|
|
|
+ if (is_->isAborted)
|
|
|
+ {
|
|
|
+ is_->state = HTTP_CLOSED;
|
|
|
+ return CURL_READFUNC_ABORT;
|
|
|
+ }
|
|
|
|
|
|
- mutex_.Acquire();
|
|
|
+ // Find the size in bytes.
|
|
|
+ size_t real_size(size * nitems);
|
|
|
|
|
|
- // Wait until enough space in the main thread's ring buffer
|
|
|
- for (;;)
|
|
|
+ // Read as much as we can from the upload buffer queue.
|
|
|
+ Deserializer* upload(dynamic_cast<Deserializer*>(is_->upload.Get()));
|
|
|
+ size_t size_queued(upload->GetSize());
|
|
|
+ size_t size_left(real_size);
|
|
|
+ if ((size_left > 0) && (size_queued > 0))
|
|
|
{
|
|
|
- unsigned spaceInBuffer = READ_BUFFER_SIZE - ((writePosition_ - readPosition_) & (READ_BUFFER_SIZE - 1));
|
|
|
- if ((int)spaceInBuffer > bytesRead || !shouldRun_)
|
|
|
- break;
|
|
|
+ size_t read_size(std::min(size_queued, size_left));
|
|
|
+ upload->Read(buffer, (unsigned int)read_size);
|
|
|
+ size_left -= read_size;
|
|
|
+ }
|
|
|
|
|
|
- mutex_.Release();
|
|
|
- Time::Sleep(5);
|
|
|
- mutex_.Acquire();
|
|
|
+ // If we still have bytes to fill, then emit a "upload_chunk" event.
|
|
|
+ if (size_left > 0)
|
|
|
+ {
|
|
|
+ VariantMap eventData;
|
|
|
+ eventData.Insert(MakePair(StringHash("upload"), Variant(is_->upload)));
|
|
|
+ eventData.Insert(MakePair(StringHash("size"), Variant((unsigned int)size_left)));
|
|
|
+ is_->es.SendEvent("upload_chunk", eventData);
|
|
|
}
|
|
|
|
|
|
- if (!shouldRun_)
|
|
|
+ // Read as much as we can from the upload buffer queue (again).
|
|
|
+ size_queued = upload->GetSize();
|
|
|
+ size_left = real_size;
|
|
|
+ if ((size_left > 0) && (size_queued > 0))
|
|
|
{
|
|
|
- mutex_.Release();
|
|
|
- break;
|
|
|
+ size_t read_size(std::min(size_queued, size_left));
|
|
|
+ upload->Read(buffer, (unsigned int)read_size);
|
|
|
+ size_left -= read_size;
|
|
|
}
|
|
|
|
|
|
- if (writePosition_ + bytesRead <= READ_BUFFER_SIZE)
|
|
|
- memcpy(readBuffer_.Get() + writePosition_, httpReadBuffer_.Get(), (size_t)bytesRead);
|
|
|
- else
|
|
|
+ // If we still have bytes to fill, then something went wrong, so we should abort.
|
|
|
+ if (size_left > 0)
|
|
|
{
|
|
|
- // Handle ring buffer wrap
|
|
|
- unsigned part1 = READ_BUFFER_SIZE - writePosition_;
|
|
|
- unsigned part2 = bytesRead - part1;
|
|
|
- memcpy(readBuffer_.Get() + writePosition_, httpReadBuffer_.Get(), part1);
|
|
|
- memcpy(readBuffer_.Get(), httpReadBuffer_.Get() + part1, part2);
|
|
|
+ is_->isAborted = true;
|
|
|
+ return CURL_READFUNC_ABORT;
|
|
|
}
|
|
|
|
|
|
- writePosition_ += bytesRead;
|
|
|
- writePosition_ &= READ_BUFFER_SIZE - 1;
|
|
|
+ return real_size;
|
|
|
+ }
|
|
|
|
|
|
- mutex_.Release();
|
|
|
+ void onEnd(int code)
|
|
|
+ {
|
|
|
+ VariantMap eventData;
|
|
|
+ if (code != CURLE_OK)
|
|
|
+ {
|
|
|
+ state = HTTP_ERROR;
|
|
|
+ eventData.Insert(MakePair(StringHash("error"), Variant(String(error, (unsigned int)strnlen(error, sizeof(error))))));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ state = HTTP_CLOSED;
|
|
|
+ eventData.Insert(MakePair(StringHash("download"), Variant(download)));
|
|
|
+ eventData.Insert(MakePair(StringHash("upload"), Variant(upload)));
|
|
|
+ }
|
|
|
+ es.SendEvent("complete", eventData);
|
|
|
}
|
|
|
+};
|
|
|
+
|
|
|
+WebRequest::WebRequest(Context* context, const String& verb, const String& url, double requestContentSize) :
|
|
|
+ Object(context),
|
|
|
+ is_(new WebRequestInternalState(*this))
|
|
|
+{
|
|
|
+ is_->url = url.Trimmed();
|
|
|
+ is_->verb = verb;
|
|
|
+ is_->upload = new BufferQueue(context);
|
|
|
+ is_->download = new BufferQueue(context);
|
|
|
+ is_->state = HTTP_INITIALIZING;
|
|
|
+ is_->curlm = NULL;
|
|
|
+ is_->curl = NULL;
|
|
|
+ is_->requestContentSize = curl_off_t(std::floor(requestContentSize));
|
|
|
+ is_->isAborted = false;
|
|
|
+ is_->isAddedToMulti = false;
|
|
|
+
|
|
|
+}
|
|
|
|
|
|
- // Close the connection
|
|
|
- mg_close_connection(connection);
|
|
|
+WebRequest::~WebRequest()
|
|
|
+{
|
|
|
+ LOGDEBUG("Destroy WebRequest");
|
|
|
|
|
|
+ curl_slist_free_all(is_->headers);
|
|
|
+ if (is_->curlm == NULL)
|
|
|
{
|
|
|
- MutexLock lock(mutex_);
|
|
|
- state_ = HTTP_CLOSED;
|
|
|
+ return;
|
|
|
}
|
|
|
+ curl_easy_cleanup(is_->curl);
|
|
|
+ delete is_;
|
|
|
}
|
|
|
|
|
|
-unsigned WebRequest::Read(void* dest, unsigned size)
|
|
|
+void WebRequest::setup(asio::io_service *service, CURLM *curlm)
|
|
|
{
|
|
|
- mutex_.Acquire();
|
|
|
+ LOGDEBUG("Create WebRequest");
|
|
|
|
|
|
- unsigned char* destPtr = (unsigned char*)dest;
|
|
|
- unsigned sizeLeft = size;
|
|
|
- unsigned totalRead = 0;
|
|
|
+ is_->service = service;
|
|
|
+ is_->curlm = curlm;
|
|
|
+ is_->curl = curl_easy_init();
|
|
|
|
|
|
- for (;;)
|
|
|
- {
|
|
|
- unsigned bytesAvailable;
|
|
|
+ LOGDEBUG("HTTP " + is_->verb + " request to URL " + is_->url);
|
|
|
|
|
|
- for (;;)
|
|
|
- {
|
|
|
- bytesAvailable = CheckEofAndAvailableSize();
|
|
|
- if (bytesAvailable || IsEof())
|
|
|
- break;
|
|
|
- // While no bytes and connection is still open, block until has some data
|
|
|
- mutex_.Release();
|
|
|
- Time::Sleep(5);
|
|
|
- mutex_.Acquire();
|
|
|
- }
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_ERRORBUFFER, is_->error);
|
|
|
+ is_->error[0] = '\0';
|
|
|
|
|
|
- if (bytesAvailable)
|
|
|
- {
|
|
|
- if (bytesAvailable > sizeLeft)
|
|
|
- bytesAvailable = sizeLeft;
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_URL, is_->url.CString());
|
|
|
|
|
|
- if (readPosition_ + bytesAvailable <= READ_BUFFER_SIZE)
|
|
|
- memcpy(destPtr, readBuffer_.Get() + readPosition_, bytesAvailable);
|
|
|
- else
|
|
|
- {
|
|
|
- // Handle ring buffer wrap
|
|
|
- unsigned part1 = READ_BUFFER_SIZE - readPosition_;
|
|
|
- unsigned part2 = bytesAvailable - part1;
|
|
|
- memcpy(destPtr, readBuffer_.Get() + readPosition_, part1);
|
|
|
- memcpy(destPtr + part1, readBuffer_.Get(), part2);
|
|
|
- }
|
|
|
+ // All callbacks must look at is_->isAborted flag!
|
|
|
|
|
|
- readPosition_ += bytesAvailable;
|
|
|
- readPosition_ &= READ_BUFFER_SIZE - 1;
|
|
|
- sizeLeft -= bytesAvailable;
|
|
|
- totalRead += bytesAvailable;
|
|
|
- destPtr += bytesAvailable;
|
|
|
- }
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_HEADERFUNCTION, &WebRequestInternalState::onHeader);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_HEADERDATA, is_);
|
|
|
+
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_WRITEFUNCTION, &WebRequestInternalState::onWrite);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_WRITEDATA, is_);
|
|
|
+
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_NOPROGRESS, 0L);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_XFERINFOFUNCTION, &WebRequestInternalState::onProgress);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_XFERINFODATA, is_);
|
|
|
+
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_CUSTOMREQUEST, is_->verb.CString());
|
|
|
|
|
|
- if (!sizeLeft || !bytesAvailable)
|
|
|
- break;
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_PRIVATE, this);
|
|
|
+
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_READFUNCTION, &WebRequestInternalState::onRead);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_READDATA, is_);
|
|
|
+
|
|
|
+ if (is_->requestContentSize)
|
|
|
+ {
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_UPLOAD, 1L);
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_INFILESIZE_LARGE, is_->requestContentSize);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void WebRequest::internalNotify(WebRequest *wr, int code)
|
|
|
+{
|
|
|
+ wr->is_->onEnd(code);
|
|
|
+ if (wr->is_->isAddedToMulti)
|
|
|
+ {
|
|
|
+ curl_multi_remove_handle(wr->is_->curlm, wr->is_->curl);
|
|
|
+ wr->is_->isAddedToMulti = false;
|
|
|
+ wr->is_->es_hold.Reset();
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- // Check for end-of-file once more after reading the bytes
|
|
|
- CheckEofAndAvailableSize();
|
|
|
- mutex_.Release();
|
|
|
- return totalRead;
|
|
|
+void WebRequest::Abort()
|
|
|
+{
|
|
|
+ is_->isAborted = true;
|
|
|
}
|
|
|
|
|
|
-unsigned WebRequest::Seek(unsigned position)
|
|
|
+const String& WebRequest::GetURL() const
|
|
|
{
|
|
|
- return position_;
|
|
|
+ return is_->url;
|
|
|
}
|
|
|
|
|
|
String WebRequest::GetError() const
|
|
|
{
|
|
|
- MutexLock lock(mutex_);
|
|
|
- const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
|
|
|
- return error_;
|
|
|
+ return String(is_->error);
|
|
|
}
|
|
|
|
|
|
WebRequestState WebRequest::GetState() const
|
|
|
{
|
|
|
- MutexLock lock(mutex_);
|
|
|
- const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
|
|
|
- return state_;
|
|
|
+ return is_->state;
|
|
|
+}
|
|
|
+
|
|
|
+String WebRequest::GetVerb() const
|
|
|
+{
|
|
|
+ return is_->verb;
|
|
|
+}
|
|
|
+
|
|
|
+bool WebRequest::HasDownloadChunkEvent()
|
|
|
+{
|
|
|
+ return true; // cURL based implementations always support the "download_chunk" event.
|
|
|
+}
|
|
|
+
|
|
|
+void WebRequest::SetRequestHeader(const String& key, const String& value)
|
|
|
+{
|
|
|
+ // Trim and only add non-empty header strings.
|
|
|
+ String header;
|
|
|
+ header += key.Trimmed();
|
|
|
+ header += ": ";
|
|
|
+ header += value;
|
|
|
+ if (header.Length())
|
|
|
+ {
|
|
|
+ is_->headers = curl_slist_append(is_->headers, header.CString());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void WebRequest::Send()
|
|
|
+{
|
|
|
+ if (!is_->isAddedToMulti && !is_->isAborted)
|
|
|
+ {
|
|
|
+ is_->es_hold = this;
|
|
|
+ curl_easy_setopt(is_->curl, CURLOPT_HTTPHEADER, is_->headers);
|
|
|
+ curl_multi_add_handle(is_->curlm, is_->curl);
|
|
|
+ is_->isAddedToMulti = true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+StringVector WebRequest::GetResponseHeaderKeys()
|
|
|
+{
|
|
|
+ StringVector keys;
|
|
|
+ for (auto it(is_->responseHeaders.Begin()),
|
|
|
+ itEnd(is_->responseHeaders.End()); it != itEnd; ++it)
|
|
|
+ {
|
|
|
+ keys.Push(it->second_.first_);
|
|
|
+ }
|
|
|
+ return keys;
|
|
|
}
|
|
|
|
|
|
-unsigned WebRequest::GetAvailableSize() const
|
|
|
+String WebRequest::GetResponseHeader(const String& header)
|
|
|
{
|
|
|
- MutexLock lock(mutex_);
|
|
|
- return const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
|
|
|
+ auto it(is_->responseHeaders.Find(header.ToUpper()));
|
|
|
+ if (it == is_->responseHeaders.End())
|
|
|
+ {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return it->second_.second_;
|
|
|
}
|
|
|
|
|
|
-unsigned WebRequest::CheckEofAndAvailableSize()
|
|
|
+String WebRequest::GetAllResponseHeaders()
|
|
|
{
|
|
|
- unsigned bytesAvailable = (writePosition_ - readPosition_) & (READ_BUFFER_SIZE - 1);
|
|
|
- if (state_ == HTTP_ERROR || (state_ == HTTP_CLOSED && !bytesAvailable))
|
|
|
- position_ = M_MAX_UNSIGNED;
|
|
|
- return bytesAvailable;
|
|
|
+ String allHeaders;
|
|
|
+ for (auto it(is_->responseHeaders.Begin()),
|
|
|
+ itEnd(is_->responseHeaders.End()); it != itEnd; ++it)
|
|
|
+ {
|
|
|
+ allHeaders += it->second_.first_;
|
|
|
+ allHeaders += ": ";
|
|
|
+ allHeaders += it->second_.second_;
|
|
|
+ allHeaders += "\r\n";
|
|
|
+ }
|
|
|
+ return allHeaders;
|
|
|
}
|
|
|
|
|
|
}
|