nodeJS基础篇

nodeJS简介

简单的说Node.js就是运行在服务器端的JavaScript。

Node.js是一个基于Chrome JavaScript运行时建立的一个平台。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎执行JavaScript速度回非常快,性能非常好。

<span style="color:orange">使用版本</span>

我们可以通过下面的命令查看当前的Node版本

node -v

第一个Node.js程序:hello world

1.首先我们创建一个hello.js文件(命名不要是node.js文件,会报错哦,亲测)

console.log("hello world");

2.然后我们可以执行node程序

在终端输入,node hello.js就会打印出hello world了

构建应用模块,一个基础的HTTP服务

把主文件叫做 index.js 或多或少是个标准格式。把服务器模块放进叫 server.js 的文件里则很好理解。

让我们先从服务器模块开始。在你的项目的根目录下创建一个叫 server.js的文件,并写入以下代码:

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) {
    console.log("Request received.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
}
http.createServer(onRequest).listen(8888)

<em>注意:我是在git下运行的</em>

接下来,打开浏览器访问 <code>http://localhost:8888/</code>,你会看到一个写着
“Hello World”的网页

接下来让我们分析一下HTTP服务器是怎么构成的,也就是上面的服务器代码

第一行 请求( require ) Node.js 自带的 http 模块,并且把它赋值给 http变量。

接下来我们调用 http 模块提供的函数: createServer 。这个函数会返回一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数,指定这个 HTTP 服务器监听的端口号。

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

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

服务器是如何处理请求的

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

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

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

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

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

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

服务端的模块放在哪里

我们现在在 server.js 文件中有一个非常基础的 HTTP 服务器代码,而且我提到通常我们会有一个叫 index.js 的文件去调用应用的其他模块(比如 server.js 中的 HTTP 服务器模块)来引导和启动应用。

我们现在就来谈谈怎么把 server.js 变成一个真正的 Node.js 模块,使它可以被我们(还没动工)的 index.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(8899);
    console.log("Server has started.");
}
exports.start = start;

<em>注意:我这里换了一个端口号哦</em>

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

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

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

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

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

node index.js

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

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

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

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

如何来进行请求的路由

现在我们来给 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.haha");
}
exports.start = start;

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

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

现在我们可以来编写路由了,建立一个名为 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 的模块,并对于每一个请求处理程序,添加一个占位用函数,随后将这些函数作为模块的方法导出:

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

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

我们先将这个对象引入到主文件 index.js 中:

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);

虽然 handle 并不仅仅是一个“东西”(一些请求处理程序的集合),我还是建议以一个动词作为其命名,这样做可以让我们在路由中使用更流畅的表达式,稍后会有说明。

正如所见,将不同的 URL 映射到相同的请求处理程序上是很容易的:只要在对象中添加一个键为"/"的属性,对应 requestHandlers.start 即可,这样我们就可以干净简洁地配置/start 和/的请求都交由 start 这一处理程序处理。

在完成了对象的定义后,我们把它作为额外的参数传递给服务器,为此将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;

这样我们就在 start()函数里添加了 handle 参数,并且把 handle 对象作为第一个参数传递给了 route()回调函数。

然后我们相应地在 route.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;

通过以上代码,我们首先检查给定的路径对应的请求处理程序是否存在,如果存在的话直接调用相应的函数。我们可以用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数,因此就有了简洁流畅的形如<code>handlepathname;</code>的表达式,这个感觉就像在前方中提到的那样:“嗨,请帮我处理了这个路径”。

有了这些,我们就把服务器、路由和请求处理程序在一起了。现在我们启动应用程序并在浏览器中访问<code> http://localhost:8888/start</code>,以下日志可以说明系统调用了正确的请求处理程序:

Server has started.
Request for / received.
About to route a request for /
Request handler 'start' was called.

并且在浏览器中打开<code> http://localhost:8888/</code>可以看到这个请求同样被start 请求处理程序处理了:

Request for / received.
About to route a request for /
Request handler 'start' was called.

让请求处理程序作出响应

很好。不过现在要是请求处理程序能够向浏览器返回一些有意义的信息而并非全是“Hello World”,那就更好了。

这里要记住的是,浏览器发出请求后获得并显示的“Hello World”信息仍是来自于我们 server.js 文件中的 onRequest 函数。

其实“处理请求”说白了就是“对请求作出响应”,因此,我们需要让请求处理程序能够像 onRequest 函数那样可以和浏览器进行“对话”。

不好的实现方式

让请求处理程序通过onRequest 函数直接返回(return())他们要展示给用户的信息。

我们先就这样去实现,然后再来看为什么这不是一种很好的实现方式。

让我们从让请求处理程序返回需要在浏览器中显示的信息开始。我们需要将 requestHandler.js 修改为如下形式:

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

function upload() {
    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;

如果我们运行重构后的应用,一切都会工作的很好:请求
http://localhost:8888/start,浏览器会输出“Hello Start”,请求
http://localhost:8888/upload 会输出“Hello Upload”,而请求
http://localhost:8888/foo 会输出“404 Not found”。

好,那么问题在哪里呢?简单的说就是: 当未来有请求处理程序需要进行非阻塞的操作的时候,我们的应用就“挂”了。

阻塞与非阻塞

我们来修改下 start 请求处理程序,我们让它等待 10 秒以后再返回“Hello Start”。但是当我们调用upload时候他也会等待10秒!

原因就是 start()包含了阻塞操作。形象的说就是“它阻塞了所有其他的处理工作”。

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

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

相对此前从 route()函数获取返回值的做法,这次我们将 response 对象作为第三个参数传递给 route()函数,并且,我们将 onRequest()处理程序中所有有关 response 的函数调都移除,因为我们希望这部分工作让 route()函数来完成。

下面就来看看我们的 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;

同样的模式:相对此前从请求处理程序中获取返回值,这次取而代之的是直接传递 response 对象。

如果没有对应的请求处理器处理,我们就直接返回“404”错误。

最后,我们将 requestHandler.js 修改为如下形式:

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

function start(response) {
    console.log("Request handler 'start' was called.");
    exec("ls -lah", 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;

好了。敲代码冻得手疼,明天续更吧!增加更有用的场景篇.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,078评论 25 707
  • 1. 简介 这篇文章主要的目的是分析理解express的源码,网络上关于源码的分析已经数不胜数,这篇文章准备另辟蹊...
    没事造轮子阅读 1,300评论 0 8
  • 好像起风了,窗外有沙沙的声音。 我摘下老花镜,伸了个长长的懒腰,办公桌上那绿萝翠得要滴出汁来,用手摸一摸,仿佛,心...
    秋之语阅读 458评论 8 11
  • 【大宁宁-笔记魔法分享-有感】 大宁宁能够得到六哥的关注、指点和推广,拥有自己的IP品牌,感悟颇多: 一方面,同样...
    yj_boy阅读 204评论 3 1