Node.js学习日记(七):Connect框架和一些常用的中间件

460 阅读5分钟

eg.一个简单的图片托管网站

服务器端

ar fs = require('fs')
var http=require('http')
var qs=require('querystring')

var server=http.createServer(function(req,res){
    function serve(path,type){
        res.writeHead(200,type)
        fs.createReadStream(path).pipe(res)    
    }
    if(req.method=="GET"&&req.url=="/"){
        serve(__dirname+'/index.html','text/html')
    }else if(req.method=="GET"&&req.url.substr(0,7)=="/images"&&req.url.substr(-4)==".jpg"){
        fs.stat(__dirname+req.url,function(err,stat){
            if(err||!stat.isFile()){
                res.writeHead(404)
                res.end("404 NOT FOUND")
                return
            }
             serve(__dirname+req.url,'application/jpg')
        })
    }else{
        res.writeHead(404)
        res.end("404 NOT FOUND")
    }
})
server.listen(3000)

index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>website-test</title>
</head>
<body>
    <h1>my website</h1>
    <image src="/images/1.jpg"></image>
    <image src="/images/2.jpg"></image>
    <image src="/images/3.jpg"></image>
    <image src="/images/4.jpg"></image>
</body>
</html>

下面我们可以通过中间件来实现服务器逻辑

var connect=require('connect')
//var http=require('http')
var timeout = require('connect-timeout')
var morgan = require('morgan')
var serveStatic = require('serve-static')
var server=module.exports = connect()
    .use(timeout('1s'))
    .use(morgan('dev'))
    .use(serveStatic(__dirname+'/website'))
    .use(function(req,res,next){
    console.log("%s %s",req.method,req.url)
    next()
})
server.use(function(req,res,next){
    // function serve(path,type){
    //     res.writeHead(200,type)
    //     fs.createReadStream(path).pipe(res)    
    // }
    if(req.method=="GET"&&req.url.substr(0,7)=="/images"){
        //serve(__dirname+req.url,'application/jpg')
        
    }else{
        next()
    }
})
server.use(function(req,res,next){
    // function serve(path,type){
    //     res.writeHead(200,type)
    //     fs.createReadStream(path).pipe(res)    
    // }
    if(req.method=="GET"&&req.url=="/"){
        //server.use(serveStatic(__dirname+'/website'+req.url))
    }else{
        next()
    }
})
server.use(function(req,res,next){
    res.write(404)
    res.end('404 NOT FOUND')
})
server.listen(3000)

一、npm包常用api查找

npm官网:www.npmjs.com/

原生api:nodejs.cn/

在学习《了不起的Node.js》一书时,发现它使用的connect包版本十分老旧,导致了一些中间件已经无法调用,我们可以通过文档来找到connect常用的中间件,以方便我们的学习。

这里列出了一些Connect用到的中间件:www.npmjs.com/package/con…

二、Connect

什么是中间件?

可以组织代码来与请求、响应对象进行交互的工具

Pre-Request 通常用来改写request的原始数据;

Request/Response 大部分中间件都在这里,功能各异;

Post-Response 全局异常处理,改写response数据等;

image.png 之后会提到部分中间件以及他们的作用。 Connect是一个优秀的中间件集成平台,可以通过中间件复用很多代码。同时如上面的代码所示,Connect采用的是一个中间队列,流式处理,中间件可以自由组合和插拔。

1.流式处理

var server=connect()
    .use(timeout('1s'))
    .use(morgan('dev'))
    .use(serveStatic(__dirname+'/website'))
    .use(function(req,res,next){
    console.log("%s %s",req.method,req.url)
    next()
})

2.原型

connect的核心代码,即维护了一个中间件队列,请求来临时调用中间件

app.stack = [];
app.use = function(route, fn) {
    // ...
    // add the middleware
    debug('use %s %s', route || '/', fn.name || 'an onymous');
    this.stack.push({
    route: route,
    handle: fn
});
return this;
};
function (req, res, next) {
 
// 中间件
 
}

如图所示

image.png 我们可以用原型以此处理请求并判断类型,可以使代码逻辑更加清晰。

3.静态资源托管

通过中间件 serve-static可以实现,正如我们的例子中的图片一样,只要将其资源的文件夹托管,下属的各类资源会被中间件接管,可以准确的找到所需资源,这里大大的减少了冗余的代码量。比如我们第一次实现时写的serve函数就可以被简省掉了。

var serveStatic = require('serve-static')

server.use(serveStatic(__dirname+'/website'))
server.use(function(req,res,next){
    // function serve(path,type){
    //     res.writeHead(200,type)
    //     fs.createReadStream(path).pipe(res)    
    // }
    if(req.method=="GET"&&req.url.substr(0,7)=="/images"){
        //serve(__dirname+req.url,'application/jpg')
        
    }else{
        next()
    }
})
server.use(function(req,res,next){
    // function serve(path,type){
    //     res.writeHead(200,type)
    //     fs.createReadStream(path).pipe(res)    
    // }
    if(req.method=="GET"&&req.url=="/"){
        //server.use(serveStatic(__dirname+'/website'+req.url))
    }else{
        next()
    }
})

maxAge

server.use(serveStatic(__dirname+'/website'+req.url,{maxAge:1000000})

可以设置缓存时间,对于一个不经常动用的资源来说,就可以减少它的请求次数

hideen

可以托管那些在UNIX下被隐藏的文件

logger日志记录

var morgan = require('morgan')
server.use(morgan('dev'))
C:\Users\15905\Documents\code\my-website>node index
GET / 200 6.823 ms - 461
GET /images/1.jpg 304 3.951 ms - -
GET /images/2.jpg 304 4.527 ms - -
GET /images/3.jpg 304 6.115 ms - -
GET /images/4.jpg 304 3.681 ms - -

timeout超时记录

var timeout = require('connect-timeout')
server.user(timeout('1s'))

书上给出了手动实现的方法,超过所设置的时间就输出信息,或者在响应完清除定时器。

module.exports=function(opts){
    var time=opts.time||100
    return function(req,res,next){
        var timer=setTimeout(function(){
            console.log("%s %s is taking too much time",req.method,req.url)
        },time)
    }
    var end=res.end
    res.end=function(chunk,encoding){
        res.end=end
        res.end(chunk,encoding)
        clearTimeout(timer)
    }
    next()
}

query

查询并获取url头中的一些数据,并可以去设置

server.use(connect.query)
server.use(function(req,res){
    req.query.use=="5
}

bodyparser

使用这个的时候遇到了一点问题。书上用这个包处理了form-data类型的请求头的数据,但是现在这个方法好像已经被弃置了。所以我想了两个解决方法,第一个方法是queryString,发现这个现在只能处理application/www-encoded类型的数据,也就是默认的请求头,对于form-data并不能解析。第二个方法是使用其他的包,比如connect-multiparty来解决。 服务端代码

var connect=require('connect')
var serveStatic = require('serve-static')
var fs=require('fs')
var server=connect()
var multipart = require('connect-multiparty');
server.use(serveStatic(__dirname+'/static'))
server.use(function(req,res,next){
    if(req.method=="GET"&&req.url=="/"){
        //console.log('get html')
    }else{
        next()
    }
})
var multipartMiddleware = multipart();
server.use(multipartMiddleware)
server.use(function(req,res,next){
    var post=''
    if(req.method=="POST"&&req.url=="/"){
        console.log('serve receive file')
        console.log(req.body,req.files)
        res.writeHead(200)
        res.end("ok")
    }
        else{
            next()
        }
    })
server.listen(3000,function(){
    console.log("the server is on ")
})

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="/" method="POST" enctype="multipart/form-data">
        <input type="file" name="file"/>
        <input type="text" name="username"/>
        <input type="submit" name="submit" value="发送"> 
    </form>
</body>
</html>

输出

{ username: 'eee', submit: '发送' } {
  file: {
    fieldName: 'file',
    originalFilename: 'ZIKU.circ',
    path: 'C:\\Users\\15905\\AppData\\Local\\Temp\\jUyCLNJ2HO2keFIOvsJixv5R.circ',
    headers: {
      'content-disposition': 'form-data; name="file"; filename="ZIKU.circ"',
      'content-type': 'application/octet-stream'
    },
    size: 590082,
    name: 'ZIKU.circ',
    type: 'application/octet-stream'
  }
}

cookieParser

设置cookie的两种方式,第一种直接用res.writeHead()添加在头上,为请求头添加键对值{'setCookie':[]},第二种可以用cookieParser中间件添加(connect中res.cookie好像没法添加)。cookieParse还可以解析当前请求携带的cookie值

第一种方式

var cookieParser=require('cookie-parser')
var connect=require('connect')
var app=connect()
app.use(cookieParser())
app.use(function(req,res,next){
    if(req.url=="/set"&&req.method=="GET"){
        res.writeHead(200,{
            'Set-Cookie': ["aaa=bbb","ccc=ddd","eee=fff"],
        })
        res.end("ok")

    }
    else next()
})
app.use(function(req,res,next){
    if(req.url=="/"&&req.method=="GET"){
        // Cookies that have not been s igned
        console.log('Cookies: ', req.cookies)
        // Cookies that have been signed
        console.log('Signed Cookies: ', req.signedCookies)
        res.writeHead(200)
        res.end("ok")
    }
    else next()
})
.listen(3000,function(){
    console.log('listening on 3000')
})