public
Description: A cross-platform web server that's scripted with Nu.
Homepage: http://programming.nu
Clone URL: git://github.com/timburks/nunja.git
Significant rework of get and post handlers. This will break existing 
sites, but it's worth it.

This change set causes the get and post handlers to create functions,
which gives them access to their lexical context. Previously, handlers
were evaluated in the context of (NunjaRequestHandler handleRequest:),
which simplified a few things but made it difficult to declare helper
objects, functions, and values for use in action handlers.

Now get and post implicitly declare functions that are called with
three arguments: MATCH, REQUEST, and RESPONSE. MATCH is the result
of a regex match on the action path and was previously available
through the @match instance variable.  To set the TITLE and HEAD
of the response, set the corresponding key-values in the RESPONSE
dictionary.

See the sample/site.nu for examples.

This revision contains enough of the old code that it should still
to use the new Nunja with old sites if you set the $do-it-the-old-way
global to something non-nil, but this will disappear soon.
timburks (author)
Tue Apr 29 21:18:51 -0700 2008
commit  a910c752cee11c27ed248b27e50608c0c76618e5
tree    f764ca92bc2ddd27b805e59af246c3531d707ad1
parent  7214a2325f7bfc7609bb30aa07084a719fd29c1f
...
131
132
133
134
135
136
137
 
 
 
138
139
140
...
155
156
157
158
 
 
 
 
 
159
160
161
162
 
 
 
 
 
 
163
164
165
...
169
170
171
 
 
 
 
 
 
172
173
174
...
208
209
210
211
212
213
 
 
 
 
 
 
 
 
 
214
215
216
217
218
219
220
 
 
 
 
 
 
 
 
 
221
222
223
...
131
132
133
 
134
135
 
136
137
138
139
140
141
...
156
157
158
 
159
160
161
162
163
164
165
 
 
166
167
168
169
170
171
172
173
174
...
178
179
180
181
182
183
184
185
186
187
188
189
...
223
224
225
 
 
 
226
227
228
229
230
231
232
233
234
235
236
237
238
 
 
 
239
240
241
242
243
244
245
246
247
248
249
250
0
@@ -131,10 +131,11 @@
0
      ;; Create a handler with a specified action, pattern, and statements. Used internally.
0
      (+ (id) handlerWithAction:(id)action pattern:(id)pattern statements:(id)statements is
0
         (set handler ((self alloc) init))
0
- ;(handler set:(action:action pattern:pattern statements:(cons 'progn statements)))
0
         (handler setAction:action)
0
         (handler setPattern:pattern)
0
- (handler setStatements:(cons 'progn statements))
0
+ (if (send statements isKindOfClass:NuBlock)
0
+ (then (handler setStatements:statements))
0
+ (else (handler setStatements:(cons 'progn statements))))
0
         handler)
0
      
0
      ;; Try to match the handler against a specified action and path. Used internally.
0
@@ -155,11 +156,19 @@
0
         (set response (dict))
0
         (set HEAD nil)
0
         (set TITLE nil)
0
- (set BODY (eval @statements))
0
+
0
+ (if (send @statements isKindOfClass:NuCell)
0
+ (then (set BODY (eval @statements))) ;; deprecated, evaluates statements in the instance method context
0
+ (else (set BODY (@statements @match request response)))) ;; new style, evaluates a function with a lexical closure
0
+
0
         (if (BODY isKindOfClass:NSString)
0
             (then (set html "<head>\n")
0
- (if HEAD (html appendString:HEAD))
0
- (if TITLE (html appendString:(+ "<title>" TITLE "</title>")))
0
+ (if (response "HEAD")
0
+ (then (html appendString:(response "HEAD")))
0
+ (else (if HEAD (html appendString:HEAD))))
0
+ (if (response "TITLE")
0
+ (then (html appendString:(+ "<title>" (response "TITLE") "</title>")))
0
+ (else (if TITLE (html appendString:(+ "<title>" TITLE "</title>")))))
0
                   (html appendString: (+ "</head>\n<body>\n" BODY "</body>\n"))
0
                   (request respondWithString:html))
0
             (else (request respondWithData:BODY))))
0
@@ -169,6 +178,12 @@
0
         (request setValue:location forResponseHeader:"Location")
0
         (request respondWithCode:303 message:"redirecting" string:"redirecting")))
0
 
0
+(class Nunja
0
+ ;; Return a response redirecting the client to a new location. This method may be called from action handlers.
0
+ (+ (id)redirectResponse:(id)request toLocation:(id)location is
0
+ (request setValue:location forResponseHeader:"Location")
0
+ (request respondWithCode:303 message:"redirecting" string:"redirecting")))
0
+
0
 ;; @class NunjaDelegate
0
 ;; @discussion The Nunja's delegate. Responsible for handling requests.
0
 (class NunjaDelegate is NSObject
0
@@ -208,16 +223,28 @@
0
 ;; Declare a get action.
0
 (global get
0
         (macro _
0
- (set __pattern (eval (car margs)))
0
- (set __statements (cdr margs))
0
- (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"GET" pattern:__pattern statements:__statements))))
0
+ (if $do-it-the-old-way
0
+ (then
0
+ (set __pattern (eval (car margs)))
0
+ (set __statements (cdr margs))
0
+ (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"GET" pattern:__pattern statements:__statements)))
0
+ (else
0
+ (set __pattern (eval (car margs)))
0
+ (set __function (eval (append '(do (MATCH REQUEST RESPONSE)) (cdr margs))))
0
+ (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"GET" pattern:__pattern statements:__function))))))
0
 
0
 ;; Declare a post action.
0
 (global post
0
         (macro _
0
- (set __pattern (eval (car margs)))
0
- (set __statements (cdr margs))
0
- (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"POST" pattern:__pattern statements:__statements))))
0
+ (if $do-it-the-old-way
0
+ (then
0
+ (set __pattern (eval (car margs)))
0
+ (set __statements (cdr margs))
0
+ (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"POST" pattern:__pattern statements:__statements)))
0
+ (else
0
+ (set __pattern (eval (car margs)))
0
+ (set __function (eval (append '(do (MATCH REQUEST RESPONSE)) (cdr margs))))
0
+ (($nunja handlers) << (NunjaRequestHandler handlerWithAction:"POST" pattern:__pattern statements:__function))))))
0
 
0
 ;; Set the top-level directory for a site
0
 (global root
...
15
16
17
18
19
 
 
20
21
22
23
24
 
 
25
26
27
 
28
29
30
31
32
33
34
 
35
36
37
...
52
53
54
55
 
56
57
58
...
64
65
66
67
 
68
69
70
 
71
72
73
74
75
76
77
78
79
 
 
 
80
81
 
82
83
84
...
91
92
93
94
95
96
 
 
 
97
98
99
100
 
101
102
103
...
112
113
114
115
 
116
117
118
 
 
119
120
121
122
123
124
 
 
 
125
126
127
...
129
130
131
132
 
133
...
15
16
17
 
 
18
19
20
21
22
 
 
23
24
25
26
 
27
28
29
30
31
32
33
 
34
35
36
37
...
52
53
54
 
55
56
57
58
...
64
65
66
 
67
68
69
 
70
71
72
73
74
75
76
 
 
 
77
78
79
80
 
81
82
83
84
...
91
92
93
 
 
 
94
95
96
97
98
99
 
100
101
102
103
...
112
113
114
 
115
116
 
 
117
118
119
120
121
 
 
 
122
123
124
125
126
127
...
129
130
131
 
132
133
0
@@ -15,23 +15,23 @@
0
 ;; limitations under the License.
0
 
0
 ;; global variables
0
-(set $sessionCookies (dict))
0
-(set $friends (array))
0
+(set sessionCookies (dict))
0
+(set friends (array))
0
 
0
 ;; front page.
0
 (get "/"
0
- (set sessionCookieName ((request cookies) "session"))
0
- (set sessionCookie (if sessionCookieName (then ($sessionCookies sessionCookieName)) (else nil)))
0
+ (set sessionCookieName ((REQUEST cookies) "session"))
0
+ (set sessionCookie (if sessionCookieName (then (sessionCookies sessionCookieName)) (else nil)))
0
      (set user (sessionCookie user))
0
      
0
- (set TITLE "Friends")
0
+ (RESPONSE setValue:"Friends" forKey:"TITLE")
0
      (set template <<-TEMPLATE
0
 <h1>Hello!</h1>
0
 <p>Let's make a list.</p>
0
 <% (if user (then %>
0
 <h2>Your friends</h2>
0
 <ul>
0
-<% ($friends each: (do (friend) %>
0
+<% (friends each: (do (friend) %>
0
 <% (set deletion ((dict "name" (friend "name")) urlQueryString)) %>
0
 <li><%= (friend "name") %> (<%= (friend "email") %>) (<a href="/delete?<%= deletion %>">X</a>)</li>
0
 <% )) %>
0
@@ -52,7 +52,7 @@ TEMPLATE)
0
 
0
 ;; login page.
0
 (get "/login"
0
- (set TITLE "Log in")
0
+ (RESPONSE setValue:"Log in" forKey:"TITLE")
0
      <<-HTML
0
 <form action="/login" method="post">
0
 <p>Please sign in.</p>
0
@@ -64,21 +64,21 @@ HTML)
0
 
0
 ;; login POST handler.
0
 (post "/login"
0
- (set post (request post))
0
+ (set post (REQUEST post))
0
       (if (eq (post "response") "Cancel")
0
           (then
0
- (self redirectResponse:request toLocation:"/"))
0
+ (Nunja redirectResponse:REQUEST toLocation:"/"))
0
           (else
0
                (set username (post "username"))
0
                (set password (post "password"))
0
                (if (and (> (username length) 0) (eq username password))
0
                    (then
0
                         (set sessionCookie (NunjaCookie cookieForUser:username))
0
- ($sessionCookies setObject:sessionCookie forKey:(sessionCookie value))
0
- (request setValue:(sessionCookie stringValue) forResponseHeader:"Set-Cookie")
0
- (self redirectResponse:request toLocation:"/"))
0
+ (sessionCookies setObject:sessionCookie forKey:(sessionCookie value))
0
+ (REQUEST setValue:(sessionCookie stringValue) forResponseHeader:"Set-Cookie")
0
+ (Nunja redirectResponse:REQUEST toLocation:"/"))
0
                    (else
0
- (set TITLE "Please try again")
0
+ (RESPONSE setValue:"Please try again" forKey:"TITLE")
0
                         <<-HTML
0
 <p>Invalid Password. Your password is your username.</p>
0
 <form action="/login" method="post">
0
@@ -91,13 +91,13 @@ HTML)
0
 
0
 ;; logout, also with a GET. In the real world, we would prefer a POST.
0
 (get "/logout"
0
- (set sessionCookieName ((request cookies) "session"))
0
- (if sessionCookieName ($sessionCookies removeObjectForKey:sessionCookieName))
0
- (self redirectResponse:request toLocation:"/"))
0
+ (set sessionCookieName ((REQUEST cookies) "session"))
0
+ (if sessionCookieName (sessionCookies removeObjectForKey:sessionCookieName))
0
+ (Nunja redirectResponse:REQUEST toLocation:"/"))
0
 
0
 ;; add-a-friend page.
0
 (get "/addfriend"
0
- (set TITLE "Add a friend")
0
+ (RESPONSE setValue:"Add a friend" forKey:"TITLE")
0
      <<-HTML
0
 <h1>Add a friend</h1>
0
 <form action="/addfriend" method="post">
0
@@ -112,16 +112,16 @@ HTML)
0
 
0
 ;; add-a-friend POST handler.
0
 (post "/addfriend"
0
- (set post (request post))
0
+ (set post (REQUEST post))
0
       (if (eq (post "response") "Submit")
0
- ($friends << (dict name:(post "name") email:(post "email"))))
0
- (self redirectResponse:request toLocation:"/"))
0
+ (friends << (dict name:(post "name") email:(post "email"))))
0
+ (Nunja redirectResponse:REQUEST toLocation:"/"))
0
 
0
 ;; delete-a-friend with a GET. Strictly, this should be a post, but we use a get to show how it would be done.
0
 (get (regex -"^/delete\?(.*)$")
0
- (set post ((@match groupAtIndex:1) urlQueryDictionary))
0
- (set $friends ($friends select:(do (friend) (!= (friend "name") (post "name")))))
0
- (self redirectResponse:request toLocation:"/"))
0
+ (set post ((MATCH groupAtIndex:1) urlQueryDictionary))
0
+ (set friends (friends select:(do (friend) (!= (friend "name") (post "name")))))
0
+ (Nunja redirectResponse:REQUEST toLocation:"/"))
0
 
0
 (get "/about" <<-END
0
 <h1>About this site</h1>
0
@@ -129,5 +129,5 @@ HTML)
0
 END)
0
 
0
 (get "/recycle.ico"
0
- (request setValue:"application/icon" forResponseHeader:"Content-Type")
0
+ (REQUEST setValue:"application/icon" forResponseHeader:"Content-Type")
0
      (NSData dataWithContentsOfFile:"public/favicon.ico"))

Comments

    No one has commented yet.