Skip to main content

Request and Response

2 min read
On this page

The phel.http namespace gives you one struct for the incoming request and one for the response you send back. This page shows how to read a request, build a response, and emit it.

HTTP request#

PHP scatters the request across $_GET, $_POST, $_SERVER, $_COOKIES, $_FILES. Phel normalizes them into one struct. All in phel.http.

Request struct:

(defstruct request [
  method            ; HTTP Method ("GET", "POST", ...)
  uri               ; uri struct (defined below)
  headers           ; Map of all headers. Keys are keywords, Values are string
  parsed-body       ; Parsed body: $_POST form data, or a decoded JSON body, otherwise nil
  query-params      ; Map with all query parameters ($_GET)
  cookie-params     ; Map with all cookie parameters ($_COOKIE)
  server-params     ; Map with all server parameters ($_SERVER)
  uploaded-files    ; map of uploaded-file structs (defined below)
  version           ; The HTTP Version
  attributes        ; consumer specific data to enrich the request
])

(defstruct uri [
  scheme            ; Scheme of the URI ("http", "https")
  userinfo          ; User info string
  host              ; Hostname of the URI
  port              ; Port of the URI
  path              ; Path of the URI
  query             ; Query string of the URI
  fragment          ; Fragment string of the URI
])

(defstruct uploaded-file [
  tmp-file          ; The location of the temporary file
  size              ; The file size
  error-status      ; The upload error status
  client-filename   ; The client filename
  client-media-type ; The client media type
])

Import phel.http, call request-from-globals:

(ns my-namespace
  (:require phel.http :as http))

(http/request-from-globals) ; Evaluates to a request struct

HTTP response#

phel.http includes a response struct for sending responses:

(defstruct response [
  status    ; The HTTP status code
  headers   ; A map of headers
  body      ; The body of the response (string)
  version   ; The HTTP protocol version
  reason    ; The HTTP status code reason text
])

Two helpers create responses:

(ns my-namespace
  (:require phel.http))

;; Create response from map
(http/response-from-map {:status 200 :body "Test"})
;; Evaluates to (phel.http.response 200 {} Test 1.1 OK)

;; Create response from string
(http/response-from-string "Hello World")
;; Evaluates to (phel.http.response 200 {} Hello World 1.1 OK)

json-response and html-response set the Content-Type header for you:

(ns my-namespace
  (:require phel.http))

(http/json-response 200 {:message "pong"})
;; body "{\"message\":\"pong\"}", Content-Type application/json

(http/html-response 200 "<h1>Hi</h1>")
;; body "<h1>Hi</h1>", Content-Type text/html; charset=utf-8

Send with emit-response:

(ns my-namespace
  (:require phel.http))

(let [rsp (http/response-from-map
            {:status 404 :body "Page not found"})]
  (http/emit-response rsp))

End to end#

A minimal web entry point reads the request, branches on method and path, builds a response, and emits it. Here get-in reads the path from the nested uri struct.

(ns my-app
  (:require phel.http :as http)
  (:require phel.html :refer [html]))

(defn handle [request]
  (let [method (get request :method)
        path   (get-in request [:uri :path])]
    (cond
      (and (= method "GET") (= path "/"))
      (http/response-from-map {:status 200 :body (html [:h1 "Home"])})

      (http/response-from-map {:status 404 :body "Not found"}))))

;; Wire it up: read globals, handle, emit.
(-> (http/request-from-globals)
    (handle)
    (http/emit-response))

Branching by hand stays readable for a few routes. Once you have more than a handful, reach for the router, which matches method and path for you and keeps handlers separate. The :body here comes from HTML rendering.

Next steps#