sbreatnach / treo

Simple URL routing for Ring applications

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

treo

Treo (Irish for route) is a simple Ring routing library for Clojure. What makes it different from the many, many, many other routing libraries out there? A combination of the following:

  • Has a simple set of functionality for generating routes from namespaces
  • Regular expression route matching
  • Interleaved middleware that is run AFTER route matching but BEFORE handler triggered
  • Works within the Ring system, does not try to replace it.
  • No unnecessary DSL or macros
  • Good test coverage

Getting Started

Import into your project dependencies:

[treo "0.2.1"]

Create a namespace with specifically named functions:

    (ns com.example.test.api.v1.note
      (:require [ring.util.response :as ring])
      (:import [java.net HttpURLConnection]))

    (defn show
      "Returns the list of notes. Corresponds to a GET"
      [request]
      (ring/status (ring/response [{"key" 1 "value" "note1"}])
                   HttpURLConnection/HTTP_OK))

Now generate your Ring handler using the namespace symbol. Here's an example that can be run using Leiningen's main hook:

    (ns com.example.test.api.bootstrap
      (:require [treo.dispatcher :as treo]
                [ring.middleware.format :as ring-format]
                [ring.adapter.jetty :as jetty]))

    (defn -main
      (let [handler-generator (treo/namespace-route-generator "/api/v1")]
        (jetty/run-jetty (handler-generator ["note"]
                                            'com.example.test.api.v1.note
                                            ring-format/wrap-restful-format)
                         {:port 3000})))

Once run, this should now respond to http://localhost:3000/api/v1/note

Usage

The core function is treo.dispatcher/namespace-route-generator. It creates a function which takes two required arguments:

  • route-parts - a sequence of strings representing the REST resource and any arguments
  • handler-ns - a namespace symbol defining the implemented HTTP methods

It also takes a variable number of optional Ring middleware functions, which are applied, in the order given, before the underlying namespace is triggered but after the point where the route is matched. This is extremely useful for authentication checks etc.

The output of this function is a standard Ring handler and treated as such.

For convenience, the treo.dispatcher/routes function is available and supports combining Ring handlers together into one handler.

namespace-route-generator takes one optional argument prefix, which can be used to set the prefix for all endpoints generated by the response. This is useful if you want to set a fixed version for all your endpoints.

Route Parts

Each route is made up of a sequence of strings. These strings are part of the composed URL and each URL is a regular expression for more flexible matching. For example, with a prefix of "/api/v1", here are the resulting regexes for the given arguments:

  • [""] -> "^/api/v1/?$"
  • ["note"] -> "^/api/v1/note/?$"
  • ["note" "\s+"] -> "^/api/v1/note/\s+/?$"
  • ["note" "(\s+)" "(?\d+)"] -> "^/api/v1/note/(\s)/(?\d+)/?$"
  • "note" -> "^/api/v1/note/?$"

If a string is supplied instead of a sequence of strings, it treats it as a sequence of length 1.

Any regex groups included in the URL are injected into the Ring request map. For example, with the regex "^/api/v1/note/(\s)/(?\d+)/?$" and the request URL /api/v1/note/what/342, the Ring request will now include the following data:

    {:treo/route {:groups ["what" "342"]
                  :named-groups {:id "342"}}}

Namespaces

Your target namespaces can define one of a set of fixed functions, each corresponding to a specific HTTP method. Any functions not defined will result in a 405 Not Allowed response for that HTTP method. The precise set of function names is defined at treo.dispatcher/ns-method-fns. The defaults (below) can be overridden by rebinding this variable.

HTTP Method Function Name
GET show
POST create
PUT change
PATCH change
DELETE delete

If it's necessary to access the specific request method function from the namespace handler, method-handler-fn enables this. This is useful if you wish to access metadata on a function from a middleware, for example.

Request Method Handlers

For added flexibility, you can use the treo.dispatcher/create-request-method-handler function directly. This is what the namespace introspection wraps - a function that returns a Ring handler which invokes one of a set of functions based on the incoming request method.

A simple example run from the REPL:

user> (require '[ring.mock.request :as mock])
user> (require '[treo.dispatcher :as dispatcher])
user> (def handler (dispatcher/create-request-method-handler {:get (fn [{:keys [uri]}] uri)}))
user> (handler (mock/request :get "/api/v1/test"))
/api/v1/test
user> (handler (mock/request :post "/api/v1/test"))
{:status 405, :headers {}, :body nil}

License

Copyright © 2019 Shane Breatnach

Distributed under the Eclipse Public License version 2.0.

About

Simple URL routing for Ring applications

License:Eclipse Public License 2.0


Languages

Language:Clojure 100.0%