Liking cljdoc? Tell your friends :D

postal - a postal client for clojurescript

Introduction

postal is a simple postal protocol client for ClojureScript.

Rationale

The tipical web application usually follows the api REST architecture, but the main problem of that is that is bound directly to the HTTP semantics that are not always coherent or not always clear how to use.

This is a client library that implements the postal protocol. And allows consume APIs using rich and user defined messages on top of http.

The general idea behind the protocol and this library is borrowed from Facebook Relay and Netflix’s Falcor. The main differents with them is that this library only represents the transport layer, so it’s is nonobstructive and not copupled with concrete framework.

This means that you can use it with any postal protocol compliant servers. One of the great examples where this library fits in a perfection is a transport layer for Om.Next.

Project Maturity

Since postal is a young project there can be some API breakage.

Install

The simplest way to use postal in a clojure project, is by including it in the dependency vector on your project.clj file:

[funcool/postal "0.7.0"]

Getting Started

The postal interface is really simple. The first step for use it, just define a client instance:

(require '[postal.client :as pc])

(def c (pc/client "http://yourser.ver/end/point"))

The client is stateless, so you can define it globally without any problem. It only stores the initial configuration.

By default it uses "PUT" http connection for send messages to backend. Is should be also a default value in the backend but you can change it using the second optional parameter to the client function:

(def c (pc/client uri {:method :get}))

The "GET" method allows send the messages using http GET request using query string parameter. This in some circumstances enable more facilities that it can be used in cross domain without specially setup CORS.

Additionally, you can setup initial http headers to use, using the :headers option. And them can be updated or reset after client definition using postal.client/update-headers! or postal.client/reset-headers!.

And finally, just use it using the public api. It there an example performing a :query request:

(require '[promesa.core :as p])

(p/then (pc/query c :users {:id 1})
        (fn [{:keys [data] :as msg}]
          (println "User: " data)))

You can notice that query function just return a promise that will be resolved with the response message or rejected with message if an :error message is returned. It there also can be rejected with an exception object if something foreign to transport is happens (message serialization error as example).

Additionally to query, it there is also novelty function, that sends :novelty type frame in the same way as with query.

User Guide

User defined frames

At this moment, postal library only implements query and novelty specialiezed functions for send corresponding kind of frames. Surelly, you may want to use own messages if the provided are not enough.

For that purposes, postal library exposes a send! function that just exposes a low level api for define your own helpers for send frames. Let imagine you want to send a :take type message; for that let define a helper that uses send! function for send that frame:

(defn take
  "Sends a query message to a server."
  ([client dest]
   (take client dest nil {}))
  ([client dest data]
   (take client dest data {}))
  ([client dest data opts]
   (send! client (merge {:type :query
                         :dest dest
                         :data data}
                        opts))))

Now, you can use the new defined take function in the same way as you will use the query and novelty functions.

Server Push

In some curcumstances the request/response patterns is not enough and you want get notifications from the backend in a server-push manener or full bi-directional communication with the backend.

For solve that, WebSocket approach is chosen because is more widelly supported by the browsers that Server-Sent-Events and supports bi-drectional communication with the server.

The difference with plain websockets, you will be able send more rich messages because behind the scenes messages are serialized using transit serialization format and also you should not care about socket reconnection because it is handled automatically.

It there two ways to connect to the server, in unidirectional or bidirectional way. The unidirectional approach serves just for cases where you only need server push, and the bi-directional in case you want also send messages to the server using the web socket connection. This library exposes a simple api for both approaches using beicon library (wrapper over rxjs).

Let see how it can be used:

(require '[beicon.core :as s])

(def c (pc/client "http://yourser.ver/api"))
(def bus (pc/subscribe c :timeupdate {:some "additional data"}))
(def stream (s/throttle bus 1000)))

(s/on-value stream (fn [message]
                     (println "Received: " message)))

You can close a subscription just ending the message bus:

(s/end! bus)

This will close the connection and the stream, removing all existing stream subscriptions.

Bi-directional sockets with server

You also can open a bi-directional socket to the backend using socket function:

(require '[beicon.core :as s])

(def c (pc/client "http://yourser.ver/api"))

(let [[instream outbus] (pc/socket c :timeupdate)]
  (def in instream)
  (def out outbus))

;; Send messages to the backend
;; Literally can be any kind of messages that
;; can be encoded using transit-clj.
(s/push! out {:type :message :data "Hello"})

(def stream (s/throttle in 1000)))
(s/on-value stream (fn [message]
                     (println "Received: " message)))

And you can close the socket just ending the output bus with s/end! function.

FAQ

Is it a replacement for WebSockets?

In general NO. This library/protocol does not intends to replace any existing bi-directional protocols/messaging-systems. In fact, it lives together with websockets.

With the upcoming http2 and already existing spdy, most of the performance problems of the http1.x are solved. So, the majority of the standard use of websockets can be easily soved using http (http2/spdy).

Should I use HTTP2/SPDY?

No, but is highly recommeded.

At this moment is not necesary that you server to have the http2/spdy support, is more that enough putting your application behind an http proxy like nginx that already supports SPDY and http2 support is upcoming.

http2/spdy offers connection multiplexing allowing use one unique persistent connection handle all required context, completly eliminating the overhead of creating and destroyng connectons. With that you can make multiple and repeated http connections without performance issues.

Is there any way to handle auth with sockets?

The downside of using WebSocket (like as with EventSource) is that its api does not allows append additional headers. The api is very limited for that. So you have two ways to do it:

  1. Using the implicit authentication using cookies; that only works if your endpoint is in the same domain (no cookies send in a cross domain request).

  2. Using a ticket based communication with single use tockens (just make a query request for obtain a token and later making the subscription request passing the token using and additional query parameter:

(def sub (pc/subscribe :timeupdate nil {:params {:token my-token}}))

Is there any server supports that protocol?

At this moment the unique backend/server implementation for this library is catacumba. And you can find the related documentation for the backend handlers setup.

Developers Guide

Philosophy

Five most important rules:

  • Beautiful is better than ugly.

  • Explicit is better than implicit.

  • Simple is better than complex.

  • Complex is better than complicated.

  • Readability counts.

All contributions to postal should keep these important rules in mind.

Contributing

Unlike Clojure and other Clojure contributed libraries postal does not have many restrictions for contributions. Just open an issue or pull request.

Source Code

postal is open source and can be found on github.

You can clone the public repository with this command:

git clone https://github.com/funcool/postal

Run tests

For running tests just execute this:

./scripts/build
node ./out/tests.js

License

postal is under public domain:

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close