Node.js 系列:原生 Node.js 应用

原生 Node.js 应用

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效
Node.js 的包管理器 npm,是全球最大的开源库生态系统

? 本文主要介绍构建一个 Node.js 应用的基本步骤和模块,并假定你已经对 Node.js Api 有必定的了解javascript

? 本文引用部分代码做为例子,若是但愿参看所有源码,欢迎去 github 查阅(若是以为有必定帮助,欢迎star)html

模块架构设计

整个 Node.js 应用的架构设计java

original

node.js 应用构成

  • 引入模块:经过 require 指令来引入 Node.js 模块
  • 建立服务器:服务器用来监听客户端请求
  • 接收请求和响应请求:接收客户端的HTTP请求,返回响应数据
// 经过 require 引入 http 模块,并将实例化的 HTTP 赋值给 http 变量
const http = require('http');
// 引入 url 模块,用来解析数据
const url = require('url');

function ylone(router, handleObj) {
    const hostname = '127.0.0.1';
    const port = 7777;
    // http.createServer(function(){}) 方法建立服务器,并返回一个对象
    const server = http.createServer((req, res) => {
        const path = url.parse(req.url);
        const pathName = path.pathname;
        // 处理node.js每次自动请求favicon.ico
        if (pathName !== '/favicon.ico') {
          const content = router(handleObj, pathName, res, req);
        }
    });

    // server.listen() 方法绑定主机和端口
    server.listen(port, hostname, () => {
        console.log(`服务运行在${hostname}:${port}`);
    });
}

exports.ylone = ylone;

基于事件驱动的回调

  • http.createServer((req, res) => {...}) 是一个典型的回调,事实上,这就是 Node.js 原生的工做方式
  • 这里直接将一个匿名函数做为变量进行传递,绕开了“先定义,再传递”的圈子
  • 像写 PHP 应用时:任什么时候候当有请求进入时,服务器(如Apache)就会为这个请求建立一个新的进程,而且从头到尾执行相应的 PHP 脚本
  • 在 Node.js 中,不管什么时候当一个新的请求到达指定端口时,咱们在服务器建立时传递的函数就会被调用

Node.js 模块

  • 模块意味着将 Node.js 应用(如 http.js)抽象成一个模块,经过入口文件 index.js 去调用相应的模块来引导和启动应用
  • 将代码模块化意味着咱们将提供其功能的部分(如 一个函数)导出到请求这个模块的脚本内
const server = require('./http');
const router = require('./route');
const handle = require('./requestHandle');

var handleObj = {};
// 入口 Case
handleObj['/'] = handle.hello;
// 非阻塞 Case
handleObj['/vlone'] = handle.vlone;
// post Case
handleObj['/supreme'] = handle.supreme;
// get Case
handleObj['/adidas'] = handle.adidas;

server.ylone(router.router, handleObj);

路由

  • 为了处理不一样的 http 请求,咱们须要经过建立一个路由模块来进行“路由选择”
  • 为路由提供请求的 url 和其余须要的 get 和 post 参数,随后路由根据这些数据来执行相应的代码
  • 咱们所须要的数据都在 http.createServer((req, res) => {...}) 的 req 参数中,为了解析 req,须要额外引入 urlquerystring Node.js 模块
  • 在 index.js 内引入路由对象,将路由方法传递给 http 应用,在 http.createServer((req, res) => {...}) 内解析 req 参数,而后调用 router 方法
  • 理解如下函数式编程:将 router 对象传递给 index,在 index 内将 router 方法传递给 http,由于 http 并不关心 router 方法从哪来,只须要执行方法,而后完成业务,可是首先须要保证有这个对象
  • 函数式编程最基本,最核心的即思想转换,由名词到动词,由对象到方法,行为驱动执行
function router(handleObj, pathName, res, req) {
  if (typeof handleObj[pathName] === 'function') {
    return handleObj[pathName](res, req);
  } else {
    res.writeHead(200, {
          'Content-type': 'text/plain'
    });
    const content = '404 Not Found';
    res.write(content);
    res.end();
  }
}

exports.router = router;

将路由分发到请求处理函数

  • 须要建立一个新的 resquestHandlers 模块,封装各个处理函数来对应不一样的请求
  • 在 JavaScript 中经过对象键值对来封装 路径->方法 的映射关系
  • 在 C++ 或者 C# 中,对象指的是类或者结构体的实例,对象根据其实例化的模板会拥有不一样的属性和方法
  • 在 JavaScript 中,对象是一个键值对集合
  • 在入口文件(index.js)内引入 requestHandle,同时声明一个操做对象(handleObj),用来存储 路径->方法 的映射关系,最后将路由方法和操做对象传递给服务器应用(http.js)
  • 在服务器应用内,得到浏览器请求的路径,调用路由方法(router),将操做对象(handleObj)和请求路径做为参数传递
  • 在路由内(route.js)获取路径对应的函数,自执行函数

由于文章篇幅缘由,这里只展现关键代码,源码参看 githubnode

const { exec } = require('child_process');
const querystring = require('querystring');
const url = require('url');

function createHttp(type, res, val) {
    const content = val;
    const conType = {
        plain: 'text/plain;charset=utf-8',
        html: 'text/html',
    };
    // 为隐式的响应头设置值
    res.writeHead(200, {
        'Content-type': conType[type]
    });
    // 发送响应主体
    res.write(content);
    // http 完成响应
    res.end();
}

... something else

function vlone(res) {
    exec('node --version', (error, stdout, stderr) => {
        if (error) {
            console.log(error, stdout, stderr);
            return;
        }
        const content = stdout;
        const type = 'plain';
        createHttp(type, res, content);
    });
}

... something else

阻塞与非阻塞

A() 方法读取文件,所以须要必定的响应时间,B() 方法表明其余须要执行的代码git

阻塞:在A() 执行的过程当中,B() 处于等待状态,当A() 访问文件数据准备就绪后,B() 才开始执行github

阻塞IO模型

由上图能够看出,应用程序从进行系统调用到复制数据报到应用进程缓冲区的整段过程是阻塞的,直到数据报被复制到用户空间完成后,用户进程才解除阻塞状态,继续执行下一个应用程序npm

  • 优势:可以及时返回数据,无延迟,方便调试
  • 缺点:须要等待

非阻塞:在A() 执行的过程当中,B() 同时执行,且当A() 访问文件数据准备就绪后,A() 会被执行完成编程

非阻塞IO模型

由上图能够看出,应用程序在调用过程当中,若是数据报尚未准备就绪,会先返回一个错误信息(EWOULDBLOCK),此时当前进程能够执行其余方法,而不会阻塞。而 A() 会轮询内核,返回缓冲区数据是否准备就绪api

  • 优势:不须要等待,当前线程能够处理多个任务
  • 缺点:增大了任务完成的响应延迟,由于任务可能在两次轮询间隔内完成,从而致使总体数据的吞吐量下降

以非阻塞方式进行请求响应

  • 当前的应用交互方式:(请求处理程序 -> 请求路由 -> 服务器)将请求处理程序返回的内容(请求处理程序最终要显示给用户的内容)传递给HTTP服务器
  • 当前这种交互方式的问题在于,若是请求处理程序中有 Node.js 封装的非阻塞方法A(),那么A() 在阻塞过程当中,服务器就已经将数据返回了,并不会等到A() 执行完毕
  • 为了解决上述问题,以非阻塞方式进行请求响应,相对于以前将数据传递给服务器的方式,如今咱们须要将服务器(response对象)传递给生成数据的应用内,待数据准备完毕,再返回响应数据
  • 这样能够同时请求两个路径(实际上就是触发两个函数方法),B() 并不会由于A()执行时间长而处于等待状态

处理 post 请求

  • 建立一个表单元素,设置表单提交方法为 post, 每当用户提交表单时,则触发 supreme() 方法
  • 处理 post 请求通常采用异步非阻塞方式,由于 post 请求通常会比较重,你没法控制用户输入的数据量,若是用阻塞的方式处理则必然会致使用户操做阻塞
  • 为了实现非阻塞,Node.js 会将 post 数据拆分红数据块,而后经过出发特定事件,将这些数据块传递给回调函数
  • 经常使用的 post 两个事件:data事件(新的数据块到达时触发),end事件(全部数据都已经接收完毕时触发)
  • 经过在 request 对象上注册监听器(listener)来告诉应用当 post 事件触发时,应该触发哪些回调函数

处理 get 请求

  • 经过 Node.js 封装的 url对象来解析 url 参数,获取关键数据
  • url.parse() 的第二参数 parseQueryString 若是为 true,则 query 属性老是会经过 querystring 模块的 parse() 方法生成一个对象

some pieces

  • 当写好 Node.js 脚本(如 ylone.js)后,经过 node ylone.js 命令执行脚本
  • 在浏览器访问指定地址(如 http://localhost:7777/)意味着向服务器发出请求,从而触发服务器建立时的回调函数
  • 当访问网页(如 http://localhost:7777/)时,控制台可能会输出两次 req 的数据,那是由于大部分浏览器会在访问网页时尝试读取 favicon.ico 文件
  • 针对浏览器每次发送请求,都会默认请求一次 /favicon.ico 的问题,能够在 http 中对其进行过滤,不执行操做
  • 若是但愿在 Node.js 内的传递一个 html 片断,并渲染在浏览器上,须要将 res.writeHead(200, {'Content-type': 'text/plain'}) 的 Content-type 设置为 text/html
  • Node.js 返回数据(response)在浏览器展现乱码,经过在 res.writeHead(200, {'Content-type': 'text/plain;charset=utf-8'}) 加上 charset=utf-8 配置解决

--Respect Node.js--浏览器

相关文章
相关标签/搜索