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