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修改为如下形式:
为了使整个过程非阻塞,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字段)
处理文件上传
允许用户上传图片,并将该图片在浏览器中显示出来。
需要将该文件读取到我们的服务器中,使用一个叫fs的模块。
完整的代码在Github:https://github.com/manuelkiessling/NodeBeginnerBook/tree/master/code/application
总结
介绍了:服务器JavaScript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块等等。
当然了,还有许多没有介绍到的:如何操作数据库、如何进行单元测试、如何开发Node.js的外部模块以及一些简单的诸如如何获取GET请求之类的方法。