# How to make a file upload form and make the server handle it

**URL:** <https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707>\
**Category:** Questions & Answers\
**Created:** [February 18, 2022, 3:31pm UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707 "2022-02-18T15:31:41Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Hanson](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hanson/32/388_2.png) [@Hanson](https://racket.discourse.group/u/Hanson)\
**Post date:** [February 18, 2022, 3:31pm UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/1 "2022-02-18T15:31:41Z")

</div>

How to make a file upload form and make the server handle it.  
I know how to create a upload form [https://docs.racket-lang.org/web-server/formlets.html#%28def.\_%28%28lib.\_web-server%2Fformlets%2Finput..rkt%29.\_file-upload%29%29](https://docs.racket-lang.org/web-server/formlets.html#%28def._%28%28lib._web-server%2Fformlets%2Finput..rkt%29._file-upload%29%29)  
But how to handle it?  
And also need a hint about how to design a file download function

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [February 18, 2022, 6:15pm UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/2 "2022-02-18T18:15:57Z")

</div>

Here's a toy example. It does make me think that `send/formlet` and `embed/formlet` should get an optional `#:attributes` argument, and I want to check if the web server internally understands [`filename*`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives).

```scheme
#lang web-server/insta
;; SPDX-License-Identifier: CC0-1.0

(require web-server/formlets)

(define title "File Echo Demo")

(define (start req)
  (define file
    (send/formlet*
     (formlet (#%# (p ,{=> (file-upload #:attributes '([required ""]))
                           file})
                   (p (input ([type "submit"]
                              [value "Upload"]))))
              file)
     (λ (k-url formlet-xs)
       (make-page
        "Upload a File"
        `[(p "Give me a file, and I will give it back to you.")
          (form ([method "POST"]
                 [enctype "multipart/form-data"]
                 [action ,k-url])
                ,@formlet-xs)]))))
  (redirect/get)
  (match-define (binding:file id filename-bytes headers content-bytes) file)
  (define filename
    (bytes->string/utf-8 filename-bytes))
  (send/suspend
   (λ (k-url)
     (make-page
      "Here is your file."
      `[(p (a ([href ,k-url]
               [download ,filename])
              "Click here to download it."))
        (p "I hope it used UTF-8!")
        (h3 ,filename)
        ;; x-expressions save us from injection attacks :)
        (pre ,(bytes->string/utf-8 content-bytes))])))
  (response/output
   #:mime-type (cond
                 [(headers-assq* #"Content-Type" headers)
                  => header-value]
                 [else
                  #f])
   #:headers (list (header #"Content-Disposition"
                           (bytes-append #"attachment; filename=\""
                                         filename-bytes
                                         ;; we assume it's correctly encoded,
                                         ;; since we got it from a header,
                                         ;; but see aslo filename* and RFC 5987
                                         #"\"")))
   (λ (out)
     (write-bytes content-bytes out))))

(define (send/formlet* formlet wrap)
  ;; unfortunately, send/formlet and embed/formlet don't
  ;; have a way to set enctype="multipart/form-data"
  (formlet-process 
   formlet
   (send/suspend
    (lambda (k-url)
      (wrap k-url (formlet-display formlet))))))

(define (make-page subtitle body-xs)
  (response/xexpr
   #:preamble #"<!DOCTYPE html>"
   `(html ([lang "en"])
          (head (title ,subtitle " | " ,title)
                (meta ([charset "utf-8"]))
                (meta ([name "viewport"]
                       [content "width=device-width, initial-scale=1.0"])))
          (body
           (h1 ,title)
           (h2 ,subtitle)
           ,@body-xs))))

```

_Edit: Fixed code formatting._

---

<div class="post-metadata">

**Author:** ![Hanson](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hanson/32/388_2.png) [@Hanson](https://racket.discourse.group/u/Hanson)\
**Post date:** [February 19, 2022, 3:10am UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/3 "2022-02-19T03:10:13Z")

</div>

I just copy and run it, but when I test it, something is wrong.

```scheme
Exception
The application raised an exception with the message:

bytes->string/utf-8: byte string is not a well-formed UTF-8 encoding
  byte string: #"PK\3\4\24\0\6\0\b\0\0\0!\0\337\244\322lZ\1\0\0 \5\0\0\23\0\b\2[Content_Types].xml \242\4\2(\240\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0...
Stack trace:

<unknown procedure> at:
  line 29, column 3, in file 3-unsaved-editor
<unknown procedure> at:
  line 375, column 33, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-higher-order.rkt
<unknown procedure> at:
  line 89, column 5, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\servlet\web.rkt
send/suspend at:
  line 85, column 0, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\servlet\web.rkt
<unknown procedure> at:
  line 486, column 18, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-val-first.rkt
start at:
  line 8, column 0, in file 3-unsaved-editor
<unknown procedure> at:
  line 375, column 33, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-higher-order.rkt
<unknown procedure> at:
  line 375, column 33, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-higher-order.rkt
<unknown procedure> at:
  line 375, column 33, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-higher-order.rkt
<unknown procedure> at:
  line 375, column 33, in file C:\Program Files\Racket\collects\racket\contract\private\arrow-higher-order.rkt
<unknown procedure> at:
  line 63, column 2, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\dispatchers\dispatch-servlets.rkt
select-handler/no-breaks at:
  line 163, column 2, in file C:\Program Files\Racket\collects\racket\private\more-scheme.rkt
<unknown procedure> at:
  line 101, column 2, in file C:\Program Files\Racket\share\pkgs\web-server-lib\web-server\private\dispatch-server-with-connect-unit.rkt

```

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [February 19, 2022, 5:00am UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/4 "2022-02-19T05:00:44Z")

</div>

What was the filename of the file you uploaded? Were the file's name and contents  
in UTF-8? What happens if you try uploading a different file—say, the `.rkt` file  
for the program itself?

---

<div class="post-metadata">

**Author:** ![Hanson](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hanson/32/388_2.png) [@Hanson](https://racket.discourse.group/u/Hanson)\
**Post date:** [February 19, 2022, 6:01am UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/5 "2022-02-19T06:01:31Z")

</div>

When it's a .txt file it's okay but when it come to binary file or .docx there's something wrong.  
I guess is the bytes-\>string/utf-8 problem

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [February 19, 2022, 7:08am UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/6 "2022-02-19T07:08:32Z")

</div>

Yes, that was a shortcut for the demo. Here's a variant that works with arbitrary files:

```scheme
#lang web-server/insta
;; SPDX-License-Identifier: CC0-1.0

(require web-server/formlets
         net/base64)

(define title "File Echo Demo")

(define (start req)
  (define file
    (send/formlet*
     (formlet (#%# (p ,{=> (file-upload #:attributes '([required ""]))
                           file})
                   (p (input ([type "submit"]
                              [value "Upload"]))))
              file)
     (λ (k-url formlet-xs)
       (make-page
        "Upload a File"
        `[(p "Give me a file, and I will give it back to you.")
          (form ([method "POST"]
                 [enctype "multipart/form-data"]
                 [action ,k-url])
                ,@formlet-xs)]))))
  (redirect/get)
  (match-define (binding:file id filename-bytes headers content-bytes) file)
  (define filename
    (bytes->string/utf-8 filename-bytes))
  (send/suspend
   (λ (k-url)
     (make-page
      "Here is your file."
      `[(p (a ([href ,k-url]
               [download ,filename])
              "Click here to download it."))
        (h3 ,filename)
        ;; x-expressions save us from injection attacks :)
        ,(or (with-handlers ([exn:fail? (λ (e) #f)])
               `(pre ,(bytes->string/utf-8 content-bytes)))
             `(div (p (i "Base64-encoded"))
                   (pre ,(bytes->string/utf-8
                          (base64-encode content-bytes #"\n")))))])))
  (response/output
   #:mime-type (cond
                 [(headers-assq* #"Content-Type" headers)
                  => header-value]
                 [else
                  #f])
   #:headers (list (header #"Content-Disposition"
                           (bytes-append #"attachment; filename=\""
                                         filename-bytes
                                         ;; we assume it's correctly encoded,
                                         ;; since we got it from a header,
                                         ;; but see aslo filename* and RFC 5987
                                         #"\"")))
   (λ (out)
     (write-bytes content-bytes out))))

(define (send/formlet* formlet wrap)
  ;; unfortunately, send/formlet and embed/formlet don't
  ;; have a way to set enctype="multipart/form-data"
  (formlet-process 
   formlet
   (send/suspend
    (lambda (k-url)
      (wrap k-url (formlet-display formlet))))))

(define (make-page subtitle body-xs)
  (response/xexpr
   #:preamble #"<!DOCTYPE html>"
   `(html ([lang "en"])
          (head (title ,subtitle " | " ,title)
                (meta ([charset "utf-8"]))
                (meta ([name "viewport"]
                       [content "width=device-width, initial-scale=1.0"])))
          (body
           (h1 ,title)
           (h2 ,subtitle)
           ,@body-xs))))

```

---

<div class="post-metadata">

**Author:** ![Antigen-11](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/antigen-11/32/1813_2.png) [@Antigen-11](https://racket.discourse.group/u/Antigen-11)\
**Post date:** [February 18, 2024, 9:09am UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/7 "2024-02-18T09:09:28Z")

</div>

May I ask what this form is for specifically?

> [@LiberalArtist](#):
>
> `(redirect/get)`

The program seems to work properly after it is commented out.

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [February 25, 2024, 7:37pm UTC](https://racket.discourse.group/t/how-to-make-a-file-upload-form-and-make-the-server-handle-it/707/8 "2024-02-25T19:37:26Z")

</div>

> [@Antigen-11](#):
>
> May I ask what this form is for specifically?
> 
> `(redirect/get)`

This implements the [Post–Redirect–Get](https://en.wikipedia.org/wiki/Post/Redirect/Get) pattern to prevent duplicate form submission in the presence of the "Back" button etc. In this toy example, duplicate submission isn't really a problem, but it's generally the right practice.
