blob: d722a5737349785cfcdb505b951ecd1896e59e0e (
plain)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
;; Copyright (c) James Reeves. All rights reserved.
;; The use and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which
;; can be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other, from
;; this software.
(ns compojure.http.routes
"Macros and functions for compiling routes in the form (method path & body)
into stand-alone functions that return the return value of the body, or the
keyword :next if they don't match."
(:use compojure.http.request
compojure.http.response
compojure.http.session
compojure.str-utils
compojure.map-utils
compojure.control
compojure.encodings)
(:import java.util.regex.Pattern
java.util.Map))
;; Functions for lexing a string
(defn- lex-1
"Lex one symbol from a string, and return the symbol and trailing source."
[src clauses]
(some
(fn [[re action]]
(let [matcher (re-matcher re src)]
(if (.lookingAt matcher)
[(if (fn? action) (action matcher) action)
(.substring src (.end matcher))])))
(partition 2 clauses)))
(defn- lex
"Lex a string into tokens by matching against regexs and evaluating
the matching associated function."
[src & clauses]
(loop [results []
src src
clauses clauses]
(if-let [[result src] (lex-1 src clauses)]
(let [results (conj results result)]
(if (= src "")
results
(recur results src clauses))))))
;; Functions for matching URIs using a syntax borrowed from Ruby frameworks
;; like Sinatra and Rails.
(defstruct uri-matcher
:regex
:keywords)
(defn compile-uri-matcher
"Compile a path string using the routes syntax into a uri-matcher struct."
[path]
(let [splat #"\*"
word #":([A-Za-z][\w-]*)"
literal #"(:[^A-Za-z*]|[^:*])+"]
(struct uri-matcher
(re-pattern
(apply str
(lex path
splat "(.*?)"
word "([^/.,;?]+)"
literal #(re-escape (.group %)))))
(vec
(remove nil?
(lex path
splat :*
word #(keyword (.group % 1))
literal nil))))))
;; Don't compile paths more than once.
(decorate-with memoize compile-uri-matcher)
(defmulti compile-matcher
"Compile a string or regex into a form suitable for buing passed to the
match-uri function."
class)
(defmethod compile-matcher String
[path]
(compile-uri-matcher path))
(defmethod compile-matcher Pattern
[re]
re)
(defn- assoc-keywords-with-groups
"Create a hash-map from a series of regex match groups and a collection of
keywords."
[groups keywords]
(reduce
(fn [m [k v]] (assoc-vec m k v))
{}
(map vector keywords groups)))
(defmulti match-uri
"Match a URL against a compiled URI-matcher or a regular expression. Returns
the matched URI keywords as a map, or the matched regex groups as a vector."
(fn [matcher uri] (class matcher)))
(defmethod match-uri Map
[uri-matcher uri]
(let [matcher (re-matcher (uri-matcher :regex) (or uri "/"))]
(if (.matches matcher)
(assoc-keywords-with-groups
(map urldecode (re-groups* matcher))
(uri-matcher :keywords)))))
(defmethod match-uri Pattern
[uri-pattern uri]
(let [matches (re-matches uri-pattern (or uri "/"))]
(if matches
(if (vector? matches)
(vec (map urldecode (rest matches)))
[]))))
(defn match-method
"True if this request matches the supplied method."
[method request]
(let [request-method (request :request-method)
form-method (-> request :form-params :_method)]
(or (nil? method)
(if (and form-method (= request-method :post))
(= (upcase-name method) form-method)
(= method request-method)))))
(defn request-url
"Return the complete URL for the request."
[request]
(str
(name (:scheme request))
"://"
(get-in request [:headers "host"])
(:uri request)))
(defn absolute-url?
"True if the string is an absolute URL."
[s]
(re-find #"^[a-z+.-]+://" s))
(defn get-matcher-uri
"Get the appropriate request URI for the given path pattern."
[path request]
(if (and (string? path) (absolute-url? path))
(request-url request)
(:uri request)))
(defmacro request-matcher
"Compiles a function to match a HTTP request against the supplied method
and path template. Returns a map of the route parameters if the is a match,
nil otherwise. Precompiles the route when supplied with a literal string."
[method path]
(let [matcher (if (or (string? path) (instance? Pattern path))
(compile-matcher path)
`(compile-matcher ~path))]
`(fn [request#]
(and
(match-method ~method request#)
(match-uri ~matcher (get-matcher-uri ~path request#))))))
;; Functions and macros for generating routing functions. A routing function
;; returns :next if it doesn't match, and any other value if it does.
(defmacro with-request-bindings
"Add shortcut bindings for the keys in a request map."
[request & body]
`(let [~'request ~request
~'params (:params ~'request)
~'cookies (:cookies ~'request)
~'session (:session ~'request)
~'flash (:flash ~'request)]
~@body))
(defn assoc-route-params
"Associate route parameters with the request map."
[request params]
(-> request
(assoc :route-params params)
(assoc :params (merge (:params request)
(if (map? params) params)))))
(defn compile-route
"Compile a route in the form (method path & body) into a function."
[method path body]
`(let [matcher# (request-matcher ~method ~path)]
(fn [request#]
(if-let [route-params# (matcher# request#)]
(let [request# (assoc-route-params request# route-params#)]
(create-response request#
(with-request-bindings request# ~@body)))))))
(defn routes*
"Create a Ring handler by combining several handlers into one."
[& handlers]
(fn [request]
(some #(% request) handlers)))
(defn routes
"Create a Ring handler by combining several routes into one. Adds parameters
and cookies to the request."
[& handlers]
(-> (apply routes* handlers)
with-request-params
with-cookies))
;; Macros for easily creating a compiled routing table
(defmacro defroutes
"Define a Ring handler function from a sequence of routes. Takes an optional
doc-string."
[name doc? & routes]
(let [[name & routes] (apply-doc name doc? routes)]
`(def ~name
(routes ~@routes))))
(defmacro GET "Generate a GET route."
[path & body]
(compile-route :get path body))
(defmacro POST "Generate a POST route."
[path & body]
(compile-route :post path body))
(defmacro PUT "Generate a PUT route."
[path & body]
(compile-route :put path body))
(defmacro DELETE "Generate a DELETE route."
[path & body]
(compile-route :delete path body))
(defmacro HEAD "Generate a HEAD route."
[path & body]
(compile-route :head path body))
(defmacro ANY "Generate a route that matches any method."
[path & body]
(compile-route nil path body))
|