Node.js学习笔记【1】入门(服务器JS、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块)

笔记来自《Node入门》@2011 Manuel Kiessling

JavaScript与Node.js

Node.js事实上既是一个运行时环境,同时又是一个库。

使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器。

一个基础的HTTP服务器

server.js:一个可以工作的HTTP服务器

var http = require("http");

http.createServer(function(request, response) {
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
}).listen(8888);

进行函数传递

同样的效果:

var http = require("http");

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

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

基于事件驱动的回调

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

服务器是如何处理请求的

当收到请求时,使用response.writeHead()函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用response.write()函数在HTTP相应主体中发送文本“Hello World”。最后,我们调用 response.end() 完成响应。

服务器端的模块放在哪里

把某段代码变成模块意味着我们需要把我们希望提供其功能的部分导出到请求这个模块的脚本。

代码修改如下:

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

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

server.start();

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

处理不同的HTTP请求,叫做“路由选择”。

【图】url-query

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;

router.js:路由

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

exports.route = route;

扩展一下服务器的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);

路由给真正的请求处理程序

 

requestHandlers.js:请求处理程序

function start(response) {
    console.log("Request handler 'start' was called.");
}

function upload(response, request) {
    console.log("Request handler 'upload' was called.");
}

exports.start = start;
exports.upload = upload;

确定将一系列请求处理程序通过一个对象来传递,并且需要使用松耦合的方式将这个对象注入到route()函数中。

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

var handle = {};
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;

server.start(router.route, handle);

完成对象的定义之后,我们把它作为额外的参数传递给服务器,为此将server.js修改如下:

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

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

        route(handle, 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;

在router.js文件中修改route函数。

function route(handle, pathname) {
    console.log("About to route a request for " + pathname);
    if(typeof handle[pathname] === 'function') {
        handle[pathname]();
    } else {
        console.log("No request handler found for " + pathname);
    }
}

exports.route = route;

让请求处理程序作出响应

不好的是实现方式(阻塞)

requesHandler.js

function start(response) {
    console.log("Request handler 'start' was called.");

    function sleep(milliSeconds) {
        var startTime = new Date().getTime();
        while (new Date().getTime() < startTime + milliSeconds);
    }

    sleep(10000);
    return "Hello Start";
}

function upload(response, request) {
    console.log("Request handler 'upload' was called.");
    return "Hello Upload";
}

exports.start = start;
exports.upload = upload;

router.js

function route(handle, pathname) {
    console.log("About to route a request for " + pathname);
    if(typeof handle[pathname] === 'function') {
        return handle[pathname]();
    } else {
        console.log("No request handler found for " + pathname);
        return "404 Not found";
    }
}

exports.route = route;

server.js

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

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

        response.writeHead(200, {"Content-Type": "text/plain"});
        var content = route(handle, pathname);
        response.write(content);
        response.end();
    }
    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}

exports.start = start;

阻塞与非阻塞

 

要用非阻塞操作,我们需要使用回调,通过将函数作为参数传递给其他需要花时间做处理的函数。

对Node.js来说,它是这样处理的:“嘿,probablyExpensiveFunction() 【这里指的就是需要花时间处理的函数】,你继续处理你的事情,我(Node.js线程)先不等你了,我继续去处理你后面的代码,请你提供一个callbackFunction(),等你处理完之后,我会去调用该回调函数的。”

以非阻塞操作进行请求响应

到目前为止,我们的应用已经可以通过应用各层之间传递值的方式(请求处理程序–> 请求路由 -> 服务器)将请求处理程序返回的内容(请求处理程序最终要显示给用户的内容)传递给HTTP服务器。

现在我们采用如下这种新的是实现方式:相对采用将内容传递给服务器的方式,我们这次采用将服务器“传递”给内容的方式。从实践角度来说,就是将response对象(从服务器的回调函数onRequest()获取)通过请求路由传递给请求处理程序。随后,处理程序就可以采用该对象上的函数来对请求作出响应。

server.js

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

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

        route(handle, pathname, response);
    } 

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

exports.start = start;

router.js

function route(handle, pathname, response) {
    console.log("About to route a request for " + pathname);
    if(typeof handle[pathname] === 'function') {
        handle[pathname](response);
    } else {
        console.log("No request handler found for " + pathname);
        response.writeHead(404, {"Content-type": "text/plain"});
        response.write("404 Not found");
        response.end();
    }
}

exports.route = route;

requestHandler.js

var exec = require("child_process").exec;

function start(response) {
    console.log("Request handler 'start' was called.");
    var content = "empty";

    exec("dir", function (error, stdout, stderr) {
        response.writeHead(200, {"Content-type": "text/plain"});
        response.write(stdout);
        response.end();
    });
}

function upload(response) {
    console.log("Request handler 'upload' was called.");
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write("Hello Upload");
    response.end();
}

exports.start = start;
exports.upload = upload;

更加有用的场景

 

首先,让我们来看看如何处理POST请求(非文件上传),之后,我们使用Node.js的一个用于文件上传的外部模块。

处理POST请求

我们显示一个文本区(textarea)供用户输入内容,然后通过POST请求提交给服务器。最后,服务器接受到请求,通过处理程序将输入的内容展示到浏览器中。

/start请求处理程序用于生成带文本区的表单,因此,我们将requestHandlers.js修改为如下形式:

var exec = require("child_process").exec;

function start(response) {
    console.log("Request handler 'start' was called.");

    var body = '<html>'+
        '<head>'+
        '<meta http-equiv="Content-Type" content="text/html; '+
        'charset=UTF-8" />'+
        '</head>'+
        '<body>'+
        '<form action="/upload" method="post">'+
        '<textarea name="text" rows="20" cols="60"></textarea>'+
        '<input type="submit" value="Submit text" />'+
        '</form>'+
        '</body>'+
        '</html>';

    response.writeHead(200, {"Content-Type": "text/html"});
    response.write(body);
    response.end();
}

function upload(response) {
    console.log("Request handler 'upload' was called.");
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write("Hello Upload");
    response.end();
}

exports.start = start;
exports.upload = upload;

为了使整个过程非阻塞,Node.js会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据块传递给回调函数。这里的特定的事件有data事件(表示新的小数据块到达了)以及end事件(表示所有的数据都已经接受完毕)。

回调函数通过在request对象上注册监听器来实现。这里的request对象是每次接收到HTTP请求时候,都会把该对象传递给onRequest回调函数。

server.js

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

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

        request.setEncoding("utf-8");

        request.addListener("data", function(postDataChunk) {
	    postData += postDataChunk;
	    console.log("Reveived POST data chunk '" +
		postDataChunk + "'.");
	});

	request.addListener("end", function() {
	    route(handle, pathname, response, postData);
	})
    }
    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}

exports.start = start;

router.js

function route(handle, pathname, response, postData) {
    console.log("About to route a request for " + pathname);
    if(typeof handle[pathname] === 'function') {
        handle[pathname](response, postData);
    } else {
        console.log("No request handler found for " + pathname);
        response.writeHead(404, {"Content-type": "text/plain"});
        response.write("404 Not found");
        response.end();
    }
}

exports.route = route;

requestHandlers.js(使用querystring模块获取text字段)

var querystring = require("querystring");

function start(response, postData) {
    console.log("Request handler 'start' was called.");

    var body = '<html>'+
        '<head>'+
        '<meta http-equiv="Content-Type" content="text/html; '+
        'charset=UTF-8" />'+
        '</head>'+
        '<body>'+
        '<form action="/upload" method="post">'+
        '<textarea name="text" rows="20" cols="60"></textarea>'+
        '<input type="submit" value="Submit text" />'+
        '</form>'+
        '</body>'+
        '</html>';

    response.writeHead(200, {"Content-Type": "text/html"});
    response.write(body);
    response.end();
}

function upload(response, postData) {
    console.log("Request handler 'upload' was called.");
    response.writeHead(200, {"Content-type": "text/plain"});
    response.write("You've sent: " + querystring.parse(postData).text);
    response.end();
}

exports.start = start;
exports.upload = upload;

处理文件上传

允许用户上传图片,并将该图片在浏览器中显示出来。

需要将该文件读取到我们的服务器中,使用一个叫fs的模块。

完整的代码在Github:https://github.com/manuelkiessling/NodeBeginnerBook/tree/master/code/application

总结

介绍了:服务器JavaScript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块等等。

当然了,还有许多没有介绍到的:如何操作数据库、如何进行单元测试、如何开发Node.js的外部模块以及一些简单的诸如如何获取GET请求之类的方法。

文档信息

  • 最后修改时间:2014年06月02日 02:17

Node.js学习笔记【1】入门(服务器JS、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块)

时间: 2024-07-30 08:31:30

Node.js学习笔记【1】入门(服务器JS、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块)的相关文章

ArcGIS JS 学习笔记1 用ArcGIS JS 实现仿百度地图的距离量测和面积量测

一.开篇 在博客注册了三年,今天才决定写第一篇博客,警告自己不要懒!!! 二.关于ArcGIS JS 版本选择 在写这篇博客时ArcGIS JS 4.0正式版已经发布.它和3.x版本的不同是,Map不在是一个控件,而真的只是一张“图”,Map(4.0版本)需要在一个View里面来展示,在MapView里面就是一张平面图,在SceneView里面就一张三维地图.同一张地图在不同的View里面就可以呈现出不同的效果.但是4.0版本才是一个最初的版本,还有很多3.x有的功能没有被加入到其中.所以我打算

Scala学习笔记(1) -- 为什么要学函数式编程

很久之前我就学过Lisp和Erlang,但是也就是写写HelloWorld,写个排序算法.也在Coursera上听过Scala的课,可是那时候我还不怎么用Java,所以后来也没怎么继续.可是对函数式编程的兴趣一直不减,工作中几乎不会用Scala,但是用的是Java,我一直在想着怎么把Scala用到工作中.最近在写一个工具,因为这个工具基本只有我们项目组用,而且很简单,所以我就用Scala写了.以后有机会,把Scala用在生产上. 为什么对函数式编程这么感兴趣呢. 第一个原因,可以装逼,或者说是个

JS学习笔记之入门篇一

本笔记总结精简,只做查找只用,如有不当之处,欢迎指正! 1.JS代码插入 2.JS代码引入 3.JS在页面中的位置 注意: javascript作为一种脚本语言可以放在html页面中任何位置,但是浏览器解释html时是按先后顺序的,所以前面的script就先被执行.比如进行页面显示初始化的js必须放在head里面,因为初始化都要求提前进行(如给页面body设置css等):而如果是通过事件调用执行的function那么对位置没什么要求的. 4.JS 函数 function 函数名() {     

Node.js学习笔记(2)——关于异步编程风格

Node.js的异步编程风格是它的一大特点,在代码中就是体现在回调中. 首先是代码的顺序执行: function heavyCompute(n, callback) { var count = 0, i, j; for (i = n; i > 0; --i) { for (j = n; j > 0; --j) { count += 1; } }callback(count); } heavyCompute(10000, function (count) { console.log(count)

系列文章--Node.js学习笔记系列

Node.js学习笔记系列总索引 Nodejs学习笔记(一)--- 简介及安装Node.js开发环境 Nodejs学习笔记(二)--- 事件模块 Nodejs学习笔记(三)--- 模块 Nodejs学习笔记(四)--- 与MySQL交互(felixge/node-mysql) Nodejs学习笔记(五)--- Express安装入门与模版引擎ejs Nodejs学习笔记(六)--- Node.js + Express 构建网站预备知识 Nodejs学习笔记(七)--- Node.js + Exp

node.js学习笔记目录

1.node.js学习笔记(1)--Node.js简介及环境安装 2.node.js学习笔记(2)--使用Express快速创建应用 3.node.js学习笔记(3)--Express创建的项目分析 4.node.js学习笔记(4)--使用Express完成简单的登陆 5.node.js学习笔记(5)--MongoDB下载及安装 6.node.js学习笔记(6)--MongoDB简单入门 7.node.js学习笔记(7)--Node.js与MongoDB简单交互 8.node.js学习笔记(8)

Node.js学习笔记(3) - 简单的curd

这个算是不算完结的完结吧,前段时间也是看了好久的Node相关的东西,总想着去整理一下,可是当时也没有时间: 现在看来在整理的话,就有些混乱,自己也懒了,就没在整理,只是简单的记录一下 一.demo的简单介绍 这次demo,只涉及到简单的curd操作,用到的数据库是mongo,所以要安装mongo数据库,数据库连接驱动是mongoose: 当然关于mongo的驱动有很多,比如mongous mongoskin等:(详见http://cnodejs.org/topic/4f4ca8e0940ce2e

Node.js学习笔记【3】NodeJS基础、代码的组织和部署、文件操作、网络操作、进程管理、异步编程

一.表 学生表 CREATE TABLE `t_student` ( `stuNum` int(11) NOT NULL auto_increment, `stuName` varchar(20) default NULL, `birthday` date default NULL, PRIMARY KEY  (`stuNum`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 学生分数表 CREATE TABLE `t_stu_score` ( `id` int(11

node.js学习笔记(二)——回调函数

Node.js 异步编程的直接体现就是回调. 那什么是回调呢?回调指的是将一个函数作为参数传递给另一个函数,并且通常在第一个函数完成后被调用.需要指明的是,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应.回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数.例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回.这样在执行代码时就没有阻