zoukankan      html  css  js  c++  java
  • 创业笔记-Node.js入门之基于事件驱动的回调

    基于事件驱动的回调

    这个问题可不好回答(至少对我来说),不过这是Node.js原生的工作方式。它是事件驱动的,这也是它为什么这么快的原因。

    你也许会想花点时间读一下Felix Geisendörfer的大作Understanding node.js,它介绍了一些背景知识。

    这一切都归结于“Node.js是事件驱动的”这一事实。好吧,其实我也不是特别确切的了解这句话的意思。不过我会试着解释,为什么它对我们用Node.js写网络应用(Web based application)是有意义的。

    当我们使用 http.createServer 方法的时候,我们当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。

    问题是,这是异步的:请求任何时候都可能到达,但是我们的服务器却跑在一个单进程中。

    写PHP应用的时候,我们一点也不为此担心:任何时候当有请求进入的时候,网页服务器(通常是Apache)就为这一请求新建一个进程,并且开始从头到尾执行相应的PHP脚本。

    那么在我们的Node.js程序中,当一个新的请求到达8888端口的时候,我们怎么控制流程呢?

    嗯,这就是Node.js/JavaScript的事件驱动设计能够真正帮上忙的地方了——虽然我们还得学一些新概念才能掌握它。让我们来看看这些概念是怎么应用在我们的服务器代码里的。

    我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用。

    我们不知道这件事情什么时候会发生,但是我们现在有了一个处理请求的地方:它就是我们传递过去的那个函数。至于它是被预先定义的函数还是匿名函数,就无关紧要了。

    这个就是传说中的 回调 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行 回调

    至少对我来说,需要一些功夫才能弄懂它。你如果还是不太确定的话就再去读读Felix的博客文章。

    让我们再来琢磨琢磨这个新概念。我们怎么证明,在创建完服务器之后,即使没有HTTP请求进来、我们的回调函数也没有被调用的情况下,我们的代码还继续有效呢?我们试试这个:

    var http = require("http");

    function onRequest(request, response){
      console.log("Request received.");
      response.writeHead(200,{"Content-Type":"text/plain"});
      response.write("Hello World");
      response.end();
    }

    http.createServer(onRequest).listen(8888);

    console.log("Server has started.");

    注意:在 onRequest (我们的回调函数)触发的地方,我用 console.log 输出了一段文本。在HTTP服务器开始工作之后,也输出一段文本。

    当我们与往常一样,运行它node server.js时,它会马上在命令行上输出“Server has started.”。当我们向服务器发出请求(在浏览器访问http://localhost:8888/ ),“Request received.”这条消息就会在命令行中出现。

    这就是事件驱动的异步服务器端JavaScript和它的回调啦!

    (请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次“Request received.”。那是因为大部分服务器都会在你访问 http://localhost:8888 /时尝试读取 http://localhost:8888/favicon.ico )

    服务器是如何处理请求的

    好的,接下来我们简单分析一下我们服务器代码中剩下的部分,也就是我们的回调函数 onRequest() 的主体部分。

    当回调启动,我们的 onRequest() 函数被触发的时候,有两个参数被传入: requestresponse

    它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应请求(比如向发出请求的浏览器发回一些东西)。

    所以我们的代码就是:当收到请求时,使用 response.writeHead() 函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用 response.write() 函数在HTTP相应主体中发送文本“Hello World"。

    最后,我们调用 response.end() 完成响应。

    目前来说,我们对请求的细节并不在意,所以我们没有使用 request 对象。

    服务端的模块放在哪里

    OK,就像我保证过的那样,我们现在可以回到我们如何组织应用这个问题上了。我们现在在 server.js 文件中有一个非常基础的HTTP服务器代码,而且我提到通常我们会有一个叫 index.js 的文件去调用应用的其他模块(比如 server.js 中的HTTP服务器模块)来引导和启动应用。

    我们现在就来谈谈怎么把server.js变成一个真正的Node.js模块,使它可以被我们(还没动工)的 index.js 主文件使用。

    也许你已经注意到,我们已经在代码中使用了模块了。像这样:

    var http = require("http");

    ...

    http.createServer(...);

    Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

    这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

    给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以按照自己的喜好来:

    var foo = require("http");

    ...

    foo.createServer(...);

    很好,怎么使用Node.js内部模块已经很清楚了。我们怎么创建自己的模块,又怎么使用它呢?

    等我们把 server.js 变成一个真正的模块,你就能搞明白了。

    事实上,我们不用做太多的修改。把某段代码变成模块意味着我们需要把我们希望提供其功能的部分 导出 到请求这个模块的脚本。

    目前,我们的HTTP服务器需要导出的功能非常简单,因为请求服务器模块的脚本仅仅是需要启动服务器而已。

    我们把我们的服务器脚本放到一个叫做 start 的函数里,然后我们会导出这个函数。

    var http = require("http");

    function start(){
      function onRequest(request, response){
        console.log("Request received.");
        response.writeHead(200,{"Content-Type":"text/plain"});
        response.write("Hello World");
        response.end();
      }

      http.createServer(onRequest).listen(8888);
      console.log("Server has started.");
    }

    exports.start = start;

    这样,我们现在就可以创建我们的主文件 index.js 并在其中启动我们的HTTP了,虽然服务器的代码还在 server.js 中。

    创建 index.js 文件并写入以下内容:

    var server = require("./server");

    server.start();

    正如你所看到的,我们可以像使用任何其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们使用了。

    好了。我们现在就可以从我们的主要脚本启动我们的的应用了,而它还是老样子:

    node index.js

    非常好,我们现在可以把我们的应用的不同部分放入不同的文件里,并且通过生成模块的方式把它们连接到一起了。

    我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我们得做点什么——对于不同的URL请求,服务器应该有不同的反应。

    对于一个非常简单的应用来说,你可以直接在回调函数 onRequest() 中做这件事情。不过就像我说过的,我们应该加入一些抽象的元素,让我们的例子变得更有趣一点儿。

    处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选择”——那么,我们接下来就创造一个叫做 路由 的模块吧。

    如何来进行请求的“路由”

    我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。

    因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。

    我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是urlquerystring模块。

                                   url.parse(string).query
                                               |
               url.parse(string).pathname      |
                           |                   |
                           |                   |
                         ------ -------------------
    http://localhost:8888/start?foo=bar&hello=world
                                    ---       -----
                                     |          |
                                     |          |
                  querystring(string)["foo"]    |
                                                |
                             querystring(string)["hello"]
    

    当然我们也可以用querystring模块来解析POST请求体中的参数,稍后会有演示。

    现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:

    var http = require("http");
    var url = require("url");

    function start(){
      function onRequest(request, response){
        var pathname = url.parse(request.url).pathname;
        console.log("Request for "+ pathname +" received.");
        response.writeHead(200,{"Content-Type":"text/plain"});
        response.write("Hello World");
        response.end();
      }

      http.createServer(onRequest).listen(8888);
      console.log("Server has started.");
    }

    exports.start = start;

    好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。

    在我们所要构建的应用中,这意味着来自/start/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

    现在我们可以来编写路由了,建立一个名为router.js的文件,添加以下内容:

    function route(pathname){
      console.log("About to route a request for "+ pathname);
    }

    exports.route = route;

    如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。

    我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块(你可以读读Martin Fowlers关于依赖注入的大作来作为背景知识)。

    首先,我们来扩展一下服务器的start()函数,以便将路由函数作为参数传递过去:

    var http = require("http");
    var url = require("url");

    function start(route){
      function onRequest(request, response){
        var pathname = url.parse(request.url).pathname;
        console.log("Request for "+ pathname +" received.");

        route(pathname);

        response.writeHead(200,{"Content-Type":"text/plain"});
        response.write("Hello World");
        response.end();
      }

      http.createServer(onRequest).listen(8888);
      console.log("Server has started.");
    }

    exports.start = start;

    同时,我们会相应扩展index.js,使得路由函数可以被注入到服务器中:

    var server = require("./server");
    var router = require("./router");

    server.start(router.route);

    在这里,我们传递的函数依旧什么也没做。

    如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:

    bash$ node index.js
    Request for /foo received.
    About to route a request for /foo

    (以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分)。

  • 相关阅读:
    Docker安装
    MVC-HtmlHelper简单总结
    D3.js
    分布式事务seata
    彻底搞懂JAVA路径问题
    idea 代码生成
    自动生成 serialVersionUID 的设置
    狂神说SSM框架系列连载
    缓存穿透、缓存击穿、缓存雪崩区别和解决方案
    多线程
  • 原文地址:https://www.cnblogs.com/hznet/p/5239898.html
Copyright © 2011-2022 走看看