mirror of https://github.com/axmolengine/axmol.git
Improve HttpClient, remove unsafe API `sendSync`
This commit is contained in:
parent
72251dcf86
commit
2f1a4a9921
|
@ -123,17 +123,15 @@ HttpClient::HttpClient()
|
|||
_scheduler = Director::getInstance()->getScheduler();
|
||||
|
||||
_service = new yasio::io_service(HttpClient::MAX_CHANNELS);
|
||||
_service->set_option(yasio::YOPT_S_FORWARD_EVENT, 1);
|
||||
_service->set_option(yasio::YOPT_S_DEFERRED_EVENT, 1);
|
||||
_service->set_option(yasio::YOPT_S_DNS_QUERIES_TIMEOUT, 3);
|
||||
_service->set_option(yasio::YOPT_S_DNS_QUERIES_TRIES, 1);
|
||||
_service->start([this](yasio::event_ptr&& e) { handleNetworkEvent(e.get()); });
|
||||
|
||||
for (int i = 0; i < HttpClient::MAX_CHANNELS; ++i)
|
||||
{
|
||||
_availChannelQueue.unsafe_emplace_back(i);
|
||||
}
|
||||
|
||||
_scheduler->schedule([this](float) { dispatchResponseCallbacks(); }, this, 0, false, "#");
|
||||
setDispatchOnWorkThread(false);
|
||||
|
||||
_isInited = true;
|
||||
}
|
||||
|
@ -159,7 +157,26 @@ void HttpClient::setDispatchOnWorkThread(bool bVal)
|
|||
_scheduler->unscheduleAllForTarget(this);
|
||||
_dispatchOnWorkThread = bVal;
|
||||
if (!bVal)
|
||||
_scheduler->schedule([this](float) { dispatchResponseCallbacks(); }, this, 0, false, "#");
|
||||
_scheduler->schedule([this](float) { tickInput(); }, this, 0, false, "#");
|
||||
}
|
||||
|
||||
|
||||
// Poll and notify main thread if responses exists in queue
|
||||
void HttpClient::tickInput()
|
||||
{
|
||||
_service->dispatch();
|
||||
|
||||
if (_finishedResponseQueue.unsafe_empty())
|
||||
return;
|
||||
|
||||
auto lck = _finishedResponseQueue.get_lock();
|
||||
if (!_finishedResponseQueue.unsafe_empty())
|
||||
{
|
||||
HttpResponse* response = _finishedResponseQueue.front();
|
||||
_finishedResponseQueue.unsafe_pop_front();
|
||||
lck.unlock();
|
||||
invokeResposneCallbackAndRelease(response);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClient::handleNetworkStatusChanged()
|
||||
|
@ -183,19 +200,12 @@ bool HttpClient::send(HttpRequest* request)
|
|||
return false;
|
||||
|
||||
auto response = new HttpResponse(request);
|
||||
processResponse(response, request->getUrl());
|
||||
response->setLocation(request->getUrl(), false);
|
||||
processResponse(response, -1);
|
||||
response->release();
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpResponse* HttpClient::sendSync(HttpRequest* request)
|
||||
{
|
||||
request->setSync(true);
|
||||
if (this->send(request))
|
||||
return request->wait();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int HttpClient::tryTakeAvailChannel()
|
||||
{
|
||||
auto lck = _availChannelQueue.get_lock();
|
||||
|
@ -208,18 +218,23 @@ int HttpClient::tryTakeAvailChannel()
|
|||
return -1;
|
||||
}
|
||||
|
||||
void HttpClient::processResponse(HttpResponse* response, std::string_view url)
|
||||
void HttpClient::processResponse(HttpResponse* response, int channelIndex)
|
||||
{
|
||||
auto channelIndex = tryTakeAvailChannel();
|
||||
response->retain();
|
||||
|
||||
if (channelIndex != -1)
|
||||
if (response->validateUri())
|
||||
{
|
||||
if (response->prepareForProcess(url))
|
||||
if (channelIndex == -1)
|
||||
channelIndex = tryTakeAvailChannel();
|
||||
|
||||
if (channelIndex != -1)
|
||||
{
|
||||
response->_responseHeaders.clear(); // redirect needs clear old response headers
|
||||
auto& requestUri = response->getRequestUri();
|
||||
auto channelHandle = _service->channel_at(channelIndex);
|
||||
auto channelHandle = _service->channel_at(channelIndex);
|
||||
|
||||
auto& requestUri = response->getRequestUri();
|
||||
|
||||
ax::print("###### open connection for %s", requestUri.getHostName().data());
|
||||
|
||||
channelHandle->ud_.ptr = response;
|
||||
_service->set_option(YOPT_C_REMOTE_ENDPOINT, channelIndex, requestUri.getHost().data(),
|
||||
(int)requestUri.getPort());
|
||||
|
@ -229,14 +244,10 @@ void HttpClient::processResponse(HttpResponse* response, std::string_view url)
|
|||
_service->open(channelIndex, YCK_TCP_CLIENT);
|
||||
}
|
||||
else
|
||||
{
|
||||
finishResponse(response);
|
||||
}
|
||||
_pendingResponseQueue.emplace_back(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pendingResponseQueue.emplace_back(response);
|
||||
}
|
||||
finishResponse(response);
|
||||
}
|
||||
|
||||
void HttpClient::handleNetworkEvent(yasio::io_event* event)
|
||||
|
@ -244,8 +255,7 @@ void HttpClient::handleNetworkEvent(yasio::io_event* event)
|
|||
int channelIndex = event->cindex();
|
||||
auto channel = _service->channel_at(event->cindex());
|
||||
HttpResponse* response = (HttpResponse*)channel->ud_.ptr;
|
||||
if (!response)
|
||||
return;
|
||||
assert(response);
|
||||
|
||||
bool responseFinished = response->isFinished();
|
||||
switch (event->kind())
|
||||
|
@ -253,7 +263,7 @@ void HttpClient::handleNetworkEvent(yasio::io_event* event)
|
|||
case YEK_ON_PACKET:
|
||||
if (!responseFinished)
|
||||
{
|
||||
auto&& pkt = event->packet_view();
|
||||
auto&& pkt = event->packet();
|
||||
response->handleInput(pkt.data(), pkt.size());
|
||||
}
|
||||
if (response->isFinished())
|
||||
|
@ -352,7 +362,8 @@ void HttpClient::handleNetworkEvent(yasio::io_event* event)
|
|||
char strContentLength[128] = {0};
|
||||
auto requestData = request->getRequestData();
|
||||
auto requestDataSize = request->getRequestDataSize();
|
||||
snprintf(strContentLength, sizeof(strContentLength), "Content-Length: %d\r\n\r\n", static_cast<int>(requestDataSize));
|
||||
snprintf(strContentLength, sizeof(strContentLength), "Content-Length: %d\r\n\r\n",
|
||||
static_cast<int>(requestDataSize));
|
||||
obs.write_bytes(strContentLength);
|
||||
|
||||
if (requestData && requestDataSize > 0)
|
||||
|
@ -387,6 +398,8 @@ void HttpClient::handleNetworkEvent(yasio::io_event* event)
|
|||
|
||||
void HttpClient::handleNetworkEOF(HttpResponse* response, yasio::io_channel* channel, int internalErrorCode)
|
||||
{
|
||||
channel->ud_.ptr = nullptr;
|
||||
|
||||
channel->get_user_timer().cancel(*_service);
|
||||
response->updateInternalCode(internalErrorCode);
|
||||
auto responseCode = response->getResponseCode();
|
||||
|
@ -395,54 +408,30 @@ void HttpClient::handleNetworkEOF(HttpResponse* response, yasio::io_channel* cha
|
|||
case 301:
|
||||
case 302:
|
||||
case 307:
|
||||
if (response->increaseRedirectCount() < HttpClient::MAX_REDIRECT_COUNT)
|
||||
if (response->tryRedirect())
|
||||
{
|
||||
auto iter = response->_responseHeaders.find("location");
|
||||
if (iter != response->_responseHeaders.end())
|
||||
{
|
||||
if (responseCode == 302)
|
||||
response->getHttpRequest()->setRequestType(HttpRequest::Type::GET);
|
||||
AXLOG("Process url redirect (%d): %s", responseCode, iter->second.c_str());
|
||||
_availChannelQueue.push_front(channel->index());
|
||||
processResponse(response, iter->second);
|
||||
response->release();
|
||||
return;
|
||||
}
|
||||
processResponse(response, channel->index());
|
||||
response->release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
finishResponse(response);
|
||||
|
||||
finishResponse(response);
|
||||
// try process pending response
|
||||
auto lck = _pendingResponseQueue.get_lock();
|
||||
if (!_pendingResponseQueue.unsafe_empty())
|
||||
{
|
||||
auto pendingResponse = _pendingResponseQueue.unsafe_front();
|
||||
_pendingResponseQueue.unsafe_pop_front();
|
||||
lck.unlock();
|
||||
|
||||
// recycle channel
|
||||
channel->ud_.ptr = nullptr;
|
||||
_availChannelQueue.push_front(channel->index());
|
||||
|
||||
// try process pending response
|
||||
auto lck = _pendingResponseQueue.get_lock();
|
||||
if (!_pendingResponseQueue.unsafe_empty())
|
||||
{
|
||||
auto pendingResponse = _pendingResponseQueue.unsafe_front();
|
||||
_pendingResponseQueue.unsafe_pop_front();
|
||||
lck.unlock();
|
||||
|
||||
processResponse(pendingResponse, pendingResponse->getHttpRequest()->getUrl());
|
||||
pendingResponse->release();
|
||||
}
|
||||
}
|
||||
|
||||
// Poll and notify main thread if responses exists in queue
|
||||
void HttpClient::dispatchResponseCallbacks()
|
||||
{
|
||||
if (_finishedResponseQueue.unsafe_empty())
|
||||
return;
|
||||
|
||||
auto lck = _finishedResponseQueue.get_lock();
|
||||
if (!_finishedResponseQueue.unsafe_empty())
|
||||
{
|
||||
HttpResponse* response = _finishedResponseQueue.front();
|
||||
_finishedResponseQueue.unsafe_pop_front();
|
||||
lck.unlock();
|
||||
invokeResposneCallbackAndRelease(response);
|
||||
processResponse(pendingResponse, channel->index());
|
||||
pendingResponse->release();
|
||||
}
|
||||
else
|
||||
{ // recycle channel
|
||||
_availChannelQueue.push_front(channel->index());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,6 @@ public:
|
|||
* How many requests could be perform concurrency.
|
||||
*/
|
||||
static const int MAX_CHANNELS = 21;
|
||||
static const int MAX_REDIRECT_COUNT = 3;
|
||||
|
||||
/**
|
||||
* Get instance of HttpClient.
|
||||
|
@ -120,12 +119,6 @@ public:
|
|||
*/
|
||||
bool send(HttpRequest* request);
|
||||
|
||||
/**
|
||||
* Send http request sync, will block caller thread until request finished.
|
||||
* @remark Caller must call release manually when the response never been used.
|
||||
*/
|
||||
HttpResponse* sendSync(HttpRequest* request);
|
||||
|
||||
/**
|
||||
* Set the timeout value for connecting.
|
||||
*
|
||||
|
@ -205,7 +198,7 @@ private:
|
|||
HttpClient();
|
||||
virtual ~HttpClient();
|
||||
|
||||
void processResponse(HttpResponse* response, std::string_view url);
|
||||
void processResponse(HttpResponse* response, int channelIndex);
|
||||
|
||||
int tryTakeAvailChannel();
|
||||
|
||||
|
@ -213,7 +206,7 @@ private:
|
|||
|
||||
void handleNetworkEOF(HttpResponse* response, yasio::io_channel* channel, int internalErrorCode);
|
||||
|
||||
void dispatchResponseCallbacks();
|
||||
void tickInput();
|
||||
|
||||
void finishResponse(HttpResponse* response);
|
||||
|
||||
|
|
|
@ -65,6 +65,8 @@ class AX_DLL HttpRequest : public Ref
|
|||
friend class HttpClient;
|
||||
|
||||
public:
|
||||
static const int MAX_REDIRECT_COUNT = 3;
|
||||
|
||||
/**
|
||||
* The HttpRequest type enum used in the HttpRequest::setRequestType.
|
||||
*/
|
||||
|
|
|
@ -150,51 +150,77 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response data by the string pointer and the defined size.
|
||||
* @param value a string pointer that point to response data buffer.
|
||||
* @param n the defined size that the response data buffer would be copied.
|
||||
*/
|
||||
bool prepareForProcess(std::string_view url)
|
||||
bool tryRedirect()
|
||||
{
|
||||
/* Resets response status */
|
||||
_finished = false;
|
||||
_responseData.clear();
|
||||
_currentHeader.clear();
|
||||
_responseCode = -1;
|
||||
_internalCode = 0;
|
||||
if ((_redirectCount < HttpRequest::MAX_REDIRECT_COUNT))
|
||||
{
|
||||
auto iter = _responseHeaders.find("location");
|
||||
if (iter != _responseHeaders.end())
|
||||
{
|
||||
auto redirectUrl = iter->second;
|
||||
if (_responseCode == 302)
|
||||
getHttpRequest()->setRequestType(HttpRequest::Type::GET);
|
||||
AXLOG("Process url redirect (%d): %s", _responseCode, redirectUrl.c_str());
|
||||
return setLocation(redirectUrl, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri uri = Uri::parse(url);
|
||||
if (!uri.isValid())
|
||||
return false;
|
||||
/**
|
||||
* Set new request location with url
|
||||
* @param url the actually url to request
|
||||
* @param redirect wither redirect
|
||||
*/
|
||||
bool setLocation(std::string_view url, bool redirect)
|
||||
{
|
||||
if (redirect)
|
||||
{
|
||||
++_redirectCount;
|
||||
_requestUri.invalid();
|
||||
}
|
||||
|
||||
_requestUri = std::move(uri);
|
||||
if (!_requestUri.isValid())
|
||||
{
|
||||
Uri uri = Uri::parse(url);
|
||||
if (!uri.isValid())
|
||||
return false;
|
||||
_requestUri = std::move(uri);
|
||||
|
||||
/* Initialize user callbacks and settings */
|
||||
llhttp_settings_init(&_contextSettings);
|
||||
/* Resets response status */
|
||||
_responseHeaders.clear();
|
||||
_finished = false;
|
||||
_responseData.clear();
|
||||
_currentHeader.clear();
|
||||
_responseCode = -1;
|
||||
_internalCode = 0;
|
||||
|
||||
/* Initialize the parser in HTTP_BOTH mode, meaning that it will select between
|
||||
* HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
|
||||
* input.
|
||||
*/
|
||||
llhttp_init(&_context, HTTP_RESPONSE, &_contextSettings);
|
||||
/* Initialize user callbacks and settings */
|
||||
llhttp_settings_init(&_contextSettings);
|
||||
|
||||
_context.data = this;
|
||||
/* Initialize the parser in HTTP_BOTH mode, meaning that it will select between
|
||||
* HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
|
||||
* input.
|
||||
*/
|
||||
llhttp_init(&_context, HTTP_RESPONSE, &_contextSettings);
|
||||
|
||||
/* Set user callbacks */
|
||||
_contextSettings.on_header_field = on_header_field;
|
||||
_contextSettings.on_header_field_complete = on_header_field_complete;
|
||||
_contextSettings.on_header_value = on_header_value;
|
||||
_contextSettings.on_header_value_complete = on_header_value_complete;
|
||||
_contextSettings.on_body = on_body;
|
||||
_contextSettings.on_message_complete = on_complete;
|
||||
_context.data = this;
|
||||
|
||||
/* Set user callbacks */
|
||||
_contextSettings.on_header_field = on_header_field;
|
||||
_contextSettings.on_header_field_complete = on_header_field_complete;
|
||||
_contextSettings.on_header_value = on_header_value;
|
||||
_contextSettings.on_header_value_complete = on_header_value_complete;
|
||||
_contextSettings.on_body = on_body;
|
||||
_contextSettings.on_message_complete = on_complete;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Uri& getRequestUri() const { return _requestUri; }
|
||||
bool validateUri() const { return _requestUri.isValid(); }
|
||||
|
||||
int increaseRedirectCount() { return ++_redirectCount; }
|
||||
const Uri& getRequestUri() const { return _requestUri; }
|
||||
|
||||
static int on_header_field(llhttp_t* context, const char* at, size_t length)
|
||||
{
|
||||
|
|
|
@ -84,6 +84,8 @@ public:
|
|||
/** Checks whether it's a SSL connection */
|
||||
bool isSecure() const { return _isSecure; }
|
||||
|
||||
void invalid() { _isValid = false; }
|
||||
|
||||
/** Gets the scheme name for this URI. */
|
||||
std::string_view getScheme() const { return _scheme; }
|
||||
|
||||
|
|
|
@ -104,24 +104,7 @@ HttpClientTest::~HttpClientTest()
|
|||
|
||||
void HttpClientTest::onMenuGetTestClicked(ax::Ref* sender)
|
||||
{
|
||||
// test 1(sync request test)
|
||||
{
|
||||
HttpRequest* request = new HttpRequest();
|
||||
request->setUrl("https://tool.chinaz.com");
|
||||
request->setRequestType(HttpRequest::Type::GET);
|
||||
request->setHeaders(std::vector<std::string>{CHROME_UA});
|
||||
// request->setResponseCallback(AX_CALLBACK_2(HttpClientTest::onHttpRequestCompleted, this));
|
||||
request->setTag("GET test1");
|
||||
HttpResponse* response = HttpClient::getInstance()->sendSync(request);
|
||||
if (response)
|
||||
{
|
||||
onHttpRequestCompleted(HttpClient::getInstance(), response);
|
||||
response->release();
|
||||
}
|
||||
request->release();
|
||||
}
|
||||
|
||||
// test 2
|
||||
// test 1
|
||||
{
|
||||
HttpRequest* request = new HttpRequest();
|
||||
request->setUrl("https://just-make-this-request-failed.com");
|
||||
|
@ -133,7 +116,7 @@ void HttpClientTest::onMenuGetTestClicked(ax::Ref* sender)
|
|||
request->release();
|
||||
}
|
||||
|
||||
// test 3
|
||||
// test 2
|
||||
{
|
||||
HttpRequest* request = new HttpRequest();
|
||||
// required fields
|
||||
|
@ -147,7 +130,7 @@ void HttpClientTest::onMenuGetTestClicked(ax::Ref* sender)
|
|||
request->release();
|
||||
}
|
||||
|
||||
// test 4
|
||||
// test 3
|
||||
{
|
||||
HttpRequest* request = new HttpRequest();
|
||||
request->setUrl("https://httpbin.org/get");
|
||||
|
@ -159,7 +142,7 @@ void HttpClientTest::onMenuGetTestClicked(ax::Ref* sender)
|
|||
request->release();
|
||||
}
|
||||
|
||||
// test 5
|
||||
// test 4
|
||||
{
|
||||
HttpRequest* request = new HttpRequest();
|
||||
request->setUrl("https://github.com/yasio/yasio");
|
||||
|
|
Loading…
Reference in New Issue