File uploads

F

Uploading a file in Kaya requires additional steps to normal processing of POST data. This tutorial shows the construction of a file upload handler in a webapp.

Handling file uploads

In HTTP, file uploads are handled by the web browser POSTing the data to the web server using the multipart/form-data content type. For non-file data, this content type is somewhat inefficient (though it is considerably better for uploading binary files), and so it is not the default. Additionally, file uploads can potentially be of very large files, so there is a need to store these files temporarily while script processing occurs.

Setting the correct content type is easy. If you are creating a file upload form that will use the current application as its handler, simply do:

form = addLocalForm(parent,true);

The second parameter to addLocalForm controls whether this is a file-uploading form, and is optional. If omitted, the default is false. For forms that post data to other applications, you simply set the correct FormType in the call to addRemoteForm.

form = addRemoteForm(parent,
                     "http://www.example.com/upload.cgi",
                     FormPostUpload);

You can then add file upload fields to the form as you would any text input field, by setting the TextInputType to InputFile.

uploader = addLabelledInput(f1,"Select file",
                            InputFile,"file",""));

Preparing for the temporary files takes more effort, and for this reason, file uploads are disabled by default in Kaya applications, to ensure that these files are only created when the application author wants. Enabling file uploads can only be done from within the CGI or webapp’s webconfig function. You should always set the temporary upload directory at the same time, unless you have a very good reason not to.

All temporary files created by Kaya handling uploads go into a specified directory, and are given random names in there. This directory is chosen by the following method:

  1. The directory set in webconfig using setKayaUploadDir. This is the preferred method of setting this value and should be used (with appropriate methods for local configuration) for most Kaya web applications.
  2. The contents of the HTTP_KAYA_UPLOAD_DIR environment variable. Your webserver documentation will explain how to set environment variables for CGI. This method is not generally recommended, but may be appropriate for certain types of local configuration – in which case, it is probably a good idea to check that it is set and throw an Exception if not in webconfig to avoid inadvertant fallbacks to the third case. The check must be done there, or the webapp will already have saved the temporary files by the time the check is done.
  3. If both of the above methods are absent, then Kaya will use the directory containing the CGI or webapp, as the only directory it can be sure exists. This is generally not a good idea, and you should use one of the methods above when writing programs.

In Kaya 0.2.1 to 0.2.3, file uploads were not disabled by default, and so an application that did not expect file uploads could still be tricked into temporarily creating files in its own directory. If you are using a web application compiled with these versions of Kaya, we recommend recompiling after upgrading to 0.2.4 or later (the current version is 0.2.5), or if this is not practical, making sure you set the environment variable.

Temporary uploaded files will be deleted once the webapp or CGI has finished, provided that it exits cleanly (either normally or by an uncaught Exception). If the webapp crashes, is killed, or exits using the _exit or exit functions, this will not be done. Generally this will not happen, but you may wish to periodically clean up files older than a few minutes from your temporary directory.

You must therefore either finish all processing of the file within the application execution, or copy it to permanent storage – either elsewhere on the filesystem, or in a database.

Choosing a temporary upload directory.

The ideal temporary upload directory meets the following criteria:

  • Outside the ‘web root’ – i.e. the contents of this directory are not visible on the web
  • Only readable and writeable by the user running the CGI or webapp
  • Not used for any other files
  • On a disk with sufficient space and quota to hold the files

The maximum size of uploaded files can be limited by specifying the maximum amount of POST data to receive. The default is 2 megabytes, which is fine for non-uploading applications, but may be too low for uploading some types of files. You can set a higher or lower limit with the setKayaMaxPost function, or if that is not used, the HTTP_KAYA_POST_MAX_SIZE environment variable.

Code samples

Explicitly setting the directory

webapp example;

import Webapp;
import HTMLDocument;

Void webconfig {
    allowFileUploads();
    setKayaUploadDir("/home/cgi/tmp");
}

Using the environment variable safely.

webapp example2;

import System;
import Webapp;
import HTMLDocument;

Void webconfig {
    upd = getEnv("HTTP_KAYA_UPLOAD_DIR");
    if (upd == "") {
        throw(Exception("You must set an upload directory!",1001));
    }
    allowFileUploads();
}

A simple example program

The following example program implements the basic Unix utility ‘cat’ over the web.

webapp cat;

import Binary;
import Webapp;
import HTMLDocument;
import IO;

data WebReturn = Form(HTMLDocument doc)
               | FileData(Binary file, [(String,String)] ctype);
// we might output one of two types of data

Int maxsize = 1048576; // 1Mb

Void webconfig {
   allowFileUploads();
   setKayaUploadDir("/users/kaya/tmp");
// change this to one suitable for you
   setKayaMaxPost(maxsize);
}

WebReturn webmain() {
    return runHandler(@makeForm);
}
  
WebReturn makeForm() {
    doc = HTMLDocument::new(HTML4Strict,"Webcat");
    void(addHeading(doc.body,1,"Webcat"));
    form = addLocalForm(doc.body,true);
// note second parameter to addLocalForm to enable file uploads on this form.
    f1 = addFieldset(form,"'cat' a file");
    void(addLabelledInput(f1,"Select file",InputFile,"file",""));
    void(addLocalControlInput(f1,"Upload file",[email protected](),true));
    return Form(doc);
}

WebReturn catFile(Bool dummy) {
// don't need a parameter here
    if (!incomingFileExists("file")) {
// no file uploaded, so redisplay the form
         return makeForm();
    }
    file = incomingFile("file");
    ctype = contentType(file);
    fname = originalName(file);
// get information about the file
    temp = tempPath(file);
// find the temporary filename
    fh = open(temp,[Read]);
    bin = readBlock(fh,maxsize); 
// can't be bigger than maxsize.
// if it mattered more, of course, we could check how big the
// file was before trying to read it.
    close(fh);
// and close the file handle. Now we construct our return value
    headers = [("Content-type",ctype),
               ("Content-disposition","attachment; filename="+fname)];
// these headers will serve the file back to the browser with the content
// type it gave, and if it supports Content-disposition, a suggestion that
// it use the name it gave us for it.
    return FileData(bin,headers);
}

Void displayPage(WebReturn output) {
// finally we need to write our own displayPage function for our
// custom data type
    case output of {
        Form(doc) -> displayPage(doc); 
        | FileData(bin,headers) -> displayPage(bin,headers);
    }
// in both cases we use the built-in Webapp handler for either HTML or
// Binary data
}

This application will display a basic upload form, and then return any file uploaded to the web browser (suggesting, regardless of content type, that it is treated as a file to download, rather than displayed in the browser).

Recent Comments

No comments to show.

Pages