1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
25 # Class handler for a route.
27 # **Routing** refers to determining how an application responds to a client request
28 # to a particular endpoint, which is a URI (or path) and a specific HTTP request
29 # method GET, POST, PUT or DELETE (other methods are not suported yet).
31 # Each route can have one or more handler methods, which are executed when the route is matched.
33 # Route handlers definition takes the following form:
39 # redef fun METHOD(req, res) do end
44 # * `MyHandler` is the name of the handler you will add to the app.
45 # * `METHOD` can be replaced by `get`, `post`, `put` or `delete`.
47 # The following example responds with `Hello World!` to GET and POST requests:
53 # redef fun get(req, res) do res.send "Got a GET request"
54 # redef fun post(req, res) do res.send "Got a POST request"
58 # To make your handler responds to a specific route, you have to add it to the app.
60 # Respond to POST request on the root route (`/`), the application's home page:
64 # app.use("/", new MyHandler)
67 # Respond to a request to the `/user` route:
70 # app.use("/user", new MyHandler)
72 abstract class Handler
74 # Call `all(req, res)` if `route` matches `uri`.
75 private fun handle
(route
: AppRoute, uri
: String, req
: HttpRequest, res
: HttpResponse) do
76 if route
.match
(uri
) then
77 if route
isa AppParamRoute then
78 req
.uri_params
= route
.parse_uri_parameters
(uri
)
84 # Handler to all kind of HTTP request methods.
86 # `all` is a special request handler, which is not derived from any
87 # HTTP method. This method is used to respond at a path for all request methods.
89 # In the following example, the handler will be executed for requests to "/user"
90 # whether you are using GET, POST, PUT, DELETE, or any other HTTP request method.
96 # redef fun all(req, res) do res.send "Every request to the homepage"
100 # Using the `all` method you can also implement other HTTP request methods.
106 # redef fun all(req, res) do
107 # if req.method == "MERGE" then
108 # # handle that method
109 # else super # keep handle GET, POST, PUT and DELETE methods
113 fun all
(req
: HttpRequest, res
: HttpResponse) do
114 if req
.method
== "GET" then
116 else if req
.method
== "POST" then
118 else if req
.method
== "PUT" then
120 else if req
.method
== "DELETE" then
123 res
.status_code
= 405
129 # Exemple of route responding to GET requests.
134 # redef fun get(req, res) do res.send "GETrequest received"
137 fun get
(req
: HttpRequest, res
: HttpResponse) do end
141 # Exemple of route responding to POST requests.
146 # redef fun post(req, res) do res.send "POST request received"
149 fun post
(req
: HttpRequest, res
: HttpResponse) do end
153 # Exemple of route responding to PUT requests.
158 # redef fun put(req, res) do res.send "PUT request received"
161 fun put
(req
: HttpRequest, res
: HttpResponse) do end
165 # Exemple of route responding to PUT requests.
167 # class DeleteHandler
170 # redef fun delete(req, res) do res.send "DELETE request received"
173 fun delete
(req
: HttpRequest, res
: HttpResponse) do end
176 # Static files server.
178 # To serve static files such as images, CSS files, and JavaScript files, use the
179 # Popcorn built-in handler `StaticHandler`.
181 # Pass the name of the directory that contains the static assets to the StaticHandler
182 # init method to start serving the files directly.
183 # For example, use the following code to serve images, CSS files, and JavaScript files
184 # in a directory named `public`:
188 # app.use("/", new StaticHandler("public/"))
191 # Now, you can load the files that are in the `public` directory:
194 # http://localhost:3000/images/trollface.jpg
195 # http://localhost:3000/css/style.css
196 # http://localhost:3000/js/app.js
197 # http://localhost:3000/hello.html
200 # Popcorn looks up the files relative to the static directory, so the name of the
201 # static directory is not part of the URL.
202 # To use multiple static assets directories, add the `StaticHandler` multiple times:
205 # app.use("/", new StaticHandler("public/"))
206 # app.use("/", new StaticHandler("files/"))
209 # Popcorn looks up the files in the order in which you set the static directories
210 # with the `use` method.
212 # To create a virtual path prefix (where the path does not actually exist in the file system)
213 # for files that are served by the `StaticHandler`, specify a mount path for the
214 # static directory, as shown below:
217 # app.use("/static/", new StaticHandler("public/"))
220 # Now, you can load the files that are in the public directory from the `/static`
224 # http://localhost:3000/static/images/trollface.jpg
225 # http://localhost:3000/static/css/style.css
226 # http://localhost:3000/static/js/app.js
227 # http://localhost:3000/static/hello.html
230 # However, the path that you provide to the `StaticHandler` is relative to the
231 # directory from where you launch your app.
232 # If you run the app from another directory, it’s safer to use the absolute path of
233 # the directory that you want to serve.
237 # Static files directory to serve.
238 var static_dir
: String
240 # Default file to serve if nothing matches the request.
242 # `null` for no default file.
243 var default_file
: nullable String
245 # Internal file server used to lookup and render files.
246 var file_server
: FileServer is lazy
do
247 var srv
= new FileServer(static_dir
)
248 srv
.show_directory_listing
= false
249 srv
.default_file
= default_file
253 redef fun handle
(route
, uri
, req
, res
) do
254 var answer
= file_server
.answer
(req
, route
.uri_root
(uri
))
255 if answer
.status_code
== 200 then
256 res
.status_code
= answer
.status_code
257 res
.header
.add_all answer
.header
258 res
.files
.add_all answer
.files
260 else if answer
.status_code
!= 404 then
261 res
.status_code
= answer
.status_code
268 # Use the `Router` class to create modular, mountable route handlers.
269 # A Router instance is a complete middleware and routing system; for this reason,
270 # it is often referred to as a “mini-app”.
272 # The following example creates a router as a module, loads a middleware handler in it,
273 # defines some routes, and mounts the router module on a path in the main app.
279 # redef fun get(req, res) do res.send "Site Home"
285 # redef fun all(req, res) do print "User logged"
291 # redef fun get(req, res) do res.send "User Home"
297 # redef fun get(req, res) do res.send "User Profile"
300 # var user_router = new Router
301 # user_router.use("/*", new UserLogger)
302 # user_router.use("/", new UserHome)
303 # user_router.use("/profile", new UserProfile)
306 # app.use("/", new AppHome)
307 # app.use("/user", user_router)
310 # The app will now be able to handle requests to /user and /user/profile, as well
311 # as call the `Time` middleware handler that is specific to the route.
315 # List of handlers to match with requests.
316 private var handlers
= new Map[AppRoute, Handler]
318 # List of handlers to match before every other.
319 private var pre_handlers
= new Map[AppRoute, Handler]
321 # List of handlers to match after every other.
322 private var post_handlers
= new Map[AppRoute, Handler]
324 # Register a `handler` for a route `path`.
326 # Route paths are matched in registration order.
327 fun use
(path
: String, handler
: Handler) do
328 var route
= build_route
(handler
, path
)
329 handlers
[route
] = handler
332 # Register a pre-handler for a route `path`.
334 # Prehandlers are matched before every other handlers in registrastion order.
335 fun use_before
(path
: String, handler
: Handler) do
336 var route
= build_route
(handler
, path
)
337 pre_handlers
[route
] = handler
340 # Register a post-handler for a route `path`.
342 # Posthandlers are matched after every other handlers in registrastion order.
343 fun use_after
(path
: String, handler
: Handler) do
344 var route
= build_route
(handler
, path
)
345 post_handlers
[route
] = handler
348 redef fun handle
(route
, uri
, req
, res
) do
349 if not route
.match
(uri
) then return
350 handle_pre
(route
, uri
, req
, res
)
351 handle_in
(route
, uri
, req
, res
)
352 handle_post
(route
, uri
, req
, res
)
355 private fun handle_pre
(route
: AppRoute, uri
: String, req
: HttpRequest, res
: HttpResponse) do
356 for hroute
, handler
in pre_handlers
do
357 handler
.handle
(hroute
, route
.uri_root
(uri
), req
, res
)
361 private fun handle_in
(route
: AppRoute, uri
: String, req
: HttpRequest, res
: HttpResponse) do
362 for hroute
, handler
in handlers
do
363 handler
.handle
(hroute
, route
.uri_root
(uri
), req
, res
)
364 if res
.sent
then break
368 private fun handle_post
(route
: AppRoute, uri
: String, req
: HttpRequest, res
: HttpResponse) do
369 for hroute
, handler
in post_handlers
do
370 handler
.handle
(hroute
, route
.uri_root
(uri
), req
, res
)
374 private fun build_route
(handler
: Handler, path
: String): AppRoute do
375 if handler
isa Router or handler
isa StaticHandler then
376 return new AppGlobRoute(path
)
377 else if path
.has_suffix
("*") then
378 return new AppGlobRoute(path
)
380 return new AppParamRoute(path
)
385 # Popcorn application.
387 # The `App` is the main point of the application.
388 # It acts as a `Router` that holds the top level route handlers.
390 # Here an example to create a simple web app with Popcorn:
398 # redef fun get(req, res) do res.html "<h1>Hello World!</h1>"
402 # app.use("/", new HelloHandler)
403 # # app.listen("localhost", 3000)
406 # The Popcorn app listens on port 3000 for connections.
407 # The app responds with "Hello World!" for request to the root URL (`/`) or **route**.
408 # For every other path, it will respond with a **404 Not Found**.
410 # The `req` (request) and `res` (response) parameters are the same that nitcorn provides
411 # so you can do anything else you would do in your route without Popcorn involved.
413 # Run the app with the following command:
416 # nitc app.nit && ./app
419 # Then, load [http://localhost:3000](http://localhost:3000) in a browser to see the output.
424 redef class HttpResponse
426 # Was this request sent by a handler?
429 private fun check_sent
do
430 if sent
then print
"Warning: Headers already sent!"
433 # Write data in body response and send it.
434 fun send
(raw_data
: nullable Writable, status
: nullable Int) do
435 if raw_data
!= null then
436 body
+= raw_data
.write_to_string
438 if status
!= null then
447 # Write data as HTML and set the right content type header.
448 fun html
(html
: nullable Writable, status
: nullable Int) do
449 header
["Content-Type"] = media_types
["html"].as(not null)
453 # Write data as JSON and set the right content type header.
454 fun json
(json
: nullable Serializable, status
: nullable Int) do
455 header
["Content-Type"] = media_types
["json"].as(not null)
459 send
(json
.to_json
, status
)
463 # Write data as CSV and set the right content type header.
464 fun csv
(csv
: nullable CsvDocument, status
: nullable Int) do
465 header
["Content-Type"] = media_types
["csv"].as(not null)
469 send
(csv
.write_to_string
, status
)
473 # Write error as JSON.
475 # Format: `{"message": message, "status": status}`
476 fun json_error
(message
: String, status
: Int) do
477 var obj
= new JsonObject
478 obj
["status"] = status
479 obj
["message"] = message
483 # Redirect response to `location`
485 # Use by default 303 See Other as it is the RFC
486 # way to redirect web applications to a new URI.
487 fun redirect
(location
: String, status
: nullable Int) do
488 header
["Location"] = location
489 if status
!= null then
498 # TODO The error message should be parameterizable.
499 fun error
(status
: Int) do
500 html
("Error", status
)