Uploading and downloading files via http

In this article, I would show how you can upload and download files from InterSystems products via http.

The questions about working with files over http arise fairly often on community and I'm usually linking to my FileServer project which demonstrates file upload/download but I'd like to talk a bit more on how we can serve and receive files from InterSystems products.

Downloading a file

If you have a file in on a file system and you know the path you can serve it from REST or CSP context by calling this method:

ClassMethod serve(name As %String) As %Status
{
    #dim sc As %Status = $$$OK
    #dim %response As %CSP.Response
    kill %request.Data
    set %request.Data("STREAMOID",1)= ##class(%CSP.StreamServer).Encrypt(##class(%CSP.StreamServer).GetOidForFile(name))
    if ##class(%CSP.StreamServer).OnPreHTTP() {
        set %response.Headers("Content-Disposition")="attachment; filename*=UTF-8''" _ ##class(%CSP.Page).EscapeURL(##class(%File).GetFilename(name), "UTF8")
        set sc = ##class(%CSP.StreamServer).OnPage()
    }

    quit sc
}

If you have a stream instead of a file name  you can replace

##class(%CSP.StreamServer).GetOidForFile(name)

with

stream.%Oid()

That's  it!

User in a browser would see download dialog.

Uploading a file

On a client side (I assume JS/HTML) create a file input:

<input id="myFile" type="file">

and write some JavaScript code that sends POST requests to a server (depends on a framework):

function FileLoad(){
        var formData = new FormData();
        formData.append("file", document.getElementById("myFile").files[0]);

        var xhr = new XMLHttpRequest();

        // Upload data to server
        xhr.open("POST", "/rest/path", true);

        xhr.send(formData);
        
        xhr.onload = function(e) {
            if (this.status == 200) {
                // everything is OK
            } else {
                alert(this.status + ' ' + this.statusText);
            }
        };
}

On a server side, you can get the stream as easy as

#dim %request As %CSP.Request
#dim stream As %CSP.BinaryStream
set stream = %request.GetMimeData("file")

After that, you can write this stream to a file, to a database or just process it without saving.

Links

Comments

Please make very sure when you implement it that way, to sanitize your inputs. Otherwise you very easily create a way for attackers to download all files your instance has access to.

Right you are!

I usually store files external from database and write a simple persistent class Document { GUID, DisplayName, FileStream }. User requests files by GUID, and it's served to him with Displayname in header.

Additionally files are never named on filesystem by Displayname,  or any kind of user input but rather by hash or a simple incremental counter.

Storing more than 1k (10k) files per directory is not recommended, if possible add more directories (by date, etc.)