请求响应原理及HTTP协议学习笔记

1. 服务器端基础概念

1.1网站组成

  • 网站应用程序主要分为两大部分:客户端和服务器端。
  • 客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。
  • 服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
    在这里插入图片描述

1.2Node网站服务器

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

1.3 IP地址、域名和端口

IP地址:是指互联网中设备的唯一标识。这里不多叙述,大家应该都了解一些~

域名:是指平时上网所使用的网址。如:www.baidu.com,虽然在地址栏中输入的是网址, 但是最终还是会将域名转换为ip才能访问到指定的网站服务器。
例如:http://www.baidu.com => http://220.181.38.148/

端口:是指计算机与外界通讯交流的出口,用来区分服务器电脑中提供的不同的服务。
在这里插入图片描述
因为大多数网站应用使用的是80端口号,如果输入域名或IP访问网站时没有输入端口号,浏览器在请求的时候默认在输入的域名或IP地址后面接的是80端口号,如果是非80端口号,在IP地址或域名后面加上端口号即可。

1.4 URL
统一资源定位符,又叫URL(Uniform Resource Locator),是专为标识Internet网上资源位置而设的一种编址方式,我们平时所说的网页地址指的即是URL。在URL中标注了要请求的服务器地址,提供服务的端口及要请求的资源位置。

URL的组成
传输协议://服务器IP或域名:端口/资源所在位置标识
https://editor.csdn.net/md?articleId=106287460
http:超文本传输协议,提供了一种发布和接收HTML页面的方法。

1.5开发过程中客户端和服务器端说明
在开发阶段,客户端和服务器端使用同一台电脑,即开发人员电脑。
在这里插入图片描述
我们自己电脑的服务器(本地)可以通过一组特殊的IP或域名来访问
本机域名:localhost
本机IP地址:127.0.0.1

2. 创建web服务器

我们使用nodejs当中创建网站服务器。做服务器端开发如果没有网站服务器,一切都无从谈起。
在电脑当中,我们要先安装好node软件,参考node学习笔记,再用nodejs创建软件层面上的服务器,得到请求对象和响应对象。

创建网站服务器实例代码如下图:

//引入http创建网站服务器系统模块
const http = require('http');

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

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

//监听的端口号
app.listen(3000);
console.log('网站服务器启动成功');

保存代码,打开PowerShell窗口,切换到当前目录,使用nodemon xxx.js(当前文件名),这里也可以用node命令,但是建议用nodemon,这样每次修改保存文件后可以自动运行,无需手动再去运行一遍

nodemon test.js

在这里插入图片描述
打开浏览器,在网址栏输入localhost:3000,测试成功,响应内容成功显示出来。(如果你响应的内容是中文字符,此时代码还不够完善所以显示出来的不是想要的效果,这个问题后面会提到)
在这里插入图片描述

3. HTTP协议

3.1 HTTP协议的概念
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。
在这里插入图片描述
3.2 报文
在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。
在这里插入图片描述
拿博客首页来看看
在这里插入图片描述
在这里插入图片描述
信息量挺大的,可以了解一些

3.3 请求报文
请求方式 (Request Method)

  • GET 请求数据
  • POST 发送数据

最常见的get请求就是浏览器通过地址栏中输入的网址的方式。如果既不是获取数据也不是添加数据的请求,比如登陆操作,一般是post方式,因为使用post相对于get 更安全一些。
实例代码如下图,保存文件,

//引入http,创建网站服务器模块
const http = require('http');
//app对象就是网站服务器对象
const app = http.createServer();

//当客户端有请求来的时候
app.on('request',(req,res) => {
	//获取请求方式
    // console.log(req.method);
    if(req.method=='POST'){
        res.end('post');
    }else if(req.method=='GET'){
        res.end('get');
    }else {}
});

//监听的端口号
app.listen(3000);
console.log('网站服务器启动成功');

在这里插入图片描述
编写一个表单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 method="POST" action="http://localhost:3000/">
    <input type="submit" name="提交">
    </form>
</body>
</html>

保存后运行,点击提交按钮,可以看到此时的请求方式就是post。

请求地址 (Request URL)

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

3.4 响应报文

HTTP状态码

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

内容类型

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

综合请求地址及响应报文应用实例代码如下图:

//引入http创建网站服务器系统模块
const http = require('http');
//创建网站服务器对象
const app = http.createServer();
//当客户端有请求来的时候
app.on('request',(req,res) => {   
    //获取请求地址
    // res.end(req.url);
    //响应报文
    res.writeHead(200,{
      //设置了响应报文的文本类型和字符编码格式后,中文以及html标签的格式就可以在浏览器渲染出来了
        'content-type':'text/html;charset=utf8'
    });
    if(req.url=='/index' || req.url=='/'){
        res.end('<h2>这是index界面</h2>');     
    }else if(req.url=='/list'){
        res.end('<h2>这是list界面</h2>');    
    }else {
        res.end('<h3>404</h3>');
    }
});
//监听的端口号
app.listen(3000);
console.log('网站服务器启动成功');

在这里插入图片描述

4. HTTP请求与响应处理

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

4.2 GET请求参数
参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20

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

const http = require('http');
 // 导入url系统模块 用于处理url地址
 const url = require('url');
 const app = http.createServer();
 app.on('request', (req, res) => {
	//响应报文 		
 	 res.writeHead(200,{
        'content-type':'text/html;charset=utf8'
    });
    //获取请求地址
    // console.log(req.url);
    
	// 将url路径的各个部分解析出来并返回对象
    //获取url相应对象方式,true 代表将参数解析为对象格式
    // let query = url.parse(req.url).query;
    // let paras = url.parse(req.url).query;
    // console.log(paras);
    // console.log(paras.name);
    // console.log(paras.age);
    
    // let {query} = url.parse(req.url,true);
    //let {pathname,query} = url.parse(req.url,true);
    
	let {pathname,query} = url.parse(req.url,true);
    if(pathname=='/index' || pathname=='/'){
        res.end('<h2>这是index界面</h2>');
        console.log(query);        
    }else if(pathname=='/list'){
        res.end('<h2>这是list界面</h2>');
        console.log(query);       
    }else {
        res.end('<h3>404</h3>');
    }
 });
 app.listen(3000);

在浏览器地址栏输入:http://localhost:3000/index?name=zahngsan&age=18
在这里插入图片描述
4.3 POST请求参数

  • 参数被放置在请求体中进行传输
  • 获取POST参数需要使用data事件和end事件
  • 使用querystring系统模块将参数转换为对象格式
// 导入系统模块querystring 用于将HTTP参数转换为对象格式
 const querystring = require('querystring');
 app.on('request', (req, res) => {
     let postData = '';
     // 监听参数传输事件
     req.on('data', (chunk) => postData += chunk;);
     // 监听参数传输完毕事件
     req.on('end', () => { 
         console.log(querystring.parse(postData)); 
     }); 
     res.end('hello');
 });

通过表单文件点击按钮形式请求参数
在这里插入图片描述
4.4 路由
http://localhost:3000/index
http://localhost:3000/login
路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。
在这里插入图片描述
实例代码:

// 当客户端发来请求的时候
 app.on('request', (req, res) => {
 	 //响应报文,设置文本类型和字符编码		
 	 res.writeHead(200,{
        'content-type':'text/html;charset=utf8'
     });
     //获取请求方式
    let method = req.method.toLowerCase();
    //获取请求地址的pathname对象和query参数对象
    let {pathname,query} = url.parse(req.url,true);
    if(method == 'get'){
        if(pathname=='/index' || pathname=='/'){
            res.end('<h2>这是index界面</h2>');
            console.log(query);            
        }else if(pathname=='/list'){
            res.end('<h2>这是list界面</h2>');
            console.log(query);         
        }else {
            res.end('<h3>404</h3>');
        }
    }else if(method == 'post'){
    }else {}
 });

4.5 静态资源和动态资源
静态资源:服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件。如:http://www.itcast.cn/images/logo.png
动态资源:相同的请求地址不同的响应资源,这种资源就是动态资源。
如:http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2

//创建网站服务器模块
const http = require('http');
//请求地址模块
const url = require('url');
//路径模块
const path = require('path');
//文件模块
const fs = require('fs');
//mime模块,引用前需要下载mime模块
const mime = require('mime');
//创建服务器对象
const app = http.createServer();
//请求响应服务器对象
app.on('request',(req,res) => {
    //获取请求路径
    let pathname = url.parse(req.url).pathname;
    pathname = pathname == '/'? '/default.html' : pathname;
    //转行成物理地址
    let realPath = path.join(__dirname,'public'+pathname);
    //获取文件类型
    let type = mime.getType(realPath);
    //读取文件
    fs.readFile(realPath,(err,result) => {
        if(err != null){
            res.writeHead(404,{'content-type':'text/html;charset=utf8'});
            res.end('文件读取失败!');
            return ;
        }
        //响应报文
        res.writeHead(200,{'content-type':type});
        res.end(result);
    });   
});
//监听端口号
app.listen(3000);
console.log("网站服务器启动成功");

下载mime模块命令

npm install mime

4.6 客户端请求途径
GET方式

  • 浏览器地址栏
  • link标签的 href属性
  • script标签的src属性
  • img标签的src属性
  • Form表单提交

POST方式

  • Form表单提交

5.Node.js异步编程

5.1 同步API, 异步API

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

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

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

//输出顺序:before after last
console.log('before');
setTimeout(
   () => { console.log('last');
}, 2000);
console.log('after');

5.2 同步API, 异步API的区别( 获取返回值 )
同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的

    // 同步
  function sum (n1, n2) { 
      return n1 + n2;
  } 
  const result = sum (10, 20);

    // 异步,下面代码
  function getMsg () { 
      setTimeout(function () { 
          return { msg: 'Hello Node.js' }
      }, 2000);
  }
  //结果undefined
  console.log(getMsg());

5.3 回调函数
自己定义函数让别人去调用。

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

5.4 使用回调函数获取异步API执行结果

function getMsg (callback) {
    setTimeout(function () {
        callback ({ msg: 'Hello Node.js' })
    }, 2000);
}
getMsg (function (msg) { 
    console.log(msg);
});

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 代码执行顺序分析

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

在这里插入图片描述

5.7 Node.js中的异步API
前面学过的读取文件和事件监听属于常见的异步API

//读取文件
fs.readFile('./demo.txt', (err, result) => {});
//事件监听
var server = http.createServer();
 server.on('request', (req, res) => {});

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

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

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

方法一:套娃方式,倘若读取的文件n个,显然用这种方法需要“套娃”n-1遍,造成回调地狱。这种方法显然不适合用来读取过多的文件。

const fs = require('fs');

fs.readFile('./1.txt', 'utf8', (err, result1) => {
	console.log(result1)
	fs.readFile('./2.txt', 'utf8', (err, result2) => {
		console.log(result2)
		fs.readFile('./3.txt', 'utf8', (err, result3) => {
			console.log(result3)
		})
	})
});

5.8 Promise
方法二:使用Promise,Promise出现的目的是解决Node.js异步编程中回调地狱的问题。

//resolve执行成功的返回结果,结果以对象形式存储
//reject执行失败的返回结果
//resolve和reject本质是函数
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '张三'})
        }else {
            reject('失败了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result); // {name: '张三'})
       .catch(error => console.log(error); // 失败了);

利用Promise方式读取单个文件

const fs = require('fs');
let promise = new Promise((resolve, reject) => {
	fs.readFile('./100.txt', 'utf8', (err, result) => {
		if (err != null) {//读取文件失败
			reject(err);
		}else {//成功读取文件
			//用Promise执行成功的返回函数resolve返回结果
			resolve(result);
		}
	});
});
//用then接收成功的返回结果
promise.then((result) => {
	 console.log(result);
})
//catch捕捉执行异常
.catch((err)=> {
	console.log(err);
})

利用Promise方式读取多个文件

const fs = require('fs');
function p1() {
    return new Promise((resolve,reject) => {
        fs.readFile('1.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
function p2() {
    return new Promise((resolve,reject) => {
        fs.readFile('2.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
function p3() {
    return new Promise((resolve,reject) => {
        fs.readFile('3.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
p1().then((r1) => {
    console.log(r1);
    return p2();
})
.then((r2) => {
    console.log(r2);
    return p3();
})
.then((r3) => {
    console.log(r3);
})

虽然这个方式解决了回调地狱问题,但是代码过于繁琐,怪吓人的~

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

const fn = async () => {};
async function fn () {};
//可以测试一下,使用async关键字的函数,其返回结果为promise对象
async function fn () {
	throw '发生了一些错误';
	return 123;
}
// console.log(fn ())
fn ().then(function (data) {
	console.log(data);
}).catch(function (err){
	console.log(err);
})

async关键字

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

await关键字

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

使用async关键字函数,读取多个文件,解决了上述的回调地狱问题以及代繁琐问题,从而使代码简洁易懂。

const fs = require('fs');
//改造现有的异步API,让其返回promise对象,从而支持异步函数语法
const readFile = require('util').promisify(fs.readFile);

async function run() {
    let f1 = await readFile('1.txt','utf8');
    let f2 = await readFile('2.txt','utf8');
    let f3 = await readFile('3.txt','utf8');
    console.log(f1);
    console.log(f2);
    console.log(f3);
}
run();

util模块

const fs = require('fs');
//获取util模块下的promisify方法,
const promisify = require('util').promisify
//promisify方法可以改造异步API,让其返回promise对象
const readFile = promisify(fs.readFile);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章