fyrejet
Version:
Web Framework for node.js that strives to provide (almost) perfect compatibility with Express, while providing better performance, where you need it.
249 lines (199 loc) • 5.9 kB
JavaScript
const { Writable, Readable } = require('stream')
const { toString, toLowerCase, forEachObject } = require('../utils')
const REQUEST_EVENT = 'request'
class HttpRequest extends Readable {
constructor (uRequest) {
super()
const q = uRequest.getQuery()
this.req = uRequest
this.url = uRequest.getUrl() + (q ? '?' + q : '')
this.method = uRequest.getMethod().toUpperCase()
this.statusCode = null
this.statusMessage = null
this.body = {}
this.headers = {}
uRequest.forEach((header, value) => {
this.headers[header] = value
})
}
getRawHeaders () {
const raw = []
forEachObject(this.headers, (header, value) => {
raw.push(header, value)
})
return raw
}
getRaw () {
return this.req
}
_read (size) {
return this.slice(0, size)
}
}
function writeAllHeaders () {
this.res.writeHeader('Date', this.server._date)
forEachObject(this.__headers, ([name, value]) => {
this.res.writeHeader(name, value)
})
this.headersSent = true
}
class HttpResponse extends Writable {
constructor (uResponse, uServer) {
super()
this.res = uResponse
this.server = uServer
this.statusCode = 200
this.statusMessage = 'OK'
this.__headers = {}
this.headersSent = false
this.res.onAborted(() => {
this.finished = this.res.finished = true
})
this.on('pipe', _ => {
if (this.finished) return
this.__isWritable = true
writeAllHeaders.call(this)
})
}
setHeader (name, value) {
const filterRegEx = new RegExp(`^${name},`, 'i')
let toSet = toString(value)
toSet = toSet.replace(filterRegEx, '')
this.__headers[toLowerCase(name)] = [name, toSet]
}
getHeaderNames () {
return Object.keys(this.__headers)
}
getHeaders () {
const headers = {}
forEachObject(this.__headers, ([, value], name) => {
headers[name] = value
})
return headers
}
getHeader (name) {
return this.__headers[toLowerCase(name)]
}
removeHeader (name) {
delete this.__headers[toLowerCase(name)]
}
write (data) {
if (this.finished) return
this.res.write(data)
}
writeHead (statusCode) {
if (this.finished) return
this.statusCode = statusCode
let headers
if (arguments.length === 2) {
headers = arguments[1]
} else if (arguments.length === 3) {
this.statusMessage = arguments[1]
headers = arguments[2]
} else {
headers = {}
}
forEachObject(headers, (value, name) => {
this.setHeader(name, value)
})
}
end (data = '') {
if (this.finished) return
if (typeof data !== 'string' && !Buffer.isBuffer(data) && !ArrayBuffer.isView(data)) {
// this is needed to check that we only send strings, buffers or Typed Arrays into uWebSockets.js. Otherwise, the HTTP request will just hang.
if (process.env.NODE_ENV !== 'production') {
data = 'Body has to be RecognizedString. Please see: https://unetworking.github.io/uWebSockets.js/generated/index.html#recognizedstring'
} else {
data = ''
}
this.statusCode = 500
this.statusMessage = 'Internal Server Error'
}
this.res.writeStatus(`${this.statusCode} ${this.statusMessage}`)
if (!this.__isWritable) {
writeAllHeaders.call(this)
}
this.finished = true
this.res.end(data)
}
getRaw () {
return this.res
}
}
function uWSCompatImpl (config = {}) {
try {
const uWS = require('uWebSockets.js')
let appType = 'App'
if (config.cert_file_name && config.key_file_name) {
appType = 'SSLApp'
}
let handler = (req, res) => {
res.statusCode = 404
res.statusMessage = 'Not Found'
res.end()
}
const uServer = uWS[appType](config).any('/*', (res, req) => {
res.finished = false
const reqWrapper = new HttpRequest(req)
const resWrapper = new HttpResponse(res, uServer)
reqWrapper.socket = {
destroy: function () {
return resWrapper.res.end()
}
} // needed for some middleware not to panic
const method = reqWrapper.method
if (method !== 'HEAD') { // 0http's low checks also that method !== 'GET', but many users would send request body with GET, unfortunately
res.onData((bytes, isLast) => {
const chunk = Buffer.from(bytes)
if (isLast) {
reqWrapper.push(chunk)
reqWrapper.push(null)
if (!res.finished) {
return handler(reqWrapper, resWrapper)
}
return
}
return reqWrapper.push(chunk)
})
} else if (!res.finished) {
handler(reqWrapper, resWrapper)
}
})
uServer._date = new Date().toUTCString()
const timer = setInterval(() => (uServer._date = new Date().toUTCString()), 1000)
const facade = {
on (event, cb) {
if (event !== REQUEST_EVENT) throw new Error(`Given "${event}" event is not supported!`)
handler = cb
},
close (cb) {
clearInterval(timer)
uWS.us_listen_socket_close(uServer._socket)
if (!cb) return
return cb()
}
}
facade.listen = facade.start = (port, cb) => {
uServer.listen(port, socket => {
uServer._socket = socket
if (cb) cb(socket)
})
}
facade.uwsApp = uServer
return facade
} catch (e) {
return {}
}
}
module.exports = {
uwsCompat: (config) => {
try {
require('worker_threads') // this is just a dirty and stupid check to see if worker_threads are available, as low-http-server requires them
} catch (e) {
throw new Error('You need at least node 12 to use uWebSockets.js with fyrejet OR use --experimental-worker flag')
}
return uWSCompatImpl(config)
},
uwsHttpRequest: HttpRequest,
uwsHttpResponse: HttpResponse
}