2 -- Copyright (C) 2008-2012 Matthew Wild
3 -- Copyright (C) 2008-2012 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
10 module
:depends("http_errors");
12 local portmanager
= require
"core.portmanager";
13 local moduleapi
= require
"core.moduleapi";
14 local url_parse
= require
"socket.url".parse
;
15 local url_build
= require
"socket.url".build
;
16 local normalize_path
= require
"util.http".normalize_path
;
18 local server
= require
"net.http.server";
20 server
.set_default_host(module
:get_option_string("http_default_host"));
22 server
.set_option("body_size_limit", module
:get_option_number("http_max_content_size"));
23 server
.set_option("buffer_size_limit", module
:get_option_number("http_max_buffer_size"));
25 local function get_http_event(host
, app_path
, key
)
26 local method
, path
= key
:match("^(%S+)%s+(.+)$");
27 if not method
then -- No path specified, default to "" (base path)
28 method
, path
= key
, "";
30 if method
:sub(1,1) == "/" then
33 if app_path
== "/" and path
:sub(1,1) == "/" then
37 return method
:upper().." "..app_path
..path
;
39 return method
:upper().." "..host
..app_path
..path
;
43 local function get_base_path(host_module
, app_name
, default_app_path
)
44 return (normalize_path(host_module
:get_option("http_paths", {})[app_name
] -- Host
45 or module
:get_option("http_paths", {})[app_name
] -- Global
46 or default_app_path
)) -- Default
47 :gsub("%$(%w+)", { host
= host_module
.host
});
50 local function redir_handler(event
)
51 event
.response
.headers
.location
= event
.request
.path
.."/";
52 if event
.request
.url
.query
then
53 event
.response
.headers
.location
= event
.response
.headers
.location
.. "?" .. event
.request
.url
.query
58 local ports_by_scheme
= { http
= 80, https
= 443, };
60 -- Helper to deduce a module's external URL
61 function moduleapi
.http_url(module
, app_name
, default_path
)
62 app_name
= app_name
or (module
.name
:gsub("^http_", ""));
63 local external_url
= url_parse(module
:get_option_string("http_external_url")) or {};
64 if external_url
.scheme
and external_url
.port
== nil then
65 external_url
.port
= ports_by_scheme
[external_url
.scheme
];
67 local services
= portmanager
.get_active_services();
68 local http_services
= services
:get("https") or services
:get("http") or {};
69 for interface
, ports
in pairs(http_services
) do -- luacheck: ignore 213/interface
70 for port
, service
in pairs(ports
) do -- luacheck: ignore 512
72 scheme
= (external_url
.scheme
or service
[1].service
.name
);
73 host
= (external_url
.host
or module
:get_option_string("http_host", module
.host
));
74 port
= tonumber(external_url
.port
) or port
or 80;
75 path
= normalize_path(external_url
.path
or "/", true)..
76 (get_base_path(module
, app_name
, default_path
or "/"..app_name
):sub(2));
78 if ports_by_scheme
[url
.scheme
] == url
.port
then url
.port
= nil end
79 return url_build(url
);
82 module
:log("warn", "No http ports enabled, can't generate an external URL");
83 return "http://disabled.invalid/";
86 function module
.add_host(module
)
87 local host
= module
.host
;
89 host
= module
:get_option_string("http_host", host
);
92 module
.environment
.apps
= apps
;
93 local function http_app_added(event
)
94 local app_name
= event
.item
.name
;
95 local default_app_path
= event
.item
.default_path
or "/"..app_name
;
96 local app_path
= get_base_path(module
, app_name
, default_app_path
);
99 module
:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)");
102 apps
[app_name
] = apps
[app_name
] or {};
103 local app_handlers
= apps
[app_name
];
104 for key
, handler
in pairs(event
.item
.route
or {}) do
105 local event_name
= get_http_event(host
, app_path
, key
);
107 if type(handler
) ~= "function" then
108 local data
= handler
;
109 handler
= function () return data
; end
110 elseif event_name
:sub(-2, -1) == "/*" then
111 local base_path_len
= #event_name
:match("/.+$");
112 local _handler
= handler
;
113 handler
= function (_event
)
114 local path
= _event
.request
.path
:sub(base_path_len
);
115 return _handler(_event
, path
);
117 module
:hook_object_event(server
, event_name
:sub(1, -3), redir_handler
, -1);
118 elseif event_name
:sub(-1, -1) == "/" then
119 module
:hook_object_event(server
, event_name
:sub(1, -2), redir_handler
, -1);
121 if not app_handlers
[event_name
] then
122 app_handlers
[event_name
] = handler
;
123 module
:hook_object_event(server
, event_name
, handler
);
125 module
:log("warn", "App %s added handler twice for '%s', ignoring", app_name
, event_name
);
128 module
:log("error", "Invalid route in %s, %q. See https://prosody.im/doc/developers/http#routes", app_name
, key
);
131 local services
= portmanager
.get_active_services();
132 if services
:get("https") or services
:get("http") then
133 module
:log("debug", "Serving '%s' at %s", app_name
, module
:http_url(app_name
, app_path
));
135 module
:log("warn", "Not listening on any ports, '%s' will be unreachable", app_name
);
139 local function http_app_removed(event
)
140 local app_handlers
= apps
[event
.item
.name
];
141 apps
[event
.item
.name
] = nil;
142 for event_name
, handler
in pairs(app_handlers
) do
143 module
:unhook_object_event(server
, event_name
, handler
);
147 module
:handle_items("http-provider", http_app_added
, http_app_removed
);
150 server
.add_host(host
);
151 function module
.unload()
152 server
.remove_host(host
);
157 module
.add_host(module
); -- set up handling on global context too
159 local trusted_proxies
= module
:get_option_set("trusted_proxies", { "127.0.0.1", "::1" })._items
;
161 local function get_ip_from_request(request
)
162 local ip
= request
.conn
:ip();
163 local forwarded_for
= request
.headers
.x_forwarded_for
;
164 if forwarded_for
then
165 forwarded_for
= forwarded_for
..", "..ip
;
166 for forwarded_ip
in forwarded_for
:gmatch("[^%s,]+") do
167 if not trusted_proxies
[forwarded_ip
] then
175 module
:wrap_object_event(server
._events
, false, function (handlers
, event_name
, event_data
)
176 local request
= event_data
.request
;
178 -- Not included in eg http-error events
179 request
.ip
= get_ip_from_request(request
);
181 return handlers(event_name
, event_data
);
184 module
:provides("net", {
186 listener
= server
.listener
;
193 module
:provides("net", {
195 listener
= server
.listener
;