原生koa2实现静态资源服务器

前言

一个http请求访问web服务静态资源,一般响应结果有三种情况

  • 访问文本,例如js,css,png,jpg,gif
  • 访问静态目录
  • 找不到资源,抛出404错误

原生koa2 静态资源服务器例子

demo源码

https://github.com/ChenShenhai/koa2-note/blob/master/demo/static-server/

代码目录

  1. ├── static # 静态资源目录
  2. ├── css/
  3. ├── image/
  4. ├── js/
  5. └── index.html
  6. ├── util # 工具代码
  7. ├── content.js # 读取请求内容
  8. ├── dir.js # 读取目录内容
  9. ├── file.js # 读取文件内容
  10. ├── mimes.js # 文件类型列表
  11. └── walk.js # 遍历目录内容
  12. └── index.js # 启动入口文件

代码解析

index.js

  1. const Koa = require('koa')
  2. const path = require('path')
  3. const content = require('./util/content')
  4. const mimes = require('./util/mimes')
  5. const app = new Koa()
  6. // 静态资源目录对于相对入口文件index.js的路径
  7. const staticPath = './static'
  8. // 解析资源类型
  9. function parseMime( url ) {
  10. let extName = path.extname( url )
  11. extName = extName ? extName.slice(1) : 'unknown'
  12. return mimes[ extName ]
  13. }
  14. app.use( async ( ctx ) => {
  15. // 静态资源目录在本地的绝对路径
  16. let fullStaticPath = path.join(__dirname, staticPath)
  17. // 获取静态资源内容,有可能是文件内容,目录,或404
  18. let _content = await content( ctx, fullStaticPath )
  19. // 解析请求内容的类型
  20. let _mime = parseMime( ctx.url )
  21. // 如果有对应的文件类型,就配置上下文的类型
  22. if ( _mime ) {
  23. ctx.type = _mime
  24. }
  25. // 输出静态资源内容
  26. if ( _mime && _mime.indexOf('image/') >= 0 ) {
  27. // 如果是图片,则用node原生res,输出二进制数据
  28. ctx.res.writeHead(200)
  29. ctx.res.write(_content, 'binary')
  30. ctx.res.end()
  31. } else {
  32. // 其他则输出文本
  33. ctx.body = _content
  34. }
  35. })
  36. app.listen(3000)
  37. console.log('[demo] static-server is starting at port 3000')

util/content.js

  1. const path = require('path')
  2. const fs = require('fs')
  3. // 封装读取目录内容方法
  4. const dir = require('./dir')
  5. // 封装读取文件内容方法
  6. const file = require('./file')
  7. /**
  8. * 获取静态资源内容
  9. * @param {object} ctx koa上下文
  10. * @param {string} 静态资源目录在本地的绝对路径
  11. * @return {string} 请求获取到的本地内容
  12. */
  13. async function content( ctx, fullStaticPath ) {
  14. // 封装请求资源的完绝对径
  15. let reqPath = path.join(fullStaticPath, ctx.url)
  16. // 判断请求路径是否为存在目录或者文件
  17. let exist = fs.existsSync( reqPath )
  18. // 返回请求内容, 默认为空
  19. let content = ''
  20. if( !exist ) {
  21. //如果请求路径不存在,返回404
  22. content = '404 Not Found! o(╯□╰)o!'
  23. } else {
  24. //判断访问地址是文件夹还是文件
  25. let stat = fs.statSync( reqPath )
  26. if( stat.isDirectory() ) {
  27. //如果为目录,则渲读取目录内容
  28. content = dir( ctx.url, reqPath )
  29. } else {
  30. // 如果请求为文件,则读取文件内容
  31. content = await file( reqPath )
  32. }
  33. }
  34. return content
  35. }
  36. module.exports = content

util/dir.js

  1. const url = require('url')
  2. const fs = require('fs')
  3. const path = require('path')
  4. // 遍历读取目录内容方法
  5. const walk = require('./walk')
  6. /**
  7. * 封装目录内容
  8. * @param {string} url 当前请求的上下文中的url,即ctx.url
  9. * @param {string} reqPath 请求静态资源的完整本地路径
  10. * @return {string} 返回目录内容,封装成HTML
  11. */
  12. function dir ( url, reqPath ) {
  13. // 遍历读取当前目录下的文件、子目录
  14. let contentList = walk( reqPath )
  15. let html = `<ul>`
  16. for ( let [ index, item ] of contentList.entries() ) {
  17. html = `${html}<li><a href="${url === '/' ? '' : url}/${item}">${item}</a>`
  18. }
  19. html = `${html}</ul>`
  20. return html
  21. }
  22. module.exports = dir

util/file.js

  1. const fs = require('fs')
  2. /**
  3. * 读取文件方法
  4. * @param {string} 文件本地的绝对路径
  5. * @return {string|binary}
  6. */
  7. function file ( filePath ) {
  8. let content = fs.readFileSync(filePath, 'binary' )
  9. return content
  10. }
  11. module.exports = file

util/walk.js

  1. const fs = require('fs')
  2. const mimes = require('./mimes')
  3. /**
  4. * 遍历读取目录内容(子目录,文件名)
  5. * @param {string} reqPath 请求资源的绝对路径
  6. * @return {array} 目录内容列表
  7. */
  8. function walk( reqPath ){
  9. let files = fs.readdirSync( reqPath );
  10. let dirList = [], fileList = [];
  11. for( let i=0, len=files.length; i<len; i++ ) {
  12. let item = files[i];
  13. let itemArr = item.split("\.");
  14. let itemMime = ( itemArr.length > 1 ) ? itemArr[ itemArr.length - 1 ] : "undefined";
  15. if( typeof mimes[ itemMime ] === "undefined" ) {
  16. dirList.push( files[i] );
  17. } else {
  18. fileList.push( files[i] );
  19. }
  20. }
  21. let result = dirList.concat( fileList );
  22. return result;
  23. };
  24. module.exports = walk;

util/mime.js

  1. let mimes = {
  2. 'css': 'text/css',
  3. 'less': 'text/css',
  4. 'gif': 'image/gif',
  5. 'html': 'text/html',
  6. 'ico': 'image/x-icon',
  7. 'jpeg': 'image/jpeg',
  8. 'jpg': 'image/jpeg',
  9. 'js': 'text/javascript',
  10. 'json': 'application/json',
  11. 'pdf': 'application/pdf',
  12. 'png': 'image/png',
  13. 'svg': 'image/svg+xml',
  14. 'swf': 'application/x-shockwave-flash',
  15. 'tiff': 'image/tiff',
  16. 'txt': 'text/plain',
  17. 'wav': 'audio/x-wav',
  18. 'wma': 'audio/x-ms-wma',
  19. 'wmv': 'video/x-ms-wmv',
  20. 'xml': 'text/xml'
  21. }
  22. module.exports = mimes

运行效果

启动服务

  1. node index.js

效果

访问http://localhost:3000

static-server-result

访问http://localhost:3000/index.html

static-server-result

访问http://localhost:3000/js/index.js

static-server-result