Node.js基础学习(二) | 青训营笔记

102 阅读8分钟

这是我参与「第四届青训营 」笔记创作活动的第34天

5 网站服务器端开发

5.1 服务端基础概念

  • 网站组成:
    • 客户端:浏览器中运行的部分,HTML CSS JS构建,发送请求
    • 服务器端:服务器中运行部分,存储数据,处理业务逻辑,响应请求

服务器工作.PNG

  • 基本概念:
    • IP地址:互联网设备唯一标识
    • 域名:上网所用的网址,与IP对应
    • 端口:用来区分服务器中电脑提供的不同服务
    • URL: 统一资源定位符,“网址”
      • 组成:传输协议://服务器IP地址:端口(默认80)//资源所在位置标识
  • 开发过程中的客户端和服务端说明:
    • 开发人员电脑:客户端(浏览器)和服务器(Node运行环境)端在一台电脑
    • 如何通过网络访问自己电脑服务器:
      • 本机域名: localhost
      • 本地IP: 127.0.0.1
  • Node网站服务器:提供网站服务的机器。可以接收客户端请求,并且对请求做出响应
    • 服务器搭建:
      • a) 电脑安装Node运行环境
      • b) Node.js创建能够接收请求和响应请求的对象,即创建软件层面上的服务器
    • 服务器访问:
      • 通过IP或者域名找到这台服务器,根据端口区分不同服务

5.2 创建Web服务端

  • 创建步骤:
    • 引用系统模块: http
    • 创建Web服务端: http.creatServer()
    • 为服务器添加事件: 请求事件request, 事件处理函数(req,res)
      • req: 请求对象,存储了请求相关信息,地址 IP等
      • res: 响应对象,使用该对象对请求做出响应 end
    • 监听端口:listen(3000)
    • 输出结果:服务器启动,监听3000端口,请访问localhost:3000...
    • 使用node命令执行创建的js文件, 然后就可以在浏览器中访问服务器了
    // 创建网站服务器模块
    const http = require('http');
    // app对象就是网站服务器对象
    const app = http.createServer();
    // 当客户端有请求来的时候
    app.on('request',(req, res) => {
        res.end('<h2>hello user</h2>');
    })
    // 监听端口 才能提供服务
    app.listen(3000);
    console.log('网站服务器启动成功');
    
  • 访问服务器:localhost:3000

访问服务器成功.png

5.3 HTTP协议

  • 作用:客户端和服务器端沟通的规范
  • HTTP: 超文本传输协议,规定了如何从网站服务器传输超文本到本地浏览器
  • 报文:在HTTP请求和响应过程中传递的数据块,包含数据和附加信息,并且要遵守规定好的格式(以:分隔的键值对)
    • 请求报文
    • 响应报文
  • 请求报文
    • 获取请求方式(req.method)
      • GET: 请求数据(浏览器地址栏输入网址请求, 网页跳转)
      • POST: 发送数据,较安全(表单方式)
          // 根据不同请求方式 响应不同内容
          if(req.method == 'POST'){
              res.end('post');
          }else if(req.method == 'GET'){
              res.end('get');
          }
      
    • 获取请求地址(req.url)
    • 获取请求报文(req.headers['报文键值'])
  • 响应报文
    • HTTP状态码:
      • 200 请求成功
      • 404 请求资源未找到
      • 500 服务器端错误
      • 400 客户端请求语法有问题
    • 内容类型:
      • text/html
      • text/css
      • application/javascript
      • image/jpeg
      • application/json
      // 响应对象res
      res.writeHead(200,{
          // 'content-type':'text/plain' //纯文本
          'content-type':'text/html; charset=utf8' //返回html 可以识别标签; 设置编码类型 可以返回中文
      });
      

5.4 HTTP请求与响应处理

  • 请求参数:请求中将信息通过参数传递给服务器,比如登录操作
    • GET请求参数:
      // 用于处理url地址
      const url  = require('url');
      // url解析方法:返回一个对象,对象属性是url的各个部分
      // 1)要解析的url地址
      // 2)将查询参数解析成对象形式 
      let params = url.parse(req.url,true).query; 
      console.log(params.name);
      console.log(params.age);
      
    • POST请求参数:
      • 放在请求体(请求报文)中传输
      • 获取参数需要用到date事件和end事件
      • 使用querystring系统模块将参数转换为对象格式
  • 路由
    • 概念:路由是指客户端请求地址与服务端程序代码的对应关系,简单来说,就是请求什么,响应什么。一堆判断代码
    • 实现:
    app.on('request', (req, res) => {
        // 4.实现路由功能
        // 1)获取客户端请求方式
        // 2)获取客户端请求地址
        const method = req.method.toLowerCase(); //转换为小写
        const pathname = url.parse(req.url).pathname;
    
        // 响应报文处理
        res.writeHead(200,{
            'context-type':'text/html; charset=uft8'
        });
    
        if (method == 'get') {
            if (pathname == '/' || pathname == '/index') {
                res.end('欢迎来到首页');
            } else if (pathname == '/list') {
                res.end('欢迎来到列表首页');
            } else {
                res.end('您访问的页面不存在');
            }
        } else if(method == 'post'){
    
        }
    });
    
  • 静态资源:
    • 服务器不用处理,直接响应给客户端的资源
    • 静态资源访问:
      • 获取用户请求路径:let pathname = url.parse(req.url).pathname
      • 将用户请求路径转换为服务器的硬盘路径:
        • 1)获取当前文件所在绝对路径:__dirname + 'public'
        • 2)拼接请求路径: let realpath = path.join(__dirname , 'public', pathname)
      • 读取文件,将文件内容响应给客户端
        • 报错: 返回错误404 设置编码格式
        • 正常响应:返回请求内容,需要考虑内容类型,使用第三方模块:mime(npm install mime)
          • 根据请求内容设置请求内容类型: let type = mime.getType(realpath) 'conent-type' : type
  • 动态资源:相同请求地址根据参数不同,得到不同响应资源

5.5 Node.js异步编程

  • 同步API: 只有当前API执行完成后,才能执行下一个API

  • 异步API: 当前API的执行不会阻塞后续代码的执行

    console.log('before');
    setTimeout(function(){
        console.log('last');
    });
    console.log('after');
    

    异步API结果.png

  • 两者区别:

    • 同步API:

      • 1)可以从返回值中拿到API执行的结果
      • 2)执行顺序从上到下
    • 异步API:

      • 1)不能从返回值拿到结果
          function getMsg(){
          setTimeout(function(){
              return{
                  msg: 'hello node.js'
              }
          },2000)
          // return undefined;
      }
      const msg = getMsg();
      console.log(msg);
      

      异步API返回值.png

      • 2)通过回调函数获得返回值:

        • 回调函数:
        function getData(callback){
            callback('123');
        };
        getData(function(n){
            console.log('callback函数被调用了');
            console.log(n);
        });
        

        异步API回调函数.png

        • 上述代码改造:
        function getMsg(callback){
            setTimeout(function(){
                callback({
                    msg: 'hello node.js'
                })
            },2000)
        }
        getMsg(function(data){
            console.log(data);
        });    
        

        异步API返回值2.png

        1. 异步API不会等待API执行完毕后再向下执行代码
        • js代码执行顺序:
          • 先顺序执行完同步代码执行区里面的同步API,
          • 再依次执行异步代码执行区中的异步API,
          • 执行完毕后在回调函数队列中找相应的回调函数放入同步代码执行区执行。
  • Node.js中的异步API:

    • fs.readfile('./demo.text',(err,result) => {});
    • 事件处理函数:app.on('request', (req, res) => {});
    • 问题:当异步API后面的代码执行依赖当前异步API的执行结果,但当前异步API还没有执行,怎么办呢?(需求:依次读取文件1,2,3)
      • 将执行代码放入当前异步API的回调函数中,但这可能会导致回调嵌套过多(回调地狱)问题。
    • 解决异步编程回调地狱问题方法:Promise
      • 作用:将异步编程的执行和结果进行分离
      • 语法:Promise是一个构造函数,需要new创建,参数为一个匿名函数,该函数第一个参数resolve:函数,将异步执行结果作为参数传递出去;第二个参数reject:函数,传递异步执行失败结果
        • 获取成功结果:promise.then(匿名函数),该匿名函数相当于reslove函数
        • 获取失败结果:promise.catch(匿名函数)
        • 允许链式编程
        let promise = Promise((resolve, reject)=>{
            setTimeout(() =>{
                if(true){
                    resolve({name:'张三'});
                }else{
                    reject('失败了');
                }
            },2000);
        });
        promise.then(result => console.log(result);) //链式编程 末尾不加分号
            .catch(error => console.log(error);)
        
      • 解决回调地狱问题:
        • 首先,确定由几个异步API,由几个定义几个promise
        • 其次,将promise放入函数中,方便后续调用,函数直接返回promise
        • 最后,使用.then()方法分离结果,上一个then()中可以然后下一个then()的promise,来实现链式编程
        
        // 改造回调地狱:
        function p1(){
            return new Promise((resolve,reject) =>{
                fs.readFile('./1.txt','utf-8', (err,result) =>{
                    resolve(result);
                });
            });
        };
        function p2() {
            return  new Promise((resolve,reject) =>{
                fs.readFile('./2.txt','utf-8', (err,result) =>{
                    resolve(result);
                });
            });
        };
        
        p1().then(r1 =>{
            console.log(r1);
            return p2(); // 返回的对象是下一个.then的promise
        })
        .then(r2 =>{
            console.log(r2);
        })
        
  • 异步函数:es7 解决Promise实现臃肿问题

    • 作用:异步编程的终极解决方案。将异步代码写成同步的形式,不再有回调函数的嵌套
    • 语法:async 关键字:
      1. 加关键字 async 定义异步函数
      2. 异步函数默认返回值是promise对象
      3. 使用return 关键字返回结果 结果包裹在promise对象中,return方法代替resolve方法
      4. 使用throw 关键字抛出错误
      5. 异步函数外面链式调用then()方法可以获得异步函数执行的结果,catch()犯法获得异步函数执行的错误信息
      6. 使用await关键字,可以将异步代码写成同步形式
    • await 关键字:
      1. 只能出现在异步函数中
      2. await promise 可以暂停异步函数的执行, 等待promise对象返回结果后再向下执行
      3. await后面只能写promise对象,其他类型API不可以
      async function p1(){
          return 'p1';
      }
      async function p2(){
          return 'p2';
      }
      async function run(){
          let r1 = await p1() // 直到p1()有执行结果才往下执行,另外可以通过返回值获得执行结果
          let r2 = await p2()
          console.log(r1)
          console.log(r2)
      }
      run();
      
    • 异步函数应用:使用promisify改造现有异步API
    const fs = require('fs');
    // promisify方法:改造Node.js中现有的异步API返回值为promise对象,从而支持异步函数
    const promisify = require('util').promisify
    // promisify方法:将需要改造的异步API作为参数传递给该方法
    const readFile = promisify(fs.readFile);
    
    async function run(){
        let r1 = await readFile('./1.txt','utf-8');
        let r2 = await readFile('./2.txt','utf-8');
        console.log(r1);
        console.log(r2);
    }
    run();