lib/nitcorn: handles uri parameters in reactor
[nit.git] / lib / nitcorn / reactor.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # Core of the `nitcorn` project, provides `HttpFactory` and `Action`
19 module reactor
20
21 import more_collections
22 import libevent
23
24 import vararg_routes
25 import http_request
26 import http_response
27
28 # A server handling a single connection
29 class HttpServer
30 super Connection
31
32 # The associated `HttpFactory`
33 var factory: HttpFactory
34
35 # Init the server using `HttpFactory`.
36 init(buf_ev: NativeBufferEvent, factory: HttpFactory) is old_style_init do
37 self.factory = factory
38 end
39
40 private var parser = new HttpRequestParser is lazy
41
42 redef fun read_callback(str)
43 do
44 # TODO support bigger inputs (such as big forms and file upload)
45
46 var request_object = parser.parse_http_request(str.to_s)
47
48 if request_object != null then delegate_answer request_object
49 end
50
51 # Answer to a request
52 fun delegate_answer(request: HttpRequest)
53 do
54 # Find target virtual host
55 var virtual_host = null
56 if request.header.keys.has("Host") then
57 var host = request.header["Host"]
58 if host.index_of(':') == -1 then host += ":80"
59 for vh in factory.config.virtual_hosts do
60 for i in vh.interfaces do if i.to_s == host then
61 virtual_host = vh
62 break label
63 end
64 end label
65 end
66
67 # Use default virtual host if none already responded
68 if virtual_host == null then virtual_host = factory.config.default_virtual_host
69
70 # Get a response from the virtual host
71 var response
72 if virtual_host != null then
73 var route = virtual_host.routes[request.uri]
74 if route != null then
75 # include uri parameters in request
76 request.uri_params = route.parse_params(request.uri)
77
78 var handler = route.handler
79 var root = route.path
80 var turi
81 if root != null then
82 turi = ("/" + request.uri.substring_from(root.length)).simplify_path
83 else turi = request.uri
84 response = handler.answer(request, turi)
85 else response = new HttpResponse(405)
86 else response = new HttpResponse(405)
87
88 # Send back a response
89 write response.to_s
90 close
91 end
92 end
93
94 redef abstract class Action
95 # Handle a request with the relative URI `truncated_uri`
96 #
97 # `request` is fully formed request object and has a reference to the session
98 # if one preexists.
99 #
100 # `truncated_uri` is the ending of the full request URI, truncated from the route
101 # leading to this `Action`.
102 fun answer(request: HttpRequest, truncated_uri: String): HttpResponse is abstract
103 end
104
105 # Factory to create `HttpServer` instances, and hold the libevent base handler
106 class HttpFactory
107 super ConnectionFactory
108
109 # Configuration of this server
110 #
111 # It should be populated after this object has instanciated
112 var config = new ServerConfig.with_factory(self)
113
114 # Instanciate a server and libvent
115 #
116 # You can use this to create the first `HttpFactory`, which is the most common.
117 init and_libevent do init(new NativeEventBase)
118
119 redef fun spawn_connection(buf_ev) do return new HttpServer(buf_ev, self)
120
121 # Launch the main loop of this server
122 fun run
123 do
124 event_base.dispatch
125 event_base.destroy
126 end
127 end
128
129 redef class ServerConfig
130 # Handle to retreive the `HttpFactory` on config change
131 private var factory: HttpFactory
132
133 private init with_factory(factory: HttpFactory) do self.factory = factory
134 end
135
136 redef class Sys
137 # Active listeners
138 private var listeners = new HashMap2[String, Int, ConnectionListener]
139
140 # Hosts needong each listener
141 private var listeners_count = new HashMap2[String, Int, Int]
142
143 # Activate a listener on `interfac` if there's not already one
144 private fun listen_on(interfac: Interface, factory: HttpFactory)
145 do
146 if interfac.registered then return
147
148 var name = interfac.name
149 var port = interfac.port
150
151 var listener = listeners[name, port]
152 if listener == null then
153 listener = factory.bind_to(name, port)
154 if listener != null then
155 sys.listeners[name, port] = listener
156 listeners_count[name, port] = 1
157 end
158 else
159 listeners_count[name, port] += 1
160 end
161
162 interfac.registered = true
163 end
164
165 # TODO close listener
166 end
167
168 redef class Interface
169 # Has `self` been registered by `listen_on`?
170 private var registered = false
171 end
172
173 redef class Interfaces
174 redef fun add(e)
175 do
176 super
177 if vh.server_config != null then sys.listen_on(e, vh.server_config.factory)
178 end
179
180 # TODO remove
181 end
182
183 redef class VirtualHosts
184 redef fun add(e)
185 do
186 super
187 for i in e.interfaces do sys.listen_on(i, config.factory)
188 end
189
190 # TODO remove
191 end