let url = require('url');
//一个url路径
let u = 'http://LiMing:xxxx@www.baidu.com:80/abc/index.html?a=1&b=2#hash';
let urlObj = url.parse(u);
console.log(urlObj);
let server = net.createServer(function(socket){
//实现一个parser方法,解析出请求和响应,并模拟Http服务触发request事件
parser(socket,function(req,res){
server.emit('request',req,res);
});
});
server.on('request',function(req,res){
console.log(req.url);
console.log(req.headers);
console.log(req.httpVersion);
console.log(req.method);
req.on('data',function(data){
console.log('req data ',data.toString());
});
req.on('end',function(){
res.end(`
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
hello`) //手写的响应头,不能有空格
});
})
server.on('connection',function(){
console.log('建立连接');
});
server.listen(3000);
接下来我们要实现一个parser方法,读取socket中的内容
function parser(socket,callback){
let buffers = []; // 每次读取的数据放到数组中
let sd = new StringDecoder();//解决乱码问题
functionfn(){
let content = socket.read(); // 默认将请缓存区内容读完,读完后如果还有数据会触发readable事件
buffers.push(content);
let str = sd.write(Buffer.concat(buffers));
console.log('从socket读出的内容 :',str);
}
socket.on('readable',fn);
}
//一行一行解析
function parserHeader(head){
let lines = head.split(/\r\n/);
let start = lines.shift(); //取出第一行
let method = start.split(' ')[0];
let url = start.split(' ')[1];
let httpVersion = start.split(' ')[2].split('/')[1];
let headers = {};
//解析剩余行
lines.forEach(line => {
let row = line.split(': ');
headers[row[0]] = row[1];
});
return {url,method,httpVersion,headers}
}
进一步完善parser,使能触发http的request事件和其中的data事件
function parser(socket,callback){
let buffers = []; // 每次读取的数据放到数组中
let sd = new StringDecoder();
functionfn(){
let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)}
let content = socket.read(); // 默认将请缓存区内容读完,读完后如果还有数据会触发readable事件
buffers.push(content);
let str = sd.write(Buffer.concat(buffers));
console.log('从socket读出的内容 :',str);
//如果读取的内容有两个连续的\r\n,说明请求头已读完
if(str.match(/\r\n\r\n/)){
let result = str.split('\r\n\r\n');
let head = parserHeader(result[0]);//解析请求头
Object.assign(socket,head); //解析的请求头赋给socket
socket.removeListener('readable',fn); // 读完请求头,不再继续读,移除监听
socket.unshift(Buffer.from(result[1]));// 将读取多出的内容塞回流中
callback(socket);//执行callback,触发request事件
}
}
socket.on('readable',fn)
}
let {Readable} = require('stream');
class IncomingMessage extends Readable{ //自定义可读流
_read(){}
}
function parser(socket,callback){
let buffers = [];
let sd = new StringDecoder();
let im = new IncomingMessage();//自定义可读流实例
functionfn(){
//模拟res
let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)}
let content = socket.read();
buffers.push(content);
let str = sd.write(Buffer.concat(buffers));
if(str.match(/\r\n\r\n/)){
let result = str.split('\r\n\r\n');
let head = parserHeader(result[0]);
// im = {...im,...head}
Object.assign(im,head); //将请求头对象给im可读流
socket.removeListener('readable',fn);
socket.unshift(Buffer.from(result[1]));
if(result[1]){ // 有请求体,把数据移到im可读流(data触发多次暂不考虑,认为只有一次)
socket.on('data',function(data){
im.push(data);
im.push(null); //null表示可读流结束,用于触发end事件
callback(im,res);
});
}else{ // 没请求体
im.push(null);
callback(im,res);
}
}
}
socket.on('readable',fn)
}
let net = require('net');
let {StringDecoder} = require('string_decoder');
let {Readable} = require('stream');
class IncomingMessage extends Readable{
_read(){}
}
function parser(socket,callback){
let buffers = [];
let sd = new StringDecoder();
let im = new IncomingMessage();
functionfn(){
let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)}
let content = socket.read();
buffers.push(content);
let str = sd.write(Buffer.concat(buffers));
if(str.match(/\r\n\r\n/)){
let result = str.split('\r\n\r\n');
let head = parserHeader(result[0]);
// im = {...im,...head}
Object.assign(im,head);
socket.removeListener('readable',fn);
socket.unshift(Buffer.from(result[1]));
if(result[1]){
socket.on('data',function(data){
im.push(data);
im.push(null);
callback(im,res);
});
}else{
im.push(null);
callback(im,res);
}
}
}
socket.on('readable',fn)
}
function parserHeader(head){
let lines = head.split(/\r\n/);
let start = lines.shift();
let method = start.split(' ')[0];
let url = start.split(' ')[1];
let httpVersion = start.split(' ')[2].split('/')[1];
let headers = {};
lines.forEach(line => {
let row = line.split(': ');
headers[row[0]] = row[1];
});
return {url,method,httpVersion,headers}
}
let server = net.createServer(function(socket){
parser(socket,function(req,res){
server.emit('request',req,res);
});
});
server.on('request',function(req,res){
console.log(req.url);
console.log(req.headers);
console.log(req.httpVersion);
console.log(req.method);
req.on('data',function(data){
console.log('ok',data.toString());
});
req.on('end',function(){
res.end(`
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
hello`)
});
})
server.on('connection',function(){
console.log('建立连接');
});
server.listen(3000);
URL模块
let url = require('url'); //一个url路径 let u = 'http://LiMing:xxxx@www.baidu.com:80/abc/index.html?a=1&b=2#hash'; let urlObj = url.parse(u); console.log(urlObj);
可以打印出
Url { protocol: 'http:', slashes: true, auth: 'LiMing:xxxx', host: 'www.baidu.com:80', port: '80', hostname: 'www.baidu.com', hash: '#hash', search: '?a=1&b=2', query: 'a=1&b=2', pathname: '/abc/index.html', path: '/abc/index.html?a=1&b=2', href: 'http://LiMing:xxxx@www.baidu.com:80/abc/index.html?a=1&b=2#hash' }
我们常用的有
// 加true 可以将地址中的查询字符串转化成对象 let urlObj = url.parse(u,true); console.log(urlObj.query); // 查询字符串 { a: '1', b: '2' } console.log(urlObj.pathname); // 路径
http模块(基于TCP)
创建http服务,并监听请求可用如下两种方式
let http = require('http'); //继承了net包,所以和tcp用法基本相同 let server = http.createServer(function(req,res){})
或
let server = http.createServer(); server.on('request',function(req,res){})
一些API
let http = require('http'); // 请求头 // > Host: www.baidu.com // > User-Agent: curl/7.46.0 // > Accept: */* // > Content-Length: 11 // > Content-Type: application/x-www-form-urlencoded let server = http.createServer(); // req是请求 是一个可读流 // res是响应 是一个可写流 server.on('request',function(req,res){ let method = req.method; let httpVersion = req.httpVersion; let url = req.url; let headers = req.headers; console.log(method,httpVersion,url,headers); // 如果数据 大于64k data事件可能会触发多次 let buffers = []; req.on('data',function(data){ console.log('data') buffers.push(data); }); req.on('end',function(){ console.log(Buffer.concat(buffers).toString()); // socket.write socket.end res.write('hello'); res.end('world'); }); }); // 监听请求的到来 server.on('connection',function(socket){ console.log('建立连接'); }); server.on('close',function(){ console.log('服务端关闭') }) server.on('error',function(err){ console.log(err); }); server.listen(8080);
测试
执行启动http服务
可以用curl命令发送http请求
也可以直接在浏览器中访问localhost:8080 其中
-v是打印出来的详细信息
-d 'a=1' 是添加请求体 a=1
如果没有请求体不会走on('data'),但还是会触发end事件
req.headers可以打印请求头信息,请求头中的名字都是小写的
由于http是基于tcp的,这里用tcp服务模拟http服务
使用方式如下
let server = net.createServer(function(socket){ //实现一个parser方法,解析出请求和响应,并模拟Http服务触发request事件 parser(socket,function(req,res){ server.emit('request',req,res); }); }); server.on('request',function(req,res){ console.log(req.url); console.log(req.headers); console.log(req.httpVersion); console.log(req.method); req.on('data',function(data){ console.log('req data ',data.toString()); }); req.on('end',function(){ res.end(` HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 5 hello`) //手写的响应头,不能有空格 }); }) server.on('connection',function(){ console.log('建立连接'); }); server.listen(3000);
接下来我们要实现一个parser方法,读取socket中的内容
function parser(socket,callback){ let buffers = []; // 每次读取的数据放到数组中 let sd = new StringDecoder();//解决乱码问题 function fn(){ let content = socket.read(); // 默认将请缓存区内容读完,读完后如果还有数据会触发readable事件 buffers.push(content); let str = sd.write(Buffer.concat(buffers)); console.log('从socket读出的内容 :',str); } socket.on('readable',fn); }
上面parser拿到了socket中传来的内容
启动服务
通过curl命令访问服务
curl -v -d 'a=1' http://localhost:3000/abc?a=1#aaa
打印如下:
建立连接 从socket读出的内容 : POST /abc?a=1 HTTP/1.1 Host: localhost:3000 User-Agent: curl/7.59.0 Accept: */* Content-Length: 5 Content-Type: application/x-www-form-urlencoded 'a=1'
上面打印结果中,空行之上是请求头,实现一个将请求头解析成对象的方法
//一行一行解析 function parserHeader(head){ let lines = head.split(/\r\n/); let start = lines.shift(); //取出第一行 let method = start.split(' ')[0]; let url = start.split(' ')[1]; let httpVersion = start.split(' ')[2].split('/')[1]; let headers = {}; //解析剩余行 lines.forEach(line => { let row = line.split(': '); headers[row[0]] = row[1]; }); return {url,method,httpVersion,headers} }
进一步完善parser,使能触发http的request事件和其中的data事件
function parser(socket,callback){ let buffers = []; // 每次读取的数据放到数组中 let sd = new StringDecoder(); function fn(){ let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)} let content = socket.read(); // 默认将请缓存区内容读完,读完后如果还有数据会触发readable事件 buffers.push(content); let str = sd.write(Buffer.concat(buffers)); console.log('从socket读出的内容 :',str); //如果读取的内容有两个连续的\r\n,说明请求头已读完 if(str.match(/\r\n\r\n/)){ let result = str.split('\r\n\r\n'); let head = parserHeader(result[0]);//解析请求头 Object.assign(socket,head); //解析的请求头赋给socket socket.removeListener('readable',fn); // 读完请求头,不再继续读,移除监听 socket.unshift(Buffer.from(result[1]));// 将读取多出的内容塞回流中 callback(socket);//执行callback,触发request事件 } } socket.on('readable',fn) }
上面代码 1、str.match(/\r\n\r\n/) 当str中包含连续的换行回车时,说明读完请求头了,即result[0]是请求头
2、Object.assign(socket,head); 将解析的请求头对象赋给socket
3、当读出整个请求头内容时,就停止读取,即移除readable事件监听
4、http服务的req可以监听data事件,如果readable将数据都读完了,那么data事件将无法触发,所以要将readable读取出的,除了请求头之外的数据(即result[1]),再添加回socket(socket.unshift)
5、执行callback,就会emit触发request事件
进一步完善parser,触发end事件
let {Readable} = require('stream'); class IncomingMessage extends Readable{ //自定义可读流 _read(){} }
function parser(socket,callback){ let buffers = []; let sd = new StringDecoder(); let im = new IncomingMessage();//自定义可读流实例 function fn(){ //模拟res let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)} let content = socket.read(); buffers.push(content); let str = sd.write(Buffer.concat(buffers)); if(str.match(/\r\n\r\n/)){ let result = str.split('\r\n\r\n'); let head = parserHeader(result[0]); // im = {...im,...head} Object.assign(im,head); //将请求头对象给im可读流 socket.removeListener('readable',fn); socket.unshift(Buffer.from(result[1])); if(result[1]){ // 有请求体,把数据移到im可读流(data触发多次暂不考虑,认为只有一次) socket.on('data',function(data){ im.push(data); im.push(null); //null表示可读流结束,用于触发end事件 callback(im,res); }); }else{ // 没请求体 im.push(null); callback(im,res); } } } socket.on('readable',fn) }
1.为了触发end事件,创建自定义可读流im,当可读流读到末尾,就会触发end
2.这里req即im,当req触发data事件读取到流的末尾,即触发end
3.模拟res,res.end()中手写了一个响应头,在浏览器中访问localhost:3000可以看到输出‘hello’
完整代码如下(tcp服务模拟实现http)
let net = require('net'); let {StringDecoder} = require('string_decoder'); let {Readable} = require('stream'); class IncomingMessage extends Readable{ _read(){} } function parser(socket,callback){ let buffers = []; let sd = new StringDecoder(); let im = new IncomingMessage(); function fn(){ let res = {write:socket.write.bind(socket),end:socket.end.bind(socket)} let content = socket.read(); buffers.push(content); let str = sd.write(Buffer.concat(buffers)); if(str.match(/\r\n\r\n/)){ let result = str.split('\r\n\r\n'); let head = parserHeader(result[0]); // im = {...im,...head} Object.assign(im,head); socket.removeListener('readable',fn); socket.unshift(Buffer.from(result[1])); if(result[1]){ socket.on('data',function(data){ im.push(data); im.push(null); callback(im,res); }); }else{ im.push(null); callback(im,res); } } } socket.on('readable',fn) } function parserHeader(head){ let lines = head.split(/\r\n/); let start = lines.shift(); let method = start.split(' ')[0]; let url = start.split(' ')[1]; let httpVersion = start.split(' ')[2].split('/')[1]; let headers = {}; lines.forEach(line => { let row = line.split(': '); headers[row[0]] = row[1]; }); return {url,method,httpVersion,headers} } let server = net.createServer(function(socket){ parser(socket,function(req,res){ server.emit('request',req,res); }); }); server.on('request',function(req,res){ console.log(req.url); console.log(req.headers); console.log(req.httpVersion); console.log(req.method); req.on('data',function(data){ console.log('ok',data.toString()); }); req.on('end',function(){ res.end(` HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 5 hello`) }); }) server.on('connection',function(){ console.log('建立连接'); }); server.listen(3000);