HTTP requests have a structure like the following (taken from MDN.)
They have a "method" (POST, GET, PUT, ETC), some headers (key value pairs of info), a body (where the juice is), and a route.
In the image the route is /create-page
. It's very common to want to have your server do different
things when you get requests on different routes.
reitit
to your deps.ednreitit
is one of many so-called "routing libraries." These inspect the data you get from an HTTP
request and pick a function to use to handle that request.
While it is certainly possible to make something like this yourself, it's not worth your time.
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
ring/ring {:mvn/version "1.13.0"}
metosin/reitit-ring {:mvn/version "0.7.2"}}
:aliases {:nREPL
{:extra-paths ["dev"]
:extra-deps
{nrepl/nrepl {:mvn/version "1.3.0"}}}
:dev {:extra-paths ["dev"]}}}
routes
function in the routes
namespace.This should take in the system
(to be able to forward it to different handlers)
and return a data structure which specifies the routes in your application.
This should be a "vector of vectors." Easier to show than to explain.
(ns example.routes)
(defn hello-handler
[request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world"})
(defn goodbye-handler
[request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Goodbye, world"})
(defn routes
[system]
[["/" {:get {:handler hello-handler}}]
["/goodbye" {:get {:handler goodbye-handler}}]])
system
to your handlers.This is crucial. If you don't do this then you'll have to turn to the dark arts in order to make database queries.
There are a few ways to do this forwarding, but the simplest is to have your handlers take an extra
argument and use partial
to "partially apply" that argument.
Doing this you turn your handler from
a f(system, request) -> response
to a f(request) -> response
.
(ns example.routes)
(defn hello-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world"})
(defn goodbye-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Goodbye, world"})
(defn routes
[system]
[["/" {:get {:handler (partial #'hello-handler system)}}]
["/goodbye" {:get {:handler (partial #'goodbye-handler system)}}]])
This is the actual handler function that will be called for requests. In here we will make use of routes
and "compile" a ring handler.
If you are astute you will notice that we compile the routes on every request. This will be slow, but convenient for development. Making it fast will come later.
(ns example.routes
(:require [reitit.ring :as reitit-ring]))
(defn hello-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world"})
(defn goodbye-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Goodbye, world"})
(defn routes
[system]
[["/" {:get {:handler (partial #'hello-handler system)}}]
["/goodbye" {:get {:handler (partial #'goodbye-handler system)}}]])
(defn root-handler
[system request]
(let [handler (reitit-ring/ring-handler
(reitit-ring/router
(routes system)))]
(handler request)))
system
This gets interesting because clearly the server itself needs access to the system
while itself being part of said system
To handle this we will have a notion of a "system so far." Basically just "everything minus the server." There are tricks to get the server passed down, but they aren't important enough now.
(ns example.system
(:require [ring.adapter.jetty :as jetty]
[example.routes :as routes])
(:import (org.eclipse.jetty.server Server)))
(defn start-server
[system]
(jetty/run-jetty
(partial #'routes/root-handler system)
{:port 9999
:join? false}))
(defn stop-server
[server]
(Server/.stop server))
(defn start-system
[]
(let [system-so-far {}]
{::server (start-server system-so-far)}))
(defn stop-system
[system]
(stop-server (::server system)))
http://localhost:9999
and http://localhost:9999/goodbye
You should see the hello and goodbye example responses, which means that routing is happening as expected
This is just a fallback for when someone hits a route that you didn't expect them to. Dedicated 404 responses are preferable to the server barfing in a strange way.
(ns example.routes
(:require [reitit.ring :as reitit-ring]))
(defn hello-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world"})
(defn goodbye-handler
[system request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Goodbye, world"})
(defn routes
[system]
[["/" {:get {:handler (partial #'hello-handler system)}}]
["/goodbye" {:get {:handler (partial #'goodbye-handler system)}}]])
(defn not-found-handler
[_request]
{:status 404
:headers {"Content-Type" "text/html"}
:body "Not Found"})
(defn root-handler
[system request]
(let [handler (reitit-ring/ring-handler
(reitit-ring/router
(routes system))
#'not-found-handler)]
(handler request)))
/abc
You should see the 404 response.