Socket
Socket
Sign inDemoInstall

serve-handler

Package Overview
Dependencies
18
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.0 to 1.1.0

132

lib/index.js

@@ -8,8 +8,11 @@ // Native

const slasher = require('glob-slasher');
const minimatch = require('minimatch');
const pathToRegExp = require('path-to-regexp');
const mime = require('mime/lite');
const getHandlers = methods => {
const {createReadStream} = fs;
const {stat, createReadStream} = fs;
return Object.assign({
stat,
createReadStream

@@ -19,9 +22,49 @@ }, methods);

const toRegExp = (location, keys = null) => {
const normalized = slasher(location).replace('*', '(.*)');
return pathToRegExp(normalized, keys);
const sourceMatches = (source, requestPath, allowSegments) => {
const keys = [];
const slashed = slasher(source);
let results = null;
if (allowSegments) {
const normalized = slashed.replace('*', '(.*)');
const expression = pathToRegExp(normalized, keys);
results = expression.exec(requestPath);
}
if (results || minimatch(requestPath, slashed)) {
return {
keys,
results
};
}
return null;
};
const applyRewrites = (requestPath, rewrites) => {
if (!Array.isArray(rewrites)) {
const toTarget = (source, destination, previousPath) => {
const matches = sourceMatches(source, previousPath, true);
if (!matches) {
return null;
}
const {keys, results} = matches;
const props = {};
const {protocol} = url.parse(destination);
const normalizedDest = protocol ? destination : slasher(destination);
const toPath = pathToRegExp.compile(normalizedDest);
for (let index = 0; index < keys.length; index++) {
const {name} = keys[index];
props[name] = results[index + 1];
}
return toPath(props);
};
const applyRewrites = (requestPath, rewrites = []) => {
if (rewrites.length === 0) {
return requestPath;

@@ -32,5 +75,6 @@ }

const {source, destination} = rewrites[index];
const target = toTarget(source, destination, requestPath);
if (toRegExp(source).test(requestPath)) {
return applyRewrites(slasher(destination), rewrites);
if (target) {
return applyRewrites(slasher(target), rewrites);
}

@@ -42,4 +86,4 @@ }

const applyRedirect = (rewrittenURL, redirects) => {
if (!Array.isArray(redirects)) {
const shouldRedirect = (rewrittenURL, redirects = []) => {
if (redirects.length === 0) {
return null;

@@ -51,21 +95,8 @@ }

for (let index = 0; index < redirects.length; index++) {
const {destination, source, statusCode} = redirects[index];
const {source, destination, statusCode} = redirects[index];
const target = toTarget(source, destination, rewrittenURL);
const keys = [];
const expression = toRegExp(source, keys);
const results = expression.exec(rewrittenURL);
if (results) {
const props = {};
const {protocol} = url.parse(destination);
const normalizedDest = protocol ? destination : slasher(destination);
const toPath = toRegExp.compile(normalizedDest);
for (let i = 0; i < keys.length; i++) {
const {name} = keys[i];
props[name] = results[index + 1];
}
if (target) {
return {
target: toPath(props),
target,
statusCode: statusCode || 301

@@ -79,5 +110,39 @@ };

module.exports = async (request, response, config = {}, methods) => {
const appendHeaders = (target, source) => {
for (let index = 0; index < source.length; index++) {
const {key, value} = source[index];
target[key] = value;
}
};
const getHeaders = async (handlers, customHeaders = [], {relative, absolute}) => {
const related = {};
if (customHeaders.length > 0) {
// By iterating over all headers and never stopping, developers
// can specify multiple header sources in the config that
// might match a single path.
for (let index = 0; index < customHeaders.length; index++) {
const {source, headers} = customHeaders[index];
if (sourceMatches(source, relative)) {
appendHeaders(related, headers);
}
}
}
const stats = await handlers.stat(absolute);
const defaultHeaders = {
'Content-Type': mime.getType(relative),
'Last-Modified': stats.mtime.toUTCString(),
'Content-Length': stats.size
};
return Object.assign(defaultHeaders, related);
};
module.exports = async (request, response, config = {}, methods = {}) => {
const cwd = process.cwd();
const current = config.path ? path.join(cwd, config.path) : cwd;
const current = config.public ? path.join(cwd, config.public) : cwd;
const handlers = getHandlers(methods);

@@ -87,3 +152,3 @@

const rewrittenURL = applyRewrites(pathname, config.rewrites);
const redirect = applyRedirect(rewrittenURL, config.redirects);
const redirect = shouldRedirect(rewrittenURL, config.redirects);

@@ -102,3 +167,10 @@ if (redirect) {

if (relatedExists) {
const headers = await getHeaders(handlers, config.headers, {
relative: rewrittenURL,
absolute: related
});
response.writeHead(200, headers);
handlers.createReadStream(related).pipe(response);
return;

@@ -105,0 +177,0 @@ }

{
"name": "serve-handler",
"version": "1.0.0",
"version": "1.1.0",
"description": "The routing foundation of `serve` and static deployments on Now",

@@ -36,4 +36,6 @@ "main": "lib/index.js",

"glob-slasher": "1.0.1",
"mime": "2.3.1",
"minimatch": "3.0.4",
"path-to-regexp": "2.2.1"
}
}

@@ -30,3 +30,3 @@ # serve-handler

module.exports = async (request, response) => {
await handler(request, response);
await handler(request, response);
};

@@ -37,13 +37,9 @@ ```

### Configuration
## Options
In order to allow for customizing the package's default behaviour, we implemented two more arguments for the function call. They are both to be seen as configuration arguments.
If you want to customize the package's default behaviour, you can use the third argument of the function call to pass any of the configuration options listed below. Here's an example:
#### Options
The first one is for statically defined options:
```js
await handler(request, response, {
path: 'dist'
cleanUrls: true
});

@@ -54,14 +50,147 @@ ```

| Name | Description | Default Value |
|--------|--------------------------------------------------------------------|-----------------|
| `path` | A custom directory to which all requested paths should be relative | `process.cwd()` |
- [public](#public-boolean) (set sub directory to serve)
- [cleanUrls](#cleanurls-booleanarray) (strip `.html` and `.htm` from paths)
- [rewrites](#rewrites-array) (rewrite paths to different paths)
- [redirects](#redirects-array) (forward paths to different paths or URLs)
- [headers](#headers-array) (set custom headers)
- [trailingSlash](#trailingslash-boolean) (remove or add trailing slashes to all paths)
#### Middleware
### public (Boolean)
While the second one is for passing custom methods to replace the ones used in the package:
By default, the current working directory will be served. If you only want to serve a specific path, you can use this options to pass a custom directory to be served relative to the current working directory.
For example, if serving a [Jekyll](https://jekyllrb.com/) app, it would look like this:
```json
{
"public": "_site"
}
```
### cleanUrls (Boolean|Array)
Assuming this is `true`, all `.html` and `.htm` files can be accessed without their extension (shown below).
If one of these extensions is used at the end of a filename, it will automatically perform a redirect with status code [301](https://en.wikipedia.org/wiki/HTTP_301) to the same path, but with the extension dropped.
```json
{
"cleanUrls": true
}
```
However, you can also restrict this behavior to certain paths:
```json
{
"cleanUrls": [
"/app/**",
"/!components/**"
]
}
```
### rewrites (Array)
If you want your visitors to receive a response under a certain path, but actually serve a completely different one behind the curtains, this option is what you need.
It's perfect for [single page applications](https://en.wikipedia.org/wiki/Single-page_application) (SPAs), for example:
```json
{
"rewrites": [
{ "source": "app/**", "destination": "/index.html" },
{ "source": "projects/*/edit", "destination": "/edit-project.html" }
]
}
```
You can also use so-called "routing segments" as follows:
```json
{
"rewrites": [
{ "source": "/projects/:id/edit", "destination": "/edit-project-:id.html" },
]
}
```
Now, if a visitor accesses `/projects/123/edit`, it will respond with the file `/edit-project-123.html`.
### redirects (Array)
In order to redirect visits to a certain path to a different one (or even an external URL), you can use this option:
```json
{
"redirects": [
{ "source": "/from", "destination": "/to" },
{ "source": "/old-pages/**", "destination": "/home" }
]
}
```
By default, all of them are performed with the status code [301](https://en.wikipedia.org/wiki/HTTP_301), but this behavior can be adjusted by setting the `type` property directly on the object (see below).
Just like with [rewrites](#rewrites-array), you can also use routing segments:
```json
{
"redirects": [
{ "source": "/old-docs/:id", "destination": "/new-docs/:id" },
{ "source": "/old", "destination": "/new", "type": 302 }
]
}
```
In the example above, `/old-docs/12` would be forwarded to `/new-docs/12` with status code [301](https://en.wikipedia.org/wiki/HTTP_301). In addition `/old` would be forwarded to `/new` with status code [302](https://en.wikipedia.org/wiki/HTTP_302).
### headers (Array)
Allows you to set custom headers (and overwrite the default ones) for certain paths:
```json
{
"headers": [
{
"source" : "**/*.@(jpg|jpeg|gif|png)",
"headers" : [{
"key" : "Cache-Control",
"value" : "max-age=7200"
}]
}, {
"source" : "404.html",
"headers" : [{
"key" : "Cache-Control",
"value" : "max-age=300"
}]
}]
}
}
```
### trailingSlash (Boolean)
By default, the package will try to make assumptions for when to add trailing slashes to your URLs or not. If you want to remove them, set this property to `false` and `true` if you want to force them on all URLs:
```js
{
"trailingSlash": true
}
```
With the above config, a request to `/test` would now result in a [301](https://en.wikipedia.org/wiki/HTTP_301) redirect to `/test/`.
## Middleware
If you want to replace the methods the package is using for interacting with the file system, you can pass them as the fourth argument to the function call.
This comes in handy if you're dealing with simulating a file system, for example.
These are the methods used by the package (they can all return a `Promise` or be asynchronous):
```js
await handler(request, response, null, {
createReadStream(path) {},
stat(path) {}
createReadStream(path) {},
stat(path) {}
});

@@ -68,0 +197,0 @@ ```

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc