服务器知识基础(基于nodeJS)

初识服务器笔记

node网站服务器

  • 能够提供网站的访问服务,能够接收客户端请求并对请求做出相应的相应
  • IPInternet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据报传输服务
  • 域名(英语:Domain Name),又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)。由于IP地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过网域名称系统(DNS,Domain Name System)来将域名和IP地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的IP地址数串。
  • 端口是英文port的意译,可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。电话使用RJ11插口也属于物理端口的范畴。
  • URL统一资源定位系统(uniform resource locator;URL)是因特网的万维网服务程序上用于指定信息位置的表示方法。

创建web服务器(使用http系统模块)

  1. const http = require('http') 引入模块http
  2. 创建web服务器对象 const app = http.createServer()
  3. 接收并响应请求 app.on('request', callback)
    • 事件处理回调函数中第一个参数为请求对象,第二个参数为响应对象
  4. 监听端口 app.listen(3000, [callback])

请求头和响应头

  • HTTP请求头提供了关于请求,响应或者其他的发送实体的信息。HTTP的头信息包括通用头、请求头、响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。
    • 通用头标:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关联。
    • 请求头标:允许客户端传递关于自身的信息和希望的响应形式。
    • 响应头标:服务器和于传递自身信息的响应。
    • 实体头标:定义被传送资源的信息。即可用于请求,也可用于响应。

HTTP Request Header 请求头

Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的Email From: [email protected]
Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives/71.html
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5
Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

HTTP Responses Header 响应头

Header 解释 示例
Accept-Ranges 表明服务器是否支持指定范围请求及哪种类型的分段请求 Accept-Ranges: bytes
Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12
Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD
Cache-Control 告诉所有的缓存机制是否可以缓存及哪种类型 Cache-Control: no-cache
Content-Encoding web服务器支持的返回内容压缩编码类型。 Content-Encoding: gzip
Content-Language 响应体的语言 Content-Language: en,zh
Content-Length 响应体的长度 Content-Length: 348
Content-Location 请求资源可替代的备用的另一地址 Content-Location: /index.htm
Content-MD5 返回资源的MD5校验值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022
Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8
Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag 请求变量的实体标签的当前值 ETag: “737060cd8c284d8af7ad3082f209582d”
Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified 请求资源的最后修改时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 Location: http://www.zcmhi.com/archives/94.html
Pragma 包括实现特定的指令,它可应用到响应链上的任何接收方 Pragma: no-cache
Proxy-Authenticate 它指出认证方案和可应用到代理的该URL上的参数 Proxy-Authenticate: Basic
refresh 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) Refresh: 5; url=http://www.zcmhi.com/archives/94.html
Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120
Server web服务器软件名称 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer 指出头域在分块传输编码的尾部存在 Trailer: Max-Forwards
Transfer-Encoding 文件传输编码 Transfer-Encoding:chunked
Vary 告诉下游代理是使用缓存响应还是从原始服务器请求 Vary: *
Via 告知代理客户端响应是通过哪里发送的 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 警告实体可能存在的问题 Warning: 199 Miscellaneous warning
WWW-Authenticate 表明客户端请求实体应该使用的授权方案 WWW-Authenticate: Basic

请求方式

  • w3school中的解释

    • GET - 从指定的资源请求数据。
      • GET 请求可被缓存
      • GET 请求保留在浏览器历史记录中
      • GET 请求可被收藏为书签
      • GET 请求不应在处理敏感数据时使用
      • GET 请求有长度限制
      • GET 请求只应当用于取回数据
    • POST - 向指定的资源提交要被处理的数据
      • POST 请求不会被缓存
      • POST 请求不会保留在浏览器历史记录中
      • POST 不能被收藏为书签
      • POST 请求对数据长度没有要求
  • 比较 GET 与 POST

    下面的表格比较了两种 HTTP 方法:GET 和 POST。

    GET POST
    后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
    书签 可收藏为书签 不可收藏为书签
    缓存 能被缓存 不能缓存
    编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
    历史 参数保留在浏览器历史中。 参数不会保存在浏览器历史中。
    对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
    对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
    安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
    可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。
  • 在http模块中获取请求方式 req.method (请求对象中的method属性中)

  • 获取请求地址 req.url (请求对象中的url属性中)

  • 获取到请求头信息 req.headers (请求对象中的headers属性中),获取到的是一个对象,若仅需要获取其中的某项信息可以使用 req.headers['XXX']

  • 写入响应头状态码 res.writeHead(200)

  • 设置响应内类型为html文本,编码格式为utf-8 res.writeHead(200, { 'content-type': 'text/html;charset=utf-8' })

请求参数

  1. get请求参数存在于请求地址url中格式为 www.XXX.com?XXX=XXX&YYY=YYY&····

    • 为方便处理get请求参数,node中提供了一个名为url的模块 const url = require('url')

    • 该模块中有一个解析url的方法 url.parse(XXX) ,该方法会将参数解析为一个存储了url中各个部分的对象

      Url {
        protocol: null,
        slashes: null,
        auth: null,
        host: null,
        port: null,
        hostname: null,
        hash: null,
        search: '?name=1213&password=31344',
        query: 'name=1213&password=31344',
        pathname: '/',
        path: '/?name=1213&password=31344',
        href: '/?name=1213&password=31344'
      }
      
    • url.parse(XXX, true) 方法中传入第二个参数(布尔值,是否将请求参数解析为对象格式)true将请求参数解析为对象格式

      Url {
        protocol: null,
        slashes: null,
        auth: null,
        host: null,
        port: null,
        hostname: null,
        hash: null,
        search: '?name=1213&password=31344',
        query: [Object: null prototype] { name: '1213', password: '31344' },
        pathname: '/',
        path: '/?name=1213&password=31344',
        href: '/?name=1213&password=31344'
      }
      
  2. post请求参数

    • post请求参数时通过事件的形式接收的(data事件,和end事件)

      • 由于post请求参数大小理论上时不受限制的所以在接收时可以分多次接收,接收时会不断的触发data事件,当接收完成时触发end事件
      app.on
      (
          'request',
          function(req,res)
          {
              let postParams = '';
      
              req.on
              (
                  'data',
                  function(params)
                  {
                      postParams += params;
                  }
              )
      
              req.on
              (
                  'end',
                  function()
                  {
                      console.log(postParams);
                  }
              )
      
              res.end('ok');
          }
      )
      
    • 使用querystring模块处理post参数字符串 const querystring = require('querystring ')

      • 使用该模块下的 querystring.parse(xxx) 方法来处理指定的字符串,返回值为一个对象
      [Object: null prototype] {
        username: '15554545521',
        userpassword: '213123123'
      }
      

路由

  1. 路由是指客户端请求地址与服务器段程序代码的对应关系。

    app.on
    (
        'request',
        function(req,res)
        {
            const method = req.method;
            const pathname = url.parse(req.url).pathname;
    
            if(method == 'GET')
            {
                if(pathname == '/' || pathname == '/index')
                {
                    res.end('index');
                }
                else if(pathname == '/list')
                {
                    res.end('list');
                }
                else
                {
                    res.end('404');
                }
            }
            else if(method = 'POST')
            {
    			····
            }
    
        }
    )
    

静态资源访问

app.on
(
    'request',
    function(req,res)
    {
        const pathname = url.parse(req.url).pathname;
        const realPath = path.join(__dirname, pathname);
		const fileType = mime.getType(realPath);
        fs.readFile
        (
            realPath,
            function(error, result)
            {
                if(error)
                {
                    res.end('404');
                    return;
                }
                res.writeHead(200, { 'content-type': FileType });
                res.end(result);
            }
        )
    }
)
  • 在响应文件的时候,由于文件的类型不同,在响应的时候可能会出现乱码的情况,这里使用mime模块来解决这个问题

nodeJS异步编程

  1. 同步API:只有当前的API执行完成后,才能执行下一个API
  2. 异步API:当前代码的执行不会阻塞之后代码的执行
  • 对比代码

    function fun1(x,y)
    {
        return x+y;
    }
    console.log(fun1(1,2)); // 输出3
    
    function fun2(x,y)
    {
        setTimeout( () => { return x+y; }, 2000 );
    }
    console.log(fun2(1,2)); // 输出undefined
    
  • 回调函数

    function fun3(callback)
    {
        setTimeout( function(){ callback('回调函数内部的信息!') }, 2000 );
    }
    
    function funx(str)
    {
        console.log(str);
    }
    
    fun3(funx); // 输出内容为:'回调函数内部的信息!'
    
  • nodejs在执行代码的时候会按照从上到下的顺序依次执行代码,遇到同步代码放置到同步代码执行区执行,遇到异步代码放置到异步代码执行区,当同步代码执行完毕后,执行异步代码执行区域中按照顺序执行异步代码,并放置到回调队列中。之后在回调队列中找寻符合条件的代码放置到同步代码执行区执行。

  • 回调问题

    • 当我们需要依次读取某些指定文件的时候需要使用回调函数的方式进行读取,并在一个文件读取完成后再进行下一个文件的读取,但是会出现回调地狱问题

      const fs = require('fs');
      
      fs.readFile
      (
          './1.txt',
          'utf-8',
          function(error, rusult1)
          {
              fs.readFile
              (
                  './2.txt',
                  'utf-8',
                  function(error, rusult2)
                  {
                      fs.readFile
                      (
                          './3.txt',
                          'utf-8',
                          function(error, rusult3)
                          {
                              console.log(rusult1,rusult2,rusult3);
                          }
                      )
                  }
              )
          }
      )
      
    • promise是一个构造函数,可用来解决 回调地狱 问题

      var promise = new Promise
      (
          function(resolve, reject)
          {
              fs.readFile
              (
                  './12.txt',
                  'utf-8',
                  (error, result) => { if(error){ reject(error); }else{ resolve(result); 
              )
          }
      )
      
      promise.then( (result) => { console.log(result); } ).catch( (error) => { console.log(error); } );
      
      • 构建promise实例时可以在new Promise()中传入一个匿名函数,内部就是需要处理的异步函数内容。注意,该匿名函数内部有两个参数也就是resolve和reject,实际是两个函数分别对应实例对象中的then和catch方法,也就是处理抛出的结果或者接收抛出的错误

      • 解决回调地狱问题

        function p1()
        {
            return new Promise
            (
                function(resolve, reject)
                {
                    fs.readFile
                    (
                        './1.txt',
                        'utf-8',
                        (error, result) => { resolve(result); }
                    )
                }
            )
        }
        
        function p2()
        {
            return new Promise
            (
                function(resolve, reject)
                {
                    fs.readFile
                    (
                        './2.txt',
                        'utf-8',
                        (error, result) => { resolve(result); }
                    )
                }
            )
        }
        
        
        function p3()
        {
            return new Promise
            (
                function(resolve, reject)
                {
                    fs.readFile
                    (
                        './3.txt',
                        'utf-8',
                        (error, result) => { resolve(result); }
                    )
                }
            )
        }
        
        
        p1().then( (result1) => { console.log(result1);return p2(); } )
            .then( (result2) => { console.log(result2);return p3(); } )
            .then( (result3) => { console.log(result3); } );
        
    • 更好的解决方式(异步函数

      • 异步函数的声明 async function XXX(){···} (也就是在普通函数前面加上async关键字)
      • 异步函数返回值为promise对象
      • 在异步函数中使用返回值来抛出结果,使用throw关键字来抛出异常
      • 当抛出错误后,其后的代码将不会继续执行
      • await关键字仅能在异步函数中使用,其作用:只有当当前的异步函数返回结果时代码才可以继续向下执行,确保了异步函数的运行顺序
    • 使用util模块中的promisify方法改造现有异步API使之返回一个promise对象,从而去支持异步函数的语法,注意改造后的方法会以返回值的方式抛出

      const fs = require('fs');
      const promisify = require('util').promisify;
      const readFile = promisify(fs.readFile);
      
      async function getFile()
      {
          let r1 = await readFile('./1.txt', 'utf-8');
          let r2 = await readFile('./2.txt', 'utf-8');
          let r3 = await readFile('./3.txt', 'utf-8');
          console.log(r1);
          console.log(r2);
          console.log(r3);
      }
      
      getFile();
      

你可能感兴趣的:(服务器知识基础(基于nodeJS))