http模块
- 创建服务器两种方式
// 方式一 http.createServer([requestListener])
/*
* [requestListener(req, res)] 用户请求后回调函数,有两个参数,req请求;res响应
* @return http.Server 返回一个http.Server实例
*/
var server = http.createServer((req, res) => {
res.writeHeader(200,{'Content-Type':'text/plain'});
res.end('hello world');
})
// 方式二 new http.Server()
var server = new http.Server();
server.on('request', (req, res){
res.writeHeader(200,{'Content-Type':'text/plain'});
res.end('hello world');
})
采用上面的方式并不能创建一个完成的服务器,还必须监听对应的端口,比如采用下面这种方式。
// 表示引入http模块
const http = require('http');
// 创建一个http服务
/*
request: 获取url传过来的信息 请求头
response: 像浏览器响应信息 响应头
*/
http.createServer(function(request,response){
// 设置响应头
response.writeHead(200,{'Content-Type': 'text/plain'});
response.write('1111');// 像页面返回的内容
response.write('222');
// 像浏览器输出一句话 并结束响应
response.end('1123122'); //
}).listen(8081);// 端口
为啥要监听端口(listen(8080))?
端口的功能
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。
客户端通常对它所使用的端口号并不关心,只需保证该端口号在本机上是唯一的就可以了。客户端口号又称作临时端口号(即存在时间很短暂)。这是因为它通常只是在用户运行该客户程序时才存在,而服务器则只要主机开着的,其服务就运行。
简单理解:IP就是一个电脑节点的网络物理地址,就像你的家住的那个地址;端口是该计算机逻辑通讯接口,不同的应用程序用不同的端口,就像你家里的各个不同的房间,卧室用来睡觉,餐厅用来吃饭。
理解为 node启动一个服务就是将本机当做了服务器,所以它的ip地址是127.0.0.1 ,其中端口8080端口里面是提供你要的服务。
Url模块
在上面创建的那个服务器里面,页面所有的请求都会执行createServer中传入的方法。但是不同的请求,需要做不同的处理,所以我们需求判断请求路径,以便我们做出不同的响应。
我们可以很简单的获取到请求的url,但有数据提交上来的url是十分复杂和不确定的。很不利于编写业务逻辑。所以我们就要将url拆分成我们能用的数据。node的url模块就是帮助我们对提交上来的url进行解析处理
常用方法:
parse(urlStr,queryString,AnalysisHost)
解析url,返回一个url属性对象
urlStr: 要解析的url地址
queryString: 解析出来的query是字符串还是查询对象,true是对象 false是字符串
AnalysisHost: 是否要解析出来主机名
实例代码
var url = require('url')
var obj = url.parse('http://www.baidu.com/vdsa?ie=utf-8&word=sad',true,true)
console.log(obj);
结果:
Url组成部分:
- protocol:url的通信协议(http/https)
- slashes:如果协议protocol冒号后跟的是两个斜杠字符(/),那么值为true
- auth:URL的用户名与密码部分
- host:url的主机名 “baidu.com”
- port: 端口号
- hostname: hostname是host属性排除端口port之后的小写的主机名部分
- hash:哈希#后面字符串包括#
- search:URL的查询字符串部分,包括开头的问号字符(?)
- query: 不包含问号(?)的search字符串
- pathname:URL的整个路径部分。跟在host后面,截止问号(?)或者哈希字符(#)分隔
- path:由pathname与search组成的串接,不包含hash字符后面的东西
- href:解析后的完整的URL字符串,protocol和host都会被转换成小写。
事件循环和回调函数
我们都知道,不同的请求对应的Content-type是不一样的,比如一个页面的加载html和css的Content-type是不一样的。
文件目录为:
mime.json文件的内容为:
{ ".323":"text/h323" ,
".3gp":"video/3gpp" ,
".asc":"text/plain" ,
".htm":"text/html" ,
".html":"text/html"
.....
}
我们现在通过getTypeFile方法来获取不同文件后缀对应的Content-type。
const http = require('http');
const fs = require('fs');
const path = require('path');
const URL = require('url');
const utils = require('./utils/getType');
const EventEmitter = require('./utils/Listener');
http.createServer(function(request,response){
const pathname = request.url;
let extname = path.extname(URL.parse(pathname).pathname); // 获取文件后缀名
if(pathname !== '/favicon.ico'){
返回对应的页面
response.writeHead(200,{
'Content-Type': `${utils.getTypeFile(extname)};charset='utf-8'`
});
fs.readFile('./static/'+URL.parse(pathname).pathname, (err,data)=> {
response.end(data);
}
})
}else {
response.end()
}
}).listen(8081)
console.log('服务启动了')
getTypeFile方法为:
exports.getTypeFile = function(exname){
const obj = fs.readFileSync('./mime.json').toString();
return JSON.parse(obj)[exname];
}
当getTypeFile是一个同步方法的时候,上面的代码是没有任何问题的。但是如果使用fs.readFile这个异步方法的时候就会出现问题。因为外面得不到返回的值。这里readFildSync是一个同步的方式 ,如果采用异步的方法 readFile 在app.js 如何得到想要值呢 。
一般解决这种问题的有事件循环、和回调函数的解决方式。
回调函数
// app.js
const utils = require('./utils/getType');
...code
utils.getTypeFild(exname,function(str){
response.writeHead(200,{
'Content-Type': `${str};charset='utf-8'`
});
... code
})
// getType.js
exports.getTypeFile = function(exname,callback){
fs.readFile('./mime.json',(err,data) =>{
callback(data.toString);
})
}
通过在给getTypeFile传入一个回调方法,在readFile执行完成后,在执行回调方法。Node 使用了大量的回调函数,Node 所有 API 都支持回调函数
事件驱动
event 模块只提供了一个对象 events.EventEmitter,
核心就是事件触发和事件监听功能,原生使用原理类似于观察者模式。
创建 eventEmitter 对象
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
以下程序绑定事件处理程序:
// 绑定事件及事件的处理程序
eventEmitter.on('eventName', eventHandler);
我们可以通过程序触发事件:
// 触发事件
eventEmitter.emit('eventName');
所以如果采用事件驱动驱动的方式我们的第二种实现方式为:
1. getType.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();// 实例化事件对象
exports.getTypeFile = function(exname){
fs.readFile('./mime.json',(err,data) =>{
EventEmitter.emit('exname', data.toString()); // 发送一个名为exname 的广播
})
}
2. app.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();
...code
if(pathname !== '/favicon.ico'){
// 接受一个名为 exname的广播
EventEmitter.on('exname',(data) =>{
response.writeHead(200,{
'Content-Type': `${data};charset='utf-8'`
});
...code;
})
}
这里有一个错误的地方是,两个文件中都通过new events.EventEmitter()
来生成了一个EventEmitter对象,尽管他们两个的名字一样,但是他们两个是不同的实例。所以会发现在app.js中并没有接受到一个名为 exname的广播。
为了保证是一个EventEmitter对象,我们采用这种方法,将app.js、和getType.js中的
const events= = require('events');
const EventEmitter = new events.EventEmitter();// 实例化事件对象
替换为
const EventEmitter = require('./Listener');
// Listenser.js
const events = require('events');
const EventEmitter = new events.EventEmitter();
module.exports = EventEmitter;
这样两个文件使用的就是同一个EventEmitter
参考文章:
- https://www.runoob.com/nodejs/nodejs-event-loop.html
- https://www.jianshu.com/p/be67d4419392