twosigma/jet

Name: jet

Owner: Two Sigma

Description: Jetty9 ring server adapter with WebSocket support via core.async and Jetty9 based HTTP & WebSocket clients (jvm based, no cljs)

Created: 2015-04-21 21:55:13.0

Updated: 2017-08-05 11:07:39.0

Pushed: 2018-05-07 15:52:02.0

Homepage: http://mpenet.github.io/jet/

Size: 347

Language: Clojure

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

jet

Build Status

Jet is a jetty9 server + client library for clojure. It's a drop in adapter replacement for ring apps.

What's in the box?

The server part started from the code of the various ring-jetty9-adapters out there.

The API is still subject to changes.

Documentation

codox generated documentation.

Installation

jet is available on Clojars.

Add this to your dependencies:

qbits/jet "0.6.4"]
Examples
Vanilla Ring handler

Nearly the same as any ring compliant adapter

 'qbits.jet.server)

-jetty {:ring-handler handler :port ...})
Ring Async

You can have fine control over Jetty9 Async mode using a core.async channel as response:

uire '[clojure.core.async :as async])

n async-handler [request]
et [ch (async/chan)]
(async/go
  (async/<! (async/timeout 1000))
  (async/>! ch
            {:body "foo"
             :headers {"Content-Type" "foo"}
             :status 202}))
ch))

ts.jet.server/run-jetty {:ring-handler async-handler})
Server Chunked Responses

If you return a core.async channel in a ring body jetty will go into async mode and the channel values will be streamed as chunks. If the channel is closed the connection ends. If an error occurs or the client disconnects the channel closes as well.

uire '[clojure.core.async :as async])

n handler
equest]
et [ch (async/chan 1)]
(async/go
 (dotimes [i 5]
   (async/<! (async/timeout 300))
   (async/>! ch (str i "\n")))
 (async/close! ch))
{:body ch
 :headers {"Content-Type" "prout"}
 :status 201}))

ts.jet.server/run-jetty {:ring-handler handler :port ...})
WebSocket

In the options the :websocket-handler is the root handler for all websocket connections.

The websocket handlers receive a ring request map + 3 core.async channels and the underlying WebSocketAdapter instance for potential advanced uses.

An example with a little PING/PONG between client and server:

 'qbits.jet.server)

imple ping/pong server, will wait for PING, reply PONG and close connection
-jetty
port 8013
websocket-handler
(fn [{:keys [in out ctrl ws]
      :as opts}]
    (async/go
      (when (= "PING" (async/<! in))
        (async/>! out "PONG")
        (async/close! out))))}})

The websocket client is used the same way

 'qbits.jet.client.websocket)

imple PING client to our server, sends PING, waits for PONG and
loses the connection
nect! "ws://localhost:8013/"
      (fn [{:keys [in out ctrl ws]}]
        (async/go
          (async/>! out "PING")
          (when (= "PONG" (async/<! in))
            (async/close! out))))))

If you close the :out channel, the socket will be closed, this is true for both client/server modes.

HTTP Client

The API is nearly identical to clj-http and other clients for clojure. One of the major difference is that calls to the client return a channel that will receive the eventual response asynchronously. The response is then a fairly standard ring response map, except the body, which is also a core.async channel (support for chunked responses).

Another major difference is that Jetty enforces client reuse (browser model). Calls to http client functions require a “client” argument, that can/would be shared by your app depending on context. This has a few advantages (shared cookie/auth conf, pooling etc). To quote the Jetty9 documentation:

HttpClient provides an efficient, asynchronous, non-blocking implementation to perform HTTP requests to a server through a simple API that offers also blocking semantic.

HttpClient provides easy-to-use methods such as GET(String) that allow to perform HTTP requests in a one-liner, but also gives the ability to fine tune the configuration of requests via newRequest(URI).

HttpClient acts as a central configuration point for network parameters (such as idle timeouts) and HTTP parameters (such as whether to follow redirects).

HttpClient transparently pools connections to servers, but allows direct control of connections for cases where this is needed.

HttpClient also acts as a central configuration point for cookies, via getCookieStore().

See the docs for details, HTTP client API docs qbits.jet.client.http/request & qbits.jet.client.http/client (the former builds on the later).

 'qbits.jet.client.http)
 'clojure.core.async)

 cl (client))

eturns a chan
p/get cl "http://graph.facebook.com/zuck")
> #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@731db933>

lock for the response
 (http/get cl "http://graph.facebook.com/zuck"))

> {:status 200,
   :headers
   {"content-type" "text/javascript; charset=UTF-8",
    "access-control-allow-origin" "*",
    "content-length" "173",
    "x-fb-debug"
    "jkc4w5S1VN3bLddmGEU+r3F/5ANxPZXrcqq3bUXJ3n2bwZq7WB0xy+mB/CziD56wHWd2us//p2dTmRQSIiW+Yg==",
    "facebook-api-version" "v1.0",
    "connection" "keep-alive",
    "pragma" "no-cache",
    "expires" "Sat, 01 Jan 2000 00:00:00 GMT",
    "x-fb-rev" "1358170",
    "etag" "\"3becf5f2bb7ec39daa6bb65345d40b9f4b1db483\"",
    "date" "Wed, 06 Aug 2014 15:51:02 GMT",
    "cache-control" "private, no-cache, no-store, must-revalidate"},
   :body
   #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@7ca698b0>}


et to the body
(http/get cl "http://graph.facebook.com/zuck")
<!!
:body
<!!)
> "{\"id\":\"4\",\"first_name\":\"Mark\",\"gender\":\"male\",\"last_name\":\"Zuckerberg\",\"link\":\"https:\\/\\/www.facebook.com\\/zuck\",\"locale\":\"en_US\",\"name\":\"Mark Zuckerberg\",\"username\":\"zuck\"}"

utodecode the body
(get cl "http://graph.facebook.com/zuck" {:as :json})
     async/<!!
     :body
     async/<!!)
> {:id "4",
   :first_name "Mark",
   :gender "male",
   :last_name "Zuckerberg",
   :link "https://www.facebook.com/zuck",
   :locale "en_US",
   :name "Mark Zuckerberg",
   :username "zuck"}

OST
t cl "http://foo.com" {:form-params {:foo "bar" :baz 1}})

And you can imagine (or read the api doc) how post, put, delete and other methods work. It's fairly standard. All the “method” functions are just api sugar around qbits.jet.client.http/request.

Please check the Changelog if you are upgrading.

License

Copyright © 2014 Max Penet

Distributed under the Eclipse Public License, the same as Clojure.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.