#
# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
module reactor
import more_collections
-import libevent
-import server_config
+import vararg_routes
import http_request
+import http_request_buffer
import http_response
# A server handling a single connection
class HttpServer
- super Connection
+ super HTTPConnection
# The associated `HttpFactory`
var factory: HttpFactory
- init(buf_ev: NativeBufferEvent, factory: HttpFactory) do self.factory = factory
-
private var parser = new HttpRequestParser is lazy
- redef fun read_callback(str)
- do
- # TODO support bigger inputs (such as big forms and file upload)
+ # Human readable address of the remote client
+ var remote_address: String
+ redef fun read_http_request(str)
+ do
var request_object = parser.parse_http_request(str.to_s)
-
if request_object != null then delegate_answer request_object
end
if virtual_host != null then
var route = virtual_host.routes[request.uri]
if route != null then
+ # include uri parameters in request
+ request.uri_params = route.parse_params(request.uri)
+
var handler = route.handler
- var root = route.path
+ var root = route.resolve_path(request)
var turi
if root != null then
turi = ("/" + request.uri.substring_from(root.length)).simplify_path
else turi = request.uri
- response = handler.answer(request, turi)
- else response = new HttpResponse(405)
- else response = new HttpResponse(405)
- # Send back a response
- write response.to_s
+ # Delegate the responsibility to respond to the `Action`
+ handler.prepare_respond_and_close(request, turi, self)
+ return
+ else response = new HttpResponse(404)
+ else response = new HttpResponse(404)
+
+ respond response
close
end
+
+ # Send back `response` to the client
+ fun respond(response: HttpResponse)
+ do
+ write response.to_s
+ for path in response.files do write_file path
+ end
end
redef abstract class Action
- # Handle a request with the relative URI `truncated_uri`
+ # Prepare a `HttpResponse` destined to the client in response to the `request`
#
- # `request` is fully formed request object and has a reference to the session
- # if one preexists.
+ # `request` is fully formed request object with a reference to the session
+ # if one already exists.
#
- # `truncated_uri` is the ending of the fulle request URI, truncated from the route
+ # `truncated_uri` is the ending of the full request URI, truncated from the route
# leading to this `Action`.
fun answer(request: HttpRequest, truncated_uri: String): HttpResponse is abstract
+
+ # Full to a `request` with sending the response and closing of the `http_server`
+ #
+ # Must users only need to implement `answer`, this method is for advanced use only.
+ # It can be used to delay an answer until an event is raised or work is done on a different thread.
+ #
+ # By default this method calls `answer`, relays the response to `http_server.respond` and closes `http_server`.
+ protected fun prepare_respond_and_close(request: HttpRequest, truncated_uri: String, http_server: HttpServer)
+ do
+ var response = answer(request, truncated_uri)
+ http_server.respond response
+ http_server.close
+ end
end
# Factory to create `HttpServer` instances, and hold the libevent base handler
# It should be populated after this object has instanciated
var config = new ServerConfig.with_factory(self)
- # Instanciate a server and libvent
+ # Instantiate a server and libvent
#
# You can use this to create the first `HttpFactory`, which is the most common.
init and_libevent do init(new NativeEventBase)
- redef fun spawn_connection(buf_ev) do return new HttpServer(buf_ev, self)
+ redef fun spawn_connection(buf_ev, address) do return new HttpServer(buf_ev, self, address)
- # Launch the main loop of this server
+ # Execute the main listening loop to accept connections
+ #
+ # After the loop ends, the underlying resources are freed.
+ #
+ # When the environment variable `NIT_TESTING` is set to `true`,
+ # the loop is not executed but the resources are still freed.
fun run
do
- event_base.dispatch
- event_base.destroy
+ if "NIT_TESTING".environ != "true" then
+ event_base.dispatch
+ end
+
+ event_base.free
end
end
redef class ServerConfig
# Handle to retreive the `HttpFactory` on config change
- private var factory: HttpFactory
+ private var factory: HttpFactory is noinit
private init with_factory(factory: HttpFactory) do self.factory = factory
end
listeners_count[name, port] = 1
end
else
- listeners_count[name, port] += 1
+ var value = listeners_count[name, port].as(not null)
+ listeners_count[name, port] = value + 1
end
interfac.registered = true
redef fun add(e)
do
super
- if vh.server_config != null then sys.listen_on(e, vh.server_config.factory)
+ var config = virtual_host.server_config
+ if config != null then register_and_listen(e, config)
+ end
+
+ # Indirection to `listen_on` and check if this targets all addresses
+ private fun register_and_listen(e: Interface, config: ServerConfig)
+ do
+ listen_on(e, config.factory)
+ if e.name == "0.0.0.0" or e.name == "::0" then config.default_virtual_host = virtual_host
end
# TODO remove
redef fun add(e)
do
super
- for i in e.interfaces do sys.listen_on(i, config.factory)
+ for i in e.interfaces do e.interfaces.register_and_listen(i, config)
end
# TODO remove