├── .gitignore ├── README.md ├── build.boot └── src └── tailrecursion └── ring_proxy.clj /.gitignore: -------------------------------------------------------------------------------- 1 | ### BOOT ####################################################################### 2 | 3 | /.boot/ 4 | 5 | ### LEININGEN ################################################################## 6 | 7 | .lein-* 8 | 9 | ### MAVEN (include pom.xml in /base) ########################################### 10 | 11 | *.jar 12 | *.war 13 | pom.xml 14 | pom.xml.asc 15 | 16 | ### NREPL ###################################################################### 17 | 18 | .repl-* 19 | .nrepl-* 20 | 21 | ### JAVA ####################################################################### 22 | 23 | /hs_err_pid*.log 24 | 25 | ### OSX ######################################################################## 26 | 27 | .DS_Store 28 | 29 | ### EMACS ###################################################################### 30 | 31 | [#]*[#] 32 | 33 | ### VIM ######################################################################## 34 | 35 | *.swn 36 | *.swo 37 | *.swp 38 | 39 | ### PROJECT #################################################################### 40 | 41 | /target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ring-proxy 2 | 3 | HTTP proxy [ring middleware](https://github.com/ring-clojure/ring/blob/a02518275a06835e4fdd1a3af59d7c4c0408d25b/SPEC#L12) 4 | for Clojure web applications. 5 | 6 | ### Dependency 7 | 8 | ```clojure 9 | [tailrecursion/ring-proxy "3.0.0-SNAPSHOT"] 10 | ``` 11 | 12 | ### Example 13 | 14 | Assuming your application's route handler is defined as `routes`, you 15 | may add a proxied route with something like the following: 16 | 17 | ```clojure 18 | (ns your-ns 19 | (:require [tailrecursion.ring-proxy :refer [wrap-proxy]])) 20 | 21 | (def app 22 | (-> routes 23 | (wrap-proxy "/remote" "http://some.remote.server/remote"))) 24 | ``` 25 | 26 | ## License 27 | 28 | Copyright © 2013 Alan Dipert and Micha Niskin 29 | 30 | Distributed under the Eclipse Public License, the same as Clojure. 31 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :resource-paths #{"src"} 3 | :dependencies '[[org.clojure/clojure "1.6.0" :scope "provided"] 4 | [adzerk/bootlaces "0.1.10" :scope "test"] 5 | [ring/ring-core "1.4.0-RC1"] 6 | [ring/ring-jetty-adapter "1.4.0-RC1"] 7 | [clj-http "1.1.2"] ]) 8 | 9 | (require '[adzerk.bootlaces :refer :all]) 10 | 11 | (def +version+ "3.0.0-SNAPSHOT") 12 | 13 | (task-options! 14 | pom {:project 'tailrecursion/ring-proxy 15 | :version +version+ 16 | :description "HTTP proxy ring middleware for Clojure web applications." 17 | :scm {:url "https://github.com/tailrecursion/ring-proxy"} 18 | :license {"EPL" "http://www.eclipse.org/legal/epl-v10.html"} }) -------------------------------------------------------------------------------- /src/tailrecursion/ring_proxy.clj: -------------------------------------------------------------------------------- 1 | (ns tailrecursion.ring-proxy 2 | (:import 3 | [java.net URI] ) 4 | (:require 5 | [clj-http.client :refer [request]] 6 | [clojure.string :refer [join split]] 7 | [ring.adapter.jetty :refer [run-jetty]] 8 | [ring.middleware.cookies :refer [wrap-cookies]] )) 9 | 10 | (defn prepare-cookies 11 | "Removes the :domain and :secure keys and converts the :expires key (a Date) 12 | to a string in the ring response map resp. Returns resp with cookies properly 13 | munged." 14 | [resp] 15 | (let [prepare #(-> (update-in % [1 :expires] str) 16 | (update-in [1] dissoc :domain :secure))] 17 | (assoc resp :cookies (into {} (map prepare (:cookies resp)))))) 18 | 19 | (defn slurp-binary 20 | "Reads len bytes from InputStream is and returns a byte array." 21 | [^java.io.InputStream is len] 22 | (with-open [rdr is] 23 | (let [buf (byte-array len)] 24 | (.read rdr buf) 25 | buf))) 26 | 27 | (defn wrap-proxy 28 | "Proxies requests from proxied-path, a local URI, to the remote URI at 29 | remote-base-uri, also a string." 30 | [handler ^String proxied-path remote-base-uri & [http-opts]] 31 | (wrap-cookies 32 | (fn [req] 33 | (if (.startsWith ^String (:uri req) proxied-path) 34 | (let [rmt-full (URI. (str remote-base-uri "/")) 35 | rmt-path (URI. (.getScheme rmt-full) 36 | (.getAuthority rmt-full) 37 | (.getPath rmt-full) nil nil) 38 | lcl-path (URI. (subs (:uri req) (.length proxied-path))) 39 | remote-uri (.resolve rmt-path lcl-path) ] 40 | (-> (merge {:method (:request-method req) 41 | :url (str remote-uri "?" (:query-string req)) 42 | :headers (dissoc (:headers req) "host" "content-length") 43 | :body (if-let [len (get-in req [:headers "content-length"])] 44 | (slurp-binary (:body req) (Integer/parseInt len))) 45 | :follow-redirects true 46 | :throw-exceptions false 47 | :as :stream} http-opts) 48 | request 49 | prepare-cookies)) 50 | (handler req))))) 51 | 52 | (defn run-proxy 53 | [listen-path listen-port remote-uri http-opts] 54 | (-> (constantly {:status 404 :headers {} :body "404 - not found"}) 55 | (wrap-proxy listen-path remote-uri http-opts) 56 | (run-jetty {:port listen-port}) )) 57 | --------------------------------------------------------------------------------