lib/popcorn: update README for `default_file`
[nit.git] / lib / popcorn / README.md
1 # Popcorn
2
3 **Why endure plain corn when you can pop it?!**
4
5 Popcorn is a minimal yet powerful nit web application framework that provides cool
6 features for lazy developpers.
7
8 Popcorn is built over nitcorn to provide a clean and user friendly interface
9 without all the boiler plate code.
10
11 ## What does it taste like?
12
13 Set up is quick and easy as 10 lines of code.
14 Create a file `app.nit` and add the following code:
15
16 ~~~
17 import popcorn
18
19 class HelloHandler
20         super Handler
21
22         redef fun get(req, res) do res.html "<h1>Hello World!</h1>"
23 end
24
25 var app = new App
26 app.use("/", new HelloHandler)
27 app.listen("localhost", 3000)
28 ~~~
29
30 The Popcorn app listens on port 3000 for connections.
31 The app responds with "Hello World!" for requests to the root URL (`/`) or **route**.
32 For every other path, it will respond with a **404 Not Found**.
33
34 The `req` (request) and `res` (response) parameters are the same that nitcorn provides
35 so you can do anything else you would do in your route without Popcorn involved.
36
37 Run the app with the following command:
38
39 ~~~bash
40 $ nitc app.nit && ./app
41 ~~~
42
43 Then, load [http://localhost:3000](http://localhost:3000) in a browser to see the output.
44
45 Here the output using the `curl` command:
46
47 ~~~bash
48 $ curl localhost:3000
49 <h1>Hello World!</h1>
50
51 $ curl localhost:3000/wrong_uri
52 <!DOCTYPE html>
53 <html>
54 <head>
55 <meta charset="utf-8">
56 <title>Not Found</title>
57 </head>
58 <body>
59 <h1>404 Not Found</h1>
60 </body>
61 </html>
62 ~~~
63
64 This is why we love popcorn!
65
66 ## Basic routing
67
68 **Routing** refers to determining how an application responds to a client request
69 to a particular endpoint, which is a URI (or path) and a specific HTTP request
70 method GET, POST, PUT or DELETE (other methods are not suported yet).
71
72 Each route can have one or more handler methods, which are executed when the route is matched.
73
74 Route handlers definition takes the following form:
75
76 ~~~nitish
77 import popcorn
78
79 class MyHandler
80         super Handler
81
82         redef fun METHOD(req, res) do end
83 end
84 ~~~
85
86 Where:
87 * `MyHandler` is the name of the handler you will add to the app.
88 * `METHOD` can be replaced by `get`, `post`, `put` or `delete`.
89
90 The following example responds to GET and POST requests:
91
92 ~~~
93 import popcorn
94
95 class MyHandler
96         super Handler
97
98         redef fun get(req, res) do res.send "Got a GET request"
99         redef fun post(req, res) do res.send "Got a POST request"
100 end
101 ~~~
102
103 To make your handler responds to a specific route, you have to add it to the app.
104
105 Respond to POST request on the root route (`/`), the application's home page:
106
107 ~~~
108 var app = new App
109 app.use("/", new MyHandler)
110 ~~~
111
112 Respond to a request to the `/user` route:
113
114 ~~~
115 app.use("/user", new MyHandler)
116 ~~~
117
118 For more details about routing, see the routing section.
119
120 ## Serving static files with Popcorn
121
122 To serve static files such as images, CSS files, and JavaScript files, use the
123 Popcorn built-in handler `StaticHandler`.
124
125 Pass the name of the directory that contains the static assets to the StaticHandler
126 init method to start serving the files directly.
127 For example, use the following code to serve images, CSS files, and JavaScript files
128 in a directory named `public`:
129
130 ~~~
131 app.use("/", new StaticHandler("public/"))
132 ~~~
133
134 Now, you can load the files that are in the `public` directory:
135
136 ~~~raw
137 http://localhost:3000/images/trollface.jpg
138 http://localhost:3000/css/style.css
139 http://localhost:3000/js/app.js
140 http://localhost:3000/hello.html
141 ~~~
142
143 Popcorn looks up the files relative to the static directory, so the name of the
144 static directory is not part of the URL.
145 To use multiple static assets directories, add the `StaticHandler` multiple times:
146
147 ~~~
148 app.use("/", new StaticHandler("public/"))
149 app.use("/", new StaticHandler("files/"))
150 ~~~
151
152 Popcorn looks up the files in the order in which you set the static directories
153 with the `use` method.
154
155 To create a virtual path prefix (where the path does not actually exist in the file system)
156 for files that are served by the `StaticHandler`, specify a mount path for the
157 static directory, as shown below:
158
159 ~~~
160 app.use("/static/", new StaticHandler("public/"))
161 ~~~
162
163 Now, you can load the files that are in the public directory from the `/static`
164 path prefix.
165
166 ~~~raw
167 http://localhost:3000/static/images/trollface.jpg
168 http://localhost:3000/static/css/style.css
169 http://localhost:3000/static/js/app.js
170 http://localhost:3000/static/hello.html
171 ~~~
172
173 However, the path that you provide to the `StaticHandler` is relative to the
174 directory from where you launch your app.
175 If you run the app from another directory, it’s safer to use the absolute path of
176 the directory that you want to serve.
177
178 In some cases, you can want to redirect request to static files to a default file
179 instead of returning a 404 error.
180 This can be achieved by specifying a default file in the StaticHandler:
181
182 ~~~
183 app.use("/static/", new StaticHandler("public/", "default.html"))
184 ~~~
185
186 This way all non-matched queries to the StaticHandler will be answered with the
187 `default.html` file.
188
189 ## Advanced Routing
190
191 **Routing** refers to the definition of application end points (URIs) and how
192 they respond to client requests. For an introduction to routing, see the Basic routing
193 section.
194
195 The following code is an example of a very basic route.
196
197 ~~~
198 import popcorn
199
200 class HelloHandler
201         super Handler
202
203         redef fun get(req, res) do res.send "Hello World!"
204 end
205
206 var app = new App
207 app.use("/", new HelloHandler)
208 ~~~
209
210 ### Route methods
211
212 A **route method** is derived from one of the HTTP methods, and is attached to an
213 instance of the Handler class.
214
215 The following code is an example of routes that are defined for the GET and the POST
216 methods to the root of the app.
217
218 ~~~
219 import popcorn
220
221 class GetPostHandler
222         super Handler
223
224         redef fun get(req, res) do res.send "GET request to the homepage"
225         redef fun post(req, res) do res.send "POST request to the homepage"
226 end
227
228 var app = new App
229 app.use("/", new GetPostHandler)
230 ~~~
231
232 Popcorn supports the following routing methods that correspond to HTTP methods:
233 get, post, put, and delete.
234
235 The request query string is accessed through the `req` parameter:
236
237 ~~~
238 import popcorn
239 import template
240
241 class QueryStringHandler
242         super Handler
243
244         redef fun get(req, res) do
245                 var tpl = new Template
246                 tpl.addn "URI: {req.uri}"
247                 tpl.addn "Query string: {req.query_string}"
248                 for name, arg in req.get_args do
249                         tpl.addn "{name}: {arg}"
250                 end
251         res.send tpl
252         end
253 end
254
255 var app = new App
256 app.use("/", new QueryStringHandler)
257 app.listen("localhost", 3000)
258 ~~~
259
260 Post parameters can also be accessed through the `req` parameter:
261
262 ~~~
263 import popcorn
264 import template
265
266 class PostHandler
267         super Handler
268
269         redef fun post(req, res) do
270                 var tpl = new Template
271                 tpl.addn "URI: {req.uri}"
272                 tpl.addn "Body: {req.body}"
273                 for name, arg in req.post_args do
274                         tpl.addn "{name}: {arg}"
275                 end
276         res.send tpl
277         end
278 end
279
280 var app = new App
281 app.use("/", new PostHandler)
282 app.listen("localhost", 3000)
283 ~~~
284
285 There is a special routing method, `all(res, req)`, which is not derived from any
286 HTTP method. This method is used to respond at a path for all request methods.
287
288 In the following example, the handler will be executed for requests to "/user"
289 whether you are using GET, POST, PUT, DELETE, or any other HTTP request method.
290
291 ~~~
292 import popcorn
293
294 class AllHandler
295         super Handler
296
297         redef fun all(req, res) do res.send "Every request to the homepage"
298 end
299 ~~~
300
301 Using the `all` method you can also implement other HTTP request methods.
302
303 ~~~
304 import popcorn
305
306 class MergeHandler
307         super Handler
308
309         redef fun all(req, res) do
310                 if req.method == "MERGE" then
311                         # handle that method
312                 else super # keep handle GET, POST, PUT and DELETE methods
313         end
314 end
315 ~~~
316
317 ### Route paths
318
319 **Route paths**, in combination with a request handlers, define the endpoints at
320 which requests can be made.
321 Route paths can be strings, parameterized strings or glob patterns.
322 Query strings such as `?q=foo`are not part of the route path.
323
324 Popcorn uses the `Handler::match(uri)` method to match the route paths.
325
326 Here are some examples of route paths based on strings.
327
328 This route path will match requests to the root route, `/`.
329
330 ~~~
331 import popcorn
332
333 class MyHandler
334         super Handler
335
336         redef fun get(req, res) do res.send "Got a GET request"
337 end
338
339 var app = new App
340 app.use("/", new MyHandler)
341 ~~~
342
343 This route path will match requests to `/about`.
344
345 ~~~
346 app.use("/about", new MyHandler)
347 ~~~
348
349 This route path will match requests to `/random.text`.
350
351 ~~~
352 app.use("/random.text", new MyHandler)
353 ~~~
354
355 During the query/response process, routes are matched by order of declaration
356 through the `App::use` method.
357
358 The app declared in this example will try to match the routes in this order:
359
360 1. `/`
361 2. `/about`
362 3. `/random.text`
363
364 ### Route parameters
365
366 **Route parameters** are variable parts of a route path. They can be used to path
367 arguments within the URI.\13
368 Parameters in a route are prefixed with a colon `:` like in `:userId`, `:year`.
369
370 The following example declares a handler `UserHome` that responds with the `user`
371 name.
372
373 ~~~
374 import popcorn
375
376 class UserHome
377         super Handler
378
379         redef fun get(req, res) do
380                 var user = req.param("user")
381                 if user != null then
382                         res.send "Hello {user}"
383                 else
384                         res.send("Nothing received", 400)
385                 end
386         end
387 end
388
389 var app = new App
390 app.use("/:user", new UserHome)
391 app.listen("localhost", 3000)
392 ~~~
393
394 The `UserHome` handler listen to every path matching `/:user`. This can be `/Morriar`,
395 `/10`, ... but not `/Morriar/profile` since route follow the strict matching rule.
396
397 ### Glob routes
398
399 **Glob routes** are routes that match only on a prefix, thus accepting a wider range
400 of URI.
401 Glob routes end with the symbol `*`.
402
403 Here we define a `UserItem` handler that will respond to any URI matching the prefix
404 `/user/:user/item/:item`.
405 Note that glob route are compatible with route parameters.
406
407 ~~~
408 import popcorn
409
410 class UserItem
411         super Handler
412
413         redef fun get(req, res) do
414                 var user = req.param("user")
415                 var item = req.param("item")
416                 if user == null or item == null then
417                         res.send("Nothing received", 400)
418                 else
419                         res.send "Here the item {item} of the use {user}."
420                 end
421         end
422 end
423
424 var app = new App
425 app.use("/user/:user/item/:item/*", new UserItem)
426 app.listen("localhost", 3000)
427 ~~~
428
429 ## Response methods
430
431 The methods on the response object (`res`), can is used to manipulate the
432 request-response cycle.
433 If none of these methods are called from a route handler, the client request will
434 receive a `404 Not found` error.
435
436 * `res.html()` Send a HTML response.
437 * `res.json()` Send a JSON response.
438 * `res.redirect()` Redirect a request.
439 * `res.send()` Send a response of various types.
440 * `res.error()` Set the response status code and send its message as the response body.
441
442 ## Response cycle
443
444 When the popcorn `App` receives a request, the response cycle is the following:
445
446 1. `pre-middlewares` lookup matching middlewares registered with `use_before(pre_middleware)`:
447         1. execute matching middleware by registration order
448         2. if a middleware send a response then let the `pre-middlewares` loop continue
449            with the next middleware
450 2. `response-handlers` lookup matching handlers registered with `use(handler)`:
451         1. execute matching middleware by registration order
452         2. if a middleware send a response then stop the `response-handlers` loop
453         3. if no hander matches or sends a response, generate a 404 response
454 3. `post-middlewares` lookup matching handlers registered with `use_after(post_handler)`:
455         1. execute matching middleware by registration order
456         2. if a middleware send a response then let the `post-middlewares` loop continue
457            with the next middleware
458
459 ## Middlewares
460
461 ### Overview
462
463 **Middleware** handlers are handlers that typically do not send `HttpResponse` responses.
464 Middleware handlers can perform the following tasks:
465
466 * Execute any code.
467 * Make changes to the request and the response objects.
468 * End its action and pass to the next handler in the stack.
469
470 If a middleware handler makes a call to `res.send()`, it provoques the end the
471 request-response cycle and the response is sent to the client.
472
473 ### Ultra simple logger example
474
475 Here is an example of a simple “Hello World” Popcorn application.
476 We add a middleware handler to the application called MyLogger that prints a simple
477 log message in the app stdout.
478
479 ~~~
480 import popcorn
481
482 class MyLogger
483         super Handler
484
485         redef fun all(req, res) do print "Request Logged!"
486 end
487
488 class HelloHandler
489         super Handler
490
491         redef fun get(req, res) do res.send "Hello World!"
492 end
493
494
495 var app = new App
496 app.use_before("/*", new MyLogger)
497 app.use("/", new HelloHandler)
498 app.listen("localhost", 3000)
499 ~~~
500
501 By using the `MyLogger` handler to the route `/*` we ensure that every requests
502 (even 404 ones) pass through the middleware handler.
503 This handler just prints “Request Logged!” when a request is received.
504
505 Be default, the order of middleware execution is that are loaded first are also executed first.
506 To ensure our middleware `MyLogger` will be executed before all the other, we add it
507 with the `use_before` method.
508
509 ### Ultra cool, more advanced logger example
510
511 Next, we’ll create a middleware handler called “LogHandler” that prints the requested
512 uri, the response status and the time it took to Popcorn to process the request.
513
514 This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
515
516 ~~~
517 import popcorn
518 import realtime
519
520 redef class HttpRequest
521         # Time that request was received by the Popcorn app.
522         var timer: nullable Clock = null
523 end
524
525 class RequestTimeHandler
526         super Handler
527
528         redef fun all(req, res) do req.timer = new Clock
529 end
530
531 class LogHandler
532         super Handler
533
534         redef fun all(req, res) do
535                 var timer = req.timer
536                 if timer != null then
537                         print "{req.method} {req.uri} {res.color_status} ({timer.total}s)"
538                 else
539                         print "{req.method} {req.uri} {res.color_status}"
540                 end
541         end
542 end
543
544 class HelloHandler
545         super Handler
546
547         redef fun get(req, res) do res.send "Hello World!"
548 end
549
550 var app = new App
551 app.use_before("/*", new RequestTimeHandler)
552 app.use("/", new HelloHandler)
553 app.use_after("/*", new LogHandler)
554 app.listen("localhost", 3000)
555 ~~~
556
557 First, we attach a new attribute `timer` to every `HttpRequest`.
558 Doing so we can access our data from all handlers that import our module, directly
559 from the `req` parameter.
560
561 We use the new middleware called `RequestTimeHandler` to initialize the request timer.
562 Because of the `use_before` method, the `RequestTimeHandler` middleware will be executed
563 before all the others.
564
565 We then let the `HelloHandler` produce the response.
566
567 Finally, our `LogHandler` will display a bunch of data and use the request `timer`
568 to display the time it took to process the request.
569 Because of the `use_after` method, the `LogHandler` middleware will be executed after
570 all the others.
571
572 The app now uses the `RequestTimeHandler` middleware for every requests received
573 by the Popcorn app.
574 The page is processed the `HelloHandler` to display the index page.
575 And, before every response is sent, the `LogHandler` is activated to display the
576 log line.
577
578 Because you have access to the request object, the response object, and all the
579 Popcorn API, the possibilities with middleware functions are endless.
580
581 ### Built-in middlewares
582
583 Starting with version 0.1, Popcorn provide a set of built-in middleware that can
584 be used to develop your app faster.
585
586 * `RequestClock`: initializes requests clock.
587 * `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
588 * `SessionInit`: initializes requests session (see the `Sessions` section).
589 * `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
590 * `Router`: a mountable mini-app (see the `Mountable routers` section).
591
592 ## Mountable routers
593
594 Use the `Router` class to create modular, mountable route handlers.
595 A Router instance is a complete middleware and routing system; for this reason,
596 it is often referred to as a “mini-app”.
597
598 The following example creates a router as a module, loads a middleware handler in it,
599 defines some routes, and mounts the router module on a path in the main app.
600
601 ~~~
602 import popcorn
603
604 class AppHome
605         super Handler
606
607         redef fun get(req, res) do res.send "Site Home"
608 end
609
610 class UserLogger
611         super Handler
612
613         redef fun all(req, res) do print "User logged"
614 end
615
616 class UserHome
617         super Handler
618
619         redef fun get(req, res) do res.send "User Home"
620 end
621
622 class UserProfile
623         super Handler
624
625         redef fun get(req, res) do res.send "User Profile"
626 end
627
628 var user_router = new Router
629 user_router.use("/*", new UserLogger)
630 user_router.use("/", new UserHome)
631 user_router.use("/profile", new UserProfile)
632
633 var app = new App
634 app.use("/", new AppHome)
635 app.use("/user", user_router)
636 app.listen("localhost", 3000)
637 ~~~
638
639 The app will now be able to handle requests to /user and /user/profile, as well
640 as call the `Time` middleware handler that is specific to the route.
641
642 ## Error handling
643
644 **Error handling** is based on middleware handlers.
645
646 Define error-handling middlewares in the same way as other middleware handlers:
647
648 ~~~
649 import popcorn
650
651 class SimpleErrorHandler
652         super Handler
653
654         redef fun all(req, res) do
655                 if res.status_code != 200 then
656                         print "An error occurred! {res.status_code})"
657                 end
658         end
659 end
660
661 class HelloHandler
662         super Handler
663
664         redef fun get(req, res) do res.send "Hello World!"
665 end
666
667 var app = new App
668 app.use("/", new HelloHandler)
669 app.use("/*", new SimpleErrorHandler)
670 app.listen("localhost", 3000)
671 ~~~
672
673 In this example, every non-200 response is caught by the `SimpleErrorHandler`
674 that print an error in stdout.
675
676 By defining multiple middleware error handlers, you can take multiple action depending
677 on the kind of error or the kind of interface you provide (HTML, XML, JSON...).
678
679 Here an example of the 404 custom error page in HTML:
680
681 ~~~
682 import popcorn
683 import template
684
685 class HtmlErrorTemplate
686         super Template
687
688         var status: Int
689         var message: nullable String
690
691         redef fun rendering do add """
692                 <!DOCTYPE html>
693                 <html>
694                 <head>
695                         <meta charset="utf-8">
696                         <title>{{{message or else status}}}</title>
697                 </head>
698                 <body>
699                 <h1>{{{status}}} {{{message or else ""}}}</h1>
700                 </body>
701                 </html>"""
702 end
703
704 class HtmlErrorHandler
705         super Handler
706
707         redef fun all(req, res) do
708                 if res.status_code != 200 then
709                         res.send(new HtmlErrorTemplate(res.status_code, "An error occurred!"))
710                 end
711         end
712 end
713
714 var app = new App
715 app.use("/*", new HtmlErrorHandler)
716 app.listen("localhost", 3000)
717 ~~~
718
719 ## Sessions
720
721 **Sessions** can be used thanks to the built-in `SessionMiddleware`.
722
723 Here a simple example of login button that define a value in the `req` session.
724
725 ~~~
726 import popcorn
727
728 redef class Session
729         var is_logged = false
730 end
731
732 class AppLogin
733         super Handler
734
735         redef fun get(req, res) do
736                 res.html """
737                 <p>Is logged: {{{req.session.as(not null).is_logged}}}</p>
738                 <form action="/" method="POST">
739                         <input type="submit" value="Login" />
740                 </form>"""
741         end
742
743         redef fun post(req, res) do
744                 req.session.as(not null).is_logged = true
745                 res.redirect("/")
746         end
747 end
748
749 var app = new App
750 app.use("/*", new SessionInit)
751 app.use("/", new AppLogin)
752 app.listen("localhost", 3000)
753 ~~~
754
755 Notice the use of the `SessionInit` on the `/*` route. You must use the
756 `SessionInit` first to initialize the request session.
757 Without that, your request session will be set to `null`.
758 If you don't use sessions in your app, you do not need to include that middleware.
759
760 ## Database integration
761
762 ### Mongo DB
763
764 If you want to persist your data, Popcorn works well with MongoDB.
765
766 In this example, we will show how to store and list user with a Mongo database.
767
768 First let's define a handler that access the database to list all the user.
769 The mongo database reference is passed to the UserList handler through the `db` attribute.
770
771 Then we define a handler that displays the user creation form on GET requests.
772 POST requests are used to save the user data.
773
774 ~~~
775 import popcorn
776 import mongodb
777 import template
778
779 class UserList
780         super Handler
781
782         var db: MongoDb
783
784         redef fun get(req, res) do
785                 var users = db.collection("users").find_all(new JsonObject)
786
787                 var tpl = new Template
788                 tpl.add "<h1>Users</h1>"
789                 tpl.add "<table>"
790                 for user in users do
791                         tpl.add """<tr>
792                                 <td>{{{user["login"] or else "null"}}}</td>
793                                 <td>{{{user["password"] or else "null"}}}</td>
794                         </tr>"""
795                 end
796                 tpl.add "</table>"
797                 res.html tpl
798         end
799 end
800
801 class UserForm
802         super Handler
803
804         var db: MongoDb
805
806         redef fun get(req, res) do
807                 var tpl = new Template
808                 tpl.add """<h1>Add a new user</h1>
809                 <form action="/new" method="POST">
810                         <input type="text" name="login" />
811                         <input type="password" name="password" />
812                         <input type="submit" value="save" />
813                 </form>"""
814                 res.html tpl
815         end
816
817         redef fun post(req, res) do
818                 var json = new JsonObject
819                 json["login"] = req.post_args["login"]
820                 json["password"] = req.post_args["password"]
821                 db.collection("users").insert(json)
822                 res.redirect "/"
823         end
824 end
825
826 var mongo = new MongoClient("mongodb://localhost:27017/")
827 var db = mongo.database("mongo_example")
828
829 var app = new App
830 app.use("/", new UserList(db))
831 app.use("/new", new UserForm(db))
832 app.listen("localhost", 3000)
833 ~~~
834
835 ## Angular.JS integration
836
837 Loving [AngularJS](https://angularjs.org/)? Popcorn is made for Angular and for you!
838
839 Using the StaticHandler with a glob route, you can easily redirect all HTTP requests
840 to your angular controller:
841
842 ~~~
843 import popcorn
844
845 var app = new App
846 app.use("/*", new StaticHandler("my-ng-app/", "index.html"))
847 app.listen("localhost", 3000)
848 ~~~
849
850 Because the StaticHandler will not find the angular routes as static files,
851 you must specify the path to the default angular controller.
852 In this example, the StaticHandler will redirect any unknown requests to the `index.html`
853 angular controller.
854
855 See the examples for a more detailed use case working with a JSON API.