简单的web服务器

一个简单的web服务器

创建模块

输出表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var http = require('http');
var qs = require('querystring');

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>Content-Type: ' + req.headers['content-type'] + '</p>'
// +'<p> Data: </p><pre>' + body + '</pre>'
// );
res.end('<p>Your name is <b>' + qs.parse(body).name + '</b></p>');
})
}else{
res.writeHead(404);
res.end('Not Found');
}
// }else if('/url' == req.url){
// res.writeHead(200, {'Content-Type': 'text/html'});
// res.end('You sent a <em>' + req.method + '</em> request!');
}).listen(3000);

一个twitter的web 客户端

  1. 发送一个简单的HTTP请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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(){
    console.log('\n We got: \033[96m' + body + '\033[38m\n');
    });
    }).end();

一个简单的http web服务器

//回到createServer的回调函数,我们需要检查URL是否和服务器的目录下的文件匹配,如果匹配,则读取该文件并展示出来。以后,可能会添加更多的图片,所以要确保灵活以支持这种情况。
// 首先 要检查请求方法是GET并且URL以/images开始,.jpg结束。如果URL 为/的话,则响应index.html(调用后面要实现的serve函数)。否则,发送404 not found。接着,使用fs.stat来检查文件是否存在。者里使用NODE中全局常量__dirname来获取当前服务器所在的路劲。在首个if语句后,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var http = require('http');

var fs = require('fs');

var server = http.createServer(function(req, res){
if('GET' == req.method && '/images' == req.url.substr(0, 7) && '.jpg' == req.url.substr(-4)){
fs.stat(__dirname + req.url, function(err, stat){
if(err || !stat.isFile()){
res.writeHead(404);
res.end('not found');
return;
}
serve(__dirname + req.url, 'application/jpg');
});

}else if('GET' == req.method && '/' == req.url){
serve(__dirname+'/index.html', 'text/html');
}else{
res.writeHead(404);
res.end('not found');
}
function serve(path, type){
res.writeHead(200, {'Content-Type': type});
fs.createReadStream(path).pipe(res);
}
})

server.listen(3000);
console.log('server listening in 3000...');

中间件
通过connect来实现一个简单的网站

  • 托管静态文件
  • 处理错误以及损坏的的URL
  • 处理不同类型的请求

基于http模块api上的connect,提供了一些工具方法能够让这些重复性的处理便于实现。DRY

引入模块
var connect = require(‘connect’);
var server = connect.createServer();
使用use()方法来添加static中间件。下一部分会介绍中间件的概念
中间件其实就是一个简单的javascript函数。本例中,我们这样配置static中间件—通过传递一些参数给connect.static方法,该方法本身会返回一个方法。

处理静态文件

serve.use(connect.static(__dirname + ‘/website’));
将index.html以及images目录放到/website下,确保没有将不想托管的文件放进去

server.listen(3000);

中间件

if(‘GET’ == req.method …){

}else if(‘GET’ == req.method){

}else{
res.writeHead(404);
res.end();
}
如上述代码描述,针对每个请求应用都只会做三件事情中的一件,比如: 若还要记录请求日志,就在顶部加上如下代码:
console.error(‘ %s %s ‘, req.method, req,url);
下面让我来设计一个大型应用,它能够根据每个请求的不同情况下处理以下者集中不同的任务:

  • 记录请求处理的时间
  • 托管静态文件
  • 处理授权

当然,这些任务的处理代码可以放在一个事件处理器中(createServer的回调函数中),这将是一个非常复杂的处理过程。
简单来说,中间件由函数组成,它除了处理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 && ‘/images’==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’);
});

使用中间件,不仅能够让代码有更强大的表啊能力(将应用拆分为更小的单元)
能够实现很好的重用性。我们马上就会看到,Connect已经为处理常见的任务提供了对应的中间件。例如: 要对请求进行日志记录,就可以简单的通过下面一行代码完成:

app.use(connect.logger(‘dev’));
帮你完成日志记录
下一部分会介绍如何书写一个当请求处理时间长而进行警告的中间件

书写可重用的中间件

一个用于当请求时间过长而进行提醒的中间件在很多场景下都非常有用。比如:
当页面会向数据库发起一系列的请求。在测试过程中,所有响应都在100ms内完成,但是你要确保能够将响应大于100ms的请求记录下来。
为此,我们在一个名为request-time.js的独立模块中创建一个中间件。
这个模块暴露一个函数,此函数本身又返回一个函数。这对于可配置的中间件来说是很常见的写法。在前面的例子中,我们调用connect.logger时传递一个参数,然后他自身会返回一个函数,最终来处理请求。

目前,模块就接收一个超时时间阀值选项,该选项用来界定什么时候将其记录下来。

/**

  • 请求时间中间件
  • 选项
  • -’time’(‘Number’): 超市阀值
  • @param (object) options
  • @api public
    */

module.exports = function(){
//….
};

首先将阀值设为100
var time = opt.time || 100;
随后返回一个中间件函数
return function(req, res, next){
//中间件本身创建一个计时器,并在指定时间的内触发
var timer = setTimeout(function(){
console.log(
‘\033[90m%s %s\033[39m \033[91m mis taking too long’, req.method, req.url
);
}, time);
//这里确保当响应时间在100ms以内时要清除(停下来或者取消)计时器。另外一个中间件中常用的模式叫做重写方法(猴子补丁), 能够在其他中间件调用它时,执行指定的行为。
//在本咧中,当响应结束时,我们需要清除计时器
var end = res.end;
res.end = function(chunk, encoding){
res.end = end;
res.end(chunk, encoding);
clearTimeout(timer);
};
//首先保持对原始函数的引用(var end = res.end) 然后在重写函数中,在恢复原始函数,并调用它,最后清除计时器。
//最后,总是要其他中间能够处理请求,所以得调用next,否则,程序不会做任何事情去。
next();

}