node.js是什么似乎已经不需要我多为赘述了,非阻塞的服务器语言、JS书写的后台代码,无数的文章已经很好的展示了node的魅力与展望。关于node.js的安装,大家不妨参考博客园聂微东的http://www.cnblogs.com/Darren_code/archive/2011/10/31/nodejs.html (node.js初体验),这篇文章很好的综述了node.js的一个基础(从安装到体验到模块的一个入门,个人感觉是一篇很好的文章),相信通过东哥的这篇文章大家可以对node有一个初步的了解。
node是一门很有意思的框架,它能够让一个长期执迷于前端开发的攻城湿(忘记了还有一种语言叫后端语言。。。)能够觉得很舒适(编码习惯都一样),但是也同样会让一个新手觉得无所适从(为什么么有从入门到精通哩?参考书在哪里???)。这是一个前端高手为之一亮(也许只亮了一眼o(╯□╰)o),新手眼前一晕(if you want to find more information,please read the source(╯□╰)o)的框架。为了让和我一样的新手能够多多少少摸到一点门路,我以身试法,来为新手找一条路~
本文旨在新手入门,所学尚浅,代码水准有限,这也仅仅只是一个基本入门的笔记,高手可以笑一笑然后点关闭了。。。
首先我假设你已经安装好了node(http://nodejs.org/#download,0.6.14已经很成熟了),那么首先我们来进行一个入门的编码分析。
在进行分析之前,我们先来想一下以前的服务器端语言框架的工作原理。首先,用户通过浏览器来访问我们的应用。然后服务器通过对端口的监听,来接收网络端的request请求,并进行相应的处理。最后再将处理的结果返回客户端。
好了,那么我们可以开始了。首先是服务器,要是没有办法启动服务器,那么一切都是闲的。我们也许用过很多服务器端的语言(PHP、JAVA、ASP等等),接收HTTP请求并提供Web页面的过程似乎不用我们来做,apache和IIS似乎都会帮我们来完成。但是在node里,这一步必须你自己做。我们来实现的不仅仅是一个应用,还需要实现HTTP处理的服务器。
好像很复杂的样子,但对于node这并不是什么复杂的东西。我们即将进行一个HTTP服务器的初步学习,但是在学习之前我们需要温习(预习?)一下node的模块机制。
node采取的是模块机制(和JS差不多),通过对模块的导入我们可以声明变量并将导入的模块的实例化对象赋给变量并加以使用。具体各个模块的使用方法以及用途请参考API,这里就不说了。。。
好了,我们回来看看HTTP服务器的构成。首先,我们先对HTTP模块进行一次请求:
var http = require("http");
在node中require方法用于对各个模块的引入,在这里我们需要对http模块加以使用时就可以导入。导入后将之赋予http变量,然后对http变量进行操作。让我们以hello world作为例子:
1 http.createServer(function(request, response) {
2 response.writeHead(200, {"Content-Type": "text/plain"});
3 response.write("Hello World");
4 response.end();
5 }).listen(12345,”127.0.0.1”);
每每看到hello world时内心都会有一丝变态的快意。。。
让我们来回忆一下刚才的问题:实现一个http的服务器。其实这个很简单,http模块自带的createServer方法就可以完成。该方法只有一个参数,类型为函数,在接口文档中的定义是“始终接收request事件”。而server.listen方法(server就是刚才通过createServer创建的server)的参数是(port, [hostname], [callback]),第一个是监听的端口号,第二个和第三个是可选项,第二个是主机名称,第三个回调函数。这个函数只在绑定端口后进行调用。
接下来是回调函数,回调函数的参数有两个,第一个是客户端发送的request,第二个是服务器端的response。这段代码进行了一个简单的响应操作。首先是书写了一个响应头,响应头参数包括状态码、原因描述以及头部内容。状态码即http状态码,例如200、404、500之类的。原因描述与头部内容可选,具体的就参见网络报头的书写了,这里就不多说了(其实我也不会。。。)。writehead方法必须写在end方法之前,这个是肯定的。。。
response的write方法就和JS的write所做的工作一样,就是向页面写入数据,虽然原理不尽相同,但目前没有准备去钻研这部分源码的我们可以忽略了。。。最后是end方法,它有两个可选参数,分别是data与encoding。该方法用于所有的响应头与响应正文输出之后,进行响应的终结,并将管道流中的所有响应数据输出。简单地说就是在响应最后加上去的东西,它执行后会将响应执行。如果该方法带有参数,那么就相当于先调用了response.write(data, encoding)方法,之后再调用无参数的end方法。
好了,最简单的一个http服务器已经工作起来了。当用户访问127.0.0.1的12345端口时服务器会监听到这一端口的request请求并书写报头与最简单的helloworld于页面上,用户得到响应之后会在浏览器中显示响应的内容,也就是helloworld。这个最简单的服务器已经搭好了,但我们不能只满足于这一点。
在继续下一步的学习之前,我想给所有没有使用过JS或者不怎么使用的同学大体的讲述一下一个也许你们会略微奇怪的参数传递方法——函数传递。
在JS中,函数与数字、字符串等都是以var定义的,在参数传递的过程中所接受的参数也是var这种弱类型的。而function类型也是作为弱类型传递,当我们将一个函数进行传递时,所得到的不是该函数的返回值,而是这个函数本身。也就是说,这个函数在运行时会变成传递到的函数的本地变量(自己都觉得好乱。。。)。
让我们回忆一下刚才的例子,在creatServer方法中我们使用了一个匿名函数作为参数,现在我们把这个匿名函数提出来:
1 var http = require("http");
2 var serverhandel = function(request, response) {
3 response.writeHead(200, {"Content-Type": "text/plain"});
4 response.write("Hello World");
5 response.end();
6 }
7 function serverRequest (){
8 http.createServer(serverhandel).listen(12345);
9 }
10 exports. serverRequest = serverRequest;
exports即module.exports对象,在node中可以作为全局变量的赋予。也就是说它一般用来定义全局变量的,多用于模块间的变量传递。在此我需要简单说一下JS的模块机制,JS中的模块多用闭包进行包裹(我也不知道这么说对不对),而在闭包中定义的局部变量则无法在全局展开使用,也就是说别的地方调用这个模块时不能将其中的局部变量单独的进行使用。而exports则可以在载入模块后将该函数载入全局变量的作用链中。
说到这大家也应该明白了,我们要进行一次模块引用。将这段代码存入serverRequest.js中,然后建立一个index.js文件,然后引用serverRequest模块:
1 var server = require(“./serverRequest”);
2 server. serverRequest();
这样我们就进行了一个最基本的小模块的搭建,也初步的了解了一下node的模块体系。那么下一步我们就要进行下连个个非常重要的模块的学习,也就是url模块与path模块。
url模块的作用是从请求中获取请求的url并进行处理,它有着几个常用的方法:
1 url.parse(string).pathname;
2 url.parse(string).query;
第一个方法的作用是获取url请求部分的域名之后的路径名称,第二个方法获取的则是通过get向服务器传递的参数。
而path模块的作用是解决文件路径问题,我们这次先学习三个方法:
1 path.extname(p);
2 path.join([path1], [path2], [...]);
3 path.exists(p, [callback]);
第一个方法是获取扩展名的方法,参数是url路径。第二个方法是做路径拼接使用,用来标准化最终路径,参数是需要拼接的路径。第三个方法是检验路径存在与否,第一个参数是标准化的路径,第二个是可选的回调函数,无论路径存在与否都会被调用,函数有一个exist参数,标示路径是否存在。
好了,现在我们就可以通过这两个模块进行一个简单的路径服务器的搭建了。通过这个服务器的搭建,我们可以对本地的静态网站进行部署,对于页面以及网页所需要载入的各种资源进行寻址,最后对请求的资源进行反馈。
1 //请求模块
2 var http = require('http');
3 var url=require('url');
4 var fs = require("fs"); //在这里先导入文件模块,仅仅做一个简单的操作,具体有关文件模块的学习在之后的文件服务器上会进行进一步的学习。
5 var path = require("path");
6 //创建一个http服务器
7 var server=http.createServer(start).listen(12345);
8 //依据路径获取返回内容类型字符串,用于http返回头
9 var getContentType=function(filePath){
10 var contentType="";
11 //使用路径解析模块获取文件扩展名
12 var extension=path.extname(filePath);
13 switch(extension){
14 case ".html":
15 contentType= "text/html";
16 break;
17 case ".js":
18 contentType="text/javascript";
19 break;
20 case ".css":
21 contentType="text/css";
22 break;
23 case ".gif":
24 contentType="image/gif";
25 break;
26 case ".jpg":
27 contentType="image/jpeg";
28 break;
29 case ".png":
30 contentType="image/png";
31 break;
32 case ".ico":
33 contentType="image/icon";
34 break;
35 default:
36 contentType="application/octet-stream";
37 }
38 return contentType; //返回内容类型字符串
39 }
40 //Web服务器主函数,解析请求,返回Web内容
41 var funWebSvr = function (req, res){
42 //获取请求的url
43 var url=req.url;
44 //使用url解析模块获取url中的路径名
45 var pathName = url.parse(reqUrl).pathname;
46 if (path.extname(pathName)=="") {
47 //如果路径没有扩展名
48 if (pathName.length<2) {//如果是默认域名
49 pathName+="/";
50 }
51 else{
52 pathName+=".html";
53 }
54 }
55 else{
56 if (path.extname(pathName)!=".html"){
57 pathName=".."+ pathName;
58 }
59 }
60 if (pathName.charAt(pathName.length-1)=="/"){
61 //如果访问目录
62 pathName+="login.html"; //指定为默认网页
63 }
64 var filePath = pathName;
65 //使用路径解析模块,组装实际文件路径
66 if (pathName.charAt(pathName.length).search(/./) == -1) {
67 filePath = libPath.join("./html",pathName);
68 };
69 //判断文件是否存在
70 libPath.exists(filePath,function(exists){
71 if(exists){//文件存在
72 //在返回头中写入内容类型
73 res.writeHead(200, {"Content-Type": funGetContentType(filePath) });
74 //创建只读流用于返回
75 var stream = libFs.createReadStream(filePath, {flags : "r", encoding : null});
76 //指定如果流读取错误,返回404错误
77 stream.on("error", function() {
78 res.writeHead(404);
79 res.end("<h1>404 Read Error</h1>");
80 });
81 //连接文件流和http返回流的管道,用于返回实际Web内容
82 stream.pipe(res);
83 }
84 else {//文件不存在
85 //返回404错误
86 res.writeHead(404, {"Content-Type": "text/html"});
87 res.end("<h1>404 Not Found</h1>");
88 }
89 });
90 }
这是当时对着一篇大牛的博文敲的例子,后来发现只能载入单个网页,而其他资源不能很好的载入,就进行了一次较大的改正,主要添加了对不同pathname的寻址以及载入。本例的css、js以及image文件夹都与页面所在的html文件夹在同一目录下。
相信通过这个例子大家已经能简单的让一个静态网站在我们的服务器上支持起来了。我们下一次将会简单的部署一个文件系统,希望大家能继续关注。新手上道,文章代码写的都比较粗糙,希望大家指正。bye