/
headers.clj
143 lines (129 loc) · 4.5 KB
/
headers.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
(ns clj-http.headers
"Provides wrap-header-map, which is middleware allows headers to be
specified more flexibly. In requests and responses, headers can be
accessed as strings or keywords of any case. In requests, string
header names will be sent to the server with their casing unchanged,
while keyword header names will be transformed into their canonical
HTTP representation (e.g. :accept-encoding will become
\"Accept-Encoding\")."
(:require [clojure.string :as s]
[potemkin :as potemkin])
(:import (java.util Locale)
(org.apache.http Header HeaderIterator)))
(def special-cases
"A collection of HTTP headers that do not follow the normal
Looks-Like-This casing."
["Content-MD5"
"DNT"
"ETag"
"P3P"
"TE"
"WWW-Authenticate"
"X-ATT-DeviceId"
"X-UA-Compatible"
"X-WebKit-CSP"
"X-XSS-Protection"])
(defn special-case
"Returns the special-case capitalized version of a string if that
string is a special case, otherwise returns the string unchanged."
[^String s]
(or (first (filter #(.equalsIgnoreCase ^String % s) special-cases))
s))
(defn ^String lower-case
"Converts a string to all lower-case, using the root locale.
Warning: This is not a general purpose lower-casing function -- it
is useful for case-insensitive comparisons of strings, not for
converting a string into something that's useful for humans."
[^CharSequence s]
(when s
(.toLowerCase (.toString s) Locale/ROOT)))
(defn title-case
"Converts a character to titlecase."
[^Character c]
(when c
(Character/toTitleCase c)))
(defn canonicalize
"Transforms a keyword header name into its canonical string
representation.
The canonical string representation is title-cased words separated
by dashes, like so: :date -> \"Date\", :DATE -> \"Date\", and
:foo-bar -> \"Foo-Bar\".
However, there is special-casing for some common headers, so: :p3p
-> \"P3P\", and :content-md5 -> \"Content-MD5\"."
[k]
(when k
(-> (name k)
(lower-case)
(s/replace #"(?:^.|-.)"
(fn [s]
(if (next s)
(str (first s)
(title-case (second s)))
(str (title-case (first s))))))
(special-case))))
(defn normalize
"Turns a string or keyword into normalized form, which is a
lowercase string."
[k]
(when k
(lower-case (name k))))
(defn header-iterator-seq
"Takes a HeaderIterator and returns a seq of vectors of name/value
pairs of headers."
[^HeaderIterator headers]
(for [^Header h (iterator-seq headers)]
[(.getName h) (.getValue h)]))
(defn assoc-join
"Like assoc, but will join multiple values into a vector if the
given key is already present into the map."
[headers name value]
(update-in headers [name]
(fn [existing]
(cond (vector? existing)
(conj existing value)
(nil? existing)
value
:else
[existing value]))))
;; a map implementation that stores both the original (or canonical)
;; key and value for each key/value pair, but performs lookups and
;; other operations using the normalized -- this allows a value to be
;; looked up by many similar keys, and not just the exact precise key
;; it was originally stored with.
(potemkin/def-map-type HeaderMap [m mta]
(get [_ k v]
(second (get m (normalize k) [nil v])))
(assoc [_ k v]
(HeaderMap. (assoc m (normalize k) [(if (keyword? k)
(canonicalize k)
k)
v])
mta))
(dissoc [_ k]
(HeaderMap. (dissoc m (normalize k)) mta))
(keys [_]
(map first (vals m)))
(meta [_]
mta)
(with-meta [_ mta]
(HeaderMap. m mta))
clojure.lang.Associative
(containsKey [_ k]
(contains? m (normalize k)))
(empty [_]
(HeaderMap. {} nil)))
(defn header-map
"Returns a new header map with supplied mappings."
[& keyvals]
(into (HeaderMap. {} nil)
(apply array-map keyvals)))
(defn wrap-header-map
"Middleware that converts headers from a map into a header-map."
[client]
(fn [req]
(let [req-headers (:headers req)
req (if req-headers
(-> req (assoc :headers (into (header-map) req-headers)
:use-header-maps-in-response? true))
req)]
(client req))))