UNPKG

huskee-install

Version:

Huskee server installer

231 lines (197 loc) 7.11 kB
// I use Google Closure compiler to optimize this file. // Command: npx google-closure-compiler --js=routing.js --js_output_file=routing.compiled.js --module_resolution=NODE const url = require('url') const path = require('path') // const Cache = require('./cache') const fs = require('fs') const zlib = require('zlib') const mimeModule = require('./mime') const punycode = require('punycode') const forbidden = path.join(__dirname, '../www') // let cache = new Cache() let mtimeCache = new Map() const net = require('net') const config = require('../conf/main') const hostsConfig = require('../conf/hosts') const { proxyport } = config let winStatic let sendfile const os = require('os') if(os.platform() === 'win32') { winStatic = require('./winstatic') } else { sendfile = require('../build/Release/addon') } const fsStatAsync = path => new Promise(resolve => { fs.stat(path, (err, stats) => err ? resolve(0) : resolve(stats)) }) class Connection { constructor(req, res, data) { this.req = req this.res = res this.rejected = false this.urlParsed = url.parse(req.url, true) if(data) { try { this.data = JSON.parse(data) } catch(e) { console.log(e); this.data = {} } } else this.data = this.urlParsed.query this.hostname = punycode.toUnicode(req.headers.host || '').replace(':', '_port_') this.requestPath = this.urlParsed.pathname this.extname = path.extname(this.requestPath).substring(1) this.fullPath = path.join(__dirname, '../www', String(this.hostname), String(this.requestPath)) this.protocol = req.connection.encrypted ? 'https' : 'http' // console.log(req.client._handle.fd, res.socket._handle.fd, req.client.server._handle.fd) this.fd = req.client._handle.fd this.decline = (error, errorname) => { this._endConnection(200, { error, errorname }) this.rejected = true } this.declineWithError = (error, errorname) => { this._endConnection(error, { error, errorname }) this.rejected = true } this.parseCookies = () => { const { req } = this return req.headers.cookie && req.headers.cookie.split(';').reduce ? req.headers.cookie.split(';').reduce((obj, cookie) => { const values = cookie.trim().split('=') obj[values[0]] = values[1] return obj }, {}) : false } this._connect() } async _connect() { const { fullPath, extname, res, hostname, requestPath } = this this.fullPath = path.join(this.fullPath, 'index.html') this.extname = 'html' if(!fullPath.startsWith(forbidden)) return res.end('403') const { loader } = ((hostsConfig || []).find(h => h.host === hostname) || {}) if(loader) var loaderResult = await loader({ connection: this, path: requestPath }) if(loaderResult) return const api = await this._getAPI() if(api === 'done') return const page = await this._getPage() if(page === 'unchanged') return this._endConnection(304) else if(page === 'done') return else if(page) return this._endConnectionWithPage(page) this.fullPath = fullPath this.extname = extname const file = await this._getStatic() if(file === 'unchanged') { return this._endConnection(304) } else if(file === 'done') return else if(file) return this._endConnection(200, file) this._endWith404() } async renderStatic(receivedPath) { this.fullPath = path.join(__dirname, '../www', String(this.hostname), String(receivedPath)) this.extname = receivedPath await this._getStatic() } async _getStatic() { if(winStatic) return await winStatic.bind(this)() const { req, res, fullPath, extname } = this const { headers } = req const mime = mimeModule.see(extname) const stats = await fsStatAsync(fullPath) if(!stats) return const mtime = stats.mtime.toUTCString() const { size } = stats // const fdout = this.fd const gzipped = mime.includes('text/') || mime.includes('application/') const unchanged = ( headers['cache-control'] !== 'no-cache' && headers['if-modified-since'] && headers['if-modified-since'] == mtime ) res.writeHead(unchanged ? 304 : 200, { 'Content-Encoding': gzipped ? 'gzip' : 'plain', 'Content-Type': `${mime}`, 'Cache-Control': 'no-cache', 'Last-Modified': mtime, }) if(unchanged) { res.end() return 'done' } await new Promise(resolve => fs.open(fullPath, 'r', async (error, fd) => { if(error) return console.error(error) const socket = new net.Socket() const newfd = await new Promise(resolve => { socket.connect(proxyport, () => resolve(socket._handle.fd)) }) await new Promise(resolve => { if(gzipped) socket.pipe(zlib.createGzip()).pipe(res) else socket.pipe(res) // socket.on('end', () => { // res.end() // resolve() // }) sendfile.send(fd, newfd, size) socket.end() }) resolve() })) return 'done' } async _getAPI() { const { hostname, requestPath } = this const apiPath = path.join(__dirname, '../dynamic', hostname, requestPath, 'index.js') const stats = await fsStatAsync(apiPath) if(!stats) return '' const mtime = stats.mtime.toUTCString() if(mtimeCache.get(apiPath) !== mtime) delete require.cache[require.resolve(apiPath)] try { const module = require(apiPath) mtimeCache.set(apiPath, mtime) const data = await module.connect(this) if(!data && this.rejected) return 'done' const { mime, text, code, isHTML } = data this._endConnection(code, text, mime, isHTML) return 'done' } catch(e) { //TODO: make error handling this.declineWithError(500, 'Internal Server Error') console.log(e) return '' } } async _getPage() { const pageGiven = await this._getStatic() if(!pageGiven) return '' else return pageGiven } _endConnectionWithPage(data) { this._endConnection(200, data, 'text/html; charset=utf-8') } renderFile({ file = '', mime = 'text/plain', status = 200 }) { this._endConnection(status, file, mime, true) } _endConnection(status = 200, text, mime, isHTML, isFile) { const { rejected, res } = this if(rejected) return mime = mime || (isHTML ? 'text/html; charset=utf-8' : 'application/json; charset=utf-8') try{ res.statusCode = status res.setHeader('Content-Type', mime) res.writeHead(status) } catch(e) { console.log(e) //TODO: Error checking } res.end((isHTML || isFile) ? text : JSON.stringify(text)) } _endWith404() { this._endConnection(404, 'There is no such file on the disk', 'text/html') } } exports.Connection = Connection