十一.JavaScript子集和服务端JavaScript(Node)

92 阅读5分钟

1. JavaScript子集和子集的安全性

大多数语言都会定义它们的子集,用以更安全地执行不可信的第三方代码。子集的定义大部分都是出于安全考虑,只有使用这门语言的一个安全的子集编写脚本,才能让代码执行得更安全、更稳定,比如如何更安全地执行一段由不可信第三方提供的广告代码。

子集的设计目的是能在一个容器或“沙箱”中更安全地运行不可信的第三方JavaScript代码。所有能破坏这个沙箱并影响全局执行环境的语言特性和API在这个安全子集中都是禁止的。

2. Node

  • Node是基于C++的高速JavaScript解释器,绑定了用于进程、文件和网络套接字等底层Unix API,还绑定了HTTP客户端和服务器API。
  • 除了一些专门命名的同步方法外,Node的绑定都是异步的,且Node程序默认绝不阻塞,这意味着它们通常具备强大的可伸缩能力并能有效地处理高负荷。
  • 由于API是异步的,因此Node依赖事件处理程序,其通常使用嵌套函数和闭包来实现

Node是在Google的V8 JavaScript引擎上构建而成。Node 0.4使用的是V8的3.1版本,它实现了除严格模式之外的全部ECMAScript 5。

下载、编译并安装Node后,可以使用如下命令运行Node程序:

node program.js

由于Node的函数和方法都是异步的,因此当它们等待运算完成时并不产生阻塞。非异步方法的返回值无法返回异步运算的结果给你。如果想获取异步运算的结果,当异步结果准备好时,就必须提供Node能调用的一个函数进行回调

2.1 Node fs模块

Node的设计目标是高性能I/O,因此其流API常被用到。

"fs"模块包括大量的方法,用于列出文件目录、查询文件属性等。下面的Node程序使用同步的方法列出一个目录的内容,并显示文件大小和修改日期:

var fs = require("fs"),
  path = require("path"); //加载需要的模块
var dir = process.cwd(); //当前目录
if (process.argv.length > 2) dir = process.argv[2]; //或来自命令行

var files = fs.readdirSync(dir); //读取目录内容
process.stdout.write("Name\tSize\tDate\n"); //输出头

files.forEach(function(filename) {
  //获取每个文件名
  var fullname = path.join(dir, filename); //拼接目录和文件名
  var stats = fs.statSync(fullname); //获取文件属性
  if (stats.isDirectory()) filename += "/"; //标记子目录

  process.stdout.write(
    filename +
    "\t" + //输出文件名+
    stats.size +
    "\t" + //文件大小+
      stats.mtime +
      "\n"
  ); //修改时间
});

image.png

image.png

2.2 Node示例 HTTP服务器

//这是一个简单的Node HTTP服务器,能处理当前目录的文件,
//并能实现两种特殊的URL用于测试
//用http://localhost:8000或http://127.0.0.1:8000连接这个服务器
//首先,加载所有要用的模块
var http = require("http"); //HTTP服务器API
var fs = require("fs"); //用于处理本地文件
var server = new http.Server(); //创建新的HTTP服务器
server.listen(8000); //在端口8000上运行它
//Node使用"on()"方法注册事件处理程序,
//当服务器得到新请求,则运行函数处理它
server.on("request", function(request, response) {
  //解析请求的URL
  var url = require("url").parse(request.url); //特殊URL会让服务器在发送响应前先等待
  //此处用于模拟缓慢的网络连接
  if (url.pathname === "/test/delay") {
    //使用查询字符串来获取延迟时长,或者2000毫秒
    var delay = parseInt(url.query) || 2000; //设置响应状态码和头
    response.writeHead(200, { "Content-Type": "text/plain;charset=UTF-8" }); //立即开始编写响应主体
    response.write("Sleeping for " + delay + " milliseconds..."); //在之后调用的另一个函数中完成响应
    setTimeout(function() {
      response.write("done.");
      response.end();
    }, delay);
  }
  //若请求是"/test/mirror",则原文返回它
  //当需要看到这个请求头和主体时,会很有用
  else if (url.pathname === "/test/mirror") {
    //响应状态和头
    response.writeHead(200, { "Content-Type": "text/plain;charset=UTF-8" }); 
    //用请求的内容开始编写响应主体
    response.write(
      request.method + "" + request.url + "HTTP/" + request.httpVersion + "\r\n"
    ); //所有的请求头
    for (var h in request.headers) {
      response.write(h + ":" + request.headers[h] + "\r\n");
    }
    response.write("\r\n"); //使用额外的空白行来结束头
    //在这些事件处理程序函数中完成响应:
    //当请求主体的数据块完成时,把其写入响应中
    request.on("data", function(chunk) {
      response.write(chunk);
    }); //当请求结束时,响应也完成
    request.on("end", function(chunk) {
      response.end();
    });
  }
  //否则,处理来自本地目录的文件
  else {
    //获取本地文件名,基于其扩展名推测内容类型
    var filename = url.pathname.substring(1); //去掉前导"/"
    var type;
    switch (
      filename.substring(filename.lastIndexOf(".") + 1) //扩展名
    ) {
      case "html":
      case "htm":
        type = "text/html;charset=UTF-8";
        break;
      case "js":
        type = "application/javascript;charset=UTF-8";
        break;
      case "css":
        type = "text/css;charset=UTF-8";
        break;
      case "txt":
        type = "text/plain;charset=UTF-8";
        break;
      case "manifest":
        type = "text/cache-manifest;charset=UTF-8";
        break;
      default:
        type = "application/octet-stream";
        break;
    }
    //异步读取文件,并将内容作为单独的数据块传给回调函数
    //对于确实很大的文件,使用流API fs.createReadStream()更好
    fs.readFile(filename, function(err, content) {
      if (err) {
        //如果由于某些原因无法读取该文件
        response.writeHead(404, {
          //发送404未找到状态码
          "Content-Type": "text/plain;charset=UTF-8"
        });
        response.write(err.message); //简单的错误消息主体
        response.end(); //完成
      } else {
        //否则,若读取文件成功
        response.writeHead(
          200, //设置状态码和MIME类型
          { "Content-Type": type }
        );
        response.write(content); //把文件内容作为响应主体发送
        response.end(); //完成
      }
    });
  }
});

1.模拟缓慢的网络连接

image.png

2. 若请求是"/test/mirror",则返回http请求头和主体

image.png

image.png

3.读取文件内容(注意目录中不能出现中文)

image.png

image.png