Skip to main content

Request and Response

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#