@tinyhttp/send
Advanced tools
Comparing version 2.0.5 to 2.0.6
@@ -1,197 +0,144 @@ | ||
import { format, parse } from 'es-content-type'; | ||
import { eTag } from '@tinyhttp/etag'; | ||
import { Stats, statSync, createReadStream } from 'fs'; | ||
import { STATUS_CODES } from 'http'; | ||
import { isAbsolute, join, extname } from 'path'; | ||
import { contentType } from 'es-mime-types'; | ||
/** | ||
* Respond with stringified JSON object | ||
* @param res Response | ||
*/ | ||
import { parse, format } from "es-content-type"; | ||
import { eTag } from "@tinyhttp/etag"; | ||
import { Stats, statSync, createReadStream } from "fs"; | ||
import { STATUS_CODES } from "http"; | ||
import { isAbsolute, join, extname } from "path"; | ||
import { contentType } from "es-mime-types"; | ||
const json = (res) => (body, ...args) => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
if (typeof body === 'object' && body != null) | ||
res.end(JSON.stringify(body, null, 2), ...args); | ||
else if (typeof body === 'string') | ||
res.end(body, ...args); | ||
else if (body == null) { | ||
res.removeHeader('Content-Length'); | ||
res.removeHeader('Transfer-Encoding'); | ||
res.end(null, ...args); | ||
} | ||
return res; | ||
res.setHeader("Content-Type", "application/json"); | ||
if (typeof body === "object" && body != null) | ||
res.end(JSON.stringify(body, null, 2), ...args); | ||
else if (typeof body === "string") | ||
res.end(body, ...args); | ||
else if (body == null) { | ||
res.removeHeader("Content-Length"); | ||
res.removeHeader("Transfer-Encoding"); | ||
res.end(null, ...args); | ||
} | ||
return res; | ||
}; | ||
const createETag = (body, encoding) => { | ||
if (body instanceof Stats) { | ||
return eTag(body, { weak: true }); | ||
} | ||
else { | ||
return eTag(!Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body, { weak: true }); | ||
} | ||
if (body instanceof Stats) { | ||
return eTag(body, { weak: true }); | ||
} else { | ||
return eTag(!Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body, { weak: true }); | ||
} | ||
}; | ||
function setCharset(type, charset) { | ||
const parsed = parse(type); | ||
parsed.parameters.charset = charset; | ||
return format(parsed); | ||
const parsed = parse(type); | ||
parsed.parameters.charset = charset; | ||
return format(parsed); | ||
} | ||
/** | ||
* Sends the HTTP response. | ||
* | ||
* The body parameter can be a Buffer object, a string, an object, or an array. | ||
* | ||
* This method performs many useful tasks for simple non-streaming responses. | ||
* For example, it automatically assigns the Content-Length HTTP response header field (unless previously defined) and provides automatic HEAD and HTTP cache freshness support. | ||
* | ||
* @param req Request | ||
* @param res Response | ||
*/ | ||
const send = (req, res) => (body) => { | ||
let bodyToSend = body; | ||
if (Buffer.isBuffer(body)) { | ||
bodyToSend = body; | ||
} | ||
else if (typeof body === 'object' && body !== null) { | ||
// in case of object - turn it to json | ||
bodyToSend = JSON.stringify(body, null, 2); | ||
} | ||
else if (typeof body === 'string') { | ||
// reflect this in content-type | ||
const type = res.getHeader('Content-Type'); | ||
if (type && typeof type === 'string') { | ||
res.setHeader('Content-Type', setCharset(type, 'utf-8')); | ||
} | ||
else | ||
res.setHeader('Content-Type', setCharset('text/html', 'utf-8')); | ||
} | ||
// Set encoding | ||
const encoding = 'utf8'; | ||
// populate ETag | ||
let etag; | ||
if (body && !res.getHeader('etag') && (etag = createETag(bodyToSend, encoding))) { | ||
res.setHeader('etag', etag); | ||
} | ||
// freshness | ||
if (req.fresh) | ||
res.statusCode = 304; | ||
// strip irrelevant headers | ||
if (res.statusCode === 204 || res.statusCode === 304) { | ||
res.removeHeader('Content-Type'); | ||
res.removeHeader('Content-Length'); | ||
res.removeHeader('Transfer-Encoding'); | ||
bodyToSend = ''; | ||
} | ||
if (req.method === 'HEAD') { | ||
res.end(''); | ||
return res; | ||
} | ||
if (typeof body === 'object') { | ||
if (body == null) { | ||
res.end(''); | ||
return res; | ||
} | ||
else if (Buffer.isBuffer(body)) { | ||
if (!res.getHeader('Content-Type')) | ||
res.setHeader('content-type', 'application/octet-stream'); | ||
res.end(bodyToSend); | ||
} | ||
else | ||
json(res)(bodyToSend, encoding) ; | ||
} | ||
else { | ||
if (typeof bodyToSend !== 'string') | ||
bodyToSend = bodyToSend.toString(); | ||
res.end(bodyToSend, encoding) ; | ||
} | ||
let bodyToSend = body; | ||
if (Buffer.isBuffer(body)) { | ||
bodyToSend = body; | ||
} else if (typeof body === "object" && body !== null) { | ||
bodyToSend = JSON.stringify(body, null, 2); | ||
} else if (typeof body === "string") { | ||
const type = res.getHeader("Content-Type"); | ||
if (type && typeof type === "string") { | ||
res.setHeader("Content-Type", setCharset(type, "utf-8")); | ||
} else | ||
res.setHeader("Content-Type", setCharset("text/html", "utf-8")); | ||
} | ||
const encoding = "utf8"; | ||
let etag; | ||
if (body && !res.getHeader("etag") && (etag = createETag(bodyToSend, encoding))) { | ||
res.setHeader("etag", etag); | ||
} | ||
if (req.fresh) | ||
res.statusCode = 304; | ||
if (res.statusCode === 204 || res.statusCode === 304) { | ||
res.removeHeader("Content-Type"); | ||
res.removeHeader("Content-Length"); | ||
res.removeHeader("Transfer-Encoding"); | ||
bodyToSend = ""; | ||
} | ||
if (req.method === "HEAD") { | ||
res.end(""); | ||
return res; | ||
} | ||
if (typeof body === "object") { | ||
if (body == null) { | ||
res.end(""); | ||
return res; | ||
} else if (Buffer.isBuffer(body)) { | ||
if (!res.getHeader("Content-Type")) | ||
res.setHeader("content-type", "application/octet-stream"); | ||
res.end(bodyToSend); | ||
} else | ||
json(res)(bodyToSend, encoding); | ||
} else { | ||
if (typeof bodyToSend !== "string") | ||
bodyToSend = bodyToSend.toString(); | ||
res.end(bodyToSend, encoding); | ||
} | ||
return res; | ||
}; | ||
/** | ||
* Sets the response HTTP status code to statusCode and send its string representation as the response body. | ||
* | ||
* If an unsupported status code is specified, the HTTP status is still set to statusCode and the string version of the code is sent as the response body. | ||
* | ||
* @param req Request | ||
* @param res Response | ||
*/ | ||
const sendStatus = (req, res) => (statusCode) => { | ||
const body = STATUS_CODES[statusCode] || String(statusCode); | ||
res.statusCode = statusCode; | ||
res.setHeader('Content-Type', 'text/plain'); | ||
return send(req, res)(body); | ||
const body = STATUS_CODES[statusCode] || String(statusCode); | ||
res.statusCode = statusCode; | ||
res.setHeader("Content-Type", "text/plain"); | ||
return send(req, res)(body); | ||
}; | ||
/** | ||
* Sets the HTTP status for the response. It is a chainable alias of Node’s `response.statusCode`. | ||
* | ||
* @param res Response | ||
*/ | ||
const status = (res) => (status) => { | ||
res.statusCode = status; | ||
return res; | ||
const status = (res) => (status2) => { | ||
res.statusCode = status2; | ||
return res; | ||
}; | ||
const enableCaching = (res, caching) => { | ||
let cc = caching.maxAge != null && `public,max-age=${caching.maxAge}`; | ||
if (cc && caching.immutable) | ||
cc += ',immutable'; | ||
else if (cc && caching.maxAge === 0) | ||
cc += ',must-revalidate'; | ||
if (cc) | ||
res.setHeader('Cache-Control', cc); | ||
let cc = caching.maxAge != null && `public,max-age=${caching.maxAge}`; | ||
if (cc && caching.immutable) | ||
cc += ",immutable"; | ||
else if (cc && caching.maxAge === 0) | ||
cc += ",must-revalidate"; | ||
if (cc) | ||
res.setHeader("Cache-Control", cc); | ||
}; | ||
/** | ||
* Sends a file by piping a stream to response. | ||
* | ||
* It also checks for extension to set a proper `Content-Type` header. | ||
* | ||
* Path argument must be absolute. To use a relative path, specify the `root` option first. | ||
* | ||
* @param res Response | ||
*/ | ||
const sendFile = (req, res) => (path, opts = {}, cb) => { | ||
const { root, headers = {}, encoding = 'utf-8', caching, ...options } = opts; | ||
if (!isAbsolute(path) && !root) | ||
throw new TypeError('path must be absolute'); | ||
if (caching) | ||
enableCaching(res, caching); | ||
const filePath = root ? join(root, path) : path; | ||
const stats = statSync(filePath); | ||
headers['Content-Encoding'] = encoding; | ||
headers['Last-Modified'] = stats.mtime.toUTCString(); | ||
headers['Content-Type'] = contentType(extname(path)); | ||
headers['ETag'] = createETag(stats, encoding); | ||
let status = res.statusCode || 200; | ||
if (req.headers['range']) { | ||
status = 206; | ||
const [x, y] = req.headers.range.replace('bytes=', '').split('-'); | ||
const end = (options.end = parseInt(y, 10) || stats.size - 1); | ||
const start = (options.start = parseInt(x, 10) || 0); | ||
if (start >= stats.size || end >= stats.size) { | ||
res | ||
.writeHead(416, { | ||
'Content-Range': `bytes */${stats.size}` | ||
}) | ||
.end(); | ||
return res; | ||
} | ||
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`; | ||
headers['Content-Length'] = end - start + 1; | ||
headers['Accept-Ranges'] = 'bytes'; | ||
const { root, headers = {}, encoding = "utf-8", caching, ...options } = opts; | ||
if (!isAbsolute(path) && !root) | ||
throw new TypeError("path must be absolute"); | ||
if (caching) | ||
enableCaching(res, caching); | ||
const filePath = root ? join(root, path) : path; | ||
const stats = statSync(filePath); | ||
headers["Content-Encoding"] = encoding; | ||
headers["Last-Modified"] = stats.mtime.toUTCString(); | ||
headers["Content-Type"] = contentType(extname(path)); | ||
headers["ETag"] = createETag(stats, encoding); | ||
let status2 = res.statusCode || 200; | ||
if (req.headers["range"]) { | ||
status2 = 206; | ||
const [x, y] = req.headers.range.replace("bytes=", "").split("-"); | ||
const end = options.end = parseInt(y, 10) || stats.size - 1; | ||
const start = options.start = parseInt(x, 10) || 0; | ||
if (start >= stats.size || end >= stats.size) { | ||
res.writeHead(416, { | ||
"Content-Range": `bytes */${stats.size}` | ||
}).end(); | ||
return res; | ||
} | ||
else { | ||
headers['Content-Length'] = stats.size; | ||
} | ||
for (const [k, v] of Object.entries(headers)) | ||
res.setHeader(k, v); | ||
res.writeHead(status, headers); | ||
const stream = createReadStream(filePath, options); | ||
if (cb) | ||
stream.on('error', (err) => cb(err)).on('end', () => cb()); | ||
stream.pipe(res); | ||
return res; | ||
headers["Content-Range"] = `bytes ${start}-${end}/${stats.size}`; | ||
headers["Content-Length"] = end - start + 1; | ||
headers["Accept-Ranges"] = "bytes"; | ||
} else { | ||
headers["Content-Length"] = stats.size; | ||
} | ||
for (const [k, v] of Object.entries(headers)) | ||
res.setHeader(k, v); | ||
res.writeHead(status2, headers); | ||
const stream = createReadStream(filePath, options); | ||
if (cb) | ||
stream.on("error", (err) => cb(err)).on("end", () => cb()); | ||
stream.pipe(res); | ||
return res; | ||
}; | ||
export { enableCaching, json, send, sendFile, sendStatus, status }; | ||
export { | ||
enableCaching, | ||
json, | ||
send, | ||
sendFile, | ||
sendStatus, | ||
status | ||
}; |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { IncomingMessage as I, ServerResponse as S } from 'http'; | ||
@@ -3,0 +5,0 @@ export declare type ReadStreamOptions = Partial<{ |
{ | ||
"name": "@tinyhttp/send", | ||
"version": "2.0.5", | ||
"version": "2.0.6", | ||
"type": "module", | ||
@@ -30,3 +30,3 @@ "description": "json, send, sendFile, status and sendStatus methods for tinyhttp", | ||
"dependencies": { | ||
"@tinyhttp/etag": "2.0.4", | ||
"@tinyhttp/etag": "2.0.5", | ||
"es-content-type": "^0.1.0", | ||
@@ -36,4 +36,6 @@ "es-mime-types": "^0.1.4" | ||
"scripts": { | ||
"build": "rollup -c" | ||
"dev": "vite", | ||
"build": "vite build", | ||
"postbuild": "tsc --emitDeclarationOnly" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
12
14036
249
+ Added@tinyhttp/etag@2.0.5(transitive)
- Removed@tinyhttp/etag@2.0.4(transitive)
Updated@tinyhttp/etag@2.0.5