-
-
Notifications
You must be signed in to change notification settings - Fork 10
routes
In order to stub HTTP endpoints you must conform to the route schema. Here's an example:
{ "/something" {:status 200 :body "hello"}
"/something-else" {:status 201 } }
The key in this map is the request specification and the value is the response specification. The combination of key and value (i.e. request- and response specification) is called a route. You supply this route map to the with-routes! macro or the start!
function. For example:
(with-routes!
{ "/something" {:status 200 :body "hello"}
"/something-else" {:status 201 } }
(client/get ...))
As an alternative to explicitly declaring the route map (that consists of one to many routes) you can provide it from a function that takes a server
instance as a parameter. This is useful if you need access to some data from the server
in order to build up your routes. For example:
(fn [server]
(let [absolute-uri (str (:uri server) "/somewhere")]
{ "/somewhere" {:status 200 :body absolute-uri}}))
In this example we get the URI of the server (which may be autogenerated if using the with-routes!) for later use in our response specification. For example:
(with-routes!
(fn [server]
(let [absolute-uri (str (:uri server) "/somewhere")]
{ "/somewhere" {:status 200 :body absolute-uri}}))
(client/get ...))
The server
schema is defined like this:
{:uri <string>
:port <int>
:nano-server <instance of fi.iki.elonen.NanoHTTPD>
:routes {:request-spec-fn <function that takes a request (map) that returns true/false whether this spec matches the request>
:request-spec <the request specification map>
:response-spec-fn <function that takes a request (map) and return the response-spec>
:response-spec <the response specification map>
:recordings <vector of recordings>}}
For more information on recordings refer to this wiki page.
The request specification (the key in the route map) can be created in four different ways:
-
Path only
This allows you to match a path in the request. For example:
{ "/something" { ... } }
This will match requests (regardless of http method) whose path is equal to "/something". For example it'll match the URI
http://localhost:8083/something
but nothttp://localhost:8083/something2
. -
Map
If path is not enough it's possible to define the request specification using a map. The schema is defined as this:
{:path <string> :method <any of :get :post :put :delete etc> :query-params { :query-param-name <value> }}
For example:
{{:path "/x" :method :get :query-params { :q "query" }} {...} }
This will match a
GET
request with path equal to/x
that has a query parameterq=query
. For examplehttp://localhost:8083/x?q=query
. More routing options might be available in the future. -
Function
This is the most advanced option and allows you to determine a match based on the actual request by providing a function that returns true or false on match:
(fn [request] <truthy or false>)
The request schema looks like this:
{:method <keyword of http method, for example :get :post etc> :headers <map> :content-type <the content-type header> :path <the uri path> :request-line <the request line (currently HTTP/1.1 is always added) :body <the request body> :query-params <map>
For example:
{ (fn [request] (clojure.string/starts-with? (:path request) "/my-resource")) { ... }}
-
Keyword
There's only one valid keyword and that's the
:default
keyword. A:default
route can be used to define a "catch all" response. For example you may wish to return status code 500 and body "Invalid Route" for every non matching request instead of having an exception being throw. This can be achieved like this:
{ "/something" {:status 200 :body "hello"}
:default {:status 500 :body "Invalid Route" } }
Now any request made to a resource not matching "/something" will return status code 500 with body "Invalid Route".
The response specification (the value in the route map) can be created in two different ways:
-
Map
The response can be generated from a response specification defined as a map. The schema is defined as this:
{:status <the status code as Int> :headers <map of name-value pairs> :content-type <shortcut for setting the "content-type" header> :body <The body as string> :delay <The delay before returning the response in milliseconds (long/int)>}
For example:
{{...} {:status 200 :content-type "text/plain" :body "Hello world"} }
The response generated from this specification should be self explanatory.
-
Function
This allows you generate a response based on the request as outlined in section 3 here by providing a function:
(fn [request] <response specification>)
For example:
{{...} (fn [request] {:status 200 :content-type "text/plain" :body "Hello " (->> request :headers :name)}) }
Given that the name header is defined as
NAME: John
this will generated a response with body "Hello John"