shoreleave/shoreleave-remote-ring

0.3.0


A smarter client-side with ClojureScript : Ring- (and Compojure-) server-side Remotes support

dependencies

org.clojure/clojure
1.4.0
org.clojure/tools.reader
0.7.0

dev dependencies

lein-marginalia
0.7.1



(this space intentionally left almost blank)
 

Server-side RPC support for use with shoreleave (and maybe fetch?). Mostly copied from https://github.com/shoreleave/shoreleave-remote-noir; changed to eliminate the noir-isms...

(ns 
     cemerick.shoreleave.rpc)

ATTENTION

This is here for backwards compatibility only, please use shoreleave.middleware.rpc

(def default-remote-uri "/_fetch")
(def remotes (atom {}))
(defn add-remote [key func]
  (swap! remotes assoc key func))
(defn safe-read [s]
  ;; can we please have a civilization!?
  (binding [*read-eval* false]
    (read-string s)))

Same as defn, but also registers the defined function as a remote. The name of the remote is the same as the function's name by default; You can optionally specify a different name by adding :remote-name metadata to the function name, e.g.:

(defremote ^{:remote-name :your-fn} my-fn [] ...)

(defmacro defremote
  [& [name :as body]]
  `(do
     (defn ~@body)
     (add-remote
       (keyword (or (-> (var ~name) meta :remote-name)
                    '~name))
       ~name)
     (var ~name)))
(defn call-remote
  [remote-key params]
  (if-let [func (@remotes remote-key)]
    (let [result (apply func params)]
      {:status 202
       :headers {"Content-Type" "application/edn; charset=utf-8"}
       :body (pr-str result)})
    {:status 404}))
(defn handle-rpc
  [{{:keys [params remote]} :params :as request}]
  (call-remote (keyword remote) (safe-read params)))
(defn wrap-rpc
  ([app] (wrap-rpc app default-remote-uri))
  ([app remote-uri]
    (fn [{:keys [request-method uri] :as request}]
      (if (and (= :post request-method) (= remote-uri uri))
        (handle-rpc request)
        (app request)))))
 

Server-side RPC support for use with Shoreleave, that works at the Ring level

(ns
  shoreleave.middleware.rpc
  (:require [shoreleave.server-helpers :refer [safe-read]]))

By default, remotes will hit /_shoreleave as their endpoint, but this can be overriden in the middleware hookup itself. For example: (shoreleave.middleware.rpc/wrap-rpc "/_a_different_endpoint")

(def default-remote-uri "/_shoreleave")

The remotes get collected in a hashmap: {remote-name-kw remote-fn, ...}

(def remotes (atom {}))
(defn add-remote [key func]
  (swap! remotes assoc key func))

Same as defn, but also registers the defined function as a remote. The name of the remote is the same as the function's name by default; You can optionally specify a different name by adding :remote-name metadata to the function name, e.g.:

(defremote ^{:remote-name :your-fn} my-fn [] ...)

(defmacro defremote
  [& [name :as body]]
  `(do
     (defn ~@body)
     (add-remote
       (keyword (or (-> (var ~name) meta :remote-name)
                    '~name))
       ~name)
     (var ~name)))

Exposes an entire namespace as a remote API and optionally aliases for use on the client side.

For example: (remote-ns 'baseline.controllers.api :as "api") will allow you to call your client side API calls to look like api/some-fn-there

(defn remote-ns
  [namesp-sym & opts]
  (let [{:keys [as]} (apply hash-map opts)
        namesp (try
                 (require namesp-sym)
                 (find-ns namesp-sym)
                 (catch Exception e
                   (throw (Exception. (str "Could not locate a namespace when aliasing remotes: " namesp-sym))
                          e)))
        public-fns (ns-publics namesp)]
    (doseq [[fn-name fn-var] public-fns]
      (when (fn? (var-get fn-var))
        (add-remote (keyword (str (or as namesp-sym) "/" fn-name)) fn-var)))))
(defn call-remote
  [remote-key params]
  (if-let [func (@remotes remote-key)]
    (let [result (apply func params)]
      {:status 202
       :headers {"Content-Type" "application/edn; charset=utf-8"}
       :body (pr-str result)})
    {:status 404
     :body "Remote not found."}))
(defn handle-rpc
  [{{:keys [params remote]} :params :as request}]
  (call-remote (keyword remote) (safe-read params)))

Top-level Ring middleware to enable Shoreleave RPC calls

(defn wrap-rpc
  ([app] (wrap-rpc app default-remote-uri))
  ([app remote-uri]
    (fn [{:keys [request-method uri] :as request}]
      (if (and (= :post request-method) (= remote-uri uri))
        (handle-rpc request)
        (app request)))))
 

Simple functions to smooth over server idioms

(ns shoreleave.server-helpers
  (:require [clojure.tools.reader.edn :as edn]))

This causes precious-file.txt to be created if it doesn't exist, or if it does exist, its contents will be erased (given appropriate JVM sandboxing permissions, and underlying OS file permissions). (old-safe-read "#java.io.FileWriter[\"precious-file.txt\"]")

This is a data-only // edn-only read. It takes a string of data (s) and returns Clojure/EDN data

(defn safe-read
  [s]
  (edn/read-string s))