lib: intro the web server nitcorn
[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 server_config
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(buf_ev: NativeBufferEvent, factory: HttpFactory) do self.factory = factory
36
37 private var parser = new HttpRequestParser is lazy
38
39 redef fun read_callback(str)
40 do
41 # TODO support bigger inputs (such as big forms and file upload)
42
43 var request_object = parser.parse_http_request(str.to_s)
44
45 if request_object != null then delegate_answer request_object
46 end
47
48 # Answer to a request
49 fun delegate_answer(request: HttpRequest)
50 do
51 # Find target virtual host
52 var virtual_host = null
53 if request.header.keys.has("Host") then
54 var host = request.header["Host"]
55 if host.index_of(':') == -1 then host += ":80"
56 for vh in factory.config.virtual_hosts do
57 for i in vh.interfaces do if i.to_s == host then
58 virtual_host = vh
59 break label
60 end
61 end label
62 end
63
64 # Get a response from the virtual host
65 var response
66 if virtual_host != null then
67 var route = virtual_host.routes[request.uri]
68 if route != null then
69 var handler = route.handler
70 var root = route.path
71 var turi
72 if root != null then
73 turi = ("/" + request.uri.substring_from(root.length)).simplify_path
74 else turi = request.uri
75 response = handler.answer(request, turi)
76 else response = new HttpResponse(405)
77 else response = new HttpResponse(405)
78
79 # Send back a response
80 write response.to_s
81 close
82 end
83 end
84
85 redef abstract class Action
86 # Handle a request with the relative URI `truncated_uri`
87 #
88 # `request` is fully formed request object and has a reference to the session
89 # if one preexists.
90 #
91 # `truncated_uri` is the ending of the fulle request URI, truncated from the route
92 # leading to this `Action`.
93 fun answer(request: HttpRequest, truncated_uri: String): HttpResponse is abstract
94 end
95
96 # Factory to create `HttpServer` instances, and hold the libevent base handler
97 class HttpFactory
98 super ConnectionFactory
99
100 # Configuration of this server
101 #
102 # It should be populated after this object has instanciated
103 var config = new ServerConfig.with_factory(self)
104
105 # Instanciate a server and libvent
106 #
107 # You can use this to create the first `HttpFactory`, which is the most common.
108 init and_libevent do init(new NativeEventBase)
109
110 redef fun spawn_connection(buf_ev) do return new HttpServer(buf_ev, self)
111
112 # Launch the main loop of this server
113 fun run
114 do
115 event_base.dispatch
116 event_base.destroy
117 end
118 end
119
120 redef class ServerConfig
121 # Handle to retreive the `HttpFactory` on config change
122 private var factory: HttpFactory
123
124 private init with_factory(factory: HttpFactory) do self.factory = factory
125 end
126
127 redef class Sys
128 # Active listeners
129 private var listeners = new HashMap2[String, Int, ConnectionListener]
130
131 # Hosts needong each listener
132 private var listeners_count = new HashMap2[String, Int, Int]
133
134 # Activate a listener on `interfac` if there's not already one
135 private fun listen_on(interfac: Interface, factory: HttpFactory)
136 do
137 if interfac.registered then return
138
139 var name = interfac.name
140 var port = interfac.port
141
142 var listener = listeners[name, port]
143 if listener == null then
144 listener = factory.bind_to(name, port)
145 if listener != null then
146 sys.listeners[name, port] = listener
147 listeners_count[name, port] = 1
148 end
149 else
150 listeners_count[name, port] += 1
151 end
152
153 interfac.registered = true
154 end
155
156 # TODO close listener
157 end
158
159 redef class Interface
160 # Has `self` been registered by `listen_on`?
161 private var registered = false
162 end
163
164 redef class Interfaces
165 redef fun add(e)
166 do
167 super
168 if vh.server_config != null then sys.listen_on(e, vh.server_config.factory)
169 end
170
171 # TODO remove
172 end
173
174 redef class VirtualHosts
175 redef fun add(e)
176 do
177 super
178 for i in e.interfaces do sys.listen_on(i, config.factory)
179 end
180
181 # TODO remove
182 end