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:
- The
Response.BinaryWrite()
method MAY be called multiple times per page request. - If a large amount of data is to be returned to the client, then split the data into multiple calls to
Response.BinaryWrite()
. - The "Response Buffering Limit" value in IIS (for ASP) is 4MB by default.
- If
Response.Buffer
istrue
, then multiple calls toResponse.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. - If
Response.Buffer
isfalse
, then do NOT callResponse.Flush()
, but do split the data into multiple (smaller) calls toResponse.BinaryWrite()
. - 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));
}