1 let analytics = require('./analytics')
2 let assert = require('assert')
3 let cookie = require('cookie')
4 let crypto = require('crypto')
6 let config = require('./config')
7 let resources = require('./resources')
8 let util = require('util')
9 let url = require('url')
10 let XDate = require('xdate')
12 let fs_readFile = util.promisify(fs.readFile)
13 let fs_stat = util.promisify(fs.stat)
15 let serve = (res, status, mime_type, data) => {
16 res.statusCode = status
17 // html files will be direct recipient of links/bookmarks so can't have
18 // a long lifetime, other files like css or images are often large files
19 // and won't change frequently (but we'll need cache busting eventually)
21 false && //commander.enableCaching &&
22 mime_type !== config.mime_types_html
24 res.setHeader('Cache-Control', 'max-age=3600')
25 res.setHeader('Content-Type', mime_type)
26 res.setHeader('Content-Length', data.length)
31 let body = '<html><body>Page not found</body></html>'
32 serve(res, 404, config.mime_type_html, new Buffer(body, 'utf8'))
35 let redirect = (res, location) => {
37 res.setHeader('Location', location)
38 res.end('Redirecting to ' + location)
41 let app = async (req, res, protocol) => {
42 let site = req.headers.host || 'localhost'
43 let temp = site.indexOf(':')
44 let port_suffix = temp === -1 ? '' : site.substring(temp)
45 site = site.substring(0, site.length - port_suffix.length)
46 if (!config.sites.hasOwnProperty(site)) {
47 console.log('nonexistent site', site)
51 temp = config.sites[site]
53 if (temp.type === 'redirect') {
54 let site_domain = temp.domain
55 console.log('redirecting', site, 'to', site_domain)
56 redirect(res, protocol + '://' + site_domain + port_suffix + req.url)
59 else if (temp.type === 'site')
64 // parse the pathname portion of url
65 // this is actually cheating since it's not a complete url
66 let parsed_url = url.parse(req.url, true)
67 let path = parsed_url.pathname.split('/')
69 // path must begin with /
70 if (path.length === 0 || path[0].length)
73 // path elements must be findable in the file system (thus can't be empty)
75 let dir_name_is_pub = false
76 for (let i = 1; i < path.length - 1; ++i) {
77 dir_name += '/' + path[i]
78 if (path[i].length === 0 || path[i].charAt(0) === '.') {
79 console.log(site, 'bad path component', dir_name)
84 stats = await fs_stat(site_root + dir_name)
87 if (err.code !== 'ENOENT')
89 if (!dir_name_is_pub) {
90 temp = dir_name + '.pub'
92 stats = await fs_stat(site_root + temp)
94 dir_name_is_pub = true
97 if (err2.code !== 'ENOENT')
99 console.log(site, 'directory not found', dir_name)
103 if (!stats.isDirectory()) {
104 console.log(site, 'not directory', dir_name)
110 file_name = path[path.length - 1]
111 if (file_name === '') {
112 path[path.length - 1] = 'index.html'
113 path = path.join('/')
114 console.log(site, 'redirecting', parsed_url.pathname, 'to', path)
115 redirect(res, path + (parsed_url.search || ''))
118 let page = path.slice(1).join('/')
120 temp = file_name.lastIndexOf('.')
121 let file_type = temp === -1 ? '' : file_name.substring(temp + 1)
123 config.mime_types.hasOwnProperty(file_type) ?
124 config.mime_types[file_type] :
125 config.mime_type_default
127 if (file_type == 'html') {
128 if (!analytics.sessions.hasOwnProperty(site))
129 analytics.sessions[site] = {}
130 let site_sessions = analytics.sessions[site]
131 let cookies = cookie.parse(req.headers.cookie || ''), session_key
133 !cookies.hasOwnProperty('session_key') ||
134 !site_sessions.hasOwnProperty(session_key = cookies.session_key)
136 session_key = crypto.randomBytes(16).toString('hex')
137 site_sessions[session_key] = {}
139 let session = site_sessions[session_key]
141 let expires = new XDate()
143 expires = expires.toUTCString()
146 'session_key=' + session_key + '; expires=' + expires + '; path=/;'
148 session.expires = expires
150 if (!analytics.pageviews.hasOwnProperty(site))
151 analytics.pageviews[site] = {}
152 let site_pageviews = analytics.pageviews[site]
153 if (!site_pageviews.hasOwnProperty(page))
154 site_pageviews[page] = {visits: 0, unique_visits: 0}
155 let pageview = site_pageviews[page]
159 if (!session.hasOwnProperty('analytics.pageviews'))
160 session.pageviews = {}
161 let session_pageviews = session.pageviews
162 if (!session_pageviews.hasOwnProperty(page)) {
163 session_pageviews[page] = 0
164 ++pageview.unique_visits
166 ++session_pageviews[page]
168 analytics.sessions_dirty()
169 analytics.pageviews_dirty()
172 /*let*/ page = dir_name + '/' + file_name; let data
173 if (dir_name_is_pub) {
175 let data = await fs_readFile(site_root + page)
184 serve(res, 200, mime_type, data)
188 if (err.code !== 'ENOENT')
195 let data = await fs_readFile(site_root + temp)
204 serve(res, 200, mime_type, data)
208 if (err.code !== 'ENOENT')
220 query: parsed_url.query,
224 let out = str => {buffers.push(Buffer.from(str))}
225 let req = async (str, type) => {
227 str.length > 0 && str.charAt(0) === '/' ?
229 site_root + dir_name + '/'
233 result = await (await resources.req_js(path))(env, out, req)
236 result = await resources.req_js(path)
239 result = await resources.req_json(path)
242 result = await resources.req_text(path)
245 result = await resources.req_zet(path)
253 let data = Buffer.concat(buffers)
262 serve(res, 200, mime_type, data)
266 if (err.code !== 'ENOENT') // should check error type
272 temp = page + '.less'
274 let data = await resources.req_less(site_root + temp, site_root, dir_name)
283 serve(res, 200, mime_type, data)
287 if (err.code !== 'ENOENT') // note: err.code might be undefined
294 let favicons = await resources.req_zip(site_root + '/favicons.zip')
295 temp = page.substring(1) // fix this to avoid leading / on all absolute paths
296 if (favicons.hasOwnProperty(temp)) {
297 let data = favicons[temp]
306 serve(res, 200, mime_type, data)
310 console.log(site, 'file not found', page)
314 let tryApp = async (req, res, protocol) => {
315 await config.refresh()
317 await app(req, res, protocol)
320 let message = (err.stack || err.message).toString()
321 console.error(message)
322 let body = '<html><body><pre>' + message + '</pre></body></html>'
323 serve(res, 500, config.mime_type_html, new Buffer(body, 'utf8'))
327 exports.serve = serve
329 exports.redirect = redirect
331 exports.tryApp = tryApp