SkillAgentSearch skills...

Donkey

Modern Clojure HTTP server and client built for ease of use and performance

Install / Use

/learn @AppsFlyer/Donkey
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Donkey

<p align="center"><img src="https://user-images.githubusercontent.com/29622398/160290393-63e0ccba-d7ab-4853-89d1-7b524ac79178.png" width=350/></p>

Donkey CI Coverage Status
Clojars Project

Modern Clojure, Ring compliant, HTTP server and client, designed for ease of use and performance

Note: this project is no longer maintained.

Table of Contents

TOC Created by gh-md-toc

Usage

Including the library in project.clj

[com.appsflyer/donkey "0.5.2"]

Including the library in deps.edn

com.appsflyer/donkey {:mvn/version "0.5.2"}

Including the library in pom.xml

<dependency>
    <groupId>com.appsflyer</groupId>
    <artifactId>donkey</artifactId>
    <version>0.5.2</version>
</dependency>

Requirements

Building

The preferred way to build the project for local development is using Maven. It's also possible to generate an uberjar using Leiningen, but you must use Maven to install the library locally.

Creating a jar with Maven

mvn package

Creating an uberjar with Leiningen

lein uberjar

Installing to a local repository

mvn clean install

Start up options

JVM system properties that can be supplied when running the application

  • -Dvertx.threadChecks=false: Disable blocked thread checks. Used by Vert.x to warn the user if an event loop or worker thread is being occupied above a certain threshold which will indicate the code should be examined.
  • -Dvertx.disableContextTimings=true: Disable timing context execution. These are used by the blocked thread checker. It does not disable execution metrics that are exposed via JMX.

Creating a Donkey

In Donkey, you create HTTP servers and clients using a - Donkey. Creating a Donkey is simple:

(ns com.appsflyer.sample-app
  (:require [com.appsflyer.donkey.core :refer [create-donkey]]))

  (def ^Donkey donkey-core (create-donkey))

We can also configure our donkey instance:

(ns com.appsflyer.sample-app
  (:require [com.appsflyer.donkey.core :refer [create-donkey]]))

  (def donkey-core (create-donkey {:event-loops 4}))

There should only be a single Donkey instance per application. That's because the client and server will share the same resources making them very efficient. Donkey is a factory for creating server(s) and client(s) (you can create multiple servers and clients with a Donkey, but in almost all cases you will only want a single server and / or client per application).

Server

The following examples assume these required namespaces

(:require [com.appsflyer.donkey.core :refer [create-donkey create-server]]
          [com.appsflyer.donkey.server :refer [start]]
          [com.appsflyer.donkey.result :refer [on-success]])

Creating a Server

Creating a server is done using a Donkey instance. Let's start by creating a server listening for requests on port 8080.

(->         
  (create-donkey)
  (create-server {:port 8080})
  start
  (on-success (fn [_] (println "Server started listening on port 8080"))))

Note that the following example will not work yet - for it to work we need to add a route which we will do next.

After creating the server we start it, which is an asynchronous call that may return before the server actually started listening for incoming connections. It's possible to block the current thread execution until the server is running by calling start-sync or by "derefing" the arrow macro.

The next thing we need to do is define a route. We talk about routes in depth later on, but a route is basically a definition of an endpoint. Let's define a route and create a basic "Hello world" endpoint.

(->
  (create-donkey)
  (create-server {:port   8080
                  :routes [{:handler (fn [_request respond _raise]
                                       (respond {:body "Hello, world!"}))}]})
  start
  (on-success (fn [_] (println "Server started listening on port 8080"))))

As you can see we added a :routes key to the options map used to initialize the server. A route is a map that describes what kind of requests are handled at a specific resource address (or :path), and how to handle them. The only required key is :handler, which will be called when a request matches a route. In the example above we're saying that we would like any request to be handled by our handler function.

Our handler is a Ring compliant asynchronous handler. If you are not familiar with the Ring async handler specification, here's an excerpt:

An asynchronous handler takes 3 arguments: a request map, a callback function for sending a response, and a callback function for raising an exception. The response callback takes a response map as its argument. The exception callback takes an exception as its argument.

In the handler we are calling the response callback respond with a response map where the body of the response is "Hello, world!".

If you run the example and open a browser on http://localhost:8080 you will see a page with "Hello, World!".

Routes

In Donkey HTTP requests are routed to handlers. When you initialize a server you define a set of routes that it should handle. When a request arrives the server checks if one of the routes can handle the request. If no matching route is found, then a 404 Not Found response is returned to the client.

Let's see a route example:

{
  :handler      (fn [request respond raise] ...)
  :handler-mode :non-blocking
  :path         "/api/v2"
  :match-type   :simple
  :methods      [:get :put :post :delete]
  :consumes     ["application/json"]
  :produces     ["application/json"]
  :middleware   [(fn [handler] (fn [request respond raise] (handler request respond raise)))]
}

:handler A function that accepts 1 or 3 arguments (depending on :handler-mode). The function will be called if a request matches the route. This is where you call your application code. The handler should return a response map with the following optional fields:

  • :status: The response status code (defaults to 200)
  • :headers: Map of key -> value String pairs
  • :body: The response body as byte[], String, or InputStream

:handler-mode To better understand the use of the :handler-mode, we need to first get some background about Donkey. Donkey is an abstraction built on top of a web tool-kit called Vert.x, which in turn is built on a very popular and performant networking library called Netty. Netty's architecture is based on the concept of a single threaded event loop that serves requests. An event loop is conceptually a long-running task with a queue of events it needs to dispatch. As long as events are dispatched "quickly" and don't occupy too much of the event loop's time, it can dispatch events at a very high rate. Because it is single threaded, or in other words serial, during the time it takes to dispatch one event no other event can be dispatched. Therefore, it's extremely important not to block the event loop.

The :handler-mode is a contract where you declare the type of handling your route does - :blocking or :non-blocking (default). :non-blocking means that the handler is performing very fast CPU-bound tasks, or non-blocking IO bound tasks. In both cases the guarantee is that it will not block the event loop. In this case the :handler must accept 3 arguments. Sometimes reality has it that we have to deal with legacy code that is doing some blocking operations that we just cannot change easily. For these occasions we have :blocking handler mode. In this case, the handler will be called on a separate worker thread pool without needing to worry about blocking the event loop. The worker thread pool size can be configured when creating a Donkey instance by setting the :worker-threads option.

:path is the first thing a route is matched on. It is the part after the hostname in a URI that identifies a resource on the host the client is trying to access.

View on GitHub
GitHub Stars298
CategoryDevelopment
Updated1d ago
Forks14

Languages

Java

Security Score

100/100

Audited on Apr 5, 2026

No findings