toby-one
toby-one

Reputation: 301

In racket, how do you allow file upload to the web server?

I'm trying to enable file upload to a web server using Racket's file-upload formlet (http://docs.racket-lang.org/web-server/formlets.html). The trouble is that formlet-process just returns the name of the file instead of its contents.

Here's what I have so far:

#lang web-server/insta
(require web-server/formlets
         web-server/http
         xml)

; start: request -> doesn't return
(define (start request)
  (show-page request))

; show-page: request -> doesn't return
(define (show-page request)
  ; Response generator
  (define (response-generator embed/url)
    (response/xexpr
     `(html 
       (head (title "File upload example"))
       (body (h1 "File upload example"))
       (form 
        ([action ,(embed/url upload-handler)])
        ,@(formlet-display file-upload-formlet)
        (input ([type "submit"] [value "Upload"]))))))

  (define (upload-handler request)
    (define a-file (formlet-process file-upload-formlet request))
    (display a-file)
    (response/xexpr
     `(html
       (head (title "File Uploaded"))
       (body (h1 "File uploaded")
             (p "Some text here to say file has been uploaded")))))

  (send/suspend/dispatch response-generator))


; file-upload-formlet: formlet (binding?)
(define file-upload-formlet
  (formlet
   (div ,{(required (file-upload)) . => . a-file})
   a-file))

In this case, a-file gets set to a byte string with the name of the file, instead of the contents of the file. How do I get the contents of the file so that I can write it to a file on the server?

Thanks in advance for your help!

Upvotes: 3

Views: 775

Answers (1)

toby-one
toby-one

Reputation: 301

OK, here's something that works, though I'm not sure it's the best way of doing things. Basically I had to

  1. add method="POST" and enctype="multipart/form-data" to the form field html (yeah, schoolboy error to omit these, but I'm new to this stuff)
  2. use binding:file-filename and binding:file-contents to extract the file name and contents from the binding returned by the file-upload formlet.

References that helped in figuring this out were http://lists.racket-lang.org/users/archive/2009-August/034925.html and http://docs.racket-lang.org/web-server/http.html

So here's the working code. Obviously WORKINGDIR needs to be set to some working path.

#lang web-server/insta
(require web-server/formlets)

 ; start: request -> doesn't return
(define (start request)
  (show-page request))

; show-page: request -> doesn't return
(define (show-page request)
  ; Response generator
  (define (response-generator embed/url)
    (response/xexpr
     `(html 
       (head (title "File upload example"))
       (body (h1 "File upload example"))
       (form 
        ([action ,(embed/url upload-handler)]
         [method "POST"]
         [enctype "multipart/form-data"])
        ,@(formlet-display file-upload-formlet)
        (input ([type "submit"] [value "Upload"]))))))

  (define (upload-handler request)
    (define-values (fname fcontents)
      (formlet-process file-upload-formlet request))
    (define save-name (string-append "!uploaded-" fname))
    (current-directory WORKINGDIR)
    (display-to-file fcontents save-name #:exists 'replace)
    (response/xexpr
     `(html
       (head (title "File Uploaded"))
       (body (h2 "File uploaded")
             (p ,fname)
             (h2 "File size (bytes)")
             (p ,(number->string (file-size save-name)))))))

  (send/suspend/dispatch response-generator))


; file-upload-formlet: formlet (binding?)
(define file-upload-formlet
  (formlet
   (div ,{(file-upload) . => . binds})
   ; (formlet-process file-upload-formlet request)
   ; returns the file name and contents:
   (let
       ([fname (bytes->string/utf-8 (binding:file-filename binds))]
        [fcontents (binding:file-content binds)])
     (values fname fcontents))))

Upvotes: 4

Related Questions