Explication détaillée du serveur de fichiers statiques de nœud

小云云
Libérer: 2023-03-20 22:38:01
original
1489 Les gens l'ont consulté

Fonctions prises en charge :

  1. Lire les fichiers statiques

  2. L'accès au répertoire peut automatiquement trouver le fichier index.html suivant, si S'il n'y a pas d'index.html, listez les fichiers

  3. Prise en charge du type MIME

  4. Prise en charge/contrôle du cache

  5. Prise en charge de la compression gzip

  6. Prise en charge de la plage, reprise du point d'arrêt

  7. Exécution globale des commandes

  8. Sous-processus en cours d'exécution

Cet article présente principalement des exemples de serveurs de fichiers statiques de nœuds réels. J'espère qu'il pourra vous aider.

1. Créez un service pour lire les fichiers statiques

Introduisez d'abord le module http, créez un serveur et écoutez le port de configuration :


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

Écrivez un fn spécifiquement pour gérer les requêtes et renvoyer les fichiers statiques. Le module url obtient le chemin :


<. 🎜>
 const url = require(&#39;url&#39;);
 const fs = require(&#39;fs&#39;);
 function request(req, res) {
 const { pathname } = url.parse(req.url); // 访问路径
 
 const filepath = path.join(config.root, pathname); // 文件路径
 
 fs.createReadStream(filepath).pipe(res); // 读取文件,并响应
 }
Copier après la connexion
Prend en charge la recherche index.html :



 if (pathname === &#39;/&#39;) {
  const rootPath = path.join(config.root, &#39;index.html&#39;);
  try{
   const indexStat = fs.statSync(rootPath);
   if (indexStat) {
    filepath = rootPath;
   }
  } catch(e) {
   
  }
 }
Copier après la connexion
Lors de l'accès au répertoire, répertoriez le répertoire de fichiers :



 fs.stat(filepath, (err, stats) => {
 if (err) {
  res.end(&#39;not found&#39;);
  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(&#39;Content-Type&#39;, &#39;text/html&#39;);
  res.end(html);
 }
 }
Copier après la connexion
modèle HTML :



 function list() {
  let tmpl = fs.readFileSync(path.resolve(__dirname, &#39;template&#39;, &#39;list.html&#39;), &#39;utf8&#39;);
  return handlebars.compile(tmpl);
 }
Copier après la connexion


 <!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>
Copier après la connexion
2 . Prise en charge du type MIME


Utilisez le module MIME pour obtenir le type de fichier et définir l'encodage :


res.setHeader(&#39;Content-Type&#39;, mime.getType(filepath) + &#39;;charset=utf-8&#39;);
Copier après la connexion
3. 🎜>


Cache du protocole http :

Cache-Control : contenu http1.1, indique au client comment mettre en cache les données et règles

    privé le client peut mettre en cache
  1. public Le client et le serveur proxy peuvent mettre en cache
  2. max-age=60 le contenu mis en cache expirera après 60 secondes
  3. no-cache doit utiliser le cache de comparaison pour vérifier les données et forcer le serveur source à vérifier à nouveau
  4. no-store Tout le contenu ne sera pas être mis en cache, et ni la mise en cache forcée ni la mise en cache de comparaison ne seront déclenchées
  5. Expire : le contenu http1.0, le contrôle du cache l'écrasera et indiquera au client quand le cache expirera


ETag : la valeur de hachage du contenu dans lequel se trouvera la prochaine requête du client. Ajouter if-none-match : valeur etag dans l'en-tête de la requête


Last-Modified : heure de la dernière modification . La prochaine fois que le client demandera, ajoutez la valeur if-modified-since : Last-Modified dans l'en-tête de la demande


Compression
 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;
 }
 }
Copier après la connexion

.
Le client envoie du contenu et indique au serveur quelles compressions sont prises en charge via Accept-Encoding : gzip, deflate dans l'en-tête de la requête Format, le serveur compresse le contenu selon le format de compression pris en charge. Si le serveur ne le prend pas en charge, aucune compression ne sera effectuée.



5. Reprendre le téléchargement
 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;
  }
 }
Copier après la connexion


Le serveur utilise Range: bytes=0-xxx dans l'en-tête de la requête. Déterminez s'il s'agit d'une demande de plage. Si cette valeur existe et est valide, seul le contenu du fichier demandé sera renvoyé. Le code d'état de la réponse deviendra 206, indiquant un contenu partiel, et Content-Range sera défini. S'il n'est pas valide, un code d'état 416 est renvoyé, indiquant que la plage de demande n'est pas satisfaisante. Si l’en-tête de requête Range n’est pas inclus, continuez à répondre de la manière habituelle.



6. L'exécution globale des commandes
 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
  });
 }
Copier après la connexion


est implémentée via le lien npm

    Créez un lien symbolique pour le répertoire du package npm et liez-le à {prefix}/lib/node_modules/
  1. Créez un lien symbolique pour le fichier exécutable (bin) et liez-le à {prefix }/bin/{name}
  2. La commande npm link rend la commande npm package globalement exécutable en liant les répertoires et les fichiers exécutables.


Configuration dans package.json



Créez le fichier Hope du répertoire bin sous le projet et utilisez yargs pour configurer les paramètres de transmission en ligne de commande
 {
 bin: {
 "hope-server": "bin/hope"
 }
 }
Copier après la connexion



7 Le processus enfant s'exécute
 // 告诉电脑用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);
Copier après la connexion


via spawn pour implémenter

<🎜. >

index.js



8. Code source et tests

 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();
 }
Copier après la connexion

Adresse du code source : hope-server


Entrez n'importe quel répertoire

npm install hope-server -g
Copier après la connexion



Recommandations associées :

hope-server
Copier après la connexion


Utiliser un simple serveur de fichiers statiques HTTP écrit en nodejs et Python

Un simple serveur de fichiers statiques HTTP écrit en nodejs et Python_node.js

Serveur de fichiers statiques Node.js amélioré version_node.js

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!