>  기사  >  웹 프론트엔드  >  실제 노드 정적 파일 서버의 샘플 코드

실제 노드 정적 파일 서버의 샘플 코드

亚连
亚连원래의
2018-05-31 16:45:321375검색

이 기사에서는 실제 노드 정적 파일 서버의 예를 주로 소개하고 참고할 수 있도록 하겠습니다.

이 글에서는 실제 노드 정적 파일 서버의 예시를 주로 소개하고, 자세한 내용은 다음과 같습니다.

지원 기능:

  1. 정적 파일 읽기

  2. 디렉토리에 액세스하면 자동으로 찾을 수 있습니다. index.html 파일 다음에 index.html이 없으면 파일 목록을 나열

  3. MIME 유형 지원

  4. 캐시 지원/제어

  5. gzip 압축 지원

  6. 범위 지원, 중단점 재개

  7. 전역 명령 실행

  8. 하위 프로세스 실행

1. 정적 파일을 읽는 서비스 만들기

먼저 http 모듈을 소개하고, 서버를 만들고, 구성된 포트를 수신합니다.

 const http = require('http');
 
 const server = http.createServer();
 
 // 监听请求
 server.on('request', request.bind(this));
 
 server.listen(config.port, () => {
  console.log(`静态文件服务启动成功, 访问localhost:${config.port}`);
 });

Write 특히 요청을 처리하고 정적 파일을 반환하기 위한 fn, url 모듈은 다음 경로를 얻습니다.

 const url = require('url');
 const fs = require('fs');
 function request(req, res) {
 const { pathname } = url.parse(req.url); // 访问路径
 
 const filepath = path.join(config.root, pathname); // 文件路径
 
 fs.createReadStream(filepath).pipe(res); // 读取文件,并响应
 }

index.html 검색 지원:

 if (pathname === '/') {
  const rootPath = path.join(config.root, 'index.html');
  try{
   const indexStat = fs.statSync(rootPath);
   if (indexStat) {
    filepath = rootPath;
   }
  } catch(e) {
   
  }
 }

디렉토리에 액세스할 때 파일 디렉터리 나열:

 fs.stat(filepath, (err, stats) => {
 if (err) {
  res.end('not found');
  return;
 }
 if (stats.isDirectory()) {
  let files = fs.readdirSync(filepath);
  files = files.map(file => ({
   name: file,
   url: path.join(pathname, file)
  }));
  let html = this.list()({
   title: pathname,
   files
  });
  res.setHeader('Content-Type', 'text/html');
  res.end(html);
 }
 }

html 템플릿:

 function list() {
  let tmpl = fs.readFileSync(path.resolve(__dirname, 'template', 'list.html'), 'utf8');
  return handlebars.compile(tmpl);
 }
 <!DOCTYPE html>
 <html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>{{title}}</title>
 </head>
 <body>
 <h1>hope-server静态文件服务器</h1>
 <ul>
  {{#each files}}
  <li>
   <a href={{url}}>{{name}}</a>
  </li>
  {{/each}}
 </ul>
 </body>
 </html>

2. MIME 유형 지원

mime 모듈을 사용하여 파일 유형을 가져오고 인코딩을 설정합니다:

res.setHeader(&#39;Content-Type&#39;, mime.getType(filepath) + &#39;;charset=utf-8&#39;);

3. 캐시 지원

http 프로토콜 캐시:

Cache-Control: http1.1 콘텐츠, 클라이언트에게 방법을 알려줍니다. 캐시 데이터 및 규칙

  1. 개인 클라이언트는 캐시할 수 있습니다

  2. 공개 클라이언트와 프록시 서버 모두 캐시할 수 있습니다.

  3. max-age=60 캐시된 콘텐츠는 60초 후에 만료됩니다.

  4. 캐시하지 않습니다. 비교 캐시를 사용하여 데이터를 확인하고 원본 서버에서 다시 확인하도록 강제해야 합니다.

  5. no-store 모든 콘텐츠가 캐시되지 않으며 강제 캐싱이나 비교 캐싱이 실행되지 않습니다

만료: http1. 0 콘텐츠인 경우 캐시 제어가 이를 무시하고 캐시가 만료되는 시기를 클라이언트에 알려줍니다

ETag: 콘텐츠 해시 다음 클라이언트 요청의 경우 if-none-match: etag 값을 요청 헤더에 추가하세요

Last-Modified : 마지막 수정 시간. 요청 헤더에 if-modified-since: Last-Modified 값을 추가하세요.

 handleCache(req, res, stats, hash) {
 // 当资源过期时, 客户端发现上一次请求资源,服务器有发送Last-Modified, 则再次请求时带上if-modified-since
 const ifModifiedSince = req.headers[&#39;if-modified-since&#39;];
 // 服务器发送了etag,客户端再次请求时用If-None-Match字段来询问是否过期
 const ifNoneMatch = req.headers[&#39;if-none-match&#39;];
 // http1.1内容 max-age=30 为强行缓存30秒 30秒内再次请求则用缓存 private 仅客户端缓存,代理服务器不可缓存
 res.setHeader(&#39;Cache-Control&#39;, &#39;private,max-age=30&#39;);
 // http1.0内容 作用与Cache-Control一致 告诉客户端什么时间,资源过期 优先级低于Cache-Control
 res.setHeader(&#39;Expires&#39;, new Date(Date.now() + 30 * 1000).toGMTString());
 // 设置ETag 根据内容生成的hash
 res.setHeader(&#39;ETag&#39;, hash);
 // 设置Last-Modified 文件最后修改时间
 const lastModified = stats.ctime.toGMTString();
 res.setHeader(&#39;Last-Modified&#39;, lastModified);
 
 // 判断ETag是否过期
 if (ifNoneMatch && ifNoneMatch != hash) {
  return false;
 }
 // 判断文件最后修改时间
 if (ifModifiedSince && ifModifiedSince != lastModified) {
  return false;
 }
 // 如果存在且相等,走缓存304
 if (ifNoneMatch || ifModifiedSince) {
  res.writeHead(304);
  res.end();
  return true;
 } else {
  return false;
 }
 }

4. Compression

클라이언트는 요청을 통해 어떤 압축 형식이 지원되는지 서버에 알려줍니다. 헤더 Accept-Encoding: gzip, deflate. 서버는 지원되는 압축 형식에 따라 콘텐츠를 압축합니다. 서버에서 지원하지 않으면 압축이 수행되지 않습니다.

 getEncoding(req, res) {
  const acceptEncoding = req.headers[&#39;accept-encoding&#39;];
  // gzip和deflate压缩
  if (/\bgzip\b/.test(acceptEncoding)) {
   res.setHeader(&#39;Content-Encoding&#39;, &#39;gzip&#39;);
   return zlib.createGzip();
  } else if (/\bdeflate\b/.test(acceptEncoding)) {
   res.setHeader(&#39;Content-Encoding&#39;, &#39;deflate&#39;);
   return zlib.createDeflate();
  } else {
   return null;
  }
 }

5. 재개 가능한 업로드

서버는 요청 헤더의 Range: bytes=0-xxx를 사용하여 Range 요청을 하고 있는지 확인합니다. 이 값이 존재하고 유효한 경우 파일 콘텐츠의 요청된 부분만 사용됩니다. 응답 상태 코드는 206이 되어 Partial Content를 나타내며 Content-Range가 설정됩니다. 유효하지 않은 경우 요청 범위가 만족스럽지 않음을 나타내는 416 상태 코드가 반환됩니다. Range 요청 헤더가 포함되지 않은 경우 계속해서 일반적인 방식으로 응답합니다.

 getStream(req, res, filepath, statObj) {
  let start = 0;
  let end = statObj.size - 1;
  const range = req.headers[&#39;range&#39;];
  if (range) {
   res.setHeader(&#39;Accept-Range&#39;, &#39;bytes&#39;);
   res.statusCode = 206;//返回整个内容的一块
   let result = range.match(/bytes=(\d*)-(\d*)/);
   if (result) {
    start = isNaN(result[1]) ? start : parseInt(result[1]);
    end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
   }
  }
  return fs.createReadStream(filepath, {
   start, end
  });
 }

6. 전역 명령 실행

npm 링크를 통해 달성

  1. npm 패키지 디렉터리에 대한 소프트 링크를 생성하고 이를 {prefix}/lib/node_modules/

  2. 실행 파일 생성(bin ) 소프트 링크, {prefix}/bin/{name}

에 연결 npm link 명령은 디렉터리와 실행 파일을 연결하여 npm 패키지 명령을 전역적으로 실행 가능하게 만듭니다.

package.json의 구성

 {
 bin: {
 "hope-server": "bin/hope"
 }
 }

프로젝트 아래에 bin 디렉터리 희망 파일을 생성하고 yargs를 사용하여 매개변수를 전달하도록 명령줄을 구성합니다

 // 告诉电脑用node运行我的文件
 #! /usr/bin/env node
 
 const yargs = require(&#39;yargs&#39;);
 const init = require(&#39;../src/index.js&#39;);
 const argv = yargs.option(&#39;d&#39;, {
 alias: &#39;root&#39;,
 demand: &#39;false&#39;,
 type: &#39;string&#39;,
 default: process.cwd(),
 description: &#39;静态文件根目录&#39;
 }).option(&#39;o&#39;, {
 alias: &#39;host&#39;,
 demand: &#39;false&#39;,
 default: &#39;localhost&#39;,
 type: &#39;string&#39;,
 description: &#39;配置监听的主机&#39;
 }).option(&#39;p&#39;, {
 alias: &#39;port&#39;,
 demand: &#39;false&#39;,
 type: &#39;number&#39;,
 default: 8080,
 description: &#39;配置端口号&#39;
 }).option(&#39;c&#39;, {
 alias: &#39;child&#39;,
 demand: &#39;false&#39;,
 type: &#39;boolean&#39;,
 default: false,
 description: &#39;是否子进程运行&#39;
 })
 .usage(&#39;hope-server [options]&#39;)
 .example(
 &#39;hope-server -d / -p 9090 -o localhost&#39;, &#39;在本机的9090端口上监听客户端的请求&#39;
 ).help(&#39;h&#39;).argv;
 
 // 启动服务
 init(argv);

7 실행 중인 하위 프로세스

spawn

index.js

 const { spawn } = require(&#39;child_process&#39;);
 const Server = require(&#39;./hope&#39;);
 
 function init(argv) {
  // 如果配置为子进程开启服务
  if (argv.child) {
   //子进程启动服务
   const child = spawn(&#39;node&#39;, [&#39;hope.js&#39;, JSON.stringify(argv)], {
    cwd: __dirname,
    detached: true,
    stdio: &#39;inherit&#39;
   });
 
   //后台运行
   child.unref();
   //退出主线程,让子线程单独运行
   process.exit(0);
  } else {
   const server = new Server(argv);
   server.start();
  }
 }
 
 module.exports = init;
hope.js
 if (process.argv[2] && process.argv[2].startsWith(&#39;{&#39;)) {
 const argv = JSON.parse(process.argv[2]);
 const server = new Hope(argv);
 server.start();
 }

8. 소스 코드 및 테스트

소스 코드 주소: hope-server

npm install hope-server -g

어느 디렉토리든 입력하세요

hope-server

위 내용은 제가 모두를 위해 정리한 내용입니다. 앞으로 모든 분들께 도움이 되길 바랍니다.

관련 기사:

JS에서 문자열 접합 기능을 구현하는 방법(String.prototype.format 확장)

ES6를 사용하여 WeakMap을 통해 메모리 누수 문제 해결(상세 튜토리얼)

구현 방법 JavaScript WeChat ID 무작위 전환 코드를 통해 (자세한 튜토리얼)

위 내용은 실제 노드 정적 파일 서버의 샘플 코드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.