Home > Enterprise >  Should data exceeding 4MB sent to BinaryWrite() ever be chunked?
Should data exceeding 4MB sent to BinaryWrite() ever be chunked?

Time:11-15

I have some old code that writes binary data to the Response object's BinaryWrite() method (Classic ASP). It sends the data in 4MB chunks to BinaryWrite(), but now I'm wondering whether that ever worked and whether BinaryWrite() is even designed to handle serial chunks of data (or whether it should only be called, at most, once per page request).

I found this link that describes how the "Response Buffering Limit" should be increased, and increasing it seems to have solved the issues I was seeing (without using my chunking code at all). https://docs.microsoft.com/en-us/troubleshoot/iis/http-500-response-binarywrite

This is the old code in question:

HRESULT STDMETHODCALLTYPE CQVMActiveHost::WriteData (const VOID* pcvData, DWORD cbData, __out DWORD* pcbWritten)
{
    HRESULT hr;
    DISPPARAMS dispParams = {0};
    VARIANT vWrite = {0}, vResult = {0};

    Check(LoadResponseObject());

    dispParams.cArgs = 1;
    dispParams.rgvarg = &vWrite;

    if(m_fSupportsBinary)
    {
        SAFEARRAYBOUND Bound;
        DWORD cbRemaining = cbData;

        Bound.lLbound = 0;
        vWrite.vt = VT_ARRAY | VT_UI1;

        while(0 < cbRemaining)
        {
            PVOID pbPtr;

            Bound.cElements = min(cbRemaining, 4 * 1024 * 1024);
            vWrite.parray = SafeArrayCreate(VT_UI1, 1, &Bound);
            CheckAlloc(vWrite.parray);

            SafeArrayAccessData(vWrite.parray, &pbPtr);
            CopyMemory(pbPtr, pcvData, Bound.cElements);
            SafeArrayUnaccessData(vWrite.parray);

            VariantClear(&vResult);
            Check(m_pResponse->Invoke(m_dispidBinaryWrite, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispParams, &vResult, NULL, NULL));
            SafeArrayDestroy(vWrite.parray);
            vWrite.parray = NULL;

            pcvData = reinterpret_cast<const BYTE*>(pcvData)   Bound.cElements;
            cbRemaining -= Bound.cElements;
        }

        vWrite.vt = VT_EMPTY;
    }
    else

I've seen a couple different behaviors with the old code. In some tests, the first call to BinaryWrite() succeeded, but subsequent calls failed with the "exception occurred" HRESULT. In other tests, the calls seemed to succeed, but the browser didn't receive any data.

Are there any scenarios where it would make sense to make multiple calls to BinaryWrite() with chunked data?

Or should I always increase the "Response Buffering Limit" value to more than 4MB and just make a single call to BinaryWrite() with the full data?

Thanks!

CodePudding user response:

I have to wonder whether the Response.Buffer property was false when I originally wrote the code above. What I've found is the following:

  1. The Response.BinaryWrite() method MAY be called multiple times per page request.
  2. If a large amount of data is to be returned to the client, then split the data into multiple calls to Response.BinaryWrite().
  3. The "Response Buffering Limit" value in IIS (for ASP) is 4MB by default.
  4. If Response.Buffer is true, then multiple calls to Response.BinaryWrite() may be made until the total data reaches the "Response Buffering Limit" value. At that point, Response.Flush() MUST be called. Otherwise, attempting to send more data results in error 0x80020009.
  5. If Response.Buffer is false, then do NOT call Response.Flush(), but do split the data into multiple (smaller) calls to Response.BinaryWrite().
  6. As an example, I was trying to send a 12MB file using multiple calls to Response.BinaryWrite() with each chunk being 4MB. Buffering was enabled, so the first call succeeded, but the next call failed. Raising the "Response Buffering Limit" to 16MB "solved" the issue but increased the buffering allocation for ASP.

Ultimately, I've modified my chunking code to first query the Response.Buffer property. The data is always sent in smaller fragments to Response.BinaryWrite(), but Response.Flush() is also called if buffering is enabled.

Finally, don't set the Content-Length header. The browser may not know how many bytes will be downloaded, but it will receive the file correctly without manually setting that header. Setting that header breaks the download.

And the final ASP script:

    function GoDownloadFile (strPath)
    {
        var idxName = strrchr(strPath, '/');
        var strFolder = left(strPath, idxName   1);
        var strFile = right(strPath, len(strPath) - (idxName   1));

        var oFS = Security.GetChannel().OpenFileSystem();
        oFS.Folder = strFolder;

        Response.ContentType = Host.GetContentType(strFile);
        Response.AddHeader("Content-Disposition", "attachment; FileName=\""   strFile   "\"");

        Host.BinaryWrite(oFS.ReadFile(strFile));
    }
  • Related