You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
3.1 KiB

7 years ago
  1. /*!
  2. * serve-static
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * Copyright(c) 2014 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. /**
  9. * Module dependencies.
  10. */
  11. var escapeHtml = require('escape-html');
  12. var merge = require('utils-merge');
  13. var parseurl = require('parseurl');
  14. var resolve = require('path').resolve;
  15. var send = require('send');
  16. var url = require('url');
  17. /**
  18. * @param {String} root
  19. * @param {Object} options
  20. * @return {Function}
  21. * @api public
  22. */
  23. exports = module.exports = function serveStatic(root, options) {
  24. if (!root) {
  25. throw new TypeError('root path required')
  26. }
  27. if (typeof root !== 'string') {
  28. throw new TypeError('root path must be a string')
  29. }
  30. // copy options object
  31. options = merge({}, options)
  32. // resolve root to absolute
  33. root = resolve(root)
  34. // default redirect
  35. var redirect = options.redirect !== false
  36. // headers listener
  37. var setHeaders = options.setHeaders
  38. delete options.setHeaders
  39. if (setHeaders && typeof setHeaders !== 'function') {
  40. throw new TypeError('option setHeaders must be function')
  41. }
  42. // setup options for send
  43. options.maxage = options.maxage || options.maxAge || 0
  44. options.root = root
  45. return function serveStatic(req, res, next) {
  46. if (req.method !== 'GET' && req.method !== 'HEAD') {
  47. return next()
  48. }
  49. var opts = merge({}, options)
  50. var originalUrl = parseurl.original(req)
  51. var path = parseurl(req).pathname
  52. var hasTrailingSlash = originalUrl.pathname[originalUrl.pathname.length - 1] === '/'
  53. if (path === '/' && !hasTrailingSlash) {
  54. // make sure redirect occurs at mount
  55. path = ''
  56. }
  57. // create send stream
  58. var stream = send(req, path, opts)
  59. if (redirect) {
  60. // redirect relative to originalUrl
  61. stream.on('directory', function redirect() {
  62. if (hasTrailingSlash) {
  63. return next()
  64. }
  65. // append trailing slash
  66. originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
  67. // reformat the URL
  68. var target = url.format(originalUrl)
  69. // send redirect response
  70. res.statusCode = 303
  71. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  72. res.setHeader('Location', target)
  73. res.end('Redirecting to <a href="' + escapeHtml(target) + '">' + escapeHtml(target) + '</a>\n')
  74. })
  75. } else {
  76. // forward to next middleware on directory
  77. stream.on('directory', next)
  78. }
  79. // add headers listener
  80. if (setHeaders) {
  81. stream.on('headers', setHeaders)
  82. }
  83. // forward non-404 errors
  84. stream.on('error', function error(err) {
  85. next(err.status === 404 ? null : err)
  86. })
  87. // pipe
  88. stream.pipe(res)
  89. }
  90. }
  91. /**
  92. * Expose mime module.
  93. *
  94. * If you wish to extend the mime table use this
  95. * reference to the "mime" module in the npm registry.
  96. */
  97. exports.mime = send.mime
  98. /**
  99. * Collapse all leading slashes into a single slash
  100. * @private
  101. */
  102. function collapseLeadingSlashes(str) {
  103. for (var i = 0; i < str.length; i++) {
  104. if (str[i] !== '/') {
  105. break
  106. }
  107. }
  108. return i > 1
  109. ? '/' + str.substr(i)
  110. : str
  111. }