使用Node.js搭建静态资源服务详细教程

对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解。那就开始吧,让我们的双手沾满网络请求!

Note:

当然在项目中如果有使用express框架,用express.static一行代码就可以达到目的了:

app.use(express.static('public'))

这里我们要实现的正是express.static背后所做工作的一部分,建议同步阅读该模块源码。

基本功能

不急着写下第一行代码,而是先梳理一下就基本功能而言有哪些步骤。

  1. 在本地根据指定端口启动一个http server,等待着来自客户端的请求
  2. 当请求抵达时,根据请求的url,以设置的静态文件目录为base,映射得到文件位置
  3. 检查文件是否存在
  4. 如果文件不存在,返回404状态码,发送not found页面到客户端
  5. 如果文件存在:
  • 打开文件待读取
  • 设置response header
  • 发送文件到客户端

6.等待来自客户端的下一个请求

实现基本功能

代码结构

创建一个nodejs-static-webserver目录,在目录内运行npm init初始化一个package.json文件。

mkdir nodejs-static-webserver && cd "$_"
// initialize package.json
npm init

接着创建如下文件目录:

-- config
---- default.json
-- static-server.js
-- app.js
default.json
{
 "port": 9527,
 "root": "/Users/sheila1227/Public",
 "indexPage": "index.html"
}

default.js存放一些默认配置,比如端口号、静态文件目录(root)、默认页(indexPage)等。当这样的一个请求http://localhost:9527/myfiles/抵达时. 如果根据root映射后得到的目录内有index.html,根据我们的默认配置,就会给客户端发回index.html的内容。

static-server.js

const http = require('http');
const path = require('path');
const config = require('./config/default');
class StaticServer {
 constructor() {
  this.port = config.port;
  this.root = config.root;
  this.indexPage = config.indexPage;
 }

 start() {
  http.createServer((req, res) => {
   const pathName = path.join(this.root, path.normalize(req.url));
   res.writeHead(200);
   res.end(`Requeste path: ${pathName}`);
  }).listen(this.port, err => {
   if (err) {
    console.error(err);
    console.info('Failed to start server');
   } else {
    console.info(`Server started on port ${this.port}`);
   }
  });
 }
}
module.exports = StaticServer;

在这个模块文件内,我们声明了一个StaticServer类,并给其定义了start方法,在该方法体内,创建了一个server对象,监听rquest事件,并将服务器绑定到配置文件指定的端口。在这个阶段,我们对于任何请求都暂时不作区分地简单地返回请求的文件路径。path模块用来规范化连接和解析路径,这样我们就不用特意来处理操作系统间的差异。

app.js

const StaticServer = require('./static-server');
(new StaticServer()).start();

在这个文件内,调用上面的static-server模块,并创建一个StaticServer实例,调用其start方法,启动了一个静态资源服务器。这个文件后面将不需要做其他修改,所有对静态资源服务器的完善都发生在static-server.js内。

在目录下启动程序会看到成功启动的log:

> node app.js
Server started on port 9527

在浏览器中访问,可以看到服务器将请求路径直接返回了。

路由处理

之前我们对任何请求都只是向客户端返回文件位置而已,现在我们将其替换成返回真正的文件:

 routeHandler(pathName, req, res) {
 }
 start() {
  http.createServer((req, res) => {
   const pathName = path.join(this.root, path.normalize(req.url));
   this.routeHandler(pathName, req, res);
  }).listen(this.port, err => {
   ...
  });
 }

将由routeHandler来处理文件发送。

读取静态文件

读取文件之前,用fs.stat检测文件是否存在,如果文件不存在,回调函数会接收到错误,发送404响应.

 respondNotFound(req, res) {
  res.writeHead(404, {
   'Content-Type': 'text/html'
  });
  res.end(`<h1>Not Found</h1><p>The requested URL ${req.url} was not found on this server.</p>`);
 }

 respondFile(pathName, req, res) {
  const readStream = fs.createReadStream(pathName);
  readStream.pipe(res);
 }

 routeHandler(pathName, req, res) {
  fs.stat(pathName, (err, stat) => {
   if (!err) {
    this.respondFile(pathName, req, res);
   } else {
    this.respondNotFound(req, res);
   }
  });
 }

Note:

读取文件,这里用的是流的形式createReadStream而不是readFile,是因为后者会在得到完整文件内容之前将其先读到内存里。这样万一文件很大,再遇上多个请求同时访问,readFile就承受不来了。使用文件可读流,服务端不用等到数据完全加载到内存再发回给客户端,而是一边读一边发送分块响应。这时响应里会包含如下响应头:

Transfer-Encoding:chunked

默认情况下,可读流结束时,可写流的end()方法会被调用。

MIME支持

现在给客户端返回文件时,我们并没有指定Content-Type头,虽然你可能发现访问文本或图片浏览器都可以正确显示出文字或图片,但这并不符合规范。任何包含实体主体(entity body)的响应都应在头部指明文件类型,否则浏览器无从得知类型时,就会自行猜测(从文件内容以及url中寻找可能的扩展名)。响应如指定了错误的类型也会导致内容的错乱显示,如明明返回的是一张jpeg图片,却错误指定了header:'Content-Type': 'text/html',会收到一堆乱码。

虽然有现成的mime模块可用,这里还是自己来实现吧,试图对这个过程有更清晰的理解。

在根目录下创建mime.js文件:

const path = require('path');
const mimeTypes = {
 "css": "text/css",
 "gif": "image/gif",
 "html": "text/html",
 "ico": "image/x-icon",
 "jpeg": "image/jpeg",
  ...
};
const lookup = (pathName) => {
 let ext = path.extname(pathName);
 ext = ext.split('.').pop();
 return mimeTypes[ext] || mimeTypes['txt'];
}
module.exports = {
 lookup
};

该模块暴露出一个lookup方法,可以根据路径名返回正确的类型,类型以‘type/subtype'表示。对于未知的类型,按普通文本处理。

接着在static-server.js中引入上面的mime模块,给返回文件的响应都加上正确的头部字段:

 respondFile(pathName, req, res) {
  const readStream = fs.createReadStream(pathName);
  res.setHeader('Content-Type', mime.lookup(pathName));
  readStream.pipe(res);
 }

重新运行程序,会看到图片可以在浏览器中正常显示了。

Note:

需要注意的是,Content-Type说明的应是原始实体主体的文件类型。即使实体经过内容编码(如gzip,后面会提到),该字段说明的仍应是编码前的实体主体的类型。

添加其他功能

至此,已经完成了基本功能中列出的几个步骤,但依然有很多需要改进的地方,比如如果用户输入的url对应的是磁盘上的一个目录怎么办?还有,现在对于同一个文件(从未更改过)的多次请求,服务端都是勤勤恳恳地一遍遍地发送回同样的文件,这些冗余的数据传输,既消耗了带宽,也给服务器添加了负担。另外,服务器如果在发送内容之前能对其进行压缩,也有助于减少传输时间。

读取文件目录

现阶段,用url: localhost:9527/testfolder去访问一个指定root文件夹下真实存在的testfolder的文件夹,服务端会报错:

Error: EISDIR: illegal operation on a directory, read

要增添对目录访问的支持,我们重新整理下响应的步骤:

1.请求抵达时,首先判断url是否有尾部斜杠

2.如果有尾部斜杠,认为用户请求的是目录

  • 如果目录存在
  • 如果目录下存在默认页(如index.html),发送默认页
  • 如果不存在默认页,发送目录下内容列表
  • 如果目录不存在,返回404

3.如果没有尾部斜杠,认为用户请求的是文件

  • 如果文件存在,发送文件
  • 如果文件不存在,判断同名的目录是否存在
  • 如果存在该目录,返回301,并在原url上添加上/作为要转到的location
  • 如果不存在该目录,返回404

我们需要重写一下routeHandler内的逻辑:

routeHandler(pathName, req, res) {
  fs.stat(pathName, (err, stat) => {
   if (!err) {
    const requestedPath = url.parse(req.url).pathname;
    if (hasTrailingSlash(requestedPath) && stat.isDirectory()) {
     this.respondDirectory(pathName, req, res);
    } else if (stat.isDirectory()) {
     this.respondRedirect(req, res);
    } else {
     this.respondFile(pathName, req, res);
    }
   } else {
    this.respondNotFound(req, res);
   }
  });
 }

继续补充respondRedirect方法:

 respondRedirect(req, res) {
  const location = req.url + '/';
  res.writeHead(301, {
   'Location': location,
   'Content-Type': 'text/html'
  });
  res.end(`Redirecting to <a href='${location}'>${location}</a>`);
 }

浏览器收到301响应时,会根据头部指定的location字段值,向服务器发出一个新的请求。

继续补充respondDirectory方法:

 respondDirectory(pathName, req, res) {
  const indexPagePath = path.join(pathName, this.indexPage);
  if (fs.existsSync(indexPagePath)) {
   this.respondFile(indexPagePath, req, res);
  } else {
   fs.readdir(pathName, (err, files) => {
    if (err) {
     res.writeHead(500);
     return res.end(err);
    }
    const requestPath = url.parse(req.url).pathname;
    let content = `<h1>Index of ${requestPath}</h1>`;
    files.forEach(file => {
     let itemLink = path.join(requestPath,file);
     const stat = fs.statSync(path.join(pathName, file));
     if (stat && stat.isDirectory()) {
      itemLink = path.join(itemLink, '/');
     }
     content += `<p><a href='${itemLink}'>${file}</a></p>`;
    });
    res.writeHead(200, {
     'Content-Type': 'text/html'
    });
    res.end(content);
   });
  }
 }

当需要返回目录列表时,遍历所有内容,并为每项创建一个link,作为返回文档的一部分。需要注意的是,对于子目录的href,额外添加一个尾部斜杠,这样可以避免访问子目录时的又一次重定向。

在浏览器中测试一下,输入localhost:9527/testfolder,指定的root目录下并没有名为testfolder的文件,却存在同名目录,因此第一次会收到重定向响应,并发起一个对目录的新请求。

缓存支持

为了减少数据传输,减少请求数,继续添加缓存支持。首先梳理一下缓存的处理流程:

1.如果是第一次访问,请求报文首部不会包含相关字段,服务端在发送文件前做如下处理:

  • 如服务器支持ETag,设置ETag头
  • 如服务器支持Last-Modified,设置Last-Modified头
  • 设置Expires头
  • 设置Cache-Control头(设置其max-age值)
  • 浏览器收到响应后会存下这些标记,并在下次请求时带上与ETag对应的请求首部If-None-Match或与Last-Modified对应的请求首部If-Modified-Since。

2.如果是重复的请求:

浏览器判断缓存是否过期(通过Cache-Control和Expires确定)

如果未过期,直接使用缓存内容,也就是强缓存命中,并不会产生新的请求

如果已过期,会发起新的请求,并且请求会带上If-None-Match或If-Modified-Since,或者兼具两者

服务器收到请求,进行缓存的新鲜度再验证:
首先检查请求是否有If-None-Match首部,没有则继续下一步,有则将其值与文档的最新ETag匹配,失败则认为缓存不新鲜,成功则继续下一步

接着检查请求是否有If-Modified-Since首部,没有则保留上一步验证结果,有则将其值与文档最新修改时间比较验证,失败则认为缓存不新鲜,成功则认为缓存新鲜

当两个首部皆不存在或者验证结果是不新鲜时,发送200及最新文件,并在首部更新新鲜度。

当验证结果是缓存仍然新鲜时(也就是弱缓存命中),不需发送文件,仅发送304,并在首部更新新鲜度

为了能启用或关闭某种验证机制,我们在配置文件里增添如下配置项:

default.json:

{
 ...
 "cacheControl": true,
 "expires": true,
 "etag": true,
 "lastModified": true,
 "maxAge": 5
}

这里为了能测试到缓存过期,将过期时间设成了非常小的5秒。

在StaticServer类中接收这些配置:

class StaticServer {
 constructor() {
   ...
  this.enableCacheControl = config.cacheControl;
  this.enableExpires = config.expires;
  this.enableETag = config.etag;
  this.enableLastModified = config.lastModified;
  this.maxAge = config.maxAge;
 }

现在,我们要在原来的respondFile前横加一杠,增加是要返回304还是200的逻辑。

 respond(pathName, req, res) {
  fs.stat(pathName, (err, stat) => {
   if (err) return respondError(err, res);
   this.setFreshHeaders(stat, res);
   if (this.isFresh(req.headers, res._headers)) {
    this.responseNotModified(res);
   } else {
    this.responseFile(pathName, res);
   }
  });
 }

准备返回文件前,根据配置,添加缓存相关的响应首部。

 generateETag(stat) {
  const mtime = stat.mtime.getTime().toString(16);
  const size = stat.size.toString(16);
  return `W/"${size}-${mtime}"`;
 }
 setFreshHeaders(stat, res) {
  const lastModified = stat.mtime.toUTCString();
  if (this.enableExpires) {
   const expireTime = (new Date(Date.now() + this.maxAge * 1000)).toUTCString();
   res.setHeader('Expires', expireTime);
  }
  if (this.enableCacheControl) {
   res.setHeader('Cache-Control', `public, max-age=${this.maxAge}`);
  }
  if (this.enableLastModified) {
   res.setHeader('Last-Modified', lastModified);
  }
  if (this.enableETag) {
   res.setHeader('ETag', this.generateETag(stat));
  }
 }

需要注意的是,上面使用了ETag弱验证器,并不能保证缓存文件与服务器上的文件是完全一样的。关于强验证器如何实现,可以参考etag包的源码。

下面是如何判断缓存是否仍然新鲜:

 isFresh(reqHeaders, resHeaders) {
  const noneMatch = reqHeaders['if-none-match'];
  const lastModified = reqHeaders['if-modified-since'];
  if (!(noneMatch || lastModified)) return false;
  if(noneMatch && (noneMatch !== resHeaders['etag'])) return false;
  if(lastModified && lastModified !== resHeaders['last-modified']) return false;
  return true;
 }

需要注意的是,http首部字段名是不区分大小写的(但http method应该大写),所以平常在浏览器中会看到大写或小写的首部字段。

但是node的http模块将首部字段都转成了小写,这样在代码中使用起来更方便些。所以访问header要用小写,如reqHeaders['if-none-match']。不过,仍然可以用req.rawreq.rawHeaders来访问原headers,它是一个[name1, value1, name2, value2, ...]形式的数组。

现在来测试一下,因为设置的缓存有效时间是极小的5s,所以强缓存几乎不会命中,所以第二次访问文件会发出新的请求,因为服务端文件并没做什么改变,所以会返回304。

现在来修改一下请求的这张图片,比如修改一下size,目的是让服务端的再验证失败,因而必须给客户端发送200和最新的文件。

接下来把缓存有效时间改大一些,比如10分钟,那么在10分钟之内的重复请求,都会命中强缓存,浏览器不会向服务端发起新的请求(但network依然能观察到这条请求)。

内容编码

服务器在发送很大的文档之前,对其进行压缩,可以节省传输用时。其过程是:

浏览器在访问网站时,默认会携带Accept-Encoding头

服务器在收到请求后,如果发现存在Accept-Encoding请求头,并且支持该文件类型的压缩,压缩响应的实体主体(并不压缩头部),并附上Content-Encoding首部

浏览器收到响应,如果发现有Content-Encoding首部,按其值指定的格式解压报文

对于图片这类已经经过高度压缩的文件,无需再额外压缩。因此,我们需要配置一个字段,指明需要针对哪些类型的文件进行压缩。

default.json

{
 ...
 "zipMatch": "^\\.(css|js|html)$"
}
static-server.js
 constructor() {
  ...
  this.zipMatch = new RegExp(config.zipMatch);
 }

用zlib模块来实现流压缩:

compressHandler(readStream, req, res) { const acceptEncoding = req.headers['accept-encoding']; if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) { return readStream; } else if (acceptEncoding.match(/\bgzip\b/)) { res.setHeader('Content-Encoding', 'gzip'); return readStream.pipe(zlib.createGzip()); } else if (acceptEncoding.match(/\bdeflate\b/)) { res.setHeader('Content-Encoding', 'deflate'); return readStream.pipe(zlib.createDeflate()); } }

因为配置了图片不需压缩,在浏览器中测试会发现图片请求的响应中没有Content-Encoding头。

范围请求

最后一步,使服务器支持范围请求,允许客户端只请求文档的一部分。其流程是:

  • 客户端向服务端发起请求
  • 服务端响应,附上Accept-Ranges头(值表示表示范围的单位,通常是“bytes”),告诉客户端其接受范围请求
  • 客户端发送新的请求,附上Ranges头,告诉服务端请求的是一个范围
  • 服务端收到范围请求,分情况响应:

范围有效,服务端返回206 Partial Content,发送指定范围内内容,并在Content-Range头中指定该范围

范围无效,服务端返回416 Requested Range Not Satisfiable,并在Content-Range中指明可接受范围

请求中的Ranges头格式为(这里不考虑多范围请求了):

Ranges: bytes=[start]-[end]

其中 start 和 end 并不是必须同时具有:

如果 end 省略,服务器应返回从 start 位置开始之后的所有字节

如果 start 省略,end 值指的就是服务器该返回最后多少个字节

如果均未省略,则服务器返回 start 和 end 之间的字节

响应中的Content-Range头有两种格式:

当范围有效返回 206 时:

Content-Range: bytes (start)-(end)/(total)

当范围无效返回 416 时:

Content-Range: bytes */(total)

添加函数处理范围请求:

 rangeHandler(pathName, rangeText, totalSize, res) {
  const range = this.getRange(rangeText, totalSize);
  if (range.start > totalSize || range.end > totalSize || range.start > range.end) {
   res.statusCode = 416;
   res.setHeader('Content-Range', `bytes */${totalSize}`);
   res.end();
   return null;
  } else {
   res.statusCode = 206;
   res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${totalSize}`);
   return fs.createReadStream(pathName, { start: range.start, end: range.end });
  }
 }

用 Postman来测试一下。在指定的root文件夹下创建一个测试文件:

testfile.js

This is a test sentence.

请求返回前六个字节 ”This “ 返回 206:

请求一个无效范围返回416:

读取命令行参数

至此,已经完成了静态服务器的基本功能。但是每一次需要修改配置,都必须修改default.json文件,非常不方便,如果能接受命令行参数就好了,可以借助 yargs 模块来完成。

var options = require( "yargs" )
 .option( "p", { alias: "port", describe: "Port number", type: "number" } )
 .option( "r", { alias: "root", describe: "Static resource directory", type: "string" } )
 .option( "i", { alias: "index", describe: "Default page", type: "string" } )
 .option( "c", { alias: "cachecontrol", default: true, describe: "Use Cache-Control", type: "boolean" } )
 .option( "e", { alias: "expires", default: true, describe: "Use Expires", type: "boolean" } )
 .option( "t", { alias: "etag", default: true, describe: "Use ETag", type: "boolean" } )
 .option( "l", { alias: "lastmodified", default: true, describe: "Use Last-Modified", type: "boolean" } )
 .option( "m", { alias: "maxage", describe: "Time a file should be cached for", type: "number" } )
 .help()
 .alias( "?", "help" )
 .argv;

瞅瞅 help 命令会输出啥:

这样就可以在命令行传递端口、默认页等:

node app.js -p 8888 -i main.html

总结

以上所述是小编给大家介绍的使用Node.js搭建静态资源服务详细教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2017-07-31

Node.js REPL (交互式解释器)实例详解

Node.js  REPL (交互式解释器)实例详解 Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端,我们可以在终端中输入命令,并接收系统的响应. Node 自带了交互式解释器,可以执行以下任务: 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中. 执行 - 执行输入的数据结构 打印 - 输出结果 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出. 多行表达式

Node.js 基础教程之全局对象

Node.js 基础教程之全局对象 在浏览器 JavaScript 中,通常 window 是全局对象. Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性. global 最根本的作用是作为全局变量的宿主. 注意: 永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染 命名空间,提高代码的耦合风险. __filename 脚本绝对路径 表示当前正在执行的脚本的文件名.它将输出文件所在位置的绝对路径,且和命令行参数

Node.js 事件循环详解及实例

 Node.js  事件循环详解及实例 Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现. Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. Node.js 有多个内置的事件,我们可以

使用travis-ci如何持续部署node.js应用详解

前言 在开始之前,我们先来简单介绍下Travis-ci,Travis-ci是一款持续集成(Continuous Integration)服务,它能够很好地与Github结合,每当代码更新时自动地触发集成过程. Travis-ci配置简单,很多nodejs项目都用它做自动测试.然而,对于持续集成,仅做到自动测试是不够的,还要有后续的自动部署,才能完成"提交代码 => 自动测试 => 自动部署"的集成链条. 本文以nodejs应用为例,来谈谈如何利用travis-ci完成自动部

利用node.js+mongodb如何搭建一个简单登录注册的功能详解

前言 最近突然对数据库和后台感兴趣了,就开始了漫长的学习之路,想想自己只是一个前端,只会java斯科瑞普,所以就开始看nodejs,看着看着突然发现mongodb和nodejs更配哦!,遂就开了我的mongodb之路.下面话不多说了,来一起看看详细的介绍吧. mongodb简介 就超简洁的说一下,mongo就是一个nosql的数据库,不使用sql的语法,当然其实也是大同小异的,增删改查还是差不多的,但是在概念上mongo还是跟mysql有相当大的区别的;比如在mongo中没有表的概念,而是一个集

使用Node.js实现RESTful API的示例

RESTful基础概念 REST(Representational State Transfer)描述了一个架构样式的网络系统,它首次出现在 2000 年 Roy Fielding 的博士论文中.在REST服务中,应用程序状态和功能可以分为各种资源.资源向客户端公开,客户端可以对资源进行增删改操作.资源的例子有:应用程序对象.数据库记录.算法等等. REST通过抽象资源,提供了一个非常容易理解和使用的API,它使用 URI (Universal Resource Identifier) 唯一表示

浅析Node.js 中 Stream API 的使用

本文由浅入深给大家介绍node.js stream api,具体详情请看下文吧. 基本介绍 在 Node.js 中,读取文件的方式有两种,一种是用 fs.readFile ,另外一种是利用 fs.createReadStream 来读取. fs.readFile 对于每个 Node.js 使用者来说最熟悉不过了,简单易懂,很好上手.但它的缺点是会先将数据全部读入内存,一旦遇到大文件的时候,这种方式读取的效率就非常低下了. 而 fs.createReadStream 则是通过 Stream 来读取

node.js遍历目录的方法示例

本文介绍了node.js遍历目录的方法示例,分享给大家,也给自己留个笔记,具体如下 同步遍历 const fs = require('fs'); const path=require('path'); function travel(dir,callback){ fs.readdirSync(dir).forEach((file)=>{ var pathname=path.join(dir,file) if(fs.statSync(pathname).isDirectory()){ travel

python和node.js生成当前时间戳的示例

Python # coding=utf-8 import time import sys def func(): # 格式化输出时间 s1 = int(time.strftime("%Y%m%d%H%M%S", time.localtime())) # 时间戳,由于默认是秒需要转换为毫秒输出 s2 = int(round(time.time() * 1000)) return s1, s2 def once(): '''如果没有指明命令行参数则运行一次''' s1, s2 = func

实现一个完整的Node.js RESTful API的示例

前言 这篇文章算是对Building APIs with Node.js这本书的一个总结.用Node.js写接口对我来说是很有用的,比如在项目初始阶段,可以快速的模拟网络请求.正因为它用js写的,跟iOS直接的联系也比其他语言写的后台更加接近. 这本书写的极好,作者编码的思路极其清晰,整本书虽说是用英文写的,但很容易读懂.同时,它完整的构建了RESTful API的一整套逻辑. 我更加喜欢写一些函数响应式的程序,把函数当做数据或参数进行传递对我有着莫大的吸引力. 从程序的搭建,到设计错误捕获机制

详解用Node.js实现Restful风格webservice

Restful风格的WebService正在渐渐取代传统的SOAP, Java 也有很多Restful的框架,很方便简洁,Jersey,restlet,甚至SpringMVC也可以,不得不说Rest让人从Web转型到WebService更容易和方便,当然深入Restful的理论还是发现比较复杂的,但是,开发和理论并不需要那么的贴合,有时候伪Restful更直观,靠谱些. 但是,作为很帅的Node.js怎么可以不和同样帅气的Restful相结合呢!?对于我们这种无视理论的开发者来说,Restful

Node.js使用对话框ngDialog的示例代码

做网站经常会遇到弹出对话框获取用户输入或弹出对话框让用户确认某个操作之类的情景,有一个基于AngularJS的扩展模块可以帮我们优雅地完成这类事情:ngDialog. ngDialog在github上提供了一个示例网页,演示了它的各种用法,在这里:https://github.com/likeastore/ngDialog/blob/master/example/index.html.ngDialog的github主页的readme也对常用的指令和服务做了较为详细的介绍,可以参考.我这篇就纯粹是

深入分析node.js的异步API和其局限性

用异步API的原因 异步的概念之所以首先在Web2.0中火起来,是因为在浏览器中Javascript在单线程上执行,而且他还与UI渲染公用一个线程.这意味着Javascript在执行的时候UI渲染和响应是处于停滞状态的.为了用户体验更好而采取异步的方式(当然,这在所谓的单线程语言中)不阻塞主线程继续响应用户操作.这属于用户体验的范畴. 同样的,如果有其他语言经验的工程师当然也明白,CPU在线程间切换是需要消耗大量的时间的(主要为上下文之间的切换和缓存),所以提高效率也是使用异步API的理由. 当

Node.js中调用mysql存储过程示例

例子仅在windows下测试通过,没有放在linux下测试.如有问题,可以电邮给我~ 1.安装node.js.mysql,此处略(自行搜索吧)-: 2.创建一个名为test的数据库,然后建一张名为user_info的表(仅供测试)- 这里假定mysql使用的用户名为root,密码为123456 相应的mysql如下: 复制代码 代码如下: /** * 创建名为test的数据库 */ DROP DATABASE IF EXISTS test; CREATE DATABASE test; USE t

详解本地Node.js服务器作为api服务器的解决办法

在看react-native教程的时候,遇到要在手机端调试,需要api服务器,但是由于Node.js自己就作为服务器,没有apache怎么解决这个问题,用apache和nginx也可以解决,但是有点复杂,我们就使用node已有的模块解决这个问题. //服务器端的代码 var express = require('express'); var app = express(); // set up handlebars view engine var handlebars = require('ex