了不起的Nodejs学习笔记(TCP、HTTP、Connect)

了不起的Nodejs学习笔记(TCP、HTTP、Connect)

六、TCP

1、TCP特性

  • 面向连接的通信和保证顺序的传递
    • TCP协议做基于的IP协议是面向无连接
    • IP是基于数据报的传输。这些数据报独立进行传输,送达的顺序也是无序。
    • TCP连接内进行数据传递时,发送的IP数据报包含了标识该连接以及数据流顺序的信息
  • 面向字节
    • TCP对字符以及字符编码完全无知。
    • 允许ASCII字符 或 Unicode进行传输
    • 对消息格式没有严格约束,具有很好的灵活性
  • 可靠性
    • TCP是基于底层不可靠的服务,因此它必须要基于确认和超时实现一系列的机制来达到可靠性的要求。
    • 这种机制有效解决如网络错误或网络阻塞这样不可测的情况
  • 流控制
    • 举例:两台互相通信的计算机,一台速度远快于另一台。
    • TCP通过流控制方式确保两点之间传输数据的平衡,避免发送方压垮接收方
  • 拥堵控制
    • TCP有一种内置机制能够控制数据包的延迟率及丢包率不会太高,以此确保服务质量(QoS)
    • TCP通过控制数据包的传输速率来避免拥堵的情况

2、Telnet

  • 建立TCP连接

    require('http').createServer(function (req,res) {
        res.writeHead(200,{ 'Content-Type' : 'text/html' });
        res.end('<h1>Hello World</h1>')
    }).listen(3000)
    
  • 创建一个HTTP请求并接收HTTP响应

    # windows操作
    # 需要启动Telnet客户端
    # 执行命令
    telnet localhost 3000 # 回车后黑屏 
    # 组合键
    CTRL+] # 进入Telnet界面
    # Telnet界面
    回车 # 回车后黑屏
    # 输入
    GET / HTTP/1.1
    HOST:localhost
    # 两次回车
    

3、基于TCP的聊天程序

  • 创建一个基本的TCP服务器

    • 成功连接到服务器后,服务器会显示欢迎信息,并要求输入用户名,并显示当前连接人数
    • 输入用户名后,按下回车键后,就认为成功连接
    • 连接后,可以通过输入信息再按回车健,来向其他客户端进行消息的收发
  • 为什么要按回车键?

    事实上,Telnet中输入的任何信息都会立刻发送到服务器。按下回车键是为了输入“ ”字符。

    在Node服务器端,通过" "来判断消息是否已完全到达,作为分隔符使用。

    实际上,回车键和输入字符a没什么区别

  • 实现

    {
        "name": "tcp-chat",
        "version": "0.0.1",
        "description": "our first TCP server"
    }
    
    // index.js
    // windows系统,代码做了下改动
    // 模块依赖
    var net = require('net')
    // 追踪连接数
    var count = 0,users= {};
    // 创建服务器
    // createServer指定了一个回调函数,该函数每次有新的连接建立时都会被执行
    var server = net.createServer(function (conn) {
        conn.write(
            '
     > welcome to 33[92mnode-chat33[39m
    '
            + ' > ' + count + ' other people are connected at this time.
    '
            + ' > please write your name and press enter: '
        )
        count++;
        // 设置编码
        conn.setEncoding('utf8');
        var nickname;
        // 广播消息
        function broadcast (msg,exceptMyself){
            for (var i in users){
                if(!exceptMyself || i != nickname){
                    users[i].write(msg)
                }
            }
        }
        let words = [];
        conn.on('data',function (data) {
            let str = '';
            if(data == '
    '){
                words.forEach(function (v) {
                    str += v;
                })
                words = []
            }else{
                words.push(data);
            }
            // 如果str有值,说明用户回车操作
            if(str){
                // 尚未注册用户,第一份数据是昵称
                if(!nickname){
                    if(users[str]){
                        conn.write('33[93m> nickname already in use. try again:33[39m')
                        return;
                    }else{
                        nickname = str;
                        users[nickname] = conn;
                        broadcast('33[90m > ' + nickname + ' joined the room33[39m
    ')
                    }
                }else{
                    // 否则视为聊天消息
                    broadcast('33[90m > ' + nickname + ':33[39m' + str + '
    ',true)
                }
            }
        })
        conn.on('close',function () {
            count--;
            // 当有人退出删除昵称
            delete users[nickname];
            broadcast('33[90m > ' + nickname + ' left the room33[39m
    ')
        })
    })
    // 监听
    server.listen(3000,function(){
        console.log('33[96m  server listening on *:300033[39m');
    })
    

七、HTTP

1、HTTP结构

​ HTTP协议构建在请求和响应的概念上,对应的Node.js中就是由http.ServerRequesthttp.ServerResponse这两个构造器构造出来的对象。

​ 当用户浏览一个网站时,用户代理(浏览器)会创建一个请求,该请求通过TCP发送给Web服务器,随后服务器会给出响应。

2、头信息

HTTP协议,其目的是进行文档交换。它在请求和响应消息前使用头信息(header)来描述不同的消息内容。

require('http').createServer(function (req,res) {
    // 只有加入Content-Type确定文档类型,浏览器才能识别,否则end中的html代码会以字符串形式展示在网页
    // 虽然writeHead只指定了一个头信息,但是Node还是会把另外两个头信息
    // Transfer-Encoding 和 Connection加进去
    // Transfer-Encoding头信息默认值是chunked,主要原因是Node天生的异步机制,这样响应就可以逐步产生
    res.writeHead(200, {'Content-Type':'text/html'})
    res.end('Hello <b>World</b>')
}).listen(3000)
require('http').createServer(function (req,res){
    res.writeHead(200);
    res.write('Hello');
    setTimeout(function () {
        res.end('World');
    },500);
}).listen(3000);

// 在调用end前,我们可以多次调用write方法来发送数据
// 为了尽可能快的响应客户端,在首次调用wirte时,Node就把所有的响应头信息及第一块数据(Hello)发送出去
require('http').createServer(function (req,res){
    res.writeHead(200, {'Content-Type':'image/png'});
    var stream = require('fs').createReadStream('image.png');
    stream.on('data',function (data){
        res.write(data);
    });
    stream.on('end',function () {
        res.end();
    })
}).listen(3000);
// 以一系列数据块的形式来将图片写入到响应中,有如下好处:
// 1、高效的内存分配。
//		要是对每个请求在写入前都完全把图片信息读取完(通过fs.readFile),在处理大量请求时会消耗大量内存
// 2、数据一旦就绪就可以立刻写入了
// 实际上,以上例子做的就是:
// 把一个流(Stream)(文件系统)接(peping)到了另一个流上(http.ServerRespnse对象)
// 流是Node.js中非常重要的一种抽象。流的对接是很常见的行为。
// 为此,Node.js提供了一个方法让上述例子代码变得简洁
require('http').createServer(function (req,res){
    res.writeHead(200, {'Content-Type':'image/png'});
    require('fs').createReadStream('image.png').pipe(res);
}).listen(3000)

3、连接

  • 对比TCP服务器与HTTP服务器的实现
    • 相似处
      • 都调用了createServer方法
      • 当客户端连入时,都会执行一个回调函数
    • 本质区别
      • 回调函数中的对象类型不同
        • net服务器(TCP)是一个connection对象
        • HTTP服务器,是请求和响应对象
      • 原因
        • HTTP服务器是更高层的API,提供控制和HTTP协议相关的一些功能
        • (重要原因)浏览器在访问站点时不会只用一个连接。很多浏览器为了更快地加载网站内容,会开启很多连接发送请求。

默认情况下,Node会告诉浏览器始终保持连接,通过它发送更多的请求。通过Connection头信息中的keep-alive值通知浏览器。为了提供性能(因为浏览器不想浪费时间去重新建立和关闭TCP连接),通常都是对的。不过,我们也可以调用writeHead方法,传递一个不同的值,如close,来将其重写掉。

4、一个简单的Web服务器

{
    "name": "http-form",
    "version": "0.0.1",
    "description": "An HTTP server that processes forms"
}
// 例如:http://myhost.com/url?this+is+a+long+url
// req.url = url?this+is+a+long+url
// req.method:请求的方法
// Web协议HTTP/1.1,为请求定义了以下不同的方法
// GET(默认) POST PUT DELETE PATCH(最新)
require('http').createServer(function (req,res){
    if('/' == req.url){
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end([
            '<form method="POST" action="/url">',
            '<h1>My form</h1>',
            '<fieldset>',
            '<label>Personal information</label>',
            '<p>What is your name?</p>',
            '<input type="text" name="name">',
            '<p><button>Submit</button></p>',
            '</form>'
        ].join(''));
    }else{
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end('You sent a <em>' + req.method + '</em> request')
    }  
}).listen(3000,function () {
    console.log('服务启动')
})

5、数据

当发送HTML时,需要随着响应体定义Content-Type头信息。

和响应消息一样,请求消息也可以包含Content-Type头信息。

为了更有效地处理表单,这两部分信息都是不可或缺的。

require('http').createServer(function (req,res){
    if('/' == req.url){
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end([
            '<form method="POST" action="/url">',
            '<h1>My form</h1>',
            '<fieldset>',
            '<label>Personal information</label>',
            '<p>What is your name?</p>',
            '<input type="text" name="name">',
            '<p><button>Submit</button></p>',
            '</form>'
        ].join(''));
    }else if('/url' == req.url && 'POST' == req.method){
        var body = '';
        req.on('data',function (chunk) {
            body += chunk;
        })
        req.on('end',function () {
            res.writeHead(200, {'Content-Type':'text/html'});
            res.end(
           // Node在拿到浏览器发送的数据后,对其进行分析,然后构建一个JavaScript对象方便在脚本使用
           // 它将所有头的信息都变成小写:req.headers['content-type']
                '<p>Content-Type:' + req.headers['content-type'] + '</p>'
                + '<p>Data:</p><pre>' + body + '</pre>'
            )
        })
    }  
}).listen(3000,function () {
    console.log('服务启动')
})
// 在此例中,监听了data和end时间,创建了一个body字符串用来接收数据块
// 仅当end事件出发时,我们就知道数据接收完全了
// 之所以可以逐块接收数据,是因为Node.js允许在数据到达服务器时就可以对其进行处理
// 因为数据是以不同TCP包到达服务器的,这和现实情况也完全匹配。
// 我们就先获取一部分数据,再在某个时刻获取其余数据
// querystring的模块
// querystring模块将一个字符串解析成一个对象
console.log(require('querystring').parse('name=Nickname'));
console.log(require('querystring').parse('q=Query+Nickname'));

// [Object: null prototype] { name: 'Nickname' }
// [Object: null prototype] { q: 'Query Nickname' }

6、整合

const qs = require('querystring');
require('http').createServer(function (req,res){
    if('/' == req.url){
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end([
            '<form method="POST" action="/url">',
            '<h1>My form</h1>',
            '<fieldset>',
            '<label>Personal information</label>',
            '<p>What is your name?</p>',
            '<input type="text" name="name">',
            '<p><button>Submit</button></p>',
            '</form>'
        ].join(''));
    }else if('/url' == req.url && 'POST' == req.method){
        var body = '';
        req.on('data',function (chunk) {
            body += chunk;
        })
        req.on('end',function () {
            res.writeHead(200, {'Content-Type':'text/html'});
            // 使用querystring parse 对请求内容进行解析
            // 然后,从解析生成的对象中获取name值,并展示给用户
            // 这里的name是指<input>标签中的name值
            res.end(
                '<p>Your name is <b>' + qs.parse(body).name + '</b></p>'
            )
        })
    }  
}).listen(3000,function () {
    console.log('服务启动')
})

7、让程序更健壮

const qs = require('querystring');
require('http').createServer(function (req,res){
    if('/' == req.url){
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end([
            '<form method="POST" action="/url">',
            '<h1>My form</h1>',
            '<fieldset>',
            '<label>Personal information</label>',
            '<p>What is your name?</p>',
            '<input type="text" name="name">',
            '<p><button>Submit</button></p>',
            '</form>'
        ].join(''));
    }else if('/url' == req.url && 'POST' == req.method){
        var body = '';
        req.on('data',function (chunk) {
            body += chunk;
        })
        req.on('end',function () {
            res.writeHead(200, {'Content-Type':'text/html'});
            res.end(
                '<p>Your name is <b>' + qs.parse(body).name + '</b></p>'
            )
        })
    }else{
        // URL没有匹配到任何判断条件,服务器端一直都没响应,浏览器一直处于挂起状态。
        // 当服务器不知道如何处理该请求时,发送404状态码给客户端
        res.wirteHead(404);
        res.end('Not Found');
    }  
}).listen(3000,function () {
    console.log('服务启动')
})

8、Twitter Web客户端

学习如何使用Node.js向其他Web服务器发送请求是十分重要的。

HTTP已经演变成并非仅用于交换最终渲染、展示给用户的标记语言(如HTML)。

它还是服务器在不同网络环境传递数据到一种方式。

JSON因其语法衍生自JavaScript线性对象,也快速成为了HTTP默认的标准数据格式。

// 服务端
require('http').createServer(function (req,res){
    res.writeHead(200);
    res.end('Hello World')
}).listen(3000);
// 客户端
require('http').request({
    host:'127.0.0.1',
    port:3000,
    url:'/',
    method:'GET'
},function (res) {
    var body = '';
    res.setEncoding('utf8');
    res.on('data',function (chunk) {
        body += chunk;
    });
    res.on('end',function (chunk) {
        console.log('
 We got: 33[96m' + body + '33[39m
')
    });
}).end();

9、发送数据

// 服务端
const qs = require('querystring');
require('http').createServer(function (req,res){
    var body = '';
    req.on('data',function (chunk) {
        body += chunk;
    })
    req.on('end',function () {
        res.writeHead(200);
        res.end('Done')
        console.log('
 got name 33[90m' + qs.parse(body).name + '33[39m
')
    })
}).listen(3000,function () {
    console.log('服务启动')
})
// 客户端
var http = require('http'),qs = require('querystring')
function send (theName) {
    http.request({
        host:'127.0.0.1',
        port:3000,
        url:'/',
        method:'GET'
    },function (res) {
        res.setEncoding('utf8');
        res.on('end',function () {
            console.log('
 33[90m request complete!33[39m');
            process.stdout.write('
 your name:')
        })
    }).end(qs.stringify({name:theName}))
}
process.stdout.write('
 your name:')
process.stdin.resume();
process.stdin.setEncoding('utf-8');
process.stdin.on('data',function (name) {
    send(name.replace('
',''))
})

10、获取推文

// 客户端
var http = require('http'),qs = require('querystring')
// process.argv.slice(2).获取Node程序运行时真正的参数值
var search = process.argv.slice(2).join(' ').trim()
if(!search.length) {
    return console.log('
 Usage: node tweets <search term>
');
}
console.log('
 search for: 33[96m' + search + '33[39m
')
http.request({
    host:'search.twitter.com',
    path:'/search.json?' + qs.stringify({q:search})
},function (res) {
    var body = '';
    res.setEncoding('utf8');
    res.on('data',function(chunk) {
        body += chunk;
    })
    res.on('end',function () {
        var obj = JSON.parse(body);
        obj.results.forEach(function (tweet) {
            console.log(' 33[90m' + tweet.text + '33[39m')
            console.log(' 33[94m' + tweet.from_user + '33[39m')
            console.log('--')
        })
    })
}).end();
// 执行 node res.js
// Usage: node tweets <search term>
// 执行 node .
es.js Justin Bieber
// 此例会返回错误提示:
// The Twitter REST API v1 is no longer active. Please migrate to API v1.1
// 证明方法可用,但是Twitter已经不支持这个API了
// 改写上例子
var http = require('http'),qs = require('querystring')
var search = process.argv.slice(2).join(' ').trim()
if(!search.length) {
    return console.log('
 Usage: node tweets <search term>
');
}
console.log('
 search for: 33[96m' + search + '33[39m
')
http.get({
    host:'search.twitter.com',
    path:'/search.json?' + qs.stringify({q:search})
},function (res) {
    var body = '';
    res.setEncoding('utf8');
    res.on('data',function(chunk) {
        body += chunk;
    })
    res.on('end',function () {
        var obj = JSON.parse(body);
        obj.results.forEach(function (tweet) {
            console.log(' 33[90m' + tweet.text + '33[39m')
            console.log(' 33[94m' + tweet.from_user + '33[39m')
            console.log('--')
        })
    })
})
// 本质不同就是无须调用end方法了,而且从语义上更显然能够看出是要获取数据

11、superagent

npm install superagent

// superagent改写上例
var request = require('superagent')
require.get('https://twitter.com/search.json')
// send和set方法可以多次调用,并且均为渐进式API,可以链式调用,并通过end方法结束
	   .send({q:'justin bieber'})
		// 设置请求头
	   .set('Date',new Date)
	   .end(function (res) {console.log(res.body)})

八、Connect

​ Connect是一个基于HTTP服务器的工具集。

​ 它提供了一种新的组织代码的方式来与请求、响应对象进行交互,称为中间件

1、通过Connect实现一个简单的网站

  • 托管静态文件
  • 处理错误以及损坏或者不存在的URL
  • 处理不同类型的请求
{
    "name":"my-website",
    "version":"0.0.1",
    "dependencies":{
        "connect":"1.8.7"
    }
}
npm install
// 模块依赖
var connect = require('connect');
// 创建服务器
var server = connect.createServer();
// 处理静态文件
server.use(connect.static(__dirname + '/website'))
// 监听
server.listen(3000);

1、中间件

中间件由函数组成,它除了处理req和res对象之外,还接收一个next函数来做流控制。

根据每个请求的不同情况处理以下几种不同的任务:

  • 记录请求处理时间
  • 托管静态文件
  • 处理授权
// 利用中间件模式满足伤处要求的应用
server.use(function (req,res,next) {
    // 记录日志
    console.error(' %s %s ',req.method,req.url);
    next();
})
server.use(function (req,res,next) {
    if('GET' == req.method && '/image' == req.url.substr(0,7)){
        // 托管图片
    }else{
        // 交给其他中间件处理
        next()
    }
})
server.use(function (req,res,next) {
    if('GET' == req.method && '/' == req.url){
        // 响应index文件
    }else{
        // 交给其他中间件处理
        next()
    }
})
server.use(function (req,res,next) {
    // 最后一个中间件,到了这里返回404
    res.writeHead(404);
    res.end('Not Found')
})

2、书写可重用的中间件

// request-time.js
// 页面向数据库发起一系列请求,记录响应时间大于100ms的请求记录
/**
*  请求时间中间件
* 选项:
* 	- 'time'('Number'):超时阙值(默认100)
*/
// 暴露一个函数,此函数本身又返回一个函数
module.exports = function (opts) {
    // 默认超时阙值100
    var time = opts.time || 100;
    // 返回一个中间件函数
    return function (req,res,next){
        // 创建一个计时器,并在指定时间内触发
        var timer = setTimeout(function () {
            console.log(
            	'33[90m%s %s33[39m 33[91mis taking too long!33[39m',
                req.method,
                req.url
            )
        },time)
        // 当响应结束时,清除计时器
        var end = res.end;	// 首先保持对原函数的引用
        // 重写函数,再恢复原始函数,并调用它,最后清除计时器
        res.end = function (chunk,encoding){
            res.end = end;	// 恢复原始函数
            res.end(chunk,encoding); // 调用
            clearTimeout(timer); // 清除计时器
        }
        next()
    }
}
// 创建一个Connect应用,并创建两条路由
// 第一条路由很快得到响应,第二条路由1秒后得到响应

// 模块依赖
var connect = require('connect'),time = require('./request-time');
// 创建服务器
var server = connect.createServer();
// 记录请求情况
server.use(connect.logger('dev'))
// 实现中间件
server.use(time({time:500}));
// 实现快速响应
server.use(function (req,res,next){
    if('/a' == req.url){
        res.writeHead(200);
        res.end('Fast!')
    }else{
        next();
    }
})
// 实现模拟的慢速响应
server.use(function (req,res,next){
    if('/b' == req.url){
        setTimeout(function () {
            res.writeHead(200);
            res.end('Slow!')
        },1000)
    }else{
        next();
    }
})
// 服务器监听端口
server.listen(3000);

3、static中间件

3.1、挂载

Connect允许中间件挂载到URL上。比如,static允许将任意一个URL匹配到文件系统中任意目录。

举例来说,假设要让/my-images这个URL和名为/images的目录对应,可以如下设置

server.use('/my-images',connect.static('/path/to/images'))

3.2、maxAge

static中间件接收一个名为maxAge的选项,这个选项代表一个资源在客户端缓存的时间。

对一些不经常改动的资源来说,浏览器无须每次都去请求它

// 比如:一个Web应用常见的实践方式就是将所有的客户端JavaScript文件都合并一个文件中。
// 在其文件名中加上修订号。设置maxAge选项,让其永远缓存
server.use('js',connect.static('/path/to/bundles',{maxAge:1000000000000000000}))

3.3、hidden

如果该选项为true,Connect就会托管那些文件名以点(.)开始的在UNIX文件系统中被认为是隐藏的文件

server.use(connect.static('/path/to/resources',{hidden:true}))

3.4、query中间件

有时要发送给服务器的数据会以查询字符串形式,作为URL的一部分。

比如,url /blog-posts?page=5。当在浏览器中访问该URL时,Node会以字符串的形式将该URL存储到req.url变量中。

server.use(function (req) {
    // req.url == '/blog-posts?page=5'
}) 

使用query中间件,就能够通过req.query对象自动获取这些数据

server.use(connect.query)
server.use(function (req,res) {
    // req.query.page == '5'
})

3.5、logger中间件

logger中间件时一个Web应用非常有用的诊断工具。

它将发送进来的请求信息和发送出去的响应信息打印在终端。

它提供了四种日志格式:

  • default
  • dev
  • short
  • tiny
// 初始化logger中间件
server.use(connect.logger('dev'))
// 自定义日志输出格式
server.use(connect.logger(':method:remote-addr'));
// 通过动态的req和res来记录头信息
// 以下:记录响应的content-length和content-type信息
server.use(
    connect.logger(
        'type is :res[content-type],length is ' 
        + ':res[content-length] and it took :reponse-time ms.'
))

3.6、body parser中间件

// 添加后,能够在req.body中获取POST数据了
server.use(connect.bodyParser())
server.use(function (req,res) {
    // req.body.myinput
})

3.6.1、处理上传

bodyParser另一个功能就是使用formidable模块,它可以让你处理用户上传的文件

// 服务端
var server = connect(
	connect.bodyParser(),
    connect.static('static')
)
function (req,res,next){
    if('POST' == req.method){
        console.log(req.body.file)
    }else{
        next()
    }
}
<!--单文件-->
<form action="/" method="POST" enctype="multipart/form-data">
    <input type='file'/>
    <button>
        Send File
    </button>
</form>
<!--多文件-->
<!--req.body.files就包含一个数组,数组中的元素就是此前单个文件的对象-->
<form action="/" method="POST" enctype="multipart/form-data">
    <input type='file' name="files[]"/>
    <input type='file' name="files[]"/>
    <button>
        Send File
    </button>
</form>

3.7、cookie

当浏览器发送cookie数据时,会将其写道Cookie头信息中。其数据格式和URL中的查询字符串类似。

如果不想手动解析,也不想使用正则表达式去抽取,就可以使用cookieParser中间件

server.use(connect.cookieParser())
// 访问cookie数据
server.use(function () {
    // req.cookies.secret = "value"
})

3.8、会话(session)

​ 在绝大多数Web应用中,多个请求间共享“用户会话”的概念非常必要。

​ “登录”一个网站时,多少会使用某种形式的会话系统,它主要通过在浏览器中设置cookie来实现,该cookie信息会在随后所有的请求头信息中被带回到服务器。

Connect提供了简单的实现方式。

简单的登录系统

  • user.json

    {
        "tobi":{
            "pwd":"123",
            "name":"tobi"
        }
    }
    
  • client.js

    // 这里直接require了JSON文件。
    // 当只要对外暴露数据时,就不需要加module.exports,直接把数据暴露出来就好
    var connect = require('connect'),users = require('./users')
    // logger、bodyParser和session中间件
    // 由于session中间件要操作cookie,所以在之前要引入cookieParser中间件
    var server = connect(
    	connect.logger('dev'),
        connect.bodyParser(),
        connect.cookieParser(),
        // 出于安全考虑,在初始化session中间件的时候需要提供secret选项
        connect.session({secret:'my app secret'}),
        function (req,res,next){
            // 验证用户是否登录
            if('/' == req.url && req.session.logged_in){
                res.writeHead(200,{'Content-Type':'text/html'});
                res.end(
                    'Welcome back, <b>' + req.session.name + '</b>'
                    + '<a href="/logout">Logout</a>'
                )
            }else{
                // 没有登录交给其他中间件处理
                next();
            }
        },
        // 登录页
        function (req,res,next){
            // 展示登录表单
            if('/' == req.url && 'GET' == req.method) {
                res.writeHead(200,{'Content-Type':'text/html'})
                res.end([
                    '<form action="/login" method="POST">',
                    '<fieldset>',
                    '<legend>Please log in</legend>',
                    '<p>User: <input type="text" name="user"></p>',
                    '<p>Password:<input type="password" name="pwd"></p>',
                    '<button>Submit</button>',
                    '</fieldset>',
                    '</form>'
                ].join(''))
            }else{
                next()
            }
        },
        function (req,res,next){
            // 登录操作
            if('/login' == req.url && 'POST' == req.method) {
                res.writeHead(200);
                // 校验登录凭证
                if(!users[req.body.user] || req.body.pwd != users[req.body.user].pwd){
                    res.end('Bad username/password')
                }else{
                    // 修改req.session对象
                    // 保存 logged_in name
                    // req.session对象在响应发送出去时就会自动保存,无须手动处理
                    req.session.logged_in = true;
                    req.session.name = users[req.body.user].name;
                    res.end('Authenticated!')
                }
            }else{
                next();
            }
        },
        // 退出操作
        function (req,res,next){
            if('/logout' == req.url){
                // 修改req.session对象
                req.session.logged_in = false;
                res.writeHead(200);
                res.end('Logged out');
            }else{
                next();
            }
        },
        // 以上操作都不拦截,那就提示‘Not Found’
        function (req,res){
            res.writeHead(404);
            res.end('Not Found')
        }
    );
    server.listen(3000)
    

3.9、Redis session

登陆后,重启node服务器,刷新浏览器,发现session丢失

原因就在于session默认的存储方式是在内存。这就意味着session数据都是存在在内存中,当进程退出后,session数据自然也就丢失

在生产环境中,需要一种当应用重启,还能够将session信息持久化存储下来的机制,如Redis

var connect = require('connect'),RedisStore = require('connect-redis')(connect)
// 将其作为store选项的值传递给session中间件
server.use(connect.session({store:new RedisStore,secret:'my secret'}))

3.10、methodOverride中间件

一些早期浏览器不支持创建如PUT、DELETE、PATCH这样的请求。

解决方案是在GET或者POST请求中加上一个_method变量来模拟上述请求。

// POST url?_method=PUT HTTP/1.1
// 中间件是串行执行的,所以务必确保把它放在其他处理请求中间件之前
server.use(connect.methodOverride())

3.11、basicAuth中间件

有些项目需要对客户端进行基本的身份验证,为此,Connect提供了一个名为basicAuth的中间件。

// 创建一个简单的身份验证系统,并且通过命令行对用户进行验证操作


var connect = require('connect')
var server = connect.createServer();
// 等待输入
process.stdin.resume();
process.stdin.setEncoding('ascii');
// 添加basicAuth中间件
connect.basicAuth(function (user,pass,fn) {
    process.stdout.write(
        'Allow user 33[96m' + user + '33[39m' +
        'with pass 33[90m' + pass + '33[39m ? [y/n]: '
    );
    // 只需要对每个请求获取一次数据
    process.stdin.once('data',function (data) {
        if(data[0] == 'y'){
            // 通过验证,传递null作为第一个参数
            // 以及user对象用来生成req.remoteUser对象(供后续中间件使用)
            fn(null,{username:user});
        }else{
            // 未过验证,传递Error对象作为第一个参数
            fn(new Error('Unauthorized'))
        }
    })
}),
function (req,res){
    res.writeHead(200);
    res.end('Welcome to the protected area,' + req.remoteUser.username);
}
server.listen(3000)
原文地址:https://www.cnblogs.com/luckyzs/p/13502902.html