Commit | Line | Data |
---|---|---|
bd299b17 | 1 | var url = require('url'), |
0fa2ca42 JA |
2 | fs = require('fs'), |
3 | node_static = require('node-static'), | |
88a9b153 | 4 | Negotiator = require('negotiator'), |
840d6a6e | 5 | winston = require('winston'), |
0ca6adac D |
6 | SettingsGenerator = require('./settingsgenerator.js'), |
7 | Stats = require('./stats.js'); | |
3eb6b5fd D |
8 | |
9 | ||
10 | ||
4624f8b5 D |
11 | // Cached list of available translations |
12 | var cached_available_locales = null; | |
13 | ||
14 | ||
a8bf3ea4 | 15 | |
186531ed | 16 | var HttpHandler = function (config) { |
ceb43091 D |
17 | var public_http = config.public_http || 'client/'; |
18 | this.file_server = new node_static.Server(public_http); | |
4624f8b5 D |
19 | |
20 | if (!cached_available_locales) { | |
21 | updateLocalesCache(); | |
22 | } | |
a8bf3ea4 JA |
23 | }; |
24 | ||
186531ed D |
25 | module.exports.HttpHandler = HttpHandler; |
26 | ||
27 | ||
28 | ||
29 | HttpHandler.prototype.serve = function (request, response) { | |
b65ad8f1 | 30 | // The incoming requests base path (ie. /kiwiclient) |
ba317e87 D |
31 | var base_path, base_check, |
32 | whitelisted_folders = ['/assets', '/src'], | |
33 | is_whitelisted_folder = false; | |
b65ad8f1 | 34 | |
ba317e87 D |
35 | // Trim off any trailing slashes from the base_path |
36 | base_path = global.config.http_base_path || ''; | |
b65ad8f1 D |
37 | if (base_path.substr(base_path.length - 1) === '/') { |
38 | base_path = base_path.substr(0, base_path.length - 1); | |
39 | } | |
0fa2ca42 | 40 | |
f5114ea7 D |
41 | // Normalise the URL + remove query strings to compare against the base_path |
42 | base_check = request.url.split('?')[0]; | |
ba317e87 D |
43 | if (base_check.substr(base_check.length - 1) !== '/') { |
44 | base_check += '/'; | |
45 | } | |
46 | ||
47 | // Normalise the URL we use by removing the base path | |
48 | if (base_check.indexOf(base_path + '/') === 0) { | |
49 | request.url = request.url.replace(base_path, ''); | |
50 | ||
51 | } else if (base_check !== '/') { | |
52 | // We don't handle requests outside of the base path and not /, so just 404 | |
53 | response.writeHead(404); | |
54 | response.write('Not Found'); | |
55 | response.end(); | |
56 | return; | |
57 | } | |
58 | ||
903f9288 D |
59 | // Map any whitelisted folders to the local directories |
60 | whitelisted_folders.forEach(function(folder) { | |
ba317e87 D |
61 | if (request.url.indexOf(folder) === 0) { |
62 | is_whitelisted_folder = true; | |
63 | } | |
903f9288 | 64 | }); |
c3511215 | 65 | |
ba317e87 D |
66 | // Any requests not for whitelisted assets returns the index page |
67 | if (!is_whitelisted_folder) { | |
92f1ff73 | 68 | request.url = '/index.html'; |
b075a0d6 D |
69 | } |
70 | ||
0ca6adac D |
71 | if (request.url === '/index.html') { |
72 | Stats.incr('http.homepage'); | |
73 | } | |
74 | ||
0fa2ca42 | 75 | // If the 'magic' translation is requested, figure out the best language to use from |
b722f3c6 | 76 | // the Accept-Language HTTP header. If nothing is suitible, fallback to our en-gb default translation |
28cde487 JA |
77 | if (request.url.substr(0, 16) === '/assets/locales/') { |
78 | if (request.url === '/assets/locales/magic.json') { | |
79 | return serveMagicLocale.call(this, request, response); | |
80 | } else { | |
81 | response.setHeader('Content-Language', request.url.substr(16, request.url.indexOf('.') - 16)); | |
82 | } | |
cbcd1a23 JA |
83 | } else if (request.url.substr(0, 21) === '/assets/settings.json') { |
84 | return serveSettings.call(this, request, response); | |
0fa2ca42 | 85 | } |
0ae87edb | 86 | |
186531ed | 87 | this.file_server.serve(request, response, function (err) { |
a8bf3ea4 | 88 | if (err) { |
bd299b17 JA |
89 | response.writeHead(err.status, err.headers); |
90 | response.end(); | |
a8bf3ea4 JA |
91 | } |
92 | }); | |
0fa2ca42 JA |
93 | }; |
94 | ||
3eb6b5fd | 95 | |
88a9b153 | 96 | |
840d6a6e | 97 | |
4624f8b5 D |
98 | /** |
99 | * Cache the available locales we have so we don't read the same directory for each request | |
100 | **/ | |
101 | function updateLocalesCache() { | |
102 | cached_available_locales = []; | |
103 | ||
104 | fs.readdir(global.config.public_http + '/assets/locales', function (err, files) { | |
105 | if (err) { | |
106 | if (err.code === 'ENOENT') { | |
107 | winston.error('No locale files could be found at ' + err.path); | |
108 | } else { | |
109 | winston.error('Error reading locales.', err); | |
110 | } | |
88a9b153 | 111 | } |
4624f8b5 D |
112 | |
113 | (files || []).forEach(function (file) { | |
114 | if (file.substr(-5) === '.json') { | |
115 | cached_available_locales.push(file.slice(0, -5)); | |
116 | } | |
117 | }); | |
88a9b153 | 118 | }); |
4624f8b5 | 119 | } |
88a9b153 D |
120 | |
121 | ||
122 | ||
3eb6b5fd D |
123 | /** |
124 | * Handle the /assets/locales/magic.json request | |
125 | * Find the closest translation we have for the language | |
126 | * set in the browser. | |
127 | **/ | |
81f42bda | 128 | function serveMagicLocale(request, response) { |
88a9b153 D |
129 | var default_locale_id = 'en-gb', |
130 | found_locale, negotiator; | |
8e1ab29d | 131 | |
132ba3df D |
132 | if (!request.headers['accept-language']) { |
133 | // No accept-language specified in the request so send the default | |
88a9b153 | 134 | found_locale = default_locale_id; |
cbc8feae | 135 | |
88a9b153 D |
136 | } else { |
137 | negotiator = new Negotiator(request); | |
138 | found_locale = negotiator.language(cached_available_locales); | |
e539b24c D |
139 | |
140 | // If a locale couldn't be negotiated, use the default | |
141 | found_locale = found_locale || default_locale_id; | |
88a9b153 | 142 | } |
3eb6b5fd | 143 | |
88a9b153 D |
144 | // Send a locale to the browser |
145 | this.file_server.serveFile('/assets/locales/' + found_locale + '.json', 200, { | |
3eb6b5fd | 146 | Vary: 'Accept-Language', |
88a9b153 | 147 | 'Content-Language': found_locale |
3eb6b5fd | 148 | }, request, response); |
81f42bda | 149 | } |
cbcd1a23 | 150 | |
3eb6b5fd | 151 | |
88a9b153 | 152 | |
3eb6b5fd D |
153 | /** |
154 | * Handle the settings.json request | |
155 | */ | |
81f42bda | 156 | function serveSettings(request, response) { |
3eb6b5fd | 157 | var referrer_url, |
81f42bda | 158 | debug = false; |
3eb6b5fd D |
159 | |
160 | // Check the referrer for a debug option | |
81f42bda JA |
161 | if (request.headers.referer) { |
162 | referrer_url = url.parse(request.headers.referer, true); | |
3eb6b5fd D |
163 | if (referrer_url.query && referrer_url.query.debug) { |
164 | debug = true; | |
165 | } | |
498afd32 | 166 | } |
cbe80532 | 167 | |
81f42bda JA |
168 | SettingsGenerator.get(debug, function(err, settings) { |
169 | if (err) { | |
170 | winston.error('Error generating settings', err); | |
171 | response.writeHead(500, 'Internal Server Error'); | |
172 | return response.end(); | |
173 | } | |
174 | ||
132ba3df D |
175 | if (request.headers['if-none-match'] && request.headers['if-none-match'] === settings.hash) { |
176 | response.writeHead(304, 'Not Modified'); | |
177 | return response.end(); | |
cbcd1a23 JA |
178 | } |
179 | ||
132ba3df D |
180 | response.writeHead(200, { |
181 | 'ETag': settings.hash, | |
182 | 'Content-Type': 'application/json' | |
cbe80532 | 183 | }); |
132ba3df | 184 | response.end(settings.settings); |
cbe80532 | 185 | }); |
81f42bda | 186 | } |