Chunked and Resumable File Upload in Classic ASP

Split the file in the browser — what it buys you, and what ASP Uploader does instead.

The problem chunking solves

A normal upload is one HTTP request carrying one file. That single fact is the root of nearly every Classic ASP upload limitation:

  • Request.BinaryRead buffers the entire body in the worker process, so a 2 GB file needs 2 GB of RAM — several times that once you parse it.
  • The request must fit under maxRequestEntityAllowed (2 GB hard ceiling) and maxAllowedContentLength.
  • The request must complete inside Server.ScriptTimeout and the connection timeout.
  • A dropped connection at 98% loses everything. There is nothing to resume from.

Chunking attacks all four at once by changing the shape of the transfer rather than the limits.

How chunked upload works

The browser slices the file and sends the pieces as separate requests. The server appends each one to a temporary file and immediately forgets it.

var CHUNK = 4 * 1024 * 1024;               // 4 MB
var offset = 0;

function sendNext(file, uploadId) {
    if (offset >= file.size) { finish(uploadId); return; }

    var slice = file.slice(offset, offset + CHUNK);   // Blob.slice - no copy of the whole file

    var xhr = new XMLHttpRequest();
    xhr.open("POST", "chunk-handler.asp?id=" + uploadId + "&offset=" + offset);
    xhr.onload = function () {
        offset += CHUNK;
        setProgress(Math.min(offset, file.size) / file.size);
        sendNext(file, uploadId);                     // one chunk in flight at a time
    };
    xhr.send(slice);
}

And on the server, each request is small and short-lived:

<%
' chunk-handler.asp - append this piece to the part-file
Dim stm, data, partPath

partPath = Server.MapPath("temp/" & SafeId(Request.QueryString("id")) & ".part")
data     = Request.BinaryRead(Request.TotalBytes)     ' one chunk only - a few MB

Set stm = Server.CreateObject("ADODB.Stream")
stm.Type = 1
stm.Open
If FileExists(partPath) Then
    stm.LoadFromFile partPath
    stm.Position = stm.Size                           ' append
End If
stm.Write data
stm.SaveToFile partPath, 2                            ' adSaveCreateOverWrite
stm.Close
%>

What each limit becomes

ConstraintSingle requestChunked
Peak server memoryFile size × 2–3One chunk, regardless of file size
Request size limitMust exceed the fileMust exceed one chunk
Script timeoutMust cover the whole transferMust cover one chunk
Proxy or CDN body capBlocks large files outrightNever approached
Dropped connectionStart again from zeroResume from the last chunk
Progress reportingNeeds a separate mechanismFalls out of the chunk count

Making it resumable

Resume needs one extra thing: a stable identifier for the upload, and a way for the client to ask how much of it the server already has.

<%
' status.asp - how many bytes have we got?
Dim fso, partPath, size
Set fso  = Server.CreateObject("Scripting.FileSystemObject")
partPath = Server.MapPath("temp/" & SafeId(Request.QueryString("id")) & ".part")

size = 0
If fso.FileExists(partPath) Then size = fso.GetFile(partPath).Size

Response.ContentType = "application/json"
Response.Write "{""received"":" & size & "}"
%>

The browser asks first, then starts sending from that offset instead of from zero. The identifier has to survive a page reload, so derive it from something stable — file name, size and last-modified date — and keep it in localStorage.

SafeId() is not decoration. The id comes from the client and is used to build a file path, so it must be validated to a known-safe character set before it goes anywhere near Server.MapPath. Accepting it raw is a path traversal vulnerability. See the security checklist.

Expiring abandoned parts

Every resumable upload that is never finished leaves a .part file. Sweep them:

<%
Dim fso, folder, f
Set fso    = Server.CreateObject("Scripting.FileSystemObject")
Set folder = fso.GetFolder(Server.MapPath("temp"))

For Each f In folder.Files
    If LCase(fso.GetExtensionName(f.Name)) = "part" Then
        If DateDiff("h", f.DateLastModified, Now()) > 48 Then f.Delete True
    End If
Next
%>

Choosing a chunk size

Chunk sizeEffect
Under 1 MBLots of requests; per-request overhead starts to dominate on fast links
2–8 MBThe usual sweet spot — smooth progress, low memory, cheap retries
Over 32 MBCoarse progress, expensive retries, and you start meeting request limits again

Match it to the worst connection you must support, not the best. On a flaky mobile link a 32 MB chunk is 32 MB you may have to send twice.

What ASP Uploader does instead

ASP Uploader does not chunk uploads, and it is worth being precise about that, because the difference decides which limits you have to configure.

It sends the file as a single request, and the server reads that request body incrementally — a loop of small Request.BinaryRead calls appended straight to a temporary file, rather than one Request.BinaryRead(Request.TotalBytes). You get the memory benefit of chunking without the complexity:

Buffer the whole bodyASP Uploader (incremental read)True chunking
Peak server memoryFile size × 2–3One bufferOne chunk
IIS request-size limitsApplyApply — raise themEffectively bypassed
Resume after a dropNoNoYes
Code you writeA parserNoneClient and server both

So if you are accepting large files with ASP Uploader, raise maxRequestEntityAllowed and maxAllowedContentLength as described in uploading large files — the memory wall is handled for you, the request-size ceilings are not. If you need genuine resume-after-failure, that is the hand-rolled approach earlier on this page.

<%@ Language="VBScript" %>
<!-- #include file="aspuploader/include_aspuploader.asp" -->

<form id="form1" method="POST">
    <%
    Dim uploader
    Set uploader = new AspUploader
    uploader.Name = "myuploader"
    uploader.MultipleFilesUpload = true
    uploader.MaxSizeKB = 4194304                   ' 4 GB, rejected in the browser if exceeded
    uploader.InsertText = "Select a large file"
    %>
    <%= uploader.GetString() %>
</form>

<%
If Request.Form("myuploader") & "" <> "" Then
    Dim list, i, mvcfile
    list = Split(Request.Form("myuploader"), "/")
    For i = 0 To UBound(list)
        Set mvcfile = uploader.GetUploadedFile(list(i))
        mvcfile.MoveTo Server.MapPath("archive/" & mvcfile.FileName)
    Next
End If
%>

The IIS settings in uploading large files still matter if you also accept plain form posts, but no single chunked request comes close to them.

Frequently asked questions

What is chunked file upload?

Splitting a file in the browser and sending it as a series of small requests that the server appends to a temporary file, instead of one request carrying the whole file.

Can Classic ASP resume an interrupted upload?

Yes, with chunking. The server reports how many bytes it already holds for a given upload id and the browser continues from that offset.

Does chunking make uploads slower?

Marginally, from per-request overhead — and it is the difference between a 4 GB upload working and failing, so the trade is not close.

Skip the plumbing

ASP Uploader does everything on this page out of the box: multi-file selection, a real progress bar, client and server validation, incremental large-file handling, and no COM component to register. Drop the folder on your server and add one include line.

Download the free trial Try the live demo Pricing