http事务机制剖析--https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/

http事务机制剖析

这篇指导主要讲解Nodejs的http处理过程。我们假设你对http的请求是如何工作的有个大致的了解,同时,你对Nodejs的EventEmitters 和 Streams有个大致的概念。如果你对他们不是特别熟悉的话,我们建议你快速的浏览有关它们的API文档。

建立服务器

任何关于node的web应用都会在一定程度上建立一个web服务器,一般来说,我们通过createServer函数来建立服务器。

const http = require('http')
const webServer = http.createServer((request, response) => {
  
})
每当有请求到达这个服务器时,createServer都会执行一次,所以,这个函数又被叫做请求回调函数。实际上,createServer函数返回的Server对象本质上是一个事件发射器EventEmitter,我们在这里要做的是为这个事件发射器添加监听者。
const http = require('http')
const webServer = http.createServer()
webServer.on('request', (request, response) => {
})

当一个http请求抵达服务器,node就会利用request和response对象来处理http事务。

为了能够处理请求,服务对象需要调用listen方法,在大多数情况下,你只需要向listen方法传入监听端口号。当然,还可以传入其他选项。

方法,url和Headers

当处理一个请求时,你想做的第一件事情或许是查看请求的类型和url,进而可以针对它们执行相应的操作。

const {method, url, headers} = request
我们可以通过以上方式来获取 请求类型、请求的url和请求头。
需要大家注意的非常重要的一点是,不管客户端如何发送请求头,所有的请求头都是小写的。这个限制将方便请求头的解析。
如果一些请求头重复了,它们的值会用逗号拼接成一个字符串。

Request Body

当服务器接收到一个请求,请求体对你的应用来讲是非常重要的。获取请求体数据比获取请求头数据复杂的多。request对象实现了ReadableStream接口,这个请求流像其他流那样被监听和传送。我们可以通过监听流的data事件和end事件将数据从流中取出。
data事件吐出的数据是Buffer对象,如果你需要一个字符串数据,最好的方法是将它们收集在一个数组中,最后在流的end事件发生时,将它们连接成一个字符串。
const http = require('http')
const webServer = http.createServer((request, response) => {
  const {method, url, headers} = request
  //console.log('method', method)
  //console.log('url', url)
  //console.log('headers',headers)
  //console.log(headers['user-agent'])
  let body = []
  request.on('data', function (chunk){
    body.push(chunk)
    console.log(chunk)
  })
  request.on('end', function (data) {
   body = Buffer.concat(body).toString()
   console.log(body)
  })
  response.end('hello world')
}).listen(4000);             
获取请求body的方式如上述代码

A Quick Thing About Errors

request对象既是一个ReableStream对象,又是一个EventEmitter对象,当错误发生时,它的表现如下:作为一个stream对象,它会在错误发生时发射一个error事件。如果你没有对这个stream对象监听error事件,这个错误就会抛出继而中断你的node程序。所以,你应该对request streams添加error监听事件。

request.on('error', (err) => {
  // This prints the error message and stack trace to `stderr`.
  console.error(err.stack);
});

What We've Got so Far

到现在为止,我们已经学习了如何建立一个web服务器,如何获取请求头、请求体、请求方法和url,如何处理错误。总结下来用如下代码表示:
const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    // At this point, we have the headers, method, url and body, and can now
    // do whatever we need to in order to respond to this request.
  });
}).listen(8080); // Activates this server, listening on port 8080.
如果你运行这段代码,你将会获得请求数据,但是没有响应这些请求。实际上,如果你在浏览器访问这个服务器,你的请求会超时,因为没有任何东西发送到客户端。
到现在为止,我们还没有接触到response对象,它是ServerResponse和WritableStream的实例。它包含了许多用来向客户端发送数据的方法,我们将在下节阐述。

HTTP Status Code

如果你不设置http状态码,那么,它永远是200。当然,在某些情况下,你希望发送其他的状态码,你可以通过设置statusCode来实现。
response.statusCode = 404;

Setting Response Headers

可以通过以下方法来设置响应头:
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');
当设置响应头时,名称大小写不敏感,如果你重复设置了某个响应头属性,那么,会覆盖前一个。

Explicitly Sending Header Data

上面讲到的设置响应头属性和状态码的方法是隐性的,这意味着你将依赖node为你在正确的时间发送响应头,当然,你也可以显式的写响应头,node提供writeHead方法,利用这个方法可以将状态码和响应头写进流。
response.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'bacon'
});
无论你显式或者隐式地设置响应头,都将意味着你准备向客户端发送数据。

Sending Response Body

由于响应对象是一个WritableStream类,使用通用的stream方法就可以向客户端发送数据。
response.write('<html>');
response.write('<body>');
response.write('<h1>Hello, World!</h1>');
response.write('</body>');
response.write('</html>');
response.end();
end方法可以携带一些数据作为流的最后的数据,所以,以上代码可以简写如下
response.end('<html><body><h1>Hello, World!</h1></body></html>');
注意:在向响应body里写数据之前,设置状态码和响应头是非常必要的。这将使得这个请求更易于理解。

Another Quick Thing About Errors

response流也可以触发error事件,在某些情况下,你需要处理它。

Put It All Together

现在我们已经学会了发送HTTP的响应,让我们串一下。基于上述建立的服务器,我们将用户发送给我们的数据 再返回给用户。我们用JSON.stringify函数将data处理成JSON数据。
const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    // BEGINNING OF NEW STUFF

    response.on('error', (err) => {
      console.error(err);
    });

    response.statusCode = 200;
    response.setHeader('Content-Type', 'application/json');
    // Note: the 2 lines above could be replaced with this next one:
    // response.writeHead(200, {'Content-Type': 'application/json'})

    const responseBody = { headers, method, url, body };

    response.write(JSON.stringify(responseBody));
    response.end();
    // Note: the 2 lines above could be replaced with this next one:
    // response.end(JSON.stringify(responseBody))

    // END OF NEW STUFF
  });
}).listen(8080);

Echo Server Example

让我们简化上述例子,建立一个简单的echo服务器,即将request请求原封不动的返回。我们需要做的就是从request数据流中获得请求数据,然后将数据写进response数据流。

const http = require('http');

http.createServer((request, response) => {
  let body = [];
  request.on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    response.end(body);
  });
}).listen(8080);
让我们改变一下,我们希望只是在以下情况成立时才返回请求数据,其余情况下发送404.代码如下:
const http = require('http');

http.createServer((request, response) => {
  if (request.method === 'POST' && request.url === '/echo') {
    let body = [];
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString();
      response.end(body);
    });
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);

通过上述对url的判断,我们其实在做路由的映射。其他形式的路由简单点的用switch语句实现,复杂的写成一个框架,例如express。如果你在寻找路由的某些东西,尝试一下router

很棒!现在我们来总结一下,记住request对象是可读流ReadableStream  ,response对象是可写数据流WritableStream  。这意味着我们可以使用管道操作这些数据流,使数据流可以从一个操作流向下个操作。

const http = require('http');

http.createServer((request, response) => {
  if (request.method === 'POST' && request.url === '/echo') {
    request.pipe(response);
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);
还没完,正如上文中多次提到的那样,error随时有可能发生,我们需要处理它。为了处理request流的错误,我们将会打印出这些错误,同时发送预示着错误请求的400状态码。在真正的应用程序中,我们希望能检查出错误类型,并发送相应的状态码。你应该去看错误手册。
在响应中,我们将在stdout中打印出错误。
const http = require('http');

http.createServer((request, response) => {
  request.on('error', (err) => {
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error', (err) => {
    console.error(err);
  });
  if (request.method === 'POST' && request.url === '/echo') {
    request.pipe(response);
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);

本篇文章已经涵盖了http请求的基础,你需要学会:

*建立一个http服务器,并监听某个端口

*获得请求头、url、方法、请求数据

*根据request做路由映射

*通过response对象发送 响应头、http状态码、和数据

*在request和response对象中处理错误信息。

最后:
建立一个node静态服务器,将与服务器所在文件共同位置的文件发送到浏览器

const http = require('http')  
var fs = require('fs')
var path = require('path')
http.createServer((req, res) => {
  fs.readFile(path.join(__dirname, 'test.html'), (err, data) => {
    if (err) {
      throw err;
    }
    res.writeHead(200,{"Content-Type":"text/html"});
    res.write(data.toString());
    res.end()
  })
}).listen(4000)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值