Skip to content
Johan Haleby edited this page Mar 3, 2022 · 30 revisions

Definitions

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 ...))

Function

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.

Request Specification

The request specification (the key in the route map) can be created in four different ways:

  1. 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 not http://localhost:8083/something2.

  2. 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 parameter q=query. For example http://localhost:8083/x?q=query. More routing options might be available in the future.

  3. 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")) { ... }}
  4. 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".

Response Specification

The response specification (the value in the route map) can be created in two different ways:

  1. 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.

  2. 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"

Clone this wiki locally