1 # This file is part of NIT ( http://www.nitlanguage.org ).
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>
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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.
19 # Core of the `nitcorn` project, provides `HttpFactory` and `Action`
22 import more_collections
26 import http_request_buffer
29 # A server handling a single connection
33 # The associated `HttpFactory`
34 var factory
: HttpFactory
36 private var parser
= new HttpRequestParser is lazy
38 # Human readable address of the remote client
39 var remote_address
: String
41 redef fun read_http_request
(str
)
43 var request_object
= parser
.parse_http_request
(str
.to_s
)
44 if request_object
!= null then delegate_answer request_object
48 fun delegate_answer
(request
: HttpRequest)
50 # Find target virtual host
51 var virtual_host
= null
52 if request
.header
.keys
.has
("Host") then
53 var host
= request
.header
["Host"]
54 if host
.index_of
(':') == -1 then host
+= ":80"
55 for vh
in factory
.config
.virtual_hosts
do
56 for i
in vh
.interfaces
do if i
.to_s
== host
then
63 # Use default virtual host if none already responded
64 if virtual_host
== null then virtual_host
= factory
.config
.default_virtual_host
66 # Get a response from the virtual host
68 if virtual_host
!= null then
69 var route
= virtual_host
.routes
[request
.uri
]
71 # include uri parameters in request
72 request
.uri_params
= route
.parse_params
(request
.uri
)
74 var handler
= route
.handler
75 var root
= route
.resolve_path
(request
)
78 turi
= ("/" + request
.uri
.substring_from
(root
.length
)).simplify_path
79 else turi
= request
.uri
81 # Delegate the responsibility to respond to the `Action`
82 handler
.prepare_respond_and_close
(request
, turi
, self)
84 else response
= new HttpResponse(404)
85 else response
= new HttpResponse(404)
91 # Send back `response` to the client
92 fun respond
(response
: HttpResponse)
95 for path
in response
.files
do write_file path
99 redef abstract class Action
100 # Prepare a `HttpResponse` destined to the client in response to the `request`
102 # `request` is fully formed request object with a reference to the session
103 # if one already exists.
105 # `truncated_uri` is the ending of the full request URI, truncated from the route
106 # leading to this `Action`.
107 fun answer
(request
: HttpRequest, truncated_uri
: String): HttpResponse is abstract
109 # Full to a `request` with sending the response and closing of the `http_server`
111 # Must users only need to implement `answer`, this method is for advanced use only.
112 # It can be used to delay an answer until an event is raised or work is done on a different thread.
114 # By default this method calls `answer`, relays the response to `http_server.respond` and closes `http_server`.
115 protected fun prepare_respond_and_close
(request
: HttpRequest, truncated_uri
: String, http_server
: HttpServer)
117 var response
= answer
(request
, truncated_uri
)
118 http_server
.respond response
123 # Factory to create `HttpServer` instances, and hold the libevent base handler
125 super ConnectionFactory
127 # Configuration of this server
129 # It should be populated after this object has instanciated
130 var config
= new ServerConfig.with_factory
(self)
132 # Instantiate a server and libvent
134 # You can use this to create the first `HttpFactory`, which is the most common.
135 init and_libevent
do init(new NativeEventBase)
137 redef fun spawn_connection
(buf_ev
, address
) do return new HttpServer(buf_ev
, self, address
)
139 # Execute the main listening loop to accept connections
141 # After the loop ends, the underlying resources are freed.
143 # When the environment variable `NIT_TESTING` is set to `true`,
144 # the loop is not executed but the resources are still freed.
147 if "NIT_TESTING".environ
!= "true" then
155 redef class ServerConfig
156 # Handle to retreive the `HttpFactory` on config change
157 private var factory
: HttpFactory is noinit
159 private init with_factory
(factory
: HttpFactory) do self.factory
= factory
164 private var listeners
= new HashMap2[String, Int, ConnectionListener]
166 # Hosts needong each listener
167 private var listeners_count
= new HashMap2[String, Int, Int]
169 # Activate a listener on `interfac` if there's not already one
170 private fun listen_on
(interfac
: Interface, factory
: HttpFactory)
172 if interfac
.registered
then return
174 var name
= interfac
.name
175 var port
= interfac
.port
177 var listener
= listeners
[name
, port
]
178 if listener
== null then
179 listener
= factory
.bind_to
(name
, port
)
180 if listener
!= null then
181 sys
.listeners
[name
, port
] = listener
182 listeners_count
[name
, port
] = 1
185 var value
= listeners_count
[name
, port
].as(not null)
186 listeners_count
[name
, port
] = value
+ 1
189 interfac
.registered
= true
192 # TODO close listener
195 redef class Interface
196 # Has `self` been registered by `listen_on`?
197 private var registered
= false
200 redef class Interfaces
204 var config
= vh
.server_config
205 if config
!= null then sys
.listen_on
(e
, config
.factory
)
211 redef class VirtualHosts
215 for i
in e
.interfaces
do sys
.listen_on
(i
, config
.factory
)