奇舞-服务器与http基础-笔记

网络分层模型
这里写图片描述

web协议栈

这里写图片描述

Internet Protocol (IP)

IP 地址、Mac 地址

这里写图片描述

URI 和 URL

URI:统一资源标识符
URL:统一资源定位符

这里写图片描述

简单的HTTP 请求和相应

const net = require('net');

let responseDataTpl = `HTTP/1.1 200 OK
Connection:keep-alive
Date: ${new Date()}
Content-Length: 12
Content-Type: text/plain

Hello world!
`;


let server = net.createServer((socket) => {

  socket.setKeepAlive(true, 60000);

  socket.write(responseDataTpl);
  socket.pipe(socket);

  socket.on('data', function(data){
    console.log('DATA ' + socket.remoteAddress + ': ' + data);
    //socket.end('goodbye\n');
  });

  socket.on('close', function(){
    console.log('connection closed, goodbye!\n\n\n');
  });
}).on('error', (err) => {
  // handle errors here
  throw err;
});

server.listen({
  host: 'localhost',
  port: 9999,
  exclusive: true
}, ()=>{
  console.log('opened server on', server.address());

HTML 资源和gzip

const net = require('net');
const zlib = require('zlib');

let responseData = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>
`;



let server = net.createServer((socket) => {

  socket.setKeepAlive(true, 60000);

  zlib.gzip(new Buffer(responseData), function(err, content){

    let responseDataTpl = `HTTP/1.1 200 OK
Connection: keep-alive
Date: ${new Date()}
Content-Length: ${content.length}
Content-Type: text/html
Content-encoding: gzip

`;

    socket.write(responseDataTpl);
    socket.write(content, 'binary');

    socket.pipe(socket);
  });


  socket.on('data', function(data){
    console.log('DATA ' + socket.remoteAddress + ': ' + data);
  });

  socket.on('close', function(){
    console.log('connection closed, goodbye!\n\n\n');
  });
}).on('error', (err) => {
  // handle errors here
  throw err;
});

server.listen({
  host: '0.0.0.0',
  port: 10080,
  exclusive: true
}, ()=>{
  console.log('opened server on', server.address());
});

使用http 模块

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('hello world');
})

server.on('clientError', (err, socket) => {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})

server.listen(10080)

Stream

这里写图片描述

http 与资源流

const http = require('http');
const url = require('url');
const fs = require("fs");

const server = http.createServer((req, res) => {
  let srvUrl = url.parse(`http://${req.url}`);
  let path = srvUrl.path;
  if(path === '/') path = '/index.html';

  let resPath = '.' + path; 

  if(!fs.existsSync(resPath)){
    res.writeHead(404, {'Content-Type': 'text/html'});
    return res.end('<h1>404 Not Found</h1>');
  }

  let resStream = fs.createReadStream(resPath);
  res.writeHead(200, {'Content-Type': 'text/html'});
  resStream.pipe(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

HTTP状态吗

这里写图片描述

常用状态吗

  • 200
  • 301
  • 302
  • 304
  • 403
  • 500

内容的格式

MIME(Multipurpose Internet Mail Extensions)

const http = require('http');
const url = require('url');
const fs = require("fs");

function getMimeType(res){
  const EXT_MIME_TYPES = {
    'default': 'text/html',
    '.js': 'text/javascript',
    '.css': 'text/css',
    '.json': 'text/json',

    '.jpeg': 'image/jpeg',
    '.jpg': 'image/jpg',
    '.png': 'image/png',

    //...
  }

  let path = require('path');
  let mime_type = EXT_MIME_TYPES[path.extname(res)] || EXT_MIME_TYPES['default'];
  return mime_type;
}

const server = http.createServer((req, res) => {
  let srvUrl = url.parse(`http://${req.url}`);
  let path = srvUrl.path;
  if(path === '/') path = '/index.html';

  let resPath = '.' + path; 

  if(!fs.existsSync(resPath)){
    res.writeHead(404, {'Content-Type': 'text/html'});
    return res.end('<h1>404 Not Found</h1>');
  }

  let resStream = fs.createReadStream(resPath);
  res.writeHead(200, {'Content-Type': getMimeType(resPath)});
  resStream.pipe(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

响应内容协商

const http = require('http');
const json2html = require('node-json2html');

const server = http.createServer((req, res) => {
  let responseData = {
    name: 'akria',
    birthday: '1981-12-29'
  };

  let accept = req.headers['accept'];

  if(accept.indexOf('text/json') >= 0){ //不严格的判断
    res.writeHead(200, {'Content-Type': 'text/json'});
    res.end(JSON.stringify(responseData));
  }else{
    res.writeHead(200, {'Content-Type': 'text/html'});

    let transform = {'tag': 'div', 'html': '${name} : ${birthday}'};
    res.end(json2html.transform(responseData, transform));
  }
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

HTTP Verbs

这里写图片描述

表单数据POST

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method == 'POST') {
    let body = '';

    req.on('data', function (data) {
        body += data;

        // Too much POST data, kill the connection!
        // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
        if (body.length > 1e6)
            req.connection.destroy();
    });

    req.on('end', function () {
        console.log(body);
    });
    res.writeHead(200, {'Content-Type': 'text/json'});
    res.end('{"err":"","state":"success"}');
  }else{
    res.writeHead(405, {'Content-Type': 'text/html'});
    res.end('<h1>Method not allowed</h1>');
  }
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

表单数据POST + 使用流

const http = require('http');
const through = require('through2');

const server = http.createServer((req, res) => {
  if (req.method == 'POST') {
      let body = '';

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

      req.pipe(through.obj((content, encode, done) => {
        if(encode === 'buffer'){
          content = {
            srcData: content.toString('utf-8'),
            err: '',
            state: 'success'
          };
          encode = 'utf-8';
        }
        done(null, JSON.stringify(content), encode)
      })).pipe(res);
  }else{
    res.writeHead(405, {'Content-Type': 'text/html'});
    res.end('<h1>Method not allowed</h1>');
  }
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

Cache Control

  1. 又称强缓存,普通刷新会忽略它,但并不会清除它,需要强制刷新
  2. 浏览器强制刷新,请求会带上 Cache-Control: no-cache 和 Pragma:no-cache 头
const http = require('http');
const url = require('url');
const fs = require("fs");

function getMimeType(res){
  const EXT_MIME_TYPES = {
    'default': 'text/html',
    '.js': 'text/javascript',
    '.css': 'text/css',
    '.json': 'text/json',

    '.jpeg': 'image/jpeg',
    '.jpg': 'image/jpg',
    '.png': 'image/png',

    //...
  }

  let path = require('path');
  let mime_type = EXT_MIME_TYPES[path.extname(res)] || EXT_MIME_TYPES['default'];
  return mime_type;
}

const server = http.createServer((req, res) => {
  let srvUrl = url.parse(`http://${req.url}`);
  let path = srvUrl.path;
  if(path === '/') path = '/index.html';

  let resPath = '.' + path; 

  if(!fs.existsSync(resPath)){
    res.writeHead(404, {'Content-Type': 'text/html'});
    return res.end('<h1>404 Not Found</h1>');
  }

  let resStream = fs.createReadStream(resPath);

  res.writeHead(200, {
        'Content-Type': getMimeType(resPath),
        'Cache-Control': 'max-age=86400'
    });

  resStream.pipe(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

协商缓存

  1. 又称若缓存,普通刷新会启用弱缓存,忽略强缓存
  2. 只有从地址栏或收藏夹输入地址,通过链接引用资源的情况下,浏览器才会启用强缓存
  3. 这也是为什么有时候我们更新一张图片,一个js文件,页面内容依旧是旧的, 但是直接浏览器访问那个图片或者文件,看到的内容确实新的。
const http = require('http');
const url = require('url');
const fs = require("fs");

function getMimeType(res){
  const EXT_MIME_TYPES = {
    'default': 'text/html',
    '.js': 'text/javascript',
    '.css': 'text/css',
    '.json': 'text/json',

    '.jpeg': 'image/jpeg',
    '.jpg': 'image/jpg',
    '.png': 'image/png',

    //...
  }

  let path = require('path');
  let mime_type = EXT_MIME_TYPES[path.extname(res)] || EXT_MIME_TYPES['default'];
  return mime_type;
}

const server = http.createServer((req, res) => {
  let srvUrl = url.parse(`http://${req.url}`);
  let path = srvUrl.path;
  if(path === '/') path = '/index.html';

  let resPath = '.' + path; 

  let lastModified = req.headers['if-modified-since'];

  if(lastModified && Date.now() - new Date(lastModified) < 86400000){
    res.writeHead(304, {
        'Content-Type': getMimeType(resPath),
        'Last-Modified': new Date(lastModified),
        'Expires': new Date(new Date(lastModified) + 86400000)
    });
    res.end();
  }else{

    if(!fs.existsSync(resPath)){
      res.writeHead(404, {'Content-Type': 'text/html'});
      return res.end('<h1>404 Not Found</h1>');
    }

    let resStream = fs.createReadStream(resPath);
    res.writeHead(200, {
        'Content-Type': getMimeType(resPath),
        'Last-Modified': new Date(),
        'Expires': new Date(Date.now() + 86400000)
    });

    resStream.pipe(res);
  }
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

协商缓存: etag

const http = require('http');
const url = require('url');
const fs = require("fs");
const checksum = require('checksum');

function getMimeType(res){
  const EXT_MIME_TYPES = {
    'default': 'text/html',
    '.js': 'text/javascript',
    '.css': 'text/css',
    '.json': 'text/json',

    '.jpeg': 'image/jpeg',
    '.jpg': 'image/jpg',
    '.png': 'image/png',

    //...
  }

  let path = require('path');
  let mime_type = EXT_MIME_TYPES[path.extname(res)] || EXT_MIME_TYPES['default'];
  return mime_type;
}

const server = http.createServer((req, res) => {
  let srvUrl = url.parse(`http://${req.url}`);
  let path = srvUrl.path;
  if(path === '/') path = '/index.html';

  let resPath = '.' + path; 


  if(!fs.existsSync(resPath)){
    res.writeHead(404, {'Content-Type': 'text/html'});
    return res.end('<h1>404 Not Found</h1>');
  }
  checksum.file(resPath, (err, sum) => {
    let resStream = fs.createReadStream(resPath);
    sum = '"' + sum + '"'; //etag 要加双引号

    if(req.headers['if-none-match'] === sum){
      res.writeHead(304, {
        'Content-Type': getMimeType(resPath),
        'etag': sum  
      });
      res.end();
    }else{
      res.writeHead(200, {
          'Content-Type': getMimeType(resPath),
          'etag': sum  
      });

      resStream.pipe(res);
    }
  });
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(10080);

HTTP Upgrade(WebSocket)

const net = require('net');
const http = require('http');
const crypto = require('crypto');

const content = `
<h1>
  <a href="https://developer.mozilla.org/zh-CN/docs/WebSockets/Writing_WebSocket_servers">
    WebSocket is HTTP Upgrade
  </a>
</h1>
<script>
var ws = new WebSocket('ws://127.0.0.1:10080');
ws.onmessage = function(evt){
  console.log('received: ' + evt.data);
}
ws.onopen = function(){
  console.log('opened');
  ws.send('hello~');
}
</script>
`;

class WSServer{
  constructor(){
    const httpServer = new http.Server();

    httpServer.addListener("connection", function(){
      // requests_recv++;
    });

    httpServer.addListener("request", function(req, res){
      res.writeHead(200, {"Content-Type": "text/html"});
      res.write(content);
      res.end();
    });

    httpServer.addListener("upgrade", function(req, socket, upgradeHead){
      let secWebSocketAccept = req.headers['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
      let sha1 = crypto.createHash('sha1');

      sha1.update(secWebSocketAccept);
      secWebSocketAccept = sha1.digest('base64');

      let serverHandshake = `HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ${secWebSocketAccept}

`;

      socket.write(serverHandshake);

//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-------+-+-------------+-------------------------------+
// |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
// |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
// |N|V|V|V|       |S|             |   (if payload len==126/127)   |
// | |1|2|3|       |K|             |                               |
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
// |     Extended payload length continued, if payload len == 127  |
// + - - - - - - - - - - - - - - - +-------------------------------+
// |                               |Masking-key, if MASK set to 1  |
// +-------------------------------+-------------------------------+
// | Masking-key (continued)       |          Payload Data         |
// +-------------------------------- - - - - - - - - - - - - - - - +
// :                     Payload Data continued ...                :
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// |                     Payload Data continued ...                |
// +---------------------------------------------------------------+

      socket.on('data', function(buffer){
        let dataArr = new Uint8Array(buffer);
        let payload_len = (dataArr[1] & 0x7f);

        let MASK = dataArr.slice(2, 6);
        let ENCODED = dataArr.slice(6, 6 + payload_len);
        let DECODED = [];

        for (let i = 0; i < ENCODED.length; i++) {
            DECODED[i] = ENCODED[i] ^ MASK[i % 4];
        }
        console.log('server received: ' + String.fromCharCode(...DECODED));

        let sendDataArr = [];
        sendDataArr[0] = 0x81;
        sendDataArr[1] = 0x2;
        sendDataArr[2] = 'o'.charCodeAt(0);
        sendDataArr[3] = 'k'.charCodeAt(0);

        socket.write(new Buffer(sendDataArr));
      });
    }); 

    this.httpServer = httpServer;   
  }
  listen(port){
    this.httpServer.listen(port);
  }
}


var server = new WSServer();
server.listen(10080);

Node.js 常用Web服务器

  1. connect
  2. express
  3. thinkjs

    实战 :使用thinkJS 实现匿名聊天室

  4. 安装thinkJS

  5. 创建项目
  6. 安装依赖
  7. 配置端口
  8. 启动项目
  9. 聊天室主页面
  10. 拉取全部聊天记录
  11. 发送消息
  12. 推送消息更新

总结

本课学习了什么?

  1. TCP和HTTP协议
  2. 服务端开发基础

课后练习

完善匿名聊天室 , 增加你认为有用/有趣的功能
应用上之前所学的各节课程中所介绍的内容
尝试融会贯通

代码提交到github

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值