Skip to content

Commit fabd57b

Browse files
coreyfarrellmcollina
authored andcommitted
feat: Add redirect option (#93)
* feat: Add redirect option This adds an option to redirect requests for directories when the tailing slash is missing. Fixes #92 * feat: Fix `wildcard: false` handling of other options * Support `redirect: true` for folders which provide an index file. * Support the `index` option. * Fix handling of query string in root redirect with wildcard * Document incompatibility with server option ignoreTrailingSlash.
1 parent a133e38 commit fabd57b

File tree

5 files changed

+553
-11
lines changed

5 files changed

+553
-11
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ The following options are also supported and will be passed directly to the
7373
- [`lastModified`](https://www.npmjs.com/package/send#lastmodified)
7474
- [`maxAge`](https://www.npmjs.com/package/send#maxage)
7575

76+
#### `redirect`
77+
78+
Default: `false`
79+
80+
If set to `true`, `fastify-static` redirects to the directory with a trailing slash.
81+
82+
This option cannot be set to `true` with `wildcard` set to `false` on a server
83+
with `ignoreTrailingSlash` set to `true`.
84+
7685
#### `wildcard`
7786

7887
Default: `true`
@@ -85,6 +94,9 @@ those.
8594
The default options of https://www.npmjs.com/package/glob are applied
8695
for getting the file list.
8796

97+
This option cannot be set to `false` with `redirect` set to `true` on a server
98+
with `ignoreTrailingSlash` set to `true`.
99+
88100
#### Disable serving
89101

90102
If you'd just like to use the reply decorator and not serve whole directories automatically, you can simply pass the option `{ serve: false }`. This will prevent the plugin from serving everything under `root`.

index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare const fastifyStatic: fastify.Plugin<Server, IncomingMessage, ServerRespo
1818
decorateReply?: boolean;
1919
schemaHide?: boolean;
2020
setHeaders?: (...args: any[]) => void;
21+
redirect?: boolean;
2122

2223
// Passed on to `send`
2324
acceptRanges?: boolean;
@@ -31,4 +32,4 @@ declare const fastifyStatic: fastify.Plugin<Server, IncomingMessage, ServerRespo
3132
maxAge?: string | number;
3233
}>;
3334

34-
export = fastifyStatic;
35+
export = fastifyStatic;

index.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const path = require('path')
4+
const url = require('url')
45
const statSync = require('fs').statSync
56
const { PassThrough } = require('readable-stream')
67
const glob = require('glob')
@@ -76,6 +77,13 @@ function fastifyStatic (fastify, opts, next) {
7677
stream.on('headers', setHeaders)
7778
}
7879

80+
if (opts.redirect === true) {
81+
stream.on('directory', function (res, path) {
82+
const parsed = url.parse(request.raw.url)
83+
reply.redirect(301, parsed.pathname + '/' + (parsed.search || ''))
84+
})
85+
}
86+
7987
stream.on('error', function (err) {
8088
if (err) {
8189
if (err.code === 'ENOENT') {
@@ -106,25 +114,44 @@ function fastifyStatic (fastify, opts, next) {
106114
fastify.get(prefix + '*', schema, function (req, reply) {
107115
pumpSendToReply(req, reply, '/' + req.params['*'])
108116
})
117+
if (opts.redirect === true && prefix !== opts.prefix) {
118+
fastify.get(opts.prefix, schema, function (req, reply) {
119+
const parsed = url.parse(req.raw.url)
120+
reply.redirect(301, parsed.pathname + '/' + (parsed.search || ''))
121+
})
122+
}
109123
} else {
110-
glob(path.join(sendOptions.root, '**/*'), function (err, files) {
124+
glob(path.join(sendOptions.root, '**/*'), { nodir: true }, function (err, files) {
111125
if (err) {
112126
return next(err)
113127
}
128+
const indexDirs = new Set()
129+
const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index || [])
114130
for (let file of files) {
115-
file = file.replace(sendOptions.root.replace(/\\/g, '/'), '')
116-
const route = (prefix + file).replace(/^\/\//, '/')
131+
file = file.replace(sendOptions.root.replace(/\\/g, '/'), '').replace(/^\//, '')
132+
const route = (prefix + file).replace(/\/\//g, '/')
117133
fastify.get(route, schema, function (req, reply) {
118134
pumpSendToReply(req, reply, '/' + file)
119135
})
120136

121-
if (file.match(/index\.html$/)) {
122-
const route2 = route.replace(/index\.html$/, '')
123-
fastify.get(route2, schema, function (req, reply) {
124-
pumpSendToReply(req, reply, '/' + file)
125-
})
137+
if (indexes.includes(path.posix.basename(route))) {
138+
indexDirs.add(path.posix.dirname(route))
126139
}
127140
}
141+
indexDirs.forEach(function (dirname) {
142+
const pathname = dirname + (dirname.endsWith('/') ? '' : '/')
143+
const file = '/' + pathname.replace(prefix, '')
144+
145+
fastify.get(pathname, schema, function (req, reply) {
146+
pumpSendToReply(req, reply, file)
147+
})
148+
149+
if (opts.redirect === true) {
150+
fastify.get(pathname.replace(/\/$/, ''), schema, function (req, reply) {
151+
pumpSendToReply(req, reply, file.replace(/\/$/, ''))
152+
})
153+
}
154+
})
128155
next()
129156
})
130157

0 commit comments

Comments
 (0)