Uploading Files

There are a few special considerations when designing a form that allows file upload, so this section gives an example of creating a Web site that allows a user to upload image files.

The complete example is here:

Full listing

File upload form field

To create a file-upload form field you use the CL-HTTP function:

(accept-input 'file name :stream stream)

As usual, the (accept-input 'file ...) form must occur within the body of a with-fillout-form procedure, but to support the file data, this must specify the :multipart :form-data encoding type, as in:

(with-fillout-form (:post url :stream stream :encoding-type '(:multipart :form-data)) ...)

Specifying the upload directory

By default the uploaded files are put in the CL-HTTP upload directory, specified by the variable *file-upload-default-directory*, which is #p"http:uploads;". You can redefine this variable to use a different directory for all the uploads.

Alternatively, you can specify a directory for each upload using the optional :directory keyword, as in the following example. This is useful if, for example, you want to store each user's uploads in a separate directory.

To avoid exposing the directory structure to the client CL-HTTP allocates a random key to each directory, and maintains a hash table to associate each key with the appropriate directory. The key, together with the original value of the name tag, is stored in an encoded string in the name tag. Note that the hash table isn't saved over server reboots; so, for example, if a user tries to upload a file from an old copy of the page it will fail.

Example image site

The following example allows a user to post images to a site. It's similar to the bulletin form example in Creating a Form.

The main routine displays the images, initially empty, and then draws a form containing a file-upload field:


Clicking Upload then uploads the image to the server, and adds it to the page:


First we define a parameter to hold the list of image pathnames:

(defparameter *images* nil)

Here's the definition of the routine to display the images and upload form:

(defun display-image-site (url stream)
  "Routine to display an image upload site."
  (with-page (url stream "Images")
    (dolist (pathname *images*)
      (image pathname pathname :stream stream))
    (with-paragraph (:stream stream)
          (:post url :stream stream :encoding-type '(:multipart :form-data))
        (accept-input 'file "photo" :directory #P"/uploads/" :stream stream)
        (accept-input 'submit-button "SUBMIT" :display-string "Upload" :stream stream)))))

The response function processes the uploaded values to get the image pathname, and adds it to the list of images:

(defun update-image-site (url stream alist)
  "Response function to add an image to the page."
  (bind-query-values (photo) (url alist)
    (push (namestring photo) *images*)
  (display-image-site url stream)))

We need to export the upload directory so the images are available to the Web:

(export-url "http://localhost:8000/uploads/"
            :pathname #P"/uploads/")

Then we need to export the image form:

(export-url "http://localhost:8000/images.html"
            :form-function 'display-image-site
            :response-function 'update-image-site)

Additional points

The CL-HTTP variable *file-upload-maximum-size* determines the maximum file size that a user can upload - otherwise an error is generated. By default it is set to 10MBytes.

The alist passed to the response function contains the original filename, file type, and copy mode. For example:

((:PHOTO #P"/uploads/apple-with-leaf.jpg" NIL
  (:UPLOAD-FILENAME "apple-with-leaf.jpg"
 (:SUBMIT "Upload"))

The following version of the update-image-site routine parses this to extract the pathname. You could use the other parameters to find the file type and original filename:

(defun update-image-site (url stream alist)
  "Response function to add an image to the page."
      (keyword pathname formname 
               (&key upload-filename content-type copy-mode)) 
      (assoc :photo alist)
    (declare (ignore keyword formname upload-filename content-type copy-mode))
    (push (namestring pathname) *images*)
  (display-image-site url stream)))

blog comments powered by Disqus