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;
好了。敲代码冻得手疼,明天续更吧!增加更有用的场景篇.