From cb3a343defc147ce9f67db6ea9c6fb36f90f1fbf Mon Sep 17 00:00:00 2001 From: Roman Scherer Date: Tue, 22 Apr 2014 22:12:03 +0200 Subject: [PATCH] Conform to Ring SPEC and add more well known ports. --- README.md | 16 ++++++------- src/cemerick/url.cljx | 48 +++++++++++++++++++++---------------- test/cemerick/test_url.cljx | 24 +++++++++---------- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 1095d5a..8634e52 100644 --- a/README.md +++ b/README.md @@ -51,23 +51,23 @@ in the base URL: ```clojure => (url "https://api.twitter.com/") -#cemerick.url.URL{:protocol "https", :username nil, :password nil, - :host "api.twitter.com", :port -1, :path "/", :query nil, - :anchor nil} +#cemerick.url.URL{:scheme :https, :username nil, :password nil, + :server-name "api.twitter.com", :server-port -1, :uri "/", :query-params nil, + :fragment nil} => (url "https://api.twitter.com/" "1" "users" "profile_image" "cemerick") -#cemerick.url.URL{:protocol "https", :username nil, :password nil, - :host "api.twitter.com", :port -1, - :path "/1/users/profile_image/cemerick", :query nil, :anchor nil} +#cemerick.url.URL{:scheme :https, :username nil, :password nil, + :server-name "api.twitter.com", :server-port -1, + :uri "/1/users/profile_image/cemerick", :query-params nil, :fragment nil} => (str *1) "https://api.twitter.com/1/users/profile_image/cemerick" => (str (url "https://api.twitter.com/1/users/profile_image/cemerick" "../../lookup.json")) "https://api.twitter.com/1/users/lookup.json" ``` -The `:query` slot can be a string or a map of params: +The `:query-params` slot can be a string or a map of params: ```clojure -=> (str (assoc *3 :query {:a 5 :b 6})) +=> (str (assoc *3 :query-params {:a 5 :b 6})) "https://api.twitter.com/1/users/profile_image/cemerick?a=5&b=6" ``` diff --git a/src/cemerick/url.cljx b/src/cemerick/url.cljx index 75ff53d..9b2c9c0 100644 --- a/src/cemerick/url.cljx +++ b/src/cemerick/url.cljx @@ -5,6 +5,15 @@ [clojure.string :as string] #+cljs [goog.Uri :as uri])) +(def ^:dynamic *ports* + {:amqp 5672 + :amqps 5671 + :http 80 + :https 443 + :mysql 3306 + :postgresql 5432 + :ssh 22}) + #+clj (defn url-encode [string] @@ -55,11 +64,10 @@ (apply hash-map)))) (defn- port-str - [protocol port] + [scheme port] (when (and (not= nil port) (not= -1 port) - (not (and (== port 80) (= protocol "http"))) - (not (and (== port 443) (= protocol "https")))) + (not (= port (get *ports* (keyword scheme))))) (str ":" port))) (defn- url-creds @@ -68,34 +76,35 @@ (str username ":" password))) (defrecord URL - [protocol username password host port path query anchor] + [scheme username password server-name server-port uri query-params fragment] Object (toString [this] (let [creds (url-creds username password)] - (str protocol "://" + (str (name scheme) "://" creds (when creds \@) - host - (port-str protocol port) - path - (when (seq query) (str \? (if (string? query) - query - (map->query query)))) - (when anchor (str \# anchor)))))) + server-name + (port-str scheme server-port) + uri + (when (seq query-params) + (str \? (if (string? query-params) + query-params + (map->query query-params)))) + (when fragment (str \# fragment)))))) #+clj (defn- url* [url] - (let [url (java.net.URL. url) + (let [url (java.net.URI. url) [user pass] (string/split (or (.getUserInfo url) "") #":" 2)] - (URL. (.toLowerCase (.getProtocol url)) + (URL. (keyword (.toLowerCase (.getScheme url))) (and (seq user) user) (and (seq pass) pass) (.getHost url) (.getPort url) (pathetic/normalize (.getPath url)) (query->map (.getQuery url)) - (.getRef url)))) + (.getFragment url)))) #+cljs (defn translate-default @@ -109,7 +118,7 @@ [url] (let [url (goog.Uri. url) [user pass] (string/split (or (.getUserInfo url) "") #":" 2)] - (URL. (.getScheme url) + (URL. (keyword (.getScheme url)) (and (seq user) user) (and (seq pass) pass) (.getDomain url) @@ -125,7 +134,7 @@ a pre-existing URL record instance that will serve as the basis for the new URL. Any additional arguments must be strings, which are interpreted as relative paths that are successively resolved against the base url's path - to construct the final :path in the returned URL record. + to construct the final :uri in the returned URL record. This function does not perform any url-encoding. Use `url-encode` to encode URL path segments as desired before passing them into this fn." @@ -135,7 +144,6 @@ (url* url))) ([base-url & path-segments] (let [base-url (if (instance? URL base-url) base-url (url base-url))] - (assoc base-url :path (pathetic/normalize (reduce pathetic/resolve - (:path base-url) + (assoc base-url :uri (pathetic/normalize (reduce pathetic/resolve + (:uri base-url) path-segments)))))) - diff --git a/test/cemerick/test_url.cljx b/test/cemerick/test_url.cljx index fe2e6d1..8349631 100644 --- a/test/cemerick/test_url.cljx +++ b/test/cemerick/test_url.cljx @@ -1,5 +1,5 @@ (ns cemerick.test-url - #+clj (:import java.net.URL) + #+clj (:import java.net.URI) #+clj (:use cemerick.url clojure.test) #+cljs (:require-macros [cemerick.cljs.test :refer (are is deftest with-test run-tests testing)]) @@ -18,10 +18,10 @@ (deftest url-roundtripping (let [aurl (url "https://username:password@some.host.com/database?query=string")] (is (= "https://username:password@some.host.com/database?query=string" (str aurl))) - (is (== -1 (:port aurl))) + (is (== -1 (:server-port aurl))) (is (= "username" (:username aurl))) (is (= "password" (:password aurl))) - (is (= "https://username:password@some.host.com" (str (assoc aurl :path nil :query nil)))))) + (is (= "https://username:password@some.host.com" (str (assoc aurl :uri nil :query-params nil)))))) (deftest url-segments (is (= "http://localhost:5984/a/b" (url-str "http://localhost:5984" "a" "b"))) @@ -29,14 +29,15 @@ (is (= "http://localhost:5984/a/b/c" (url-str (url "http://localhost:5984" "a") "b" "c")))) (deftest port-normalization - #+clj (is (== -1 (-> "https://foo" url-str URL. .getPort))) + #+clj (is (== -1 (-> "https://foo" url-str URI. .getPort))) (is (= "http://localhost" (url-str "http://localhost"))) (is (= "http://localhost" (url-str "http://localhost:80"))) (is (= "http://localhost:8080" (url-str "http://localhost:8080"))) (is (= "https://localhost" (url-str "https://localhost"))) (is (= "https://localhost" (url-str "https://localhost:443"))) (is (= "https://localhost:8443" (url-str "https://localhost:8443"))) - (is (= "http://localhost" (str (map->URL {:host "localhost" :protocol "http"}))))) + (is (= "http://localhost" (str (map->URL {:server-name "localhost" :scheme :http})))) + (is (= "mysql://localhost" (str (map->URL {:server-name "localhost" :scheme :mysql }))))) (deftest query-params (are [query map] (is (= map (query->map query))) @@ -53,24 +54,23 @@ ["a" nil] "http://a:@foo" ["a" "b:c"] "http://a:b:c@foo")) -(deftest path-normalization +(deftest uri-normalization (is (= "http://a/" (url-str "http://a/b/c/../.."))) - + (is (= "http://a/b/c" (url-str "http://a/b/" "c"))) (is (= "http://a/b/c" (url-str "http://a/b/.." "b" "c"))) (is (= "http://a/b/c" (str (url "http://a/b/..////./" "b" "c" "../././.." "b" "c")))) (is (= "http://a/" (str (url "http://a/b/..////./" "b" "c" "../././.." "b" "c" "/")))) - + (is (= "http://a/x" (str (url "http://a/b/c" "/x")))) (is (= "http://a/" (str (url "http://a/b/c" "/")))) (is (= "http://a/" (str (url "http://a/b/c" "../..")))) (is (= "http://a/x" (str (url "http://a/b/c" "../.." "." "./x"))))) -(deftest anchors +(deftest fragments (is (= "http://a#x" (url-str "http://a#x"))) (is (= "http://a?b=c#x" (url-str "http://a?b=c#x"))) - (is (= "http://a?b=c#x" (-> "http://a#x" url (assoc :query {:b "c"}) str)))) + (is (= "http://a?b=c#x" (-> "http://a#x" url (assoc :query-params {:b "c"}) str)))) (deftest no-bare-? - (is (= "http://a" (-> "http://a?b=c" url (update-in [:query] dissoc "b") str)))) - + (is (= "http://a" (-> "http://a?b=c" url (update-in [:query-params] dissoc "b") str))))