How to cancel file upload and download task

Oct 11, 2013 at 6:18 PM
Edited Oct 11, 2013 at 6:20 PM
I am working on a product that synchronizes files between a Windows system and a storage service. We're using Casablanca for the communication. We need the ability to stop a file upload or download on short notice. On a download (GET) we have code that looks something like this
  http_response response = client.request(request).get();
  if (response.status_code() == 200) {
    auto body = response.body();
    while (body.is_eof() == false) {
      body.read(filestream.streambuf(), BUFFERSIZE).get();
      if (m_interruptTransfer) {
        body.close();
        break;
      }
    }
  }
Closing the body seems like a bad idea, but this does seem to be working and we can't find a better solution. We're having even more difficulty finding a solution for uploads (PUT). I've tried using a producer_consumer_buffer for the upload, but I can't figure out how to enable flow control.

Is there a safe way to cancel a PUT request? Where can I find sample code?

Thank you for any help you can provide.
Coordinator
Oct 18, 2013 at 11:31 PM
Hi wbingham

Currently, we do not have support for cancelling HTTP requests. This feature is in our backlog and we plan to implement it in one of our upcoming releases.

Regarding the above sample code: Closing the body stream does not actually cancel the request. response.body() returns an input stream and calling close on it simply closes the read head of the stream. The write head is still open => Even after calling body.close(), our HTTP library will continue to download the data in the background, and writes it to the stream.

While uploading data, closing the output end of the stream will close the request. Hence, one option is to set a producer_consumer_stream as the request body and read data from a file stream to the producer_consumer_stream. When you want to stop the upload, close the producer_consumer_stream (by calling buf.close(std::ios::out)). Please note, there are two issues with this approach:
  1. It is not very elegant, we are introducing extra copies here.
  2. This is not the same as cancelling the request, it will upload all the data present in the producer_consumer_stream and then close the request gracefully.
With our current implementation, we do not have a workaround to cancel requests.
Thanks for taking the time to report this functionality gap. We will definitely try to fill this gap as soon as possible.

Thanks
Kavya
Nov 20, 2013 at 12:10 AM
I have been able to use the set_nativehandle_options() and, when called by this, calling WinHttpSetStatusCallback to intercept and process Winhttp callbacks. If I close the request handle in my callback intercept, I can cancel the HTTP/S request. Since I am closing the handle in the callback, there are no surprises for the WinHttp stack.

Give it a shot.
Apr 1, 2014 at 7:21 PM
Hi kavyako,

I would like to know if there is any update on this issue since the latest post (Nov 19, 2013).
In our current application we need to be able to cancel the data transfer request. If there is still not a way to do that in Casablanca, we are wondering to implement this feature on Casablanca Server by ourselves. Hence, I have two questions:
  1. Is it still unsupported?
  2. How complex do you think that it would be implementing (customizing) Casablanca to enable this?
I am looking forward to have your reply.

Best Regards,
Matheus
Coordinator
Apr 1, 2014 at 11:56 PM
Hi Matheus

This request was to implement cancellation for upload and download requests on the client side. In our 2.0.0 release, we have added support for request cancellation through cancellation_tokens for Windows platforms.
You can refer to the http_client cancellation tests (cancel_while_uploading_data, cancel_while_downloading_data) for the usage: https://casablanca.codeplex.com/SourceControl/latest#Release/tests/Functional/http/client/connections_and_errors.cpp

Are you looking for the cancellation support at the client side or the listener side, also on which platforms?

Thanks
Kavya
Apr 2, 2014 at 7:24 PM
Edited Apr 2, 2014 at 7:44 PM
Hi Kavya,

Thanks for your quick response.

Our target is Windows desktop.
The tests codes were helpful, but I still can't cancel/stop my download. I think the biggest issue is that I need to process the download in background (as a async task).

Please see the code example below. It illustrates:
1 - Request file
2 - Answer OK response
3 - When download complete, save it into hard disk (async)

When I request to cancel, it raises an "Unknown exception".

PS: I used a sleep in the progress (or in the body reading) to be able to cancel it while download is not complete.
int Download()
{
 
    std::wstring fileURL = L"https://download-codeplex.sec.s-msft.com/Images/v20885/loading_animation.gif";
 
    http_request req;
    http_client client(uri::encode_uri(fileURL));
    req.set_method(methods::GET);
    req.set_progress_handler([](message_direction::direction type, utility::size64_t bytesRead)
    {
        //sleep?
        //std::this_thread::sleep_for(std::chrono::milliseconds(500));
    });
 
    auto token = m_tokenSource.get_token();
    client.request(req, token).then([fileURL](http_response response)
    {
        auto cbuffer = std::make_shared<concurrency::streams::container_buffer<std::vector<unsigned char>>>();
        std::vector<unsigned char, std::allocator<unsigned char>> buffer;
 
        if (response.status_code() == web::http::status_codes::OK)
        {
 
            auto body = response.body();
            while (body.is_eof() == false)
            {
                body.read(*cbuffer, 20).get();
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
 
            buffer = cbuffer->collection();
 
            int indexOfSlash = (int) fileURL.find_last_of(L"/");
            if (indexOfSlash > 0)
            {
                std::wstring fileId = fileURL.substr(indexOfSlash);
 
                std::ofstream writeFile;
                writeFile.open(L"C:\\temp\\" + fileId, std::ios::out | std::ios::binary);
                if (!buffer.empty())
                    writeFile.write((char*) &buffer[0], buffer.size() * sizeof(uint8_t));
                writeFile.close();
            }
        }
        else
        {
            // TODO
        }
    }, token);
 
    return S_OK;
}

int CancelDownload()
{
 
    m_tokenSource.cancel();
    return S_OK;
 
}
Coordinator
Apr 4, 2014 at 1:03 AM
Hi Matheus

Did you try to catch the exception by adding a task based continuation at the end of the client.request continuation? When a request is cancelled, the request task will throw an "operation cancelled" exception. Please ensure to catch this in your code. Can you confirm the exception message, does it still say "Unknown Exception"?
client.request().then([](http_response resp)
    {
    // Handle response, read data etc.
    }).then([&](pplx::task<void> t)   // Task based continuation to catch exceptions
    {
        try
        {
            t.get();
        }
        catch(const http_exception& ex)
        {
            std::cout << ex.what();
        }
    });
Also, one small suggestion, if you are not already aware of this feature. You can use Casablanca file_stream and http_request::set_response_stream feature to directly download the contents of http_response to a file on the disk. This way, you can avoid an extra copy of copying data from the vector:
    http_request req;
    http_client client(uri::encode_uri(fileURL));
    req.set_method(methods::GET);
    auto ofs = Concurrency::streams::file_stream<uint8_t>::open_ostream(L"C:\\temp\\image.jpg", std::ios_base::out).get();
    req.set_response_stream(ofs); // This stream will hold the body of the HTTP response message, i.e the data we are downloading.

    client.request(req, token).then([&](http_response response)
    {
        return response.content_ready();
    }, token).then([&](http_response resp)
    {
        std::cout << "Data has been downloaded to the file.";
    }).then([&](pplx::task<void> t)
    {
        try
        {
            t.get();
        }
        catch(const http_exception& ex)
        {
            std::cout << ex.what();
        }
        ofs.close().wait(); // Do not forget to close the file.
    });
Apr 7, 2014 at 11:38 AM
Hello Kavya,

Thank you very much for your help! It worked and now I am able to cancel the file transfer :)

Best Regards,
Matheus