3

I'm new to Racket though I've managed so far to play around with servlets and make few things work. What I would like to do now is to validate a simple username/password POST request. I've had success in doing it so through the "web-server/formlets" module but the formlet-process function is forcing me to name all inputs with the pattern: input_0, input_1, etc. which I find awkward and inconvenient for every single input element present in the form. I don't understand the reason(s) behind not allowing the user to override automatic naming of HTML inputs to be processed by a formlet:

(define (get-username/password request)
  (define login-formlet
    (formlet
      (#%# ,{input-string . => . username}
           ,{input-string . => . password})
      (values username password)))

  (formlet-process login-formlet request))

The above function expects the POST request to contain the username in the input_0 key and the password in the input_1 key, so my HTML template is forced to name the "username" input as input_0 and the "password" as input_1 (otherwise formlet-process will complain):

<html>
    <head>
        <title>Please login</title>
    </head>
    <body>
        <form action="" method="post">
            <label for="username">Username:</label>
            <input type="text" name="input_0" required>
            <label for="password">Password:</label>
            <input type="password" name="input_1" required>
            <button type="submit" value="Login">Login</button>
        </form>
        <div>
            <p></p>
        </div>
    </body>
</html>

If there's a way to override automatic naming I couldn't find it (hope I didn't miss anything from the docs!):

https://docs.racket-lang.org/web-server/formlets.html

So I've decided to take a step back and try to do the processing by myself and landed at:

https://docs.racket-lang.org/web-server/http.html?q=request-bindings%2Fraw#%28mod-path._web-server%2Fhttp%2Fbindings%29

but the documentation itself discourages its use (without pointing out an alternative raw safe way to do so!) as it might be hard to get sanitized values from it. So here is my question: Is there a built-in way to properly and safely extract/process sanitized values from a POST request without requiring more than built-in or simple functions in Racket? I'm really interested in not depending on any 3rd party package or module but what Racket already provides.

Thanks in advance!

N001
  • 51
  • 5

1 Answers1

1

To start with you final question, the right way to extract bindings manually (which web-server/formlets uses internally) is request-bindings/raw combined with utility functions that operate on the binding:form and binding:file structures, like bindings-assq-all. The documentation definitely should be changed to more clearly point to the non-deprecated API.

As far as formlets, the pattern for input names is part of the design of the formlet abstraction, but it isn't something you are expected to interact with directly. Maybe you already know this, but, since you mention formlet-process but not formlet-display, I wonder if you might have stumbled into web-server/formlets without seeing all of that background (which is easy to do!). There is an academic paper describing the design in detail, but I'll try to get into a little bit here (using an example from the paper).

Imagine you want to create a form component to ask the user for a date. You need to write code for two distinct tasks: generating the HTML and processing the submitted request. However, these tasks are closely interrelated. If you change the HTML you generate (which will likely include multiple input elements), you will often have to make a corresponding change to the processing code. We want a way to help keep the display and processing code in sync. Furthermore, once you've designed a great date component, you may want to use it more than once in the same form: perhaps you even want to build up a date range component. Because HTML input elements are identified by unique ids, you need some means of abstraction to make your code reusable.

Formlets offer a solution to these problems. A formlet encapsulates both rendering and processing code, keeping them in sync. To let your formlets truly encapsulate some unit of functionality, the web-server/formlets library generates all of the ids.

That's all a bit abstract, so here's an example of using formlets to get a username and password:

#lang web-server/insta

(require web-server/formlets)

(define (start request)
  (define-values [username password]
    (get-username+password-from-user))
  (redirect/get)
  (response/xexpr
   `(html (head (title "You Logged In")
                  (meta ([charset "utf-8"]))
                  (meta ([name "viewport"]
                         [content "width=device-width,initial-scale=1"])))
            (body (h1 "You Logged In")
                  (p (b "Your username: ") ,username)
                  ;; obviously don't do this for real:
                  (p (b "Your password: ") ,password)))))


(define login-formlet
  (formlet
   (#%# (p (label "Username: "
                  ,{=> (to-string
                        (required
                         (text-input
                          #:attributes '([required "required"]))))
                       username}))
        (p (label "Password: "
                  ,{=> (to-string
                        (required
                         (password-input
                          #:attributes '([required "required"]))))
                       password}))
        (p (input ([type "submit"]
                   [value "Log In"]))))
   (values username password)))

(define (get-username+password-from-user)
  (send/formlet
   login-formlet
   #:wrap
   (λ (rendered-form)
     `(html (head (title "Please Log In")
                  (meta ([charset "utf-8"]))
                  (meta ([name "viewport"]
                         [content "width=device-width,initial-scale=1"])))
            (body (h1 "Please Log In")
                  ,rendered-form)))))
LiberalArtist
  • 509
  • 4
  • 13
  • FWIW the Continue tutorial also has a section on formlets: https://docs.racket-lang.org/continue/index.html#%28part._.Using_.Formlets%29 – Stephen Feb 14 '20 at 16:57
  • @liberalartist Thanks for your answer! I understand that the formlet paper must be a novel approach when handling forms however I still feel that it is inflexible in making decisions that can't be overridden. I might want to just render a form or just validate it or do both and there shouldn't be any hard impositions/assumptions made. Consider for example what would happen when testing a webapp through Selenium. Everything is in place and tests pass. A simple change in the field order in the form now break tests. Automatic input naming has a negative impact outside the code itself. – N001 Feb 16 '20 at 02:03