url-encode & url-decode

This commit is contained in:
Tommi Reiman 2018-08-01 18:05:14 +03:00
parent bb4f861f00
commit 0b4d1d2ee1
3 changed files with 241 additions and 7 deletions

View file

@ -166,15 +166,40 @@
;; Path-parameters, see https://github.com/metosin/reitit/issues/75 ;; Path-parameters, see https://github.com/metosin/reitit/issues/75
;; ;;
#?(:clj
(def hex-digit
{0 "0" 1 "1" 2 "2" 3 "3"
4 "4" 5 "5" 6 "6" 7 "7"
8 "8" 9 "9" 10 "A" 11 "B"
12 "C" 13 "D" 14 "E" 15 "F"}))
#?(:clj
(defn byte->percent [byte]
(let [byte (bit-and 0xFF byte)
low-nibble (bit-and 0xF byte)
high-nibble (bit-shift-right byte 4)]
(str "%" (hex-digit high-nibble) (hex-digit low-nibble)))))
#?(:clj
(defn percent-encode [^String unencoded]
(->> (.getBytes unencoded "UTF-8") (map byte->percent) (str/join))))
;; + is safe, but removed so it would work the same as with js
(defn url-encode [s] (defn url-encode [s]
(some-> s (if s
#?(:clj (URLEncoder/encode "UTF-8") #?(:clj (str/replace s #"[^A-Za-z0-9\!'\(\)\*_~.-]+" percent-encode)
:cljs (js/encodeURIComponent)) :cljs (js/encodeURIComponent s))))
#?(:clj (.replace "+" "%20"))))
(defn url-decode [s] (defn url-decode [s]
(some-> s #?(:clj (URLDecoder/decode "UTF-8") (if s
:cljs (js/decodeURIComponent)))) #?(:clj (if (.contains ^String s "%")
(URLDecoder/decode
(if (.contains ^String s "+")
(.replace ^String s "+" "%2B")
s)
"UTF-8")
s)
:cljs (js/decodeURIComponent s))))
(defprotocol IntoString (defprotocol IntoString
(into-string [_])) (into-string [_]))
@ -203,7 +228,7 @@
(into-string [this] (str this)) (into-string [this] (str this))
nil nil
(into-string [this])) (into-string [_]))
(defn path-params (defn path-params
"shallow transform of the path parameters values into strings" "shallow transform of the path parameters values into strings"

View file

@ -0,0 +1,116 @@
(ns reitit.impl-perf-test
(:require [criterium.core :as cc]
[reitit.perf-utils :refer :all]
[ring.util.codec]
[reitit.impl])
(:import (java.net URLDecoder URLEncoder)))
;;
;; start repl with `lein perf repl`
;; perf measured with the following setup:
;;
;; Model Name: MacBook Pro
;; Model Identifier: MacBookPro11,3
;; Processor Name: Intel Core i7
;; Processor Speed: 2,5 GHz
;; Number of Processors: 1
;; Total Number of Cores: 4
;; L2 Cache (per Core): 256 KB
;; L3 Cache: 6 MB
;; Memory: 16 GB
;;
(defn test! [f input]
(do
(println "\u001B[33m")
(println (pr-str input) "=>" (pr-str (f input)))
(println "\u001B[0m")
(cc/quick-bench (f input))))
(defn url-decode-naive [s]
(URLDecoder/decode
(.replace ^String s "+" "%2B")
"UTF-8"))
(defn decode! []
;; ring
;; 890ns
;; 190ns
;; 90ns
;; 80ns
;; naive
;; 750ns
;; 340ns
;; 420ns
;; 200ns
;; reitit
;; 630ns (-29%)
;; 12ns (-94%)
;; 8ns (-91%)
;; 8ns (-90%)
(doseq [fs ['ring.util.codec/url-decode
'url-decode-naive
'reitit.impl/url-decode]
:let [f (deref (resolve fs))]]
(suite (str fs))
(doseq [s ["aja%20hiljaa+sillalla"
"aja_hiljaa_sillalla"
"1+1"
"1"]]
(test! f s))))
(defn url-encode-naive [^String s]
(cond-> (.replace (URLEncoder/encode s "UTF-8") "+" "%20")
(.contains s "+") (.replace "%2B" "+")
(.contains s "~") (.replace "%7E" "~")
(.contains s "=") (.replace "%3D" "=")
(.contains s "!") (.replace "%21" "!")
(.contains s "'") (.replace "%27" "'")
(.contains s "(") (.replace "%28" "(")
(.contains s ")") (.replace "%29" ")")))
(defn encode! []
;; ring
;; 2500ns
;; 610ns
;; 160ns
;; 120ns
;; naive
;; 1000ns
;; 440ns
;; 570ns
;; 200ns
;; reitit
;; 1400ns
;; 740ns
;; 180ns
;; 130ns
(doseq [fs ['ring.util.codec/url-encode
'url-encode-naive
'reitit.impl/url-encode]
:let [f (deref (resolve fs))]]
(suite (str fs))
(doseq [s ["aja hiljaa+sillalla"
"aja_hiljaa_sillalla"
"1+1"
"1"]]
(test! f s))))
(comment
(decode!)
(encode!))

View file

@ -64,3 +64,96 @@
;{:a ["c" "b"]} "a=c&a=b" ;{:a ["c" "b"]} "a=c&a=b"
;{:a (seq [1 2])} "a=1&a=2" ;{:a (seq [1 2])} "a=1&a=2"
;{:a #{"c" "b"}} "a=b&a=c" ;{:a #{"c" "b"}} "a=b&a=c"
(deftest url-encode-test
(are [in out]
(= out (impl/url-encode in))
"/" "%2F"
"?" "%3F"
"#" "%23"
"[" "%5B"
"]" "%5D"
"!" "!"
#_#_"$" "$"
#_#_"&" "&"
"'" "'"
"(" "("
")" ")"
"*" "*"
#_#_"+" "+"
#_#_"," ","
#_#_";" ";"
#_#_"=" "="
#_#_":" ":"
#_#_"@" "@"
"a" "a"
"z" "z"
"A" "A"
"Z" "Z"
"0" "0"
"9" "9"
"-" "-"
"." "."
"_" "_"
"~" "~"
"\000" "%00"
"\037" "%1F"
" " "%20"
"\"" "%22"
"%" "%25"
"<" "%3C"
">" "%3E"
"\\" "%5C"
"^" "%5E"
"`" "%60"
"{" "%7B"
"|" "%7C"
"}" "%7D"
"\177" "%7F"
#_#_"\377" "%FF"
"£0.25" "%C2%A30.25"
"€100" "%E2%82%AC100"
"«küßî»" "%C2%ABk%C3%BC%C3%9F%C3%AE%C2%BB"
"“ЌύБЇ”" "%E2%80%9C%D0%8C%CF%8D%D0%91%D0%87%E2%80%9D"
"\000" "%00"
#_#_"\231" "%99"
#_#_"\252" "%AA"
#_#_"\377" "%FF"
"" ""
"1" "1"
"12" "12"
"123" "123"
"1234567890" "1234567890"
"Hello world" "Hello%20world"
"/home/foo" "%2Fhome%2Ffoo"
" " "%20"
"+" "%2B" #_"+"
" +" "%20%2B" #_"%20+"
#_#_"1+2=3" "1+2=3"
#_#_"1 + 2 = 3" "1%20+%202%20=%203"))
(deftest url-decode-test
(are [in out]
(= out (impl/url-decode in))
"1+1" "1+1"
"%21" "!"
"%61" "a"
"%31%32%33" "123"
"%2b" "+"
"%7e" "~"
"hello%20world" "hello world"
"a%2fb" "a/b"
"a/.." "a/.."
"a/." "a/."
"//a" "//a"
"a//b" "a//b"
"a//" "a//"
"/path/%C2%ABk%C3%BC%C3%9F%C3%AE%C2%BB" "/path/«küßî»"
"/path/%E2%80%9C%D0%8C%CF%8D%D0%91%D0%87%E2%80%9D" "/path/“ЌύБЇ”"))