Allow to cancel asynchronous HTTP requests (#332)
Usage: auto args = this->httpClient.createRequest(url, method); httpClient.performRequest(args, ...); [...] // Oops, we don't actually want to complete the request! args->cancel = true;
This commit is contained in:
parent
688f85fda6
commit
66cd29e747
@ -517,6 +517,9 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ok will be false if your httpClient is not async
|
// ok will be false if your httpClient is not async
|
||||||
|
|
||||||
|
// A request in progress can be cancelled by setting the cancel flag. It does nothing if the request already completed.
|
||||||
|
args->cancel = true;
|
||||||
```
|
```
|
||||||
|
|
||||||
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
|
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "IXProgressCallback.h"
|
#include "IXProgressCallback.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include <atomic>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ namespace ix
|
|||||||
TooManyRedirects = 12,
|
TooManyRedirects = 12,
|
||||||
ChunkReadError = 13,
|
ChunkReadError = 13,
|
||||||
CannotReadBody = 14,
|
CannotReadBody = 14,
|
||||||
|
Cancelled = 15,
|
||||||
Invalid = 100
|
Invalid = 100
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,6 +89,7 @@ namespace ix
|
|||||||
bool compressRequest = false;
|
bool compressRequest = false;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
OnProgressCallback onProgressCallback;
|
OnProgressCallback onProgressCallback;
|
||||||
|
std::atomic<bool> cancel;
|
||||||
};
|
};
|
||||||
|
|
||||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||||
|
@ -241,17 +241,21 @@ namespace ix
|
|||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
|
||||||
// Make a cancellation object dealing with connection timeout
|
// Make a cancellation object dealing with connection timeout
|
||||||
auto isCancellationRequested =
|
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel);
|
||||||
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
|
|
||||||
|
auto isCancellationRequested = [&]() {
|
||||||
|
return cancelled() || _stop;
|
||||||
|
};
|
||||||
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::CannotConnect,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
ss.str(),
|
ss.str(),
|
||||||
@ -260,7 +264,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a new cancellation object dealing with transfer timeout
|
// Make a new cancellation object dealing with transfer timeout
|
||||||
isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
|
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel);
|
||||||
|
|
||||||
if (args->verbose)
|
if (args->verbose)
|
||||||
{
|
{
|
||||||
@ -277,10 +281,11 @@ namespace ix
|
|||||||
|
|
||||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError;
|
||||||
std::string errorMsg("Cannot send request");
|
std::string errorMsg("Cannot send request");
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::SendError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -296,10 +301,11 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineValid)
|
if (!lineValid)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
|
||||||
std::string errorMsg("Cannot retrieve status line");
|
std::string errorMsg("Cannot retrieve status line");
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::CannotReadStatusLine,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -333,10 +339,11 @@ namespace ix
|
|||||||
|
|
||||||
if (!headersValid)
|
if (!headersValid)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
|
||||||
std::string errorMsg("Cannot parse http headers");
|
std::string errorMsg("Cannot parse http headers");
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::HeaderParsingError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -405,10 +412,11 @@ namespace ix
|
|||||||
contentLength, args->onProgressCallback, isCancellationRequested);
|
contentLength, args->onProgressCallback, isCancellationRequested);
|
||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::ChunkReadError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -424,6 +432,7 @@ namespace ix
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
|
||||||
lineResult = _socket->readLine(isCancellationRequested);
|
lineResult = _socket->readLine(isCancellationRequested);
|
||||||
line = lineResult.second;
|
line = lineResult.second;
|
||||||
|
|
||||||
@ -431,7 +440,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::ChunkReadError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -458,10 +467,11 @@ namespace ix
|
|||||||
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
|
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
|
||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::ChunkReadError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -475,9 +485,10 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
|
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
HttpErrorCode::ChunkReadError,
|
errorCode,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
|
@ -221,4 +221,57 @@ TEST_CASE("http_client", "[http]")
|
|||||||
REQUIRE(statusCode1 == 200);
|
REQUIRE(statusCode1 == 200);
|
||||||
REQUIRE(statusCode2 == 200);
|
REQUIRE(statusCode2 == 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Async API, cancel")
|
||||||
|
{
|
||||||
|
bool async = true;
|
||||||
|
HttpClient httpClient(async);
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
SocketTLSOptions tlsOptions;
|
||||||
|
tlsOptions.caFile = "cacert.pem";
|
||||||
|
httpClient.setTLSOptions(tlsOptions);
|
||||||
|
|
||||||
|
std::string url("http://httpbin.org/delay/10");
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg) { std::cout << msg; };
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool {
|
||||||
|
std::cerr << "\r"
|
||||||
|
<< "Downloaded " << current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<bool> requestCompleted(false);
|
||||||
|
std::atomic<HttpErrorCode> errorCode(HttpErrorCode::Invalid);
|
||||||
|
|
||||||
|
httpClient.performRequest(
|
||||||
|
args, [&requestCompleted, &errorCode](const HttpResponsePtr& response) {
|
||||||
|
errorCode = response->errorCode;
|
||||||
|
requestCompleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// cancel immediately
|
||||||
|
args->cancel = true;
|
||||||
|
|
||||||
|
int wait = 0;
|
||||||
|
while (wait < 5000)
|
||||||
|
{
|
||||||
|
if (requestCompleted) break;
|
||||||
|
|
||||||
|
std::chrono::duration<double, std::milli> duration(10);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
wait += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Done" << std::endl;
|
||||||
|
REQUIRE(errorCode == HttpErrorCode::Cancelled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user