03 请求响应原理及HTTP协议

1. 服务器端基础概念

1.1 网站的组成

网站应用程序主要分为两大部分:客户端和服务器端。

客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使
用HTML、CSS、JavaScript构建。

服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。

1.2 Node网站服务器

能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应。

1.3 IP地址

互联网中设备的唯一标识。

IP是Internet Protocol Address的简写,代表互联网协议地址.

1.4 域名

由于IP地址难于记忆,所以产生了域名的概念,所谓域名就是平时上网所使用的网址。

http://www.itheima.com => http://124.165.219.100/

虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器。

1.5 端口

端口是计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。

1.6 URL

统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL。

url的组成

传输协议://服务器IP或域名:端口/资源所在位置标识

http://www.baidu.cn/news/20181018/09152238514.html

http:超文本传输协议,提供了一种发布和接收HTML页面的方法。

1.7 开发过程中客户端和服务器端说明

在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。

客户端 (浏览器)

服务器端(Node)

本机域名:localhost
本地IP :127.0.0.1

2.创建web服务器

浏览器输入localhost:3000 访问

  // 引用系统模块
 const http = require('http');
  // 创建web服务器
 const app = http.createServer();
  // 当客户端发送请求的时候
 app.on('request', (req, res) => {
        //  响应
       res.end('<h1>hi, user</h1>');
 });
  // 监听3000端口
 app.listen(3000);
 console.log('服务器已启动,监听3000端口,请访问 localhost:3000')

3.HTTP协议

3.1 HTTP协议概念

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。

3.2 报文

在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。

3.3 请求报文

  1. 请求方式(request method)

GET 请求数据

POST 发送数据

 app.on('request', (req, res) => {
     req.headers  // 获取请求报文
     req.url      // 获取请求地址
     req.method   // 获取请求方法
 });

演示

// 创建网站服务器的模块
const http = require('http')

// app 对象就是网站服务对象
const app = http.createServer()

// 当客户端有请求来的时候
app.on('request', (req, res) => {

    // 获取请求报文信息
    // req.headers
    console.log(req.headers);
    console.log(req.headers['accept']);

    // 获取请求地址
    // req.url
    // console.log(req.url);
    if (req.url == '/index' || '/') {
        res.end('index')
    } else if (req.url == '/list') {
        res.end('list')
    } else {
        res.end('not found')
    }

    // 获取请求方式
    // req.method

    if (req.method == "POST") {
        res.end('post')
    } else if (req.method == "GET") {
        res.end('get')

    }

})

// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");

3.4 响应报文

1.HTTP状态码

200 请求成功
404 请求的资源没有被找到
500 服务器端错误
400 客户端请求有语法错误

2.内容类型

text/html
text/css
application/javascript
image/jpeg
application/json

 app.on('request', (req, res) => {
     // 设置响应报文
     res.writeHead(200, {         '
	Content-Type': 'text/html;charset=utf8‘
     });
 });

4. HTTP请求与响应处理

4.1 请求参数

客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。

4.2 GET请求参数

  • 参数被放在浏览器地址中 http://localhost:3000/?name=zhangsan&age=20

  • 参数获取需要借助系统模块url,url模块用来处理url地址

    // 创建网站服务器的模块
    const http = require('http')

    // 处理url地址
    const url = require('url')

    // app 对象就是网站服务对象
    const app = http.createServer()

    // 当客户端有请求来的时候
    app.on('request', (req, res) => {
    // 设置相应报文
    res.writeHead(200, {
    // 返回类型
    'content-type': 'text/html;charset=utf8' // 纯文本 设置中文编码
    })

      // url解析
      console.log('url   ' + req.url);
      // 第一个参数解析URl,第二个参数查询参数转换为对象形式保存
      let { query, pathname } = url.parse(req.url, true)
      console.log(query.name);
      console.log(query.age);
    
      // 获取请求地址
      // req.url
      // console.log(req.url);
      if (pathname == '/index' || pathname == '/') {
          res.end('<h2>来到index</h2>')
      } else if (pathname == '/list') {
          res.end('list')
      } else {
          res.end('not found')
      }
    
      // 获取请求方式
      // req.method
    
      if (req.method == "POST") {
          res.end('post')
      } else if (req.method == "GET") {
          res.end('get')
      }
    

    })

    // 监听端口
    app.listen(8080)
    console.log("网站服务器启动成功");

4.3 POST请求参数

  • 参数被放置在请求体中进行传输

  • 获取POST参数需要使用data事件和end事件

  • 使用querystring系统模块将参数转换为对象格式

// 创建网站服务器的模块
const http = require('http')

// app 对象就是网站服务对象
const app = http.createServer()

// 处理请求参数模块
const querystring = require('querystring')

html文件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- method:指定当前表单提交方式 -->
    <!-- action: 指定当前表单提交的地址 -->
    <form action="http://localhost:8080" method="post">
        <input type="text" name="username" id="">
        <input type="password" name="password" id="">
        <input type="submit" name="" id=""></form>
</body>

</html>

js文件

// 当客户端有请求来的时候
app.on('request', (req, res) => {
    // post参数是通过事件的方式接收
    // data 请求参数传递的时候触发data事件
    // end 请求参数传递完成的时候触发end事件

    let postParams = ''

    // params:当前传递过来的参数
    req.on('data', params => {
        postParams += params
    })
    req.on('end', () => {
        // 参数转换字符串对象
        console.log(querystring.parse(postParams));
    })

    // 给客户端一个响应内容
    res.end('ok')
})

// 监听端口
app.listen(8080)
console.log("网站服务器启动成功");

4.4 路由

http://localhost:3000/index
http://localhost:3000/login
路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。

4.5 静态资源

服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件。

4.6 动态资源

相同的请求地址不同的响应资源,这种资源就是动态资源。

// 1.创建网站服务器的模块
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime')


// 2.创建网站服务器
const app = http.createServer()

// 3.监听请求事件
app.on('request', (req, res) => {

    // 获取用户请求路径
    let pathname = url.parse(req.url).pathname
    pathname = pathname == '/' ? '/default.html' : pathname

    // 根据路径返回资源类型
    let type = mime.getType(pathname)

    // __dirname:获取当前文件所在路径
    // 将用户请求路径转换为实际的服务器硬盘路径
    resPath = path.join(__dirname, 'pulic', pathname)
        // res.end(resPath)
    console.log(resPath);


    // 读取文件
    fs.readFile(resPath, (error, result) => {
        // 如果文件读取出错
        if (error != null) {
            res.writeHead(404, {
                'content-type': 'text/html;charset=utf8'
            })
            res.end('文件读取失败');
            return;
        }
        res.writeHead(200, {
            'content-type': type
        })

        res.end(result);
    })

})

// 监听端口 
app.listen(8080)
console.log("网站服务器启动成功");\

5. Node.js异步编程

5.1 同步API, 异步API

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

console.log('before'); 
console.log('after');

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

 // 路径拼接
 const public = path.join(__dirname, 'public');
 // 请求地址解析
 const urlObj = url.parse(req.url);
 // 读取文件
 fs.readFile('./demo.txt', 'utf8', (err, result) => {
     console.log(result);
 });

5.2 同步API, 异步API的区别( 获取返回值 )

同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的

function getMsg() {
    setTimeout(function() {
        return {
            msg: 'hello'
        }
    }, 2000)
}
// 执行这个的时候 没过两秒拿不到返回值 
const msg = getMsg()
console.log(msg); 

5.3 回调函数

怎么才能拿到异步API的返回值,自己定义函数让别人去调用。

function getMsg(callback) {
    setTimeout(function() {
        callback({
            msg: 'hello'
        })
    }, 2000)
}
getMsg(function(data) {
    console.log(data);
})

5.4 5.5 同步API, 异步API的区别(代码执行顺序)

  • 同步API从上到下依次执行,前面代码会阻塞后面代码的执行

演示

for (var i = 0; i < 100000; i++) { 
    console.log(i);
}
console.log('for循环后面的代码');
  • 异步API不会等待API执行完成后再向下执行代码

演示

console.log('代码开始执行'); 
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0); 
console.log('代码结束执行');

先执行同步,后执行异步(先执行即将开始的)

5.6 Node.js中的异步API

1

fs.readFile('./demo.txt', (err, result) => {});

2

var server = http.createServer();
server.on('request', (req, res) => {});

如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?

fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');

需求:依次读取A文件、B文件、C文件

const fs = require('fs')
// 回调地狱
fs.readFile('./1.txt', 'utf8', (err, res1) => {
    console.log(res1);
    fs.readFile('./2.txt', 'utf8', (err, res2) => {
        console.log(res2);
        fs.readFile('./3.txt', 'utf8', (err, res3) => {
            console.log(res3);
        })
    })
})

5.8 Promise

Promise出现的目的是解决Node.js异步编程中回调地狱的问题。

十分臃肿

const fs = require('fs')
    // 回调地狱
    // fs.readFile('./1.txt', 'utf8', (err, res1) => {
    //     console.log(res1);
    //     fs.readFile('./2.txt', 'utf8', (err, res2) => {
    //         console.log(res2);
    //         fs.readFile('./3.txt', 'utf8', (err, res3) => {
    //             console.log(res3);
    //         })
    //     })
    // })

function p1() {
    return new Promise((reslove, reject) => {
        fs.readFile('./1.txt', 'utf8', (err, res) => {
            reslove(res)
        })
    })
}

function p2() {
    return new Promise((reslove, reject) => {
        fs.readFile('./2.txt', 'utf8', (err, res) => {
            reslove(res)
        })
    })
}

function p3() {
    return new Promise((reslove, reject) => {
        fs.readFile('./3.txt', 'utf8', (err, res) => {
            reslove(res)
        })
    })
}

p1().then((r1) => {
    console.log(r1);
    return p2()
}).then((r2) => {
    console.log(r2);
    return p3()
}).then((r3) => {
    console.log(r3);
})

5.9 异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

const fn = async () => {};
async function fn () {}

async关键字

1. 普通函数定义前加async关键字 普通函数变成异步函数
2. 异步函数默认返回promise对象
3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
4. 在异步函数内部使用throw关键字抛出程序异常
5. 调用异步函数再链式调用then方法获取异步函数执行结果
6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息

await关键字

1. await关键字只能出现在异步函数中
2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
3. await关键字可是暂停异步函数向下执行 直到promise返回结果

异步函数

// 1.在普通函数定义的前面加上async关键字
// 2.异步函数默认的返回值是promise对象
// 3.在异步函数内部使用throw关键字进行错误抛出
// async function fn() {
//     throw '发生了一些错误' // 抛出错误异常
//     return 123

//     // await关键字
//     // 1.它只能出现在异步函数中
//     // 2.await promise 它可以暂停异步函数的执行 等待promise对象返回结果再向下执行

// }

// fn().then(function(data) {
//     console.log(data);
// }).catch(function(err) {
//     console.log(err);
// })

异步函数的基本用法

async function p1() {
    return 'p1'
}

async function p2() {
    return 'p2'
}

async function p3() {
    return 'p3'
}

async function run() {
    let r1 = await p1() // p1函数执行有返回结果再向下执行
    let r2 = await p2()
    let r3 = await p3()
    console.log(r1);
    console.log(r2);
    console.log(r3);

}

run()

重写读取文件异步任务

const fs = require('fs')
const promisify = require('util').promisify
    // fs.readFile函数不能返回 promise 对象  所以要给他包装个promisify,能让异步API返回promise 对象
const readFile = promisify(fs.readFile)

async function run() {
    let r1 = await readFile('./1.txt', 'utf8')
    let r2 = await readFile('./2.txt', 'utf8')
    let r3 = await readFile('./3.txt', 'utf8')
    console.log(r1);
    console.log(r2);
    console.log(r3);
}
run()
posted @ 2020-06-13 15:51  xujing123  阅读(419)  评论(0编辑  收藏  举报