Node--一个简单的HTTP服务器(实现表单请求和响应)

先复习一下HTTP~

HTTP

超文本传输协议,又称为HTTP,是一种web协议,属于TCP上层的协议。

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

头信息

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

web页面会分发不同类型的内容:文本(Text)、HTML、XML、JSON、PNG以及JPEG图片,等等。
发送内容的类型(type)就是在著名的Content-Type头信息中标注的。
一个简单的实践:

var http = require('http');
http.createServer(function(req,res){
    res.writeHead(200);
    res.end('Hello <b>World</b>');
}).listen(3000);

在终端通过telnet连接:
在这里插入图片描述
在浏览器中:
在这里插入图片描述
为什么浏览器没有正确解析文本呢?这是由于HTTP客户端(浏览器)并不知道服务器发过来的内容是什么类型,我们需要把这部分信息告诉浏览器。否则,浏览器以为它看到的内容是纯文本即text/plain,就不会将它作为HTML来渲染了。

解决方案——
加入正确头信息:

res.writeHead(200, {'Content-Type': 'text/html'});

在这里插入图片描述
浏览器中HTTP头部请求信息:
在这里插入图片描述
从上图中我们也可以看到,我们尽管只用writeHead API指定了一个头信息,但是,Node还是把另外两个头信息:Connection和Transfer-Encoding。

Transfer-Encoding的默认值是chunked,主要原因是Node天生的异步机制,这样响应就可以逐步发生。

var http = require('http');
http.createServer(function(req,res){
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('Hello');
    setTimeout(function() {
        res.end('world');
    },500);
}).listen(3000);

像上面的代码一样,在调用end前,我们可以多次调用write方法来发送数据。为了尽可能快地响应客户端,在首次调用write时,Node就能把所有的响应头信息以及第一块数据(Hello)发送出去。
随后,在执行setTimeot回调函数时,又写入另外一块数据。由于这次是使用end而不是write方法,因此,Node会结束响应,并不再允许往这次响应中发送数据了。

发送数据块的方式在设计文件系统的情况下会非常高效。Web服务器对硬盘是那个的文件进行托管服务是很常见的。因此Node允许以数据块的形式往响应中写数据,同时它又允许以数据块的形式读取文件,所以我们可以使用ReadStream文件系统API来实现。

下面这个例子用于读取image.png

var http = require('http');
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);

以数据块的形式将图片写到响应中,会有如下好处:

  • 高效的内存分配。要是对每个请求在写入前都完全把图片信息读取完(通过fs.readFile),在处理大量请求时会消耗大量内存。
  • 数据一旦就绪就可以立刻写入了。

上面的代码可以改为流的对接形式(把一个流接到另一个流上)

var http = require('http');
http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type': 'image/png'});
    require('fs').createReadStream('image.png').pipe(res);
}).listen(3000);

TCP服务器和HTTP服务器有相似的实现,但是它们有个本质的区别,即回调函数中对象的类型。在net服务器中,是个连接(connection)对象,而在HTTP服务器中,则是请求和响应对象。原因在于:
一、HTTP服务器是更高层的API,提供了控制和HTTP协议相关的一些功能。
Node在拿到浏览器发送的数据(代理的相关信息、是否缓存等信息)后,对其进行分析,然后构造一个JavaScript对象方便我们在脚本中使用。它甚至还将所有的信息都变成了小写,避免混淆记忆。
二、浏览器在访问站点时不会就只用一个连接。很多主流的浏览器为了更快地加载网站内容,能向一个主机打开八个不同的连接,并发送请求。
我们可以使用req.connection属性获得TCP连接对象,但是绝大多数我们还是和请求和响应的抽象打交道。

默认情况下,Node会告诉浏览器始终保持连接,通过它来发送更多的请求。

一个简单的Web服务器
定义需求

简单的表单提交和响应

细分步骤
  • 创建模块
  • 输出表单
  • method和URL
  • 数据
  • 整合
  • 让程序更健壮
创建模块
// package.json
{
    "name": "http-form",
    "description": "An HTTP server that processes forms",
    "version": "0.0.1"
}
输出表单
var http = require('http');
http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type': 'text/html'});
    res.end([
        '<form method="POST" action="/url">',
        '<h1>My form</h1>',
        '<fieldset>',
        '<label>Personnal Information</label>',
        '<p>What is your name?</p>',
        '<input type="input" name="name">',
        '<p><button>Submit</button></p>',
        ' </fieldset>',
        '</form>'
    ].join(''));
}).listen(3000);

在这里插入图片描述
为了让HTML结构更加清楚,把响应文本写在一个数组中是比较直观的!

method和URL
// index.js
var http = require('http');
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>Personnal Information</label>',
            '<p>What is your name?</p>',
            '<input type="input" name="name">',
            '<p><button>Submit</button></p>',
            ' </fieldset>',
            '</form>'
        ].join(''));
    } else {
        res.writeHead(200,{'Content-Type': 'text/html'});
        res.end('You send a <em>' + req.method + ' </em> request');
    }
}).listen(3000);

当浏览器提交表单之后,method是POST。
在这里插入图片描述
而直接在浏览器地址栏输入相同的url,显示method是GET。
在这里插入图片描述

Node会将主机名后的所有内容都放在url属性中

数据

我们需要告诉服务器我们将要请求信息的格式。添加:

// index.js
if (req.url == '/') {
    res.writeHead(200,{'Content-Type': 'text/html'});
    res.end([
        '<form method="POST" action="/url">',
        '<h1>My form</h1>',
        '<fieldset>',
        '<label>Personnal Information</label>',
        '<p>What is your name?</p>',
        '<input type="input" name="name">',
        '<p><button>Submit</button></p>',
        ' </fieldset>',
        '</form>'
    ].join(''));
} else if(req.url == '/url' && req.method == 'POST') {
    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>');
    });

}

通过监听data和end事件。创建一个nody字符串来接收数据块,仅当end事件触发时,我们就知道数据接收完全了。

之所以这样逐块接收数据,是因为Nodejs允许在数据到达服务器时就可以对其进行处理。因为数据是以不同TCP包到达服务器的,这种情况也和现实情况完全匹配。

提交表单:
在这里插入图片描述

Node.js提供了一个称为querystring的模块,可以方便地对查询字符串进行解析。
require('querystring').parse('name=GUI')
querystring模块将一个字符串解析成一个对象。

整合
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>Personnal Information</label>',
            '<p>What is your name?</p>',
            '<input type="input" name="name">',
            '<p><button>Submit</button></p>',
            ' </fieldset>',
            '</form>'
        ].join(''));
    } else if(req.url == '/url' && req.method == 'POST') {
        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>');   // 补充添加
        });

    }
}).listen(3000);
让程序更健壮

如果URL没有匹配到任何判断条件,则服务器一直都没有响应,浏览器将一直处在挂起的状态。

为解决这个问题,我们可以在服务器不知道如何处理请求的情况时,发送404状态码给客户端。

// ...
if (req.url == '/') {
	// ...
} else if(req.url == '/url' && req.method == 'POST') {
	// ...
} else {
    res.writeHead(404);
    res.end('Not Found');
}
// ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值