2020-01-13 16:50:44 +01:00
|
|
|
# vertx-clojure
|
|
|
|
|
|
|
|
A lightweight clojure adapter for vertx toolkit.
|
|
|
|
|
|
|
|
- **STATUS**: *alpha*, in design and prototyping phase.
|
|
|
|
- **AUDIENCE**: this is not a vertx documentation, this readme intends
|
|
|
|
to explain only the clojure api
|
|
|
|
|
|
|
|
Example code on `resources/user.clj` file.
|
|
|
|
|
|
|
|
|
|
|
|
## Install
|
|
|
|
|
|
|
|
Using `deps.edn`:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
vertx-clojure/vertx {:mvn/version "0.0.0-SNAPSHOT"}
|
|
|
|
```
|
|
|
|
|
|
|
|
Using Leiningen:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
[vertx-clojure/vertx "0.0.0-SNAPSHOT"]
|
|
|
|
```
|
|
|
|
|
|
|
|
## User Guide
|
|
|
|
|
|
|
|
|
|
|
|
### Verticles
|
|
|
|
|
|
|
|
Verticles is the basic "unit of execution" in the vertx toolkit. The
|
|
|
|
concept is very simular to actors with the exception that a verticle
|
|
|
|
does not have a inbox, and verticles communicates with other verticles
|
|
|
|
or with the rest of the world using **eventbus**.
|
|
|
|
|
|
|
|
For create a verticle, you will need to create first a system instance
|
|
|
|
(a name that we give to the `Vertx` instance):
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(require '[vertx.core :as vc])
|
|
|
|
|
|
|
|
(def system (vc/system))
|
|
|
|
```
|
|
|
|
|
|
|
|
Then, you can proceed to create a verticle. A verticle concist on
|
|
|
|
three functions: `on-start`, `on-stop` and `on-error` (where the
|
|
|
|
`on-start` is mandatory the rest optional).
|
|
|
|
|
|
|
|
Lets define a dummy verticle that just prints hello world on start
|
|
|
|
callback:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(defn on-start
|
|
|
|
[ctx]
|
|
|
|
(println "Hello world"))
|
|
|
|
|
|
|
|
(def dummy-verticle
|
|
|
|
(vc/verticle {:on-start on-start}))
|
|
|
|
```
|
|
|
|
|
|
|
|
The `dummy-verticle` is a verticle factory, nothing is running at this
|
|
|
|
momment. For run the verticle we need to deploy it using the
|
|
|
|
previously created `system` instance:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(vc/deploy! system dummy-verticle)
|
|
|
|
```
|
|
|
|
|
|
|
|
The `deploy!` return value is a `CompletionStage` so you can deref it
|
|
|
|
like a regular `Future` or use **funcool/promesa** for chain more
|
|
|
|
complex transformations. The return value implements `AutoCloseable`
|
|
|
|
that will allow to undeploy the verticle.
|
|
|
|
|
|
|
|
The `deploy!` function also accepts an additional parameter for
|
|
|
|
options, and at this momment it only accepts as single option:
|
|
|
|
|
|
|
|
- `:instances` - number of instances to launch of the same verticle.
|
2020-02-17 09:41:00 +01:00
|
|
|
- `:worker` - use worker thread pool or the default event-loop.
|
2020-01-13 16:50:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
### Event Bus
|
|
|
|
|
|
|
|
The **eventbus** is the central communication system for verticles. It
|
|
|
|
has different patterns of communication. On this documentation we will
|
|
|
|
cover the: `publish/subscribe` and `request/reply`.
|
|
|
|
|
|
|
|
Lets define a simple echo verticle:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(require '[vertx.eventbus :as ve])
|
|
|
|
|
|
|
|
(defn on-message
|
|
|
|
[msg]
|
|
|
|
(:body msg))
|
|
|
|
|
|
|
|
(defn on-start
|
|
|
|
[ctx]
|
|
|
|
(vc/consumer ctx "test.echo" on-message))
|
|
|
|
|
|
|
|
(def echo-verticle
|
|
|
|
(vc/verticle {:on-start on-start}))
|
|
|
|
```
|
|
|
|
|
|
|
|
And then, lets deploy 4 instances of it:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(vc/deploy! system echo-verticle {:instances 4})
|
|
|
|
```
|
|
|
|
|
|
|
|
Now, depending on how you send the messages to the "test.echo" topic,
|
|
|
|
the message will be send to a single instance of will be broadcasted
|
|
|
|
to all verticle instances subscribed to it.
|
|
|
|
|
|
|
|
To send a message and expect a response we need to use the
|
|
|
|
`ve/request!` function:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
@(ve/request! system {:foo "bar"})
|
|
|
|
;; => #vertx.eventbus.Msg{:body {:foo "bar"}}
|
|
|
|
```
|
|
|
|
|
|
|
|
The return value of `on-message` callback will be used as a reply and
|
|
|
|
it can be any plain value or a `CompletionStage`.
|
|
|
|
|
|
|
|
When you want to send a message but you don't need the return value,
|
|
|
|
there is the `ve/send!` function. And finally, if you want to send a
|
|
|
|
message to all instances subscribed to a topic, you will need to use
|
|
|
|
the `ve/publish!` function.
|
|
|
|
|
|
|
|
|
|
|
|
### Http Server (vertx.http)
|
|
|
|
|
|
|
|
**STATUS**: pre-alpha: experimental & incomplete
|
|
|
|
|
|
|
|
This part will explain the low-level api for the http server. It is
|
|
|
|
intended to be used as a building block for a more higher-level api or
|
|
|
|
when you know that you exactly need for a performance sensitive
|
|
|
|
applications.
|
|
|
|
|
|
|
|
The `vertx.http` exposes two main functions `handler` and
|
|
|
|
`server`. Lets start creating a simple "hello world" http server:
|
|
|
|
|
|
|
|
```
|
|
|
|
(require '[vertx.http :as vh])
|
|
|
|
|
|
|
|
(defn hello-world-handler
|
|
|
|
[req]
|
|
|
|
{:status 200
|
|
|
|
:body "Hello world\n"})
|
|
|
|
|
|
|
|
(defn on-start
|
|
|
|
[ctx]
|
|
|
|
(vh/server {:handler (vh/handler hello-world-handler)
|
|
|
|
:port 2021}))
|
|
|
|
|
|
|
|
(->> (vc/verticle {:on-start on-start})
|
|
|
|
(vc/deploy! system))
|
|
|
|
```
|
|
|
|
|
|
|
|
NOTE: you can start the server without creating a verticle but you
|
|
|
|
will loss the advantage of scaling (using verticle instances
|
|
|
|
parameter).
|
|
|
|
|
|
|
|
The `req` object is a plain map with the following keys:
|
|
|
|
|
|
|
|
- `:method` the HTTP method.
|
|
|
|
- `:path` the PATH of the requested URI.
|
|
|
|
- `:headers` a map with string lower-cased keys of headers.
|
|
|
|
- `:vertx.http/request` the underlying vertx request instance.
|
|
|
|
- `:vertx.http/response` the underlying vertx response instance.
|
|
|
|
|
|
|
|
And the response object to the ring response, it can contain
|
|
|
|
`:status`, `:body` and `:headers`.
|
|
|
|
|
|
|
|
|
|
|
|
**WARNING:** at this moment there are no way to obtain directly the
|
|
|
|
body of request using clojure api, this is in **design** phase and we
|
|
|
|
need to think how to expose it correctly without constraint too much
|
|
|
|
the user code (if you have suggestions, please open an issue).
|
|
|
|
|
|
|
|
**NOTE**: If you want completly bypass the clojure api, pass a vertx
|
|
|
|
`Handler` instance to server instead of using
|
|
|
|
`vertx.http/handler`. There is the `vertx.util/fn->handler` helper
|
|
|
|
that converts a plain clojure function into raw `Handler` instance.
|
|
|
|
|
|
|
|
|
|
|
|
### Web Server (vertx.web)
|
|
|
|
|
|
|
|
**STATUS**: alpha
|
|
|
|
|
|
|
|
This part will explain the higher-level http/web server api. It is a
|
|
|
|
general purpose with more clojure friendly api. It uses
|
|
|
|
`reitit-core`for the routing and `sieppari` for interceptors.
|
|
|
|
|
|
|
|
Lets start with a complete example:
|
|
|
|
|
|
|
|
```clojure
|
|
|
|
(require '[vertx.http :as vh])
|
|
|
|
(require '[vertx.web :as vw])
|
2020-02-17 09:41:00 +01:00
|
|
|
(require '[vertx.web.middleware :as vwm])
|
2020-01-13 16:50:44 +01:00
|
|
|
|
|
|
|
(defn hello-world-handler
|
|
|
|
[req]
|
|
|
|
{:status 200
|
|
|
|
:body "Hello world!\n"})
|
|
|
|
|
|
|
|
(defn on-start
|
|
|
|
[ctx]
|
2020-02-17 09:41:00 +01:00
|
|
|
(let [routes [["/" {:middleware [vwm/cookies]
|
|
|
|
:handler hello-world-handler
|
|
|
|
:method :get}]]
|
2020-01-13 16:50:44 +01:00
|
|
|
handler (vw/handler ctx
|
|
|
|
(vw/assets "/static/*" {:root "resources/public/static"})
|
|
|
|
(vw/router routes))]
|
|
|
|
(vh/server {:handler handler
|
|
|
|
:port 2022})))
|
|
|
|
|
|
|
|
(->> (vc/verticle {:on-start on-start})
|
|
|
|
(vc/deploy! system))
|
|
|
|
```
|
|
|
|
|
2020-02-17 09:41:00 +01:00
|
|
|
The routes are defined using `reitit-core`. The request object is very
|
|
|
|
similar to the one explained in `vertx.http`.
|
2020-01-13 16:50:44 +01:00
|
|
|
|
|
|
|
The main difference with `vertx.http` is that the handler is called
|
|
|
|
when the body is ready to be used and is available under `:body`
|
|
|
|
keyword on the request.
|
|
|
|
|
|
|
|
All additional features such that reading the query/form params,
|
2020-02-17 09:41:00 +01:00
|
|
|
parse/write cookies, cors and file uploads are provided with additional middleware
|
|
|
|
wrappers:
|
2020-01-13 16:50:44 +01:00
|
|
|
|
2020-02-17 09:41:00 +01:00
|
|
|
- `vertx.web.middleware/uploads` parses the vertx uploaded file data
|
2020-01-13 16:50:44 +01:00
|
|
|
structure and expose it as clojure maps under `:uploads` key.
|
2020-02-17 09:41:00 +01:00
|
|
|
- `vertx.web.middleware/params` parses the query string and form
|
2020-01-13 16:50:44 +01:00
|
|
|
params in the body if the content-type is appropriate and exposes
|
|
|
|
them under `:params`.
|
2020-02-17 09:41:00 +01:00
|
|
|
- `vertx.web.middleware/cors` properly sets the CORS headers.
|
|
|
|
- `vertx.web.middleware/cookies` handles the cookies reading from
|
2020-01-13 16:50:44 +01:00
|
|
|
the request and cookies writing from the response.
|
|
|
|
|
|
|
|
|
|
|
|
## License ##
|
|
|
|
|
|
|
|
```
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
```
|
|
|
|
|