dillonkearns / elm-pages / Server.Request

Server-rendered Route modules and server-rendered API Routes give you access to a Server.Request.Request argument.


type alias Request =
Internal.Request.Request

A value that lets you access data from the incoming HTTP request.

requestTime : Request -> Time.Posix

Get the Time.Posix when the incoming HTTP request was received.

Request Headers

header : String -> Request -> Maybe String

Get a header from the request. The header name is case-insensitive.

Header: Accept-Language: en-US,en;q=0.5

request |> Request.header "Accept-Language"
-- Just "Accept-Language: en-US,en;q=0.5"

headers : Request -> Dict String String

Request Method

method : Request -> Method

The HTTP request method of the incoming request.

Note that Route modules data is run for GET requests, and action is run for other request methods (including POST, PUT, DELETE). So you don't need to check the method in your Route Module's data function, though you can choose to do so in its action.


type Method
    = Connect
    | Delete
    | Get
    | Head
    | Options
    | Patch
    | Post
    | Put
    | Trace
    | NonStandard String

An Incoming HTTP Request Method.

methodToString : Method -> String

Gets the HTTP Method as an uppercase String.

Examples:

Get
    |> methodToString
    -- "GET"

Request Body

body : Request -> Maybe String

The Request body, if present (or Nothing if there is no request body).

jsonBody : Json.Decode.Decoder value -> Request -> Maybe (Result Json.Decode.Error value)

If the request has a body and its Content-Type matches JSON, then try running a JSON decoder on the body of the request. Otherwise, return Nothing.

Example:

Body: { "name": "John" }
Headers:
Content-Type: application/json
request |> jsonBody (Json.Decode.field "name" Json.Decode.string)
-- Just (Ok "John")

Body: { "name": "John" }
No Headers
jsonBody (Json.Decode.field "name" Json.Decode.string) request
-- Nothing

No Body
No Headers
jsonBody (Json.Decode.field "name" Json.Decode.string) request
-- Nothing

Forms

formData : Form.Handler.Handler error combined -> Request -> Maybe ( Form.ServerResponse error, Form.Validated error combined )

Takes a Form.Handler.Handler and parses the raw form data into a Form.Validated value.

This is the standard pattern for dealing with form data in elm-pages. You can share your code for your Form definitions between your client and server code, using this function to parse the raw form data into a Form.Validated value for the backend, and Pages.Form to render the Form on the client.

Since we are sharing the Form definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that the validation errors that the user sees on the client are protected on our backend, and vice versa.

import BackendTask exposing (BackendTask)
import FatalError exposing (FatalError)
import Form
import Server.Request as Request exposing (Request)
import Server.Response as Response exposing (Response)

type Action
    = Delete
    | CreateOrUpdate Post

formHandlers : Form.Handler.Handler String Action
formHandlers =
    deleteForm
        |> Form.Handler.init (\() -> Delete)
        |> Form.Handler.with CreateOrUpdate createOrUpdateForm

deleteForm : Form.HtmlForm String () input msg

createOrUpdateForm : Form.HtmlForm String Post Post msg

action :
    RouteParams
    -> Request
    -> BackendTask FatalError (Response ActionData ErrorPage)
action routeParams request =
    case request |> Server.Request.formData formHandlers of
        Nothing ->
            BackendTask.fail (FatalError.fromString "Missing form data")

        Just ( formResponse, parsedForm ) ->
            case parsedForm of
                Form.Valid Delete ->
                    deletePostBySlug routeParams.slug
                        |> BackendTask.map
                            (\() -> Route.redirectTo Route.Index)

                Form.Valid (CreateOrUpdate post) ->
                    let
                        createPost : Bool
                        createPost =
                            okForm.slug == "new"
                    in
                    createOrUpdatePost post
                        |> BackendTask.map
                            (\() ->
                                Route.redirectTo
                                    (Route.Admin__Slug_ { slug = okForm.slug })
                            )

                Form.Invalid _ invalidForm ->
                    BackendTask.succeed
                        (Server.Response.render
                            { errors = formResponse }
                        )

You can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests, since a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by using POST to ensure that you can't log users out by embedding an image with a logout URL in it.

If the request has HTTP method GET, the form data will come from the query parameters.

If the request has the HTTP method POST and the Content-Type is application/x-www-form-urlencoded, it will return the decoded form data from the body of the request.

Otherwise, this Parser will not match.

Note that in server-rendered Route modules, your data function will handle GET requests (and will not receive any POST requests), while your action will receive POST (and other non-GET) requests.

By default, [Form]'s are rendered with a POST method, and you can configure them to submit GET requests using withGetMethod. So you will want to handle any Form's rendered using withGetMethod in your Route's data function, or otherwise handle forms in action.

formDataWithServerValidation : Pages.Form.Handler error combined -> Request -> Maybe (BackendTask FatalError (Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))

rawFormData : Request -> Maybe (List ( String, String ))

Get the raw key-value pairs from a form submission.

If the request has the HTTP method GET, it will return the query parameters.

If the request has the HTTP method POST and the Content-Type is application/x-www-form-urlencoded, it will return the decoded form data from the body of the request.

Otherwise, this Parser will not match.

Note that in server-rendered Route modules, your data function will handle GET requests (and will not receive any POST requests), while your action will receive POST (and other non-GET) requests.

By default, [Form]'s are rendered with a POST method, and you can configure them to submit GET requests using withGetMethod. So you will want to handle any Form's rendered using withGetMethod in your Route's data function, or otherwise handle forms in action.

URL

rawUrl : Request -> String

The full URL of the incoming HTTP request, including the query params.

Note that the fragment is not included because this is client-only (not sent to the server).

rawUrl request

-- url: http://example.com?coupon=abc
-- parses into: "http://example.com?coupon=abc"

rawUrl request

-- url: https://example.com?coupon=abc&coupon=xyz
-- parses into: "https://example.com?coupon=abc&coupon=xyz"

queryParam : String -> Request -> Maybe String

Get Nothing if the query param with the given name is missing, or Just the value if it is present.

If there are multiple query params with the same name, the first one is returned.

queryParam "coupon"

-- url: http://example.com?coupon=abc
-- parses into: Just "abc"

queryParam "coupon"

-- url: http://example.com?coupon=abc&coupon=xyz
-- parses into: Just "abc"

queryParam "coupon"

-- url: http://example.com
-- parses into: Nothing

See also queryParams, or rawUrl if you need something more low-level.

queryParams : Request -> Dict String (List String)

Gives all query params from the URL.

queryParam "coupon"

-- url: http://example.com?coupon=abc
-- parses into: Dict.fromList [("coupon", ["abc"])]

queryParam "coupon"

-- url: http://example.com?coupon=abc&coupon=xyz
-- parses into: Dict.fromList [("coupon", ["abc", "xyz"])]

Content Type

matchesContentType : String -> Request -> Basics.Bool

True if the content-type header is present AND matches the given argument.

Examples:

Content-Type: application/json; charset=utf-8
request |> matchesContentType "application/json"
-- True

Content-Type: application/json
request |> matchesContentType "application/json"
-- True

Content-Type: application/json
request |> matchesContentType "application/xml"
-- False

Using Cookies

cookie : String -> Request -> Maybe String

Get a cookie from the request. For a more high-level API, see Server.Session.

cookies : Request -> Dict String String

Get all of the cookies from the incoming HTTP request. For a more high-level API, see Server.Session.