分享

node.js从入门到菜鸟——资源无法载入?你需要学会地址解析

 风声之家 2015-05-08

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:///#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文件夹在同一目录下。

相信通过这个例子大家已经能简单的让一个静态网站在我们的服务器上支持起来了。我们下一次将会简单的部署一个文件系统,希望大家能继续关注。新手上道,文章代码写的都比较粗糙,希望大家指正。


数据库de教程


在前一篇博文中,我们简单的分心了node,用node建立了一个文件解析服务器,并且在服务器中进行了WEB寻址的操作。通过这些操作,我们已经可以把一个简单的静态网站搭设在服务器之上了(http://www.cnblogs.com/xiao-yao/archive/2012/03/30/2425716.html)。

当然,仅仅完成这样的一个步骤还是远远不够的,我们需要的不是通过node搭设一个静态网站,而是通过node搭设一个完整的应用。那么我们可以想想下一步操作应该做什么了。没错,我们来尝试一下数据库的简单操作。

在尝试之前,我们需要来学习一个新的模块:querystring模块。

querystring模块的主要用途是对字符串的处理,我们暂时先学习它的两个方法:

querystring.stringify(obj, sep='&', eq='=');
querystring.parse(str, sep='&', eq='=');

前一个方法是将对象向字符串的处理,后一个方法是将字符串的处理(是不是想到了JSON?)。前一个方法的参数是待处理对象、键值对之间的分隔符号以及键值之间的分割符号;后一个方法的参数与前一个相同,只不过处理的过程是相反的而已。

好了,我们了解了这个方法之后,便可以进行下一步的工作了。首先是对参数的捕获,前端将数据传递给后台时,后台进行接收并处理,数据的传递似乎就这么简单。

往往让人莫名痛苦的就是这些简单的东西,比如数据如何传递、node如何处理、如何接收传递过来的参数。好的,我们一个一个来解决。

首先是数据从前端的传递方式,这个本不该是这里的内容,不过说说也无所谓。前端传递参数有很多方法,比如表单传递、AJAX传递,但归根结底就是两种传递方法,post传递或者get传递。

两者的区别就是get传递是通过url后面附加参数的传递方法,而post传递是通过表单的数据体附加提交。其他的区别与node无关,这里就不赘述了。

首先是get方法,通过url传递的参数的获取非常简单,记得前面曾学习过一个url.parse(string).query方法吗?这个方法获取的就是get方法下所传递的参数。

然后就是对参数的处理了,还记得前面的querystring.parse方法吧,这里我们就可以简单的使用了:

var name=querystring.parse(url.parse(req.url).query)['name'];

这样就获取了前端get方法传递的name属性的值了,方法很容易。

那么post呢?它可不在url中啊。处理post参数,我们需要另一个模块:formidable。

让我们来看一下formidable的demo吧:

复制代码
var formidable = require('formidable'),
    http = require('http'),
    util = require('util');
http.createServer(function(req, res) {
  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
    // parse a file upload
    var form = new formidable.IncomingForm();
    form.parse(req, function(err, fields, files) {
      res.writeHead(200, {'content-type': 'text/plain'});
      res.write('received upload:\n\n');
      res.end(util.inspect({fields: fields, files: files}));
    });
    return;
  }
  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" '+
    'method="post">'+
    '<input type="text" name="title"><br>'+
    '<input type="file" name="upload" multiple="multiple"><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
}).listen(8888);
复制代码

让我们简单的分析一下这个文件上传:var form = new formidable.IncomingForm();一句通过简单的引用formidable的IncomingForm方法来捕获fields与files的信息,之后使用util模块(以前的sys模块)的inspect方法来返回post对象的结构信息。通过这样的方式获取到post的对象后,便可以对对象进行操作了。

好了,现在我们分析了两种不同的参数传递以及接收的方式,现在该讲讲如何去对数据库进行操作了。关于数据库我采用的是mongo数据库,这种语法类似JS的NO-SQL数据库无非是前端攻城湿所欣赏的一种数据库了。至于mongo的基本操作我建议参考园内大牛一线码农的8天mongo系列,确实是入门的一部好系列。

在这里我们先进行做简单的find查找,其他的我们以后再去讨论。

首先我们先安装并引入mongo模块,具体的行为我们这里不再进行赘述了。对于collection的API是这样写的:

复制代码
db.open(function(err, db) {
  if(!err) {
    db.collection('test', function(err, collection) {});
    db.collection('test', {safe:true}, function(err, collection) {});
    db.createCollection('test', function(err, collection) {});
    db.createCollection('test', {safe:true}, function(err, collection) {});
  }
});
复制代码

而find的api则是这样写的:

find(query[, options][, callback]);

我们采用最简单的方法来进行一次尝试:

复制代码
var db = new mongo.Db("test", new mongo.Server('localhost', 27017, {}), {});
    db.open(function() {
        // 打开名为user的表
        db.collection("user", function(err, collection) {
            // select * from products 相当于db.products.find()
 collection.find({name:querystring.parse(url.parse(req.url).query)['name'],pwd:querystring.parse(url.parse(req.url).query)['pwd']},function(err, cursor) {
                cursor.toArray(function(err, items) {
                    if (items != null&&items.length != 0) {
                        res.writeHead(200);
                        var obj = {value:1}
                        res.end(JSON.stringify(obj)); 
                    }
                    else{
                        res.writeHead(200);
                        var obj = {value:0}
                        res.end(JSON.stringify(obj));
                    } 
                });
            });
        });
    });
复制代码

OK,我们通过对get方法传递的参数进行提取,并将之与user表中的name与pwd字段进行比较,若有该项则返回1,否则返回0。

我们已经写好了find方法,但是我不想只有一种数据库操作,所以我需要一个类似路由的方法去寻址。方法如下:

复制代码
var mongord = require("./mongord"),    //mongo数据库读取模块
    mongoinsert = require("./mongoinsert"),
    querystring = require("querystring");
function find_router(req, res){
    if (querystring.parse(url.parse(req.url).query)['num'] == 1) {
        mongord.read_collection(req,res);
    }
    else if(querystring.parse(url.parse(req.url).query)['num'] == 2){
        mongoinsert.insert_collection(req,res);
    }
    else{
}
}

exports.find_router = find_router;

 
复制代码

这样,我们就获得了一个近乎路由表的东西,由每次传递参数的value项去判断执行方法。

最后附上今天的全部代码:

在上次的文件中加入:

if (libUrl.parse(req.url).query!=undefined) {
router.find_router(req,res);
}

然后路由表文件名为router:

复制代码
var mongord = require("./mongord"),    //mongo数据库读取模块
    querystring = require("querystring");
function find_router(req, res){
    if (querystring.parse(url.parse(req.url).query)['num'] == 1) {
        mongord.read_collection(req,res);
    }
    else{
    }
}

exports.find_router = find_router;

 
复制代码

最后是数据库模块,取名为mongord:

复制代码
var http = require("http"),
    mongo = require("mongodb"),
    events = require("events"),
    url=require('url'),
    querystring = require("querystring");
function read_collection(req, res) {
    // 创建到test数据库的链接。相当于use test
    var db = new mongo.Db("test", new mongo.Server('localhost', 27017, {}), {});
    db.open(function() {
        // 打开名为user的表
        db.collection("user", function(err, collection) {
            // select * from products 相当于db.products.find()
            collection.find({name:querystring.parse(url.parse(req.url).query)['name'],pwd:querystring.parse(url.parse(req.url).query)['pwd']},function(err, cursor) {
                cursor.toArray(function(err, items) {
                    console.log(items);
                    if (items != null&&items.length != 0) {
                        res.writeHead(200);
                        var obj = {value:1}
                        res.end(JSON.stringify(obj)); 
                    }
                    else{
                        res.writeHead(200);
                        var obj = {value:0}
                        res.end(JSON.stringify(obj));
                    } 
                });
            });
        });
    });
}
exports.read_collection = read_collection;
复制代码

好的,今天所讲的全部内容就到这里了,希望对于node的新手能有一个较好的帮助,谢谢大家。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多