1、服务端JavaScript
在node出现之前,相对于JavaScript,浏览器就是提供了一个上下文,它定义JavaScript可以做些什么。而现在,node.js是另一种上下文,它允许在后端(脱离浏览器的环境)运行,JavaScript。
那么既然要在后台运行JavaScript,代码首先需要被解释然后正确的执行,node使用了Google的V8虚拟机来解释和执行JavaScript。除此之外,node加油许多有用的模块,可以简化很多重复的劳作,比如向终端输出字符串。
所以,node即是一个运行时环境,同时也是一个库。
2、创建一个简单的web应用实例
主要功能如下
用户可以通过浏览器使用我们的应用
当用户请求http://domin/start的时候,可以看到一个欢迎页面,页面上有一个文件上传的表单
用户可以选择一个图片并提交表单,随后文件将被上传到http://domin/upload,该页面完成上传后会把图片显示在页面上。
3、应用不同模块的分析
首先我们来分析,为了实现上面的例子,我们需要实现哪些部分呢?
我们需要提供web页面,所以我们需要一个http服务器
对于不同的请求,根据请求的URL,我们的服务器需要给予不同的响应,因此我们需要一个路由,用于把请求对应到请求处理程序。
当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,因此我们需要最终的请求处理程序。
路由还能要处理POST数据,并且能够将数据封装成更友好的格式传递给请求处理程序,因此需要请求数据处理功能
除了需要处理URL请求,还要把内容显示出来,这意味着我们需要一些视图逻辑供请求处理程序使用,以便将内容发送给用户的浏览器。
4、构建模块
将不同功能的代码放到不同的模块中,保持代码分离。
这种方法就是拥有一个主文件,并且同时拥有不同的模块,模块可以被主文件以及其他的模块所调用。
(1)一个基础的HTTP服务器
//server.js
var http = require('http');
http.createServer(function (request,response) {
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('Hello world,this is krysliang');
response.end();
}).listen(8888)
第一行请求中,使用了node自带的http模块,并且将它赋值给http变量。
接下来我们调用http模块提供的函数:createServer,这个函数会返回一个对象,这个对象有一个叫做listen的方法,这个方法的参数是http服务器监听的端口号。
当我们使用http.createServer的时候,我们当然不只是想要一个侦听一个端口的服务器,我们还想要它在服务器收到一个http请求的时候做点什么,但是请求任何时候都可以到达,但是我们的node却是一个单进程的,。
那么在node里面,当一个新的请求到达的时候,我们怎么控制流程呢?
--->>我们创建了服务器,并且向创建它的方法传递了一个函数,无论何时我们的服务器收到一个请求,这个函数就会被调用。这就是回调,我们给一个方法传递了一个函数,这个方法在有相应时间发生时调用这个函数来进行回调。
了解了回调函数以后我们来分析一下,这个回调函数里面的主体部分。
回调函数有两个参数被传进来了request和response
这两个都是对象,request是请求对象,response是响应对象,也就是说你可以通过request去获取一些post过来的数据,也可以通过response去给客户端返回一些东西。
第三行中,我们使用response.writeHead()去返回一个http状态码以及相应的http头的内容类型。
第四行中,使用response.write()函数在http响应主体部分发送文本
最后我们调用response.end完成响应
接下来我们把server.js里面的代码变成一个模块。把某段代码变成模块意味着我们需要把我们希望提供其功能的部分导出到请求这个模块的脚本,。
//server.js
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,this is krysliang');
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('server has started');
}
exports.start = start;
//index.js
var server = require('./server')
server.start();
然后执行node index.js
这里输出了两次request recieved
是因为大部分浏览器都会在你访问服务器的时候尝试读取favicon.ico文件
(2)创建路由
创建一个router.js
function route(pathname) {
console.log("About to route a request for " + pathname);
}
exports.route = route;
接下里看看路由跟服务器如何整合在一起。我们的服务器应当知道路由的存在并加以有效利用。
首先我们扩展一下server的start函数,以便将路由函数作为参数传递过去。
//创建http服务器
var http = require('http');
var url = require('url')
function start(route){
function onRequest(request,response) {
var pathname = url.parse(request.url).pathname;
route(pathname);
console.log('request received from',pathname);
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('Hello world,this is krysliang');
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('server has started');
}
exports.start = start;
同时在Index.js中,将router传给server
var server = require('./server')
var router = require('./router')
server.start(router.route);
(3)请求处理程序
创建一个用来处理路由请求的程序,requestHandle的模块。并对于每一个请求处理程序,添加一个占位用函数,随后将这些函数作为模块的方法导出。
在这里,我们将一系列请求处理程序通过一个对象来传递,并且需要使用松耦合的方式将这个对象注入到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);
接下来,将它作为额外的参数传递给服务器
//创建http服务器
var http = require('http');
var url = require('url')
function start(route,handle){
function onRequest(request,response) {
var pathname = url.parse(request.url).pathname;
route(handle,pathname);
console.log('request received from',pathname);
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('Hello world,this is krysliang');
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('server has started');
}
exports.start = start;
这样就将handle函数传给了路由,接下来,我们需要在路由中修改route函数:
在这里,首先检查给定的路径对应的请求处理程序是否存在,如果存在的话直接调用相应的函数。
function route(handle,pathname) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'funtion'){
handle[pathname]();
} else {
console.log('no reqest handle found for',pathname)
}
}
exports.route = route;
(4)请求处理程序作出响应
我们在这里需要根据不同的请求处理程序给出不同的响应,那么我们就将response对象传递给每一个请求处理程序,让它们自己做出相应的处理。
我们需要的是将response对象在server中传递给route,然后再路由中传递给对应的处理程序
将response对象传给处理程序
//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;
route(handle,pathname,response);
console.log('request received from',pathname);
}
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,typeof handle[pathname]);
if (typeof handle[pathname] === 'function'){
handle[pathname](response);
} else {
console.log('no reqest handle found for',pathname)
response.writeHead(404,{'Content-Type':'text/plain'});
response.write('404 Not Found');
response.end()
}
}
exports.route = route;
请求处理程序
//requestHandlers.js
function start(response) {
console.log('request hanler start was called');
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('this is start');
response.end();
}
function upload(response) {
console.log('request handle upload was called');
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('this is upload');
response.end();
}
exports.start = start;
exports.upload = upload;
接下来就是要做的处理POST请求啦
(5)处理POST请求
首先,我们在用户访问服务器的首页的时候,展示一个表单的内容给用户,然后用户输入内容,通过POST请求提交给服务器,最后服务器接收到请求,通过处理程序将输入的内容展示给浏览器。
function start(response) {
console.log('request hanler 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();
}
/start和/请求用于生成带文本的表单。所以其对应的处理程序修改成如下形式(暂时不涉及到MVC模式,后面讲实战部分的时候我再来写一篇专门讲MVC结构的)
ok接下来写upload部分的处理程序
当用户提交表单的时候,触发/upload请求处理程序处理POST的问题,这里采用异步回调的方式实现非阻塞地处理POST请求的数据。
为了使整个处理过程非阻塞,node会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据传递给回调函数。这里的特定事件有data事件(表示新的小数据块到达了)以及end事件(表示所有的数据都已经接收完毕了)。
我们在request对象上注册监听器来实现,每次接收到http请求的时候,都会把该对象传递给onRequest回调函数
request.addListener("data", function(chunk) {
// called when a new chunk of data was received
});
request.addListener("end", function() {
// called when all chunks of data have been received
});
那么这部分的监听代码要写在哪里呢?
将data和end事件的回调函数直接放在server中,在data事件回调中收集所有的POST数据,当接收到所有数据,触发end事件后,其回调函数调用请求路由,并将数据传递给它,然后请求路由再讲该数据传递给请求处理程序。
//server.js
var http = require('http');
var url = require('url')
function start(route,handle){
function onRequest(request,response) {
var postData = '';//post数据
var pathname = url.parse(request.url).pathname;
console.log('request received from',pathname);
request.setEncoding('utf8');
request.addListener('data',function (postDataChunk) {
postData += postDataChunk;
})
request.addListener("end",function () {
route(handle,pathname,response,postData);
})
}
http.createServer(onRequest).listen(8888);
console.log('server has started');
}
exports.start = start;
首先,我们设置了接收数据的编码格式为UTF8,然后注册了data事件的监听器,用于手机每次接收到的新的数据块,并将其赋值给postData变量,最后,我们将请求路由的调用移到end事件处理程序中,以确保它只会当所有数据接收完毕后才触发,并且只触发一次。同时将POST数据传递给请求路由。
然后我们在upload页面中,展示用户输入的内容。
//router.js
function route(handle,pathname,response,postData) {
console.log("About to route a request for " + pathname,typeof handle[pathname]);
if (typeof handle[pathname] === 'function'){
handle[pathname](response,postData);
} else {
console.log('no reqest handle found for',pathname)
response.writeHead(404,{'Content-Type':'text/plain'});
response.write('404 Not Found');
response.end()
}
}
exports.route = route;
在这里我们只要获取到我们感兴趣的字段,比如text,所以我们使用querystring模块来获取
//requestHandlers.js
var querystring = require("querystring");
function start(response,postData) {
console.log('request hanler 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 handle upload was called');
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('this is upload,and you have send:'+querystring.parse(postData).text);
response.end();
}
exports.start = start;
exports.upload = upload;
ok接下来就是处理文件上传了。
(6)处理文件上传
在这里我们允许用户上传图片,并将该图片在浏览器中显示出来。
在这里我能需要用到一个叫node-formidable的模块,它对解析上传的文件数据做了很好的抽象。
首先安装这个模块(你需要先创建一个package.json的文件)
npm install formidable
在server中引入
var formidable = require("formidable");
在这里改模块做的就是将通过HTTP POST请求提交的表单,在node.js中可以被解析到。
接下来,
在start表单中添加一个文件上传元素
将formidable整合到我们的upload请求处理程序中,用于将上传的图片保存到tmp/test.png中
将上传的图片显示出来
首先来第一步
在表单中添加一个文件上传组件,并将提交按钮的文案改为upload file
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" '+
'content="text/html; charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';
接下来第二步
因为如果我们需要在upload中要对上传的文件进行处理的话,我们就需要将request对象传递给node-formidable的form.parse函数。
但是目前来说我们只是把response对象以及postData数组传过去给了upload处理程序,所以我们需要再把request对象传过去。
所以我们来重整项目的代码
//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 received from',pathname);
route(handle, pathname, response, request);
}
http.createServer(onRequest).listen(8888);
console.log('server has started');
}
exports.start = start;
//router.js
function route(handle,pathname,response,request) {
console.log("About to route a request for " + pathname,typeof handle[pathname]);
if (typeof handle[pathname] === 'function'){
handle[pathname](response,request);
} else {
console.log('no reqest handle found for',pathname)
response.writeHead(404,{'Content-Type':'text/plain'});
response.write('404 Not Found');
response.end()
}
}
exports.route = route;
这样就把response和request两个对象都传给了upload处理程序了。
//requestHandlers.js
var querystring = require("querystring"),fs = require("fs"),formidable = require("formidable");
function start(response) {
console.log('request hanler start was called');
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" '+
'content="text/html; charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
function upload(response,request) {
console.log('request handle upload was called');
var form = new formidable.IncomingForm();
form.parse(request, function(error, fields, files) {
console.log("parsing done");
fs.renameSync(files.upload.path, "tmp/test.png");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("received image:<br/>");
response.write("<img src='/show' />");
response.end();
});
}
function show(response) {
console.log("Request handler 'show' was called.");
fs.readFile("tmp/test.png", "binary", function(error, file) {
if(error) {
response.writeHead(500, {"Content-Type": "text/plain"});
response.write(error + "\n");
response.end();
} else {
response.writeHead(200, {"Content-Type": "image/png"});
response.write(file, "binary");
response.end();
}
});
}
exports.start = start;
exports.upload = upload;
exports.show = show;