Route handlers.

Introduced classes

class App

popcorn :: App

Popcorn application.
abstract class Handler

popcorn :: Handler

Class handler for a route.
class Router

popcorn :: Router

Mountable routers
class StaticHandler

popcorn :: StaticHandler

Static files server.

Redefined classes

redef class HttpResponse

popcorn :: pop_handlers $ HttpResponse

A response to send over HTTP

All class definitions

class App

popcorn $ App

Popcorn application.
abstract class Handler

popcorn $ Handler

Class handler for a route.
redef class HttpResponse

popcorn :: pop_handlers $ HttpResponse

A response to send over HTTP
class Router

popcorn $ Router

Mountable routers
class StaticHandler

popcorn $ StaticHandler

Static files server.
package_diagram popcorn::pop_handlers pop_handlers popcorn::pop_routes pop_routes popcorn::pop_handlers->popcorn::pop_routes csv csv popcorn::pop_handlers->csv nitcorn nitcorn popcorn::pop_routes->nitcorn core core csv->core ...nitcorn ... ...nitcorn->nitcorn ...core ... ...core->core popcorn::pop_logging pop_logging popcorn::pop_logging->popcorn::pop_handlers popcorn::pop_json pop_json popcorn::pop_json->popcorn::pop_handlers popcorn::pop_sessions pop_sessions popcorn::pop_sessions->popcorn::pop_handlers popcorn::pop_tasks pop_tasks popcorn::pop_tasks->popcorn::pop_handlers github::loader loader github::loader->popcorn::pop_logging popcorn::popcorn popcorn popcorn::popcorn->popcorn::pop_logging popcorn::popcorn->popcorn::pop_sessions popcorn::popcorn->popcorn::pop_tasks github::loader... ... github::loader...->github::loader popcorn::popcorn... ... popcorn::popcorn...->popcorn::popcorn popcorn::pop_auth pop_auth popcorn::pop_auth->popcorn::pop_json popcorn::pop_auth->popcorn::pop_sessions popcorn::pop_templates pop_templates popcorn::pop_templates->popcorn::pop_json popcorn::pop_tracker pop_tracker popcorn::pop_tracker->popcorn::pop_json popcorn::example_angular example_angular popcorn::example_angular->popcorn::pop_json popcorn::pop_auth... ... popcorn::pop_auth...->popcorn::pop_auth popcorn::pop_templates... ... popcorn::pop_templates...->popcorn::pop_templates popcorn::pop_tracker... ... popcorn::pop_tracker...->popcorn::pop_tracker popcorn::example_angular... ... popcorn::example_angular...->popcorn::example_angular

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module base64

base64 :: base64

Offers the base 64 encoding and decoding algorithms
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module file_server

nitcorn :: file_server

Provides the FileServer action, which is a standard and minimal file server
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module http_errors

nitcorn :: http_errors

Offers ErrorTemplate to display error pages
module http_request

nitcorn :: http_request

Provides the HttpRequest class and services to create it
module http_request_buffer

nitcorn :: http_request_buffer

Http request parsing for buffered inputs.
module http_response

nitcorn :: http_response

Provides the HttpResponse class and http_status_codes
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module libevent

libevent :: libevent

Low-level wrapper around the libevent library to manage events on file descriptors
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module md5

md5 :: md5

Native MD5 digest implementation as Text::md5
module media_types

nitcorn :: media_types

Services to identify Internet media types (or MIME types, Content-types)
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module native

core :: native

Native structures for text and bytes
module nitcorn

nitcorn :: nitcorn

The nitcorn Web server framework creates server-side Web apps in Nit
module numeric

core :: numeric

Advanced services for Numeric types
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module reactor

nitcorn :: reactor

Core of the nitcorn project, provides HttpFactory and Action
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module server_config

nitcorn :: server_config

Classes and services to configure the server
module sessions

nitcorn :: sessions

Automated session management
module signal_handler

nitcorn :: signal_handler

Handle SIGINT and SIGTERM to close the server after all active events
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module token

nitcorn :: token

Simple generate_token service, independent of the rest of the nitcorn framework
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module vararg_routes

nitcorn :: vararg_routes

Routes with parameters.

Parents

module csv

csv :: csv

CSV document handling.
module pop_routes

popcorn :: pop_routes

Internal routes representation.

Children

module pop_json

popcorn :: pop_json

Introduce useful services for JSON REST API handlers.
module pop_sessions

popcorn :: pop_sessions

Session handlers
module pop_tasks

popcorn :: pop_tasks

Popcorn threaded tasks

Descendants

# Route handlers.
module pop_handlers

import pop_routes
import csv

# Class handler for a route.
#
# **Routing** refers to determining how an application responds to a client request
# to a particular endpoint, which is a URI (or path) and a specific HTTP request
# method GET, POST, PUT or DELETE (other methods are not suported yet).
#
# Each route can have one or more handler methods, which are executed when the route is matched.
#
# Route handlers definition takes the following form:
#
# ~~~nitish
# class MyHandler
#	super Handler
#
#	redef fun METHOD(req, res) do end
# end
# ~~~
#
# Where:
# * `MyHandler` is the name of the handler you will add to the app.
# * `METHOD` can be replaced by `get`, `post`, `put` or `delete`.
#
# The following example responds with `Hello World!` to GET and POST requests:
#
# ~~~
# class MyHandler
#	super Handler
#
#	redef fun get(req, res) do res.send "Got a GET request"
#	redef fun post(req, res) do res.send "Got a POST request"
# end
# ~~~
#
# To make your handler responds to a specific route, you have to add it to the app.
#
# Respond to POST request on the root route (`/`), the application's home page:
#
# ~~~
# var app = new App
# app.use("/", new MyHandler)
# ~~~
#
# Respond to a request to the `/user` route:
#
# ~~~
# app.use("/user", new MyHandler)
# ~~~
abstract class Handler

	# Call `all(req, res)` if `route` matches `uri`.
	private fun handle(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do
		if route.match(uri) then
			if route isa AppParamRoute then
				req.uri_params = route.parse_uri_parameters(uri)
			end
			all(req, res)
		end
	end

	# Handler to all kind of HTTP request methods.
	#
	# `all` is a special request handler, which is not derived from any
	# HTTP method. This method is used to respond at a path for all request methods.
	#
	# In the following example, the handler will be executed for requests to "/user"
	# whether you are using GET, POST, PUT, DELETE, or any other HTTP request method.
	#
	# ~~~
	# class AllHandler
	#	super Handler
	#
	#	redef fun all(req, res) do res.send "Every request to the homepage"
	# end
	# ~~~
	#
	# Using the `all` method you can also implement other HTTP request methods.
	#
	# ~~~
	# class MergeHandler
	#	super Handler
	#
	#	redef fun all(req, res) do
	#		if req.method == "MERGE" then
	#			# handle that method
	#		else super # keep handle GET, POST, PUT and DELETE methods
	#	end
	# end
	# ~~~
	fun all(req: HttpRequest, res: HttpResponse) do
		if req.method == "GET" then
			get(req, res)
		else if req.method == "POST" then
			post(req, res)
		else if req.method == "PUT" then
			put(req, res)
		else if req.method == "DELETE" then
			delete(req, res)
		else
			res.status_code = 405
		end
	end

	# GET handler.
	#
	# Exemple of route responding to GET requests.
	# ~~~
	# class GetHandler
	#	super Handler
	#
	#	redef fun get(req, res) do res.send "GETrequest received"
	# end
	# ~~~
	fun get(req: HttpRequest, res: HttpResponse) do end

	# POST handler.
	#
	# Exemple of route responding to POST requests.
	# ~~~
	# class PostHandler
	#	super Handler
	#
	#	redef fun post(req, res) do res.send "POST request received"
	# end
	# ~~~
	fun post(req: HttpRequest, res: HttpResponse) do end

	# PUT handler.
	#
	# Exemple of route responding to PUT requests.
	# ~~~
	# class PutHandler
	#	super Handler
	#
	#	redef fun put(req, res) do res.send "PUT request received"
	# end
	# ~~~
	fun put(req: HttpRequest, res: HttpResponse) do end

	# DELETE handler.
	#
	# Exemple of route responding to PUT requests.
	# ~~~
	# class DeleteHandler
	#	super Handler
	#
	#	redef fun delete(req, res) do res.send "DELETE request received"
	# end
	# ~~~
	fun delete(req: HttpRequest, res: HttpResponse) do end
end

# Static files server.
#
# To serve static files such as images, CSS files, and JavaScript files, use the
# Popcorn built-in handler `StaticHandler`.
#
# Pass the name of the directory that contains the static assets to the StaticHandler
# init method to start serving the files directly.
# For example, use the following code to serve images, CSS files, and JavaScript files
# in a directory named `public`:
#
# ~~~
# var app = new App
# app.use("/", new StaticHandler("public/"))
# ~~~
#
# Now, you can load the files that are in the `public` directory:
#
# ~~~raw
# http://localhost:3000/images/trollface.jpg
# http://localhost:3000/css/style.css
# http://localhost:3000/js/app.js
# http://localhost:3000/hello.html
# ~~~
#
# Popcorn looks up the files relative to the static directory, so the name of the
# static directory is not part of the URL.
# To use multiple static assets directories, add the `StaticHandler` multiple times:
#
# ~~~
# app.use("/", new StaticHandler("public/"))
# app.use("/", new StaticHandler("files/"))
# ~~~
#
# Popcorn looks up the files in the order in which you set the static directories
# with the `use` method.
#
# To create a virtual path prefix (where the path does not actually exist in the file system)
# for files that are served by the `StaticHandler`, specify a mount path for the
# static directory, as shown below:
#
# ~~~
# app.use("/static/", new StaticHandler("public/"))
# ~~~
#
# Now, you can load the files that are in the public directory from the `/static`
# path prefix.
#
# ~~~raw
# http://localhost:3000/static/images/trollface.jpg
# http://localhost:3000/static/css/style.css
# http://localhost:3000/static/js/app.js
# http://localhost:3000/static/hello.html
# ~~~
#
# However, the path that you provide to the `StaticHandler` is relative to the
# directory from where you launch your app.
# If you run the app from another directory, it’s safer to use the absolute path of
# the directory that you want to serve.
class StaticHandler
	super Handler

	# Static files directory to serve.
	var static_dir: String

	# Default file to serve if nothing matches the request.
	#
	# `null` for no default file.
	var default_file: nullable String

	# Internal file server used to lookup and render files.
	var file_server: FileServer is lazy do
		var srv = new FileServer(static_dir)
		srv.show_directory_listing = false
		srv.default_file = default_file
		return srv
	end

	redef fun handle(route, uri, req, res) do
		var answer = file_server.answer(req, route.uri_root(uri))
		if answer.status_code == 200 then
			res.status_code = answer.status_code
			res.header.add_all answer.header
			res.files.add_all answer.files
			res.send
		else if answer.status_code != 404 then
			res.status_code = answer.status_code
		end
	end
end

# Mountable routers
#
# Use the `Router` class to create modular, mountable route handlers.
# A Router instance is a complete middleware and routing system; for this reason,
# it is often referred to as a “mini-app”.
#
# The following example creates a router as a module, loads a middleware handler in it,
# defines some routes, and mounts the router module on a path in the main app.
#
# ~~~
# class AppHome
#	super Handler
#
#	redef fun get(req, res) do res.send "Site Home"
# end
#
# class UserLogger
#	super Handler
#
#	redef fun all(req, res) do print "User logged"
# end
#
# class UserHome
#	super Handler
#
#	redef fun get(req, res) do res.send "User Home"
# end
#
# class UserProfile
#	super Handler
#
#	redef fun get(req, res) do res.send "User Profile"
# end
#
# var user_router = new Router
# user_router.use("/*", new UserLogger)
# user_router.use("/", new UserHome)
# user_router.use("/profile", new UserProfile)
#
# var app = new App
# app.use("/", new AppHome)
# app.use("/user", user_router)
# ~~~
#
# The app will now be able to handle requests to /user and /user/profile, as well
# as call the `Time` middleware handler that is specific to the route.
class Router
	super Handler

	# List of handlers to match with requests.
	private var handlers = new Map[AppRoute, Handler]

	# List of handlers to match before every other.
	private var pre_handlers = new Map[AppRoute, Handler]

	# List of handlers to match after every other.
	private var post_handlers = new Map[AppRoute, Handler]

	# Register a `handler` for a route `path`.
	#
	# Route paths are matched in registration order.
	fun use(path: String, handler: Handler) do
		var route = build_route(handler, path)
		handlers[route] = handler
	end

	# Register a pre-handler for a route `path`.
	#
	# Prehandlers are matched before every other handlers in registrastion order.
	fun use_before(path: String, handler: Handler) do
		var route = build_route(handler, path)
		pre_handlers[route] = handler
	end

	# Register a post-handler for a route `path`.
	#
	# Posthandlers are matched after every other handlers in registrastion order.
	fun use_after(path: String, handler: Handler) do
		var route = build_route(handler, path)
		post_handlers[route] = handler
	end

	redef fun handle(route, uri, req, res) do
		if not route.match(uri) then return
		handle_pre(route, uri, req, res)
		handle_in(route, uri, req, res)
		handle_post(route, uri, req, res)
	end

	private fun handle_pre(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do
		for hroute, handler in pre_handlers do
			handler.handle(hroute, route.uri_root(uri), req, res)
		end
	end

	private fun handle_in(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do
		for hroute, handler in handlers do
			handler.handle(hroute, route.uri_root(uri), req, res)
			if res.sent then break
		end
	end

	private fun handle_post(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do
		for hroute, handler in post_handlers do
			handler.handle(hroute, route.uri_root(uri), req, res)
		end
	end

	private fun build_route(handler: Handler, path: String): AppRoute do
		if handler isa Router or handler isa StaticHandler then
			return new AppGlobRoute(path)
		else if path.has_suffix("*") then
			return new AppGlobRoute(path)
		else
			return new AppParamRoute(path)
		end
	end
end

# Popcorn application.
#
# The `App`  is the main point of the application.
# It acts as a `Router` that holds the top level route handlers.
#
# Here an example to create a simple web app with Popcorn:
#
# ~~~
# import popcorn
#
# class HelloHandler
#	super Handler
#
#	redef fun get(req, res) do res.html "<h1>Hello World!</h1>"
# end
#
# var app = new App
# app.use("/", new HelloHandler)
# # app.listen("localhost", 3000)
# ~~~
#
# The Popcorn app listens on port 3000 for connections.
# The app responds with "Hello World!" for request to the root URL (`/`) or **route**.
# For every other path, it will respond with a **404 Not Found**.
#
# The `req` (request) and `res` (response) parameters are the same that nitcorn provides
# so you can do anything else you would do in your route without Popcorn involved.
#
# Run the app with the following command:
#
# ~~~bash
# nitc app.nit && ./app
# ~~~
#
# Then, load [http://localhost:3000](http://localhost:3000) in a browser to see the output.
class App
	super Router
end

redef class HttpResponse

	# Was this request sent by a handler?
	var sent = false

	private fun check_sent do
		if sent then print "Warning: Headers already sent!"
	end

	# Write data in body response and send it.
	fun send(raw_data: nullable Writable, status: nullable Int) do
		if raw_data != null then
			body = raw_data
		end
		if status != null then
			status_code = status
		else
			status_code = 200
		end
		check_sent
		sent = true
	end

	# Write data as HTML and set the right content type header.
	fun html(html: nullable Writable, status: nullable Int) do
		header["Content-Type"] = media_types["html"].as(not null)
		send(html, status)
	end

	# Write data as CSV and set the right content type header.
	fun csv(csv: nullable CsvDocument, status: nullable Int) do
		header["Content-Type"] = media_types["csv"].as(not null)
		if csv == null then
			send(null, status)
		else
			send(csv.write_to_string, status)
		end
	end

	# Redirect response to `location`
	#
	# Use by default 303 See Other as it is the RFC
	# way to redirect web applications to a new URI.
	fun redirect(location: String, status: nullable Int) do
		header["Location"] = location
		if status != null then
			status_code = status
		else
			status_code = 303
		end
		check_sent
		sent = true
	end

	# TODO The error message should be parameterizable.
	fun error(status: Int) do
		html("Error", status)
	end
end
lib/popcorn/pop_handlers.nit:17,1--480,3