├── .gitignore ├── .travis.yml ├── README.md ├── project.clj ├── run.js ├── src └── cljs_http │ ├── client.cljs │ ├── core.cljs │ └── util.cljs ├── test-runner.js └── test └── cljs_http └── test ├── client.cljs └── util.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /pom.xml 2 | *jar 3 | /lib 4 | /classes 5 | /native 6 | /.lein-failures 7 | /checkouts 8 | /.lein-deps-sum 9 | /.clojurescript-output 10 | /main.js 11 | /.lein-cljsbuild-compiler-* 12 | /.lein-cljsbuild-repl 13 | /.lein-plugins/checksum 14 | /resources/public/javascripts/cljs-http-dev.js 15 | /resources/public/javascripts/cljs-http.js 16 | /target 17 | /out 18 | /.lein-repl-history 19 | /repl 20 | /pom.xml.asc 21 | /.repl 22 | /.nrepl-port 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cljs-http 2 | 3 | A ClojureScript HTTP library for node.js. 4 | 5 | This is a fork of [cljs-http](https://github.com/r0man/cljs-http), which 6 | unfortunately does not run at all under node.js due to the absence of 7 | XMLHttpRequest. The only thing that needs to be rewritten is the 8 | core/request function, which I've reimplemented using core.async and 9 | node's underlying request module. Work-in-progress, very rough at present. 10 | 11 | Please refer to the [original project](https://github.com/r0man/cljs-http) for 12 | license and usage information. 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cljs-http-node "0.1.19-SNAPSHOT" 2 | :description "FIXME: write this!" 3 | :url "http://example.com/FIXME" 4 | 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [org.clojure/clojurescript "0.0-2311"] 7 | [org.clojure/core.async "0.1.303.0-886421-alpha"] 8 | [noencore "0.1.16"] 9 | [com.cognitect/transit-cljs "0.8.161"]] 10 | 11 | :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"] 12 | [org.bodil/lein-noderepl "0.1.11"] 13 | [com.cemerick/clojurescript.test "0.3.1"]] 14 | 15 | :cljsbuild { 16 | :test-commands {"node" ["node" "test-runner.js" "test-js" "test-node.js"]} 17 | :builds [{:source-paths ["src"] 18 | :compiler { 19 | :output-to "cljs-http-node.js" 20 | :output-dir "js" 21 | :optimizations :none 22 | :target :nodejs 23 | :source-map "cljs-http-node.js.map"}} 24 | {:id "test-node" 25 | :source-paths ["src" "test"] 26 | :compiler { 27 | :output-to "test-node.js" 28 | :target :nodejs ;;; this target required for node, plus a *main* defined in the tests. 29 | :output-dir "test-js" 30 | :optimizations :none 31 | :pretty-print true}}]}) 32 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | try { 2 | require("source-map-support").install(); 3 | } catch(err) { 4 | } 5 | require("./js/goog/bootstrap/nodejs") 6 | require("./cljs-http") 7 | require("./js/cljs_http/core") 8 | cljs_http_node.core._main(); // TODO: fix this assumption 9 | -------------------------------------------------------------------------------- /src/cljs_http/client.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs-http.client 2 | (:refer-clojure :exclude [get]) 3 | (:require [cljs-http.core :as core] 4 | [cljs-http.util :as util] 5 | [cljs.core.async :as async :refer [> 49 | vs 50 | (map #(encode-val k %)) 51 | (join "&"))) 52 | 53 | (defn- encode-param [[k v]] 54 | (if (coll? v) 55 | (encode-vals k v) 56 | (encode-val k v))) 57 | 58 | (defn generate-query-string [params] 59 | (->> 60 | params 61 | (map encode-param) 62 | (join "&"))) 63 | 64 | (def regex-char-esc-smap 65 | (let [esc-chars "()*&^%$#!+"] 66 | (zipmap esc-chars 67 | (map #(str "\\" %) esc-chars)))) 68 | 69 | (defn escape-special 70 | "Escape special characters -- for content-type." 71 | [string] 72 | (->> string 73 | (replace regex-char-esc-smap) 74 | (reduce str))) 75 | 76 | (defn decode-body 77 | "Decocde the :body of `response` with `decode-fn` if the content type matches." 78 | [response decode-fn content-type request-method] 79 | (if (and (not= :head request-method) 80 | (re-find (re-pattern (str "(?i)" (escape-special content-type))) 81 | (str (clojure.core/get (:headers response) "content-type" "")))) 82 | (update-in response [:body] decode-fn) 83 | response)) 84 | 85 | (defn wrap-edn-params 86 | "Encode :edn-params in the `request` :body and set the appropriate 87 | Content Type header." 88 | [client] 89 | (fn [request] 90 | (if-let [params (:edn-params request)] 91 | (-> (dissoc request :edn-params) 92 | (assoc :body (pr-str params)) 93 | (assoc-in [:headers "content-type"] "application/edn") 94 | (client)) 95 | (client request)))) 96 | 97 | (defn wrap-edn-response 98 | "Decode application/edn responses." 99 | [client] 100 | (fn [request] 101 | (-> #(decode-body % read-string "application/edn" (:request-method request)) 102 | (async/map [(client request)])))) 103 | 104 | (defn wrap-accept 105 | [client & [accept]] 106 | (fn [request] 107 | (if-let [accept (or (:accept request) accept)] 108 | (client (assoc-in request [:headers "accept"] accept)) 109 | (client request)))) 110 | 111 | (defn wrap-content-type 112 | [client & [content-type]] 113 | (fn [request] 114 | (if-let [content-type (or (:content-type request) content-type)] 115 | (client (assoc-in request [:headers "content-type"] content-type)) 116 | (client request)))) 117 | 118 | (def ^{:private true} default-transit-opts 119 | {:encoding :json :encoding-opts {} 120 | :decoding :json :decoding-opts {}}) 121 | 122 | (defn wrap-transit-params 123 | "Encode :transit-params in the `request` :body and set the appropriate 124 | Content Type header. 125 | 126 | A :transit-opts map can be optionally provided with the following keys: 127 | 128 | :encoding #{:json, :json-verbose} 129 | :decoding #{:json, :json-verbose} 130 | :encoding/decoding-opts appropriate map of options to be passed to 131 | transit writer/reader, respectively." 132 | [client] 133 | (fn [request] 134 | (if-let [params (:transit-params request)] 135 | (let [{:keys [encoding encoding-opts]} (merge default-transit-opts 136 | (:transit-opts request))] 137 | (-> (dissoc request :transit-params) 138 | (assoc :body (util/transit-encode params encoding encoding-opts)) 139 | (assoc-in [:headers "content-type"] "application/transit+json") 140 | (client))) 141 | (client request)))) 142 | 143 | (defn wrap-transit-response 144 | "Decode application/transit+json responses." 145 | [client] 146 | (fn [request] 147 | (let [{:keys [decoding decoding-opts]} (merge default-transit-opts 148 | (:transit-opts request)) 149 | transit-decode #(util/transit-decode % decoding decoding-opts)] 150 | 151 | (-> #(decode-body % transit-decode "application/transit+json" (:request-method request)) 152 | (async/map [(client request)]))))) 153 | 154 | (defn wrap-json-params 155 | "Encode :json-params in the `request` :body and set the appropriate 156 | Content Type header." 157 | [client] 158 | (fn [request] 159 | (if-let [params (:json-params request)] 160 | (-> (dissoc request :json-params) 161 | (assoc :body (util/json-encode params)) 162 | (assoc-in [:headers "content-type"] "application/json") 163 | (client)) 164 | (client request)))) 165 | 166 | (defn wrap-json-response 167 | "Decode application/json responses." 168 | [client] 169 | (fn [request] 170 | (-> #(decode-body % util/json-decode "application/json" (:request-method request)) 171 | (async/map [(client request)])))) 172 | 173 | (defn wrap-query-params [client] 174 | (fn [{:keys [query-params] :as req}] 175 | (if query-params 176 | (client (-> req (dissoc :query-params) 177 | (assoc :query-string 178 | (generate-query-string query-params)))) 179 | (client req)))) 180 | 181 | (defn wrap-form-params [client] 182 | (fn [{:keys [form-params request-method] :as request}] 183 | (if (and form-params (#{:post :put :patch :delete} request-method)) 184 | (client (-> request 185 | (dissoc :form-params) 186 | (assoc :body (generate-query-string form-params)) 187 | (assoc-in [:headers "content-type"] "application/x-www-form-urlencoded"))) 188 | (client request)))) 189 | 190 | (defn wrap-android-cors-bugfix [client] 191 | (fn [request] 192 | (client 193 | (if (util/android?) 194 | (assoc-in request [:query-params :android] (Math/random)) 195 | request)))) 196 | 197 | (defn wrap-method [client] 198 | (fn [req] 199 | (if-let [m (:method req)] 200 | (client (-> req (dissoc :method) 201 | (assoc :request-method m))) 202 | (client req)))) 203 | 204 | (defn wrap-server-name [client server-name] 205 | #(client (assoc %1 :server-name server-name))) 206 | 207 | (defn wrap-url [client] 208 | (fn [{:keys [query-params] :as req}] 209 | (if-let [spec (parse-url (:url req))] 210 | (client (-> (merge req spec) 211 | (dissoc :url) 212 | (update-in [:query-params] #(merge %1 query-params)))) 213 | (client req)))) 214 | 215 | (defn wrap-basic-auth 216 | "Middleware converting the :basic-auth option or `credentials` into 217 | an Authorization header." 218 | [client & [credentials]] 219 | (fn [req] 220 | (let [credentials (or (:basic-auth req) credentials)] 221 | (if-not (empty? credentials) 222 | (client (-> (dissoc req :basic-auth) 223 | (assoc-in [:headers "authorization"] (util/basic-auth credentials)))) 224 | (client req))))) 225 | 226 | (defn wrap-oauth 227 | "Middleware converting the :oauth-token option into an Authorization header." 228 | [client] 229 | (fn [req] 230 | (if-let [oauth-token (:oauth-token req)] 231 | (client (-> req (dissoc :oauth-token) 232 | (assoc-in [:headers "authorization"] 233 | (str "Bearer " oauth-token)))) 234 | (client req)))) 235 | 236 | (defn wrap-channel-from-request-map 237 | "Pipe the response-channel into the request-map's 238 | custom channel (e.g. to enable transducers)" 239 | [client] 240 | (fn [request] 241 | (if-let [custom-channel (:channel request)] 242 | (async/pipe (client request) custom-channel) 243 | (client request)))) 244 | 245 | (defn wrap-request 246 | "Returns a batteries-included HTTP request function coresponding to the given 247 | core client. See client/client." 248 | [request] 249 | (-> request 250 | wrap-accept 251 | wrap-form-params 252 | wrap-content-type 253 | wrap-edn-params 254 | wrap-edn-response 255 | wrap-transit-params 256 | wrap-transit-response 257 | wrap-json-params 258 | wrap-json-response 259 | wrap-query-params 260 | wrap-basic-auth 261 | wrap-oauth 262 | wrap-android-cors-bugfix 263 | wrap-method 264 | wrap-url 265 | wrap-channel-from-request-map)) 266 | 267 | (def #^{:doc 268 | "Executes the HTTP request corresponding to the given map and returns the 269 | response map for corresponding to the resulting HTTP response. 270 | 271 | In addition to the standard Ring request keys, the following keys are also 272 | recognized: 273 | * :url 274 | * :method 275 | * :query-params"} 276 | request (wrap-request core/request)) 277 | 278 | (defn delete 279 | "Like #'request, but sets the :method and :url as appropriate." 280 | [url & [req]] 281 | (request (merge req {:method :delete :url url}))) 282 | 283 | (defn get 284 | "Like #'request, but sets the :method and :url as appropriate." 285 | [url & [req]] 286 | (request (merge req {:method :get :url url}))) 287 | 288 | (defn head 289 | "Like #'request, but sets the :method and :url as appropriate." 290 | [url & [req]] 291 | (request (merge req {:method :head :url url}))) 292 | 293 | (defn move 294 | "Like #'request, but sets the :method and :url as appropriate." 295 | [url & [req]] 296 | (request (merge req {:method :move :url url}))) 297 | 298 | (defn options 299 | "Like #'request, but sets the :method and :url as appropriate." 300 | [url & [req]] 301 | (request (merge req {:method :options :url url}))) 302 | 303 | (defn patch 304 | "Like #'request, but sets the :method and :url as appropriate." 305 | [url & [req]] 306 | (request (merge req {:method :patch :url url}))) 307 | 308 | (defn post 309 | "Like #'request, but sets the :method and :url as appropriate." 310 | [url & [req]] 311 | (request (merge req {:method :post :url url}))) 312 | 313 | (defn put 314 | "Like #'request, but sets the :method and :url as appropriate." 315 | [url & [req]] 316 | (request (merge req {:method :put :url url}))) 317 | 318 | (comment 319 | 320 | (ns example.core 321 | (:require [cljs-http.client :as http] 322 | [cljs.core.async :refer [! > {:status (.getStatus target) 38 | :success (.isSuccess target) 39 | :body (.getResponseText target) 40 | :headers (util/parse-headers (.getAllResponseHeaders target)) 41 | :trace-redirects [request-url (.getLastUri target)]} 42 | (async/put! channel)) 43 | (swap! pending-requests dissoc channel) 44 | (async/close! channel))) 45 | (.send xhr request-url method body headers) 46 | channel))) 47 | 48 | (def http (node/require "http")) 49 | (def https (node/require "https")) 50 | 51 | (defn ->node-req 52 | [req] 53 | (clj->js {:method (or (:request-method req) :get) 54 | :port (or (:server-port req) 80) 55 | :hostname (:server-name req) 56 | :path (:uri req) 57 | :headers (:headers req)})) 58 | 59 | (defn clean-response 60 | [res] 61 | (assoc res :body (->> res :body clj->js (.concat js/Buffer) js->clj) 62 | :status (-> res :status first (or 200)) 63 | :headers (->> res :headers (apply merge)))) 64 | 65 | (defn request 66 | "Execute the HTTP request using the node.js primitives" 67 | [{:keys [request-method headers body with-credentials?] :as request}] 68 | (let [request-url (util/build-url request) 69 | method (or request-method :get) 70 | timeout (or (:timeout request) 0) 71 | content-length (or (when body (.-length body)) 0) 72 | _ (prn content-length) 73 | headers (util/build-headers (assoc headers "content-length" content-length)) 74 | js-request (->node-req (assoc request :headers headers)) 75 | scheme (if (= (:scheme request) :https) 76 | https 77 | http) 78 | ;; This needs a stream abstraction! 79 | chunks-ch (chan) 80 | response-ch (chan) 81 | client (.request scheme js-request 82 | (fn [js-res] 83 | (put! chunks-ch {:headers (-> js-res 84 | (aget "headers") 85 | (js->clj :keywordize-keys true))}) 86 | (put! chunks-ch {:status (.-statusCode js-res)}) 87 | (doto js-res 88 | (.on "data" (fn [stuff] (put! chunks-ch {:body stuff}))) 89 | (.on "end" (fn [] (async/close! chunks-ch))))))] 90 | (go (loop [response {:status [] :body [] :headers []}] 91 | (let [stuff (! response-ch (clean-response response)) (async/close! response-ch)) 94 | (recur (merge-with conj response stuff)))))) 95 | 96 | (when body 97 | (do (prn body) (.write client body))) 98 | 99 | (doto client 100 | (.on "error" (fn [error] 101 | (do 102 | (.log js/console (str "Error in request: " error)) 103 | (put! chunks-ch {:status -1 :error error}) 104 | (async/close! chunks-ch)))) 105 | (.end)) 106 | response-ch)) 107 | -------------------------------------------------------------------------------- /src/cljs_http/util.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs-http.util 2 | (:import goog.Uri) 3 | (:require [clojure.string :refer [blank? capitalize join split lower-case]] 4 | [cognitect.transit :as t] 5 | [goog.userAgent :as agent] 6 | [no.en.core :refer [base64-encode]])) 7 | 8 | (defn basic-auth 9 | "Returns the value of the HTTP basic authentication header for 10 | `credentials`." 11 | [credentials] 12 | (if credentials 13 | (let [[username password] 14 | (if (map? credentials) 15 | (map credentials [:username :password]) 16 | credentials)] 17 | (str "Basic " (base64-encode (str username ":" password)))))) 18 | 19 | (defn build-url 20 | "Build the url from the request map." 21 | [{:keys [scheme server-name server-port uri query-string]}] 22 | (str (doto (Uri.) 23 | (.setScheme (name (or scheme :http))) 24 | (.setDomain server-name) 25 | (.setPort server-port) 26 | (.setPath uri) 27 | (.setQuery query-string true)))) 28 | 29 | (defn camelize 30 | "Returns dash separeted string `s` in camel case." 31 | [s] 32 | (->> (split (str s) #"-") 33 | (map capitalize) 34 | (join "-"))) 35 | 36 | (defn build-headers 37 | "Build the headers from the map." 38 | [m] (clj->js (zipmap (map camelize (keys m)) (vals m)))) 39 | 40 | (defn user-agent 41 | "Returns the user agent." 42 | [] (agent/getUserAgentString)) 43 | 44 | (defn android? 45 | "Returns true if the user agent is an Android client." 46 | [] (re-matches #"(?i).*android.*" (user-agent))) 47 | 48 | (defn transit-decode 49 | "Transit decode an object from `s`." 50 | [s type opts] 51 | (let [rdr (t/reader type opts)] 52 | (t/read rdr s))) 53 | 54 | (defn transit-encode 55 | "Transit encode `x` into a String." 56 | [x type opts] 57 | (let [wrtr (t/writer type opts)] 58 | (t/write wrtr x))) 59 | 60 | (defn json-decode 61 | "JSON decode an object from `s`." 62 | [s] 63 | (if-let [v (js/JSON.parse s)] 64 | (js->clj v :keywordize-keys true))) 65 | 66 | (defn json-encode 67 | "JSON encode `x` into a String." 68 | [x] (js/JSON.stringify (clj->js x))) 69 | 70 | (defn parse-headers [headers] 71 | (reduce 72 | #(let [[k v] (split %2 #":\s+")] 73 | (if (or (blank? k) (blank? v)) 74 | %1 (assoc %1 (lower-case k) v))) 75 | {} (split (or headers "") #"(\n)|(\r)|(\r\n)|(\n\r)"))) 76 | -------------------------------------------------------------------------------- /test-runner.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------- 2 | // 3 | // I am a "runner" script for use with nodejs and cemerick/clojurescript.test 4 | // I handle the case where the cljsbuild setting is ':optimizations :none' 5 | // 6 | var path = require("path"), 7 | fs = require("fs"), 8 | args = process.argv.slice(4); 9 | var thisName = process.argv[1].split('/').slice(-1); 10 | 11 | var usage = [ 12 | "", 13 | "Usage: nodejs " + thisName + " output-dir output-to [tweaks]", 14 | "", 15 | "Where:", 16 | " - \"output-dir\" and \"output-to\" should match the paths you supplied ", 17 | " to cljsbuild in the project.clj. (right next to \":optimizations :none\").", 18 | " - [tweaks] is zero or more of either:", 19 | " (1) an extra javascript file - e.g. path/to/my-shim.js ", 20 | " (2) arbitrary javascript code fragments. E.g. window.something=flag", 21 | " These tweaks will be applied to the test page prior to load of test code." 22 | ].join("\n"); 23 | 24 | //-- Colors --------------------------------------------------------------------------------------- 25 | 26 | function yellow(text) { 27 | return "\u001b[31m" + text + "\u001b[0m"; 28 | } 29 | 30 | function red(text) { 31 | return "\u001b[33m" + text + "\u001b[0m"; 32 | } 33 | 34 | function green(text) { 35 | return "\u001b[32m" + text + "\u001b[0m"; 36 | } 37 | 38 | 39 | //-- Commandline --------------------------------------------------------------------------------- 40 | 41 | if (process.argv.length < 4) { 42 | console.log(usage); 43 | process.exit(1); 44 | } 45 | 46 | // google base dir 47 | var output_to = process.argv[2]; 48 | if (output_to.slice(-1) != path.sep) // we want a trailing '/' 49 | output_to = output_to + path.sep; 50 | if (!fs.existsSync(output_to)) { 51 | console.log(red('\nError: output_to directory doesn\'t exist: ' + output_to)) 52 | process.exit(1) 53 | } 54 | 55 | var googBasedir = path.join(process.cwd(), output_to, 'goog') 56 | if (!fs.existsSync(googBasedir)) { 57 | console.log(red('\nError: goog directory doesn\'t exist: ' + googBasedir)) 58 | process.exit(1) 59 | } 60 | 61 | // test file 62 | var testFile = process.argv[3]; // output-to parameter. Eg test.js 63 | if (!fs.existsSync(testFile)) { 64 | console.log(red('\nError: test file doesn\'t exist: ' + testFile)); 65 | process.exit(1) 66 | } 67 | var haveCljsTest = function () { 68 | return (typeof cemerick !== "undefined" && 69 | typeof cemerick.cljs !== "undefined" && 70 | typeof cemerick.cljs.test !== "undefined" && 71 | typeof cemerick.cljs.test.run_all_tests === "function"); 72 | }; 73 | 74 | var failIfCljsTestUndefined = function () { 75 | if (!haveCljsTest()) { 76 | var messageLines = [ 77 | "", 78 | "ERROR: cemerick.cljs.test was not required.", 79 | "", 80 | "You can resolve this issue by ensuring [cemerick.cljs.test] appears", 81 | "in the :require clause of your test suite namespaces.", 82 | "Also make sure that your build has actually included any test files.", 83 | "", 84 | "Also remember that Node.js can be only used with simple/advanced ", 85 | "optimizations, not with none/whitespace.", 86 | "" 87 | ]; 88 | console.error(messageLines.join("\n")); 89 | process.exit(1); 90 | } 91 | } 92 | 93 | //-- Load Google Clojure ---------------------------------------------------------------------------- 94 | // global.CLOSURE_BASE_PATH = googBasedir; 95 | // require('closure').Closure(global); 96 | require(path.join(googBasedir, 'bootstrap', 'nodejs.js')) 97 | //-- Handle Any Tweaks ----------------------------------------------------------------------------- 98 | 99 | args.forEach(function (arg) { 100 | var file = path.join(process.cwd(), arg); 101 | if (fs.existsSync(file)) { 102 | try { 103 | // using eval instead of require here so that `this` is the "real" 104 | // top-level scope, not the module 105 | eval("(function () {" + fs.readFileSync(file, {encoding: "UTF-8"}) + "})()"); 106 | } catch (e) { 107 | failIfCljsTestUndefined(); 108 | console.log("Error in file: \"" + file + "\""); 109 | console.log(e); 110 | } 111 | } else { 112 | try { 113 | eval("(function () {" + arg + "})()"); 114 | } catch (e) { 115 | console.log("Could not evaluate expression: \"" + arg + "\""); 116 | console.log(e); 117 | } 118 | } 119 | }); 120 | 121 | //-- Load code into our test page ---------------------------------------------------------------- 122 | goog.nodeGlobalRequire(testFile); 123 | 124 | // This loop is where a lot of important work happens 125 | // It will inject both the unittests and code-to-be-tested into the page 126 | for(var namespace in goog.dependencies_.nameToPath) 127 | goog.require(namespace); // will trigger CLOSURE_IMPORT_SCRIPT calls which injectJs into page 128 | 129 | failIfCljsTestUndefined(); // check this before trying to call set_print_fn_BANG_ 130 | 131 | //-- Run the tests ------------------------------------------------------------------------------- 132 | // 133 | // All the code is now loaded into the test page. Time to test. 134 | console.log("about to run tests") 135 | 136 | cemerick.cljs.test.set_print_fn_BANG_(function(x) { 137 | // since console.log *itself* adds a newline 138 | var x = x.replace(/\n$/, ""); 139 | if (x.length > 0) console.log(x); 140 | }); 141 | 142 | var success = (function() { 143 | var results = cemerick.cljs.test.run_all_tests(); 144 | cemerick.cljs.test.on_testing_complete(results, function () { 145 | process.exit(cemerick.cljs.test.successful_QMARK_(results) ? 0 : 1); 146 | }); 147 | })(); 148 | -------------------------------------------------------------------------------- /test/cljs_http/test/client.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs-http.test.client 2 | (:require-macros [cemerick.cljs.test :refer [is deftest testing done]] 3 | [cljs.core.async.macros :refer [go]]) 4 | (:require [cemerick.cljs.test :as t] 5 | [cljs.nodejs :as node] 6 | [cljs.core.async :as async :refer [ request :body))))) 77 | 78 | (deftest test-wrap-edn-params 79 | (let [request ((client/wrap-edn-params identity) {:edn-params {:a 1}})] 80 | (is (= "application/edn" (get-in request [:headers "content-type"]))) 81 | (is (= (pr-str {:a 1} ) (-> request :body))))) 82 | 83 | (deftest test-wrap-json-params 84 | (let [request ((client/wrap-json-params identity) {:json-params {:a 1}})] 85 | (is (= "application/json" (get-in request [:headers "content-type"]))) 86 | (is (= (util/json-encode {:a 1}) (-> request :body))))) 87 | 88 | (deftest test-wrap-url 89 | (let [request {:request-method :get :url "http://example.com/?b=2" :query-params {:a "1"}}] 90 | ((client/wrap-url 91 | (fn [request] 92 | (is (= :get (:request-method request))) 93 | (is (= :http (:scheme request))) 94 | (is (= "example.com" (:server-name request))) 95 | (is (= "/" (:uri request))) 96 | (is (= {:a "1" :b "2"} (:query-params request))))) 97 | request))) 98 | 99 | (deftest test-wrap-form-params 100 | (testing "With form params" 101 | (let [request {:request-method :post :form-params (sorted-map :param1 "value1" :param2 "value2")} 102 | response ((client/wrap-form-params identity) request)] 103 | (is (= "param1=value1¶m2=value2" (:body response))) 104 | (is (= "application/x-www-form-urlencoded" (get-in response [:headers "content-type"]))) 105 | (is (not (contains? response :form-params)))) 106 | (let [request {:request-method :put :form-params (sorted-map :param1 "value1" :param2 "value2")} 107 | response ((client/wrap-form-params identity) request)] 108 | (is (= "param1=value1¶m2=value2" (:body response))) 109 | (is (= "application/x-www-form-urlencoded" (get-in response [:headers "content-type"]))) 110 | (is (not (contains? response :form-params)))) 111 | (let [request {:request-method :put :form-params (sorted-map :param1 [1 2 3] :param2 "value2")} 112 | response ((client/wrap-form-params identity) request)] 113 | (is (= "param1=1¶m1=2¶m1=3¶m2=value2" (:body response))) 114 | (is (= "application/x-www-form-urlencoded" (get-in response [:headers "content-type"]))) 115 | (is (not (contains? response :form-params))))) 116 | (testing "Ensure it does not affect GET requests" 117 | (let [request {:request-method :get :body "untouched" :form-params {:param1 "value1" :param2 "value2"}} 118 | response ((client/wrap-form-params identity) request)] 119 | (is (= "untouched" (:body response))) 120 | (is (not (contains? (:headers response) "content-type"))))) 121 | (testing "with no form params" 122 | (let [request {:body "untouched"} 123 | response ((client/wrap-form-params identity) request)] 124 | (is (= "untouched" (:body response))) 125 | (is (not (contains? (:headers response) "content-type")))))) 126 | 127 | (deftest test-custom-channel 128 | (let [c (async/chan 1) 129 | request-no-chan {:request-method :get :url "http://localhost/"} 130 | request-with-chan {:request-method :get :url "http://localhost/" :channel c}] 131 | (testing "request api with middleware" 132 | (is (not= c (client/request request-no-chan))) 133 | (is (= c (client/request request-with-chan)))))) 134 | 135 | (def u (node/require "util")) 136 | 137 | (deftest ^:async test-get-simple 138 | (testing "argh" 139 | (let [request (client/get "http://kyledawkins.com/test/get.json")] 140 | (go (let [response ( response :body util/json-decode (js->clj :keywordize-keys true))] 142 | (is (= (:status response) 200)) 143 | (is (= parsed {:foo "bar" :quux [ "baz" "zonk" ]})) 144 | (done)))))) 145 | 146 | (deftest ^:async test-post-simple 147 | (testing "post argh" 148 | (let [request (client/post "http://posttestserver.com/post.php" {:form-params {:foo "bar" :baz "quux"}})] 149 | (go (let [response (