Skip to content

Commit

Permalink
feat: support keepHeaderCase
Browse files Browse the repository at this point in the history
  • Loading branch information
hans00 committed Aug 25, 2022
1 parent 7c265fe commit 4aa9295
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 38 deletions.
2 changes: 2 additions & 0 deletions packages/server/js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ exports.templateEngine = Symbol('template engine')
exports.cache = Symbol('cache')

exports.trustProxy = Symbol('trust proxy')

exports.keepHeaderCase = Symbol('keep header case')
8 changes: 8 additions & 0 deletions packages/server/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ class fastWS extends Routes {
templateRender = render,
bodySize = '4mb',
forceStopTimeout = 5000,
keepHeaderCase = false,
logLevel = 'info'
} = options || {}
if (typeof keepHeaderCase !== 'boolean') {
throw new ServerError({
code: 'SERVER_INVALID_OPTIONS',
message: 'The option `keepHeaderCase` is invalid.'
})
}
if (!['error', 'warn', 'info', 'verbose'].includes(logLevel)) {
throw new ServerError({
code: 'SERVER_INVALID_OPTIONS',
Expand Down Expand Up @@ -66,6 +73,7 @@ class fastWS extends Routes {
this._socket = null
this.log = new Logger(verbose ? 'verbose' : logLevel)
this.params = {
[constants.keepHeaderCase]: keepHeaderCase,
[constants.maxBodySize]: bodySize,
[constants.templateEngine]: templateRender,
[constants.cache]: cache,
Expand Down
93 changes: 55 additions & 38 deletions packages/server/js/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const path = require('path')
const mime = require('mime-types')
const { Writable } = require('stream')
const parseRange = require('range-parser')
const { keepHeaderCase } = require('./constants')

const staticPath = path.resolve(process.cwd(), 'static')

Expand All @@ -16,6 +17,11 @@ function toArrayBuffer (buffer) {
}
}

const hContentType = 'Content-Type'
const hContentRange = 'Content-Range'
const hLastModified = 'Last-Modified'
const hCacheControl = 'Cache-Control'

const httpStatusCode = {
// Informational
100: 'Continue',
Expand Down Expand Up @@ -69,6 +75,8 @@ class Response extends Writable {
this._totalSize = 0
this._status = 200
this._headers = {}
this.headerCaseSensitive =
this.connection.app.getParam(keepHeaderCase)
this.headersSent = false
this.on('pipe', (src) => this._pipe(src))
this.on('finish', () => this._end())
Expand Down Expand Up @@ -101,8 +109,8 @@ class Response extends Writable {
if (checkModifyTime && checkModifyTime === file.mtime) {
return this.status(304).end()
} else {
this.setHeader('Last-Modified', file.mtime)
.setHeader('Cache-Control', cache)
this.setHeader(hLastModified, file.mtime)
.setHeader(hCacheControl, cache)
.send(file.content, file.contentType)
}
} else {
Expand All @@ -113,8 +121,8 @@ class Response extends Writable {
if (checkModifyTime && checkModifyTime === mtime) {
return this.status(304).end()
} else {
this.setHeader('Last-Modified', mtime)
.setHeader('Cache-Control', cache)
this.setHeader(hLastModified, mtime)
.setHeader(hCacheControl, cache)
.send(content, contentType)
}
}
Expand Down Expand Up @@ -159,21 +167,23 @@ class Response extends Writable {
}
if (!this.headersSent) {
this.headersSent = true
if (status !== 200) {
if (httpStatusCode[status]) {
this.connection.writeStatus(`${status} ${httpStatusCode[status]}`)
} else {
this.connection.writeStatus(status.toString())
}
}
Object.keys(headers).forEach(key => {
if (headers[key] instanceof Array) {
headers[key].forEach(data => {
this.connection.writeHeader(key, data)
})
} else {
this.connection.writeHeader(key, headers[key])
this.connection.cork(() => {
if (status !== 200) {
if (httpStatusCode[status]) {
this.connection.writeStatus(`${status} ${httpStatusCode[status]}`)
} else {
this.connection.writeStatus(status.toString())
}
}
Object.keys(headers).forEach(key => {
if (headers[key] instanceof Array) {
headers[key].forEach(data => {
this.connection.writeHeader(key, data)
})
} else {
this.connection.writeHeader(key, headers[key])
}
})
})
}
}
Expand Down Expand Up @@ -219,28 +229,25 @@ class Response extends Writable {
_setupStreamMeta (stream) {
if (this._streamMeta) return
this._streamMeta = true
let contentType = this._headers['content-type']
const hasType = this.has(hContentType)
if (stream.headers) { // HTTP
if (!this._totalSize) {
this._totalSize = Number(stream.headers['content-length'])
}
if (!contentType && stream.headers['content-type']) {
contentType = stream.headers['content-type']
if (!hasType && stream.headers['content-type']) {
this.set(hContentType, stream.headers['content-type'])
}
} else if (stream.path) { // FS
if (!this._totalSize) {
const { size } = fs.statSync(stream.path)
this._totalSize = size
}
if (!contentType) {
contentType = mime.lookup(stream.path) || 'application/octet-stream'
if (!hasType) {
this.set(hContentType, mime.lookup(stream.path) || 'application/octet-stream')
}
} else if (stream.bodyLength) { // Known size body
this._totalSize = stream.bodyLength
}
if (contentType) {
this.setHeader('Content-Type', contentType)
}
}

_pipe (stream) {
Expand Down Expand Up @@ -269,7 +276,7 @@ class Response extends Writable {
const [{ start, end }] = ranges
if (this._totalSize && end <= this._totalSize) {
this.status(206)
.setHeader('Content-Range', `bytes ${start}-${end}/${this._totalSize}`)
.setHeader(hContentRange, `bytes ${start}-${end}/${this._totalSize}`)
this._totalSize = end - start + 1
return stream.pipe(rangeStream(ranges)).pipe(this)
}
Expand All @@ -279,20 +286,18 @@ class Response extends Writable {

_end () {
if (!this.headersSent) {
this.connection.cork(() => {
this.writeHead()
this.connection.end()
})
this.writeHead()
this.connection.end()
} else if (!this._totalSize) {
this.connection.end()
}
}

send (data, contentType = null) {
if (!contentType && !this._headers['content-type'] && typeof data === 'string') {
this._headers['content-type'] = data.includes('<html>') ? 'text/html' : 'text/plain'
if (!contentType && !this.has('Content-Type') && typeof data === 'string') {
this.set(hContentType, data.includes('<html>') ? 'text/html' : 'text/plain')
} else if (contentType) {
this._headers['content-type'] = contentType
this.set(hContentType, contentType)
}
this._totalSize = data.length
this.write(data)
Expand All @@ -308,8 +313,12 @@ class Response extends Writable {
return this
}

_headerKey (key) {
return this.headerCaseSensitive ? key : key.toLowerCase()
}

getHeader (key) {
return this._headers[key.toLowerCase()]
return this._headers[this._headerKey(key)]
}

getHeaderNames () {
Expand All @@ -321,19 +330,27 @@ class Response extends Writable {
}

hasHeader (key) {
return key.toLowerCase() in this._headers
return this._headerKey(key) in this._headers
}

removeHeader (key) {
delete this._headers[key.toLowerCase()]
delete this._headers[this._headerKey(key)]
return this
}

setHeader (key, value) {
this._headers[key.toLowerCase()] = buildHeaderValue(value)
this._headers[this._headerKey(key)] = buildHeaderValue(value)
return this
}

has (key) {
return this.hasHeader(key)
}

get (key) {
return this.getHeader(key)
}

set (keyOrMap, value) {
if (typeof keyOrMap === 'object') {
for (const [key, val] of Object.entries(keyOrMap)) {
Expand Down
26 changes: 26 additions & 0 deletions test/cases/http-head.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const http = require('http')

module.exports = async function ({ HTTP_PORT }) {
await new Promise((resolve, reject) => {
const req = http.request({
hostname: 'localhost',
port: HTTP_PORT,
path: '/head',
method: 'HEAD',
}, res => {
if (res.statusCode !== 200) {
reject(new Error(`Response ${res.statusCode}`))
} else {
const testHeadPos = res.rawHeaders.indexOf('Test')
if (testHeadPos === -1 || res.rawHeaders[testHeadPos+1] !== 'A') {
reject(new Error('Header "Test" mot match'))
} else {
resolve()
}
}
})

req.on('error', reject)
req.end()
})
}
4 changes: 4 additions & 0 deletions test/prepare/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ module.exports = function (app) {
})
})

app.head('/head', (req, res) => {
res.set('Test', 'A').end()
})

app.serve('/*')

return app
Expand Down
1 change: 1 addition & 0 deletions test/prepare/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = function (port) {
return new Promise((resolve, reject) => {
try {
app(new fastWS({
keepHeaderCase: true,
bodySize: '10kb',
logLevel: 'verbose',
cache: new LRUCache({
Expand Down
1 change: 1 addition & 0 deletions test/prepare/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = function (port) {
fs.writeFileSync(cert_file_name, cert.cert)
try {
app(new fastWS({
keepHeaderCase: true,
bodySize: '10kb',
logLevel: 'verbose',
ssl: {
Expand Down

0 comments on commit 4aa9295

Please sign in to comment.