前后端交互(二)| Node+Gulp | (3)请求响应原理及HTTP 协议

本文介绍了HTTP协议的基础知识,包括请求响应原理、报文结构等,并详细讲解了如何使用Node.js创建Web服务器,处理HTTP请求与响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请求响应原理及HTTP协议


1. 服务器端基础概念

1.1 网站的组成

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

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

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

在这里插入图片描述

请求:客户端向服务器端发送一个指令request,告诉服务器端我要获取数据

响应:服务器端接收到指令,对客户端进行回应response,将请求的数据发送给客户端

在这里插入图片描述
前后端交互:前端页面通过工具调用后端接口,拿到数据库中的数据,然后在做前端页面的渲染。
什么是前端? 什么是后端? 什么是数据库?
在这里插入图片描述
在这里插入图片描述

1.2 Node网站服务器

能够提供网站访问服务的机器就是网站服务器,它能够接收客户端的请求,能够对请求做出响应
在这里插入图片描述

1.3 IP地址

IP地址是互联网中设备的唯一标识(biaoshi)。

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.itcast.cn/news/20181018/09152238514.html

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

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

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

在这里插入图片描述

2. 创建web服务器

2.1 什么是 Web 服务器?

Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL,与客户端的网络浏览器配合。大多数 web 服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。
目前最主流的三个Web服务器是Apache、Nginx、IIS。

下面为Web 应用架构
在这里插入图片描述

2.2 使用 Node 创建 Web 服务器

`Node 创建 Web 服务器``
1.引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
2.创建web服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
3.接收请求与响应请求: 客户端使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

Node中的http模块中主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块。
http.Server是一个基于事件的http服务器,http.request则是一个http客户端工具,用于向http服务器发起请求。而下面的createServer方法中的参数函数中的两个参数req和res则是分别代表了请求对象和响应对象。
request事件:当客户端请求到来时,该事件被触发,提供两个参数req和res,表示请求和响应信息,是最常用的事件
on(event, listener)为指定事件添加一个监听器,接受一个event和一个回调函数。

//app.js
//1.引用创建服务器http模块,将实例化的 HTTP 赋值给变量 http
const http =require('http');
//2.创建网站服务器
//app对象就是网站服务器对象
const app =http.createServer();
//对request设置监听器
app.on('request',(req,res)=>{
  //响应 res.end
  res.end('<h2>hello user</h2>');
});
//监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');

在这里插入图片描述
输入localhost:监听端口 访问内容
在这里插入图片描述
Node 应用程序是如何工作的?
在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数将接收错误对象作为第一个参数。

创建一个 input.txt ,文件内容如下:

加油啊
//main.js
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
   if (err){
      console.log(err.stack);
      return;
   }
   console.log(data.toString());
});
console.log("程序执行完毕");

以上程序中 fs.readFile() 是异步函数用于读取文件。 如果在读取文件过程中发生错误,错误 err 对象就会输出错误信息。
如果没发生错误,readFile 跳过 err 对象的输出,文件内容就通过回调函数输出。

3. HTTP协议

3.1 HTTP协议的概念

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

在这里插入图片描述

3.2 报文

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

在这里插入图片描述

3.3 请求报文

1. 请求方式 (Request Method)

●GET:从服务器上获取数据

●POST:向服务器传送数据

浏览器用GET请求来获取一个html页面/图片/css/js等资源;用POST请求来提交一个表单,并得到一个结果的网页。

2. 请求地址 (Request URL)

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

示例

//app.js
//1.引入创建服务器http模块
const http = require('http');
//2.创建网站服务器
//app对象就是网站服务器对象
const app = http.createServer();
//当客户端请求来的时候
app.on('request',(req,res)=>{
  //获取请求方式
  //req.method
  // console.log(req.method);
 if(req.method=='GET'){
    res.end('get')
  }else if(req.method=='POST'){
    res.end('post')
  }
  //获取请求地址
  // req.url
  // console.log(req.url);
     if(req.url=='/index' ||req.url=='/'){
    res.end('welcome to homepage')
  }else if (req.url=='/list'){
    res.end('welcome to listpage')
  }else {
    res.end('not found')
  }
  //获取请求报文
  // req.headers
  console.log(req.headers)
  //单独获取请求报文中的accpet内容
  //console.log(req.headers['accept']);
  //响应
  res.end('<h2>hello user</h2>');
});
//监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');
//form.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="text" name='username'>
    <input type="password" name='password'> -->
    <input type="submit" name=''>
  </form>
</body>
</html>

获取请求方式
在这里插入图片描述

获取请求地址

在这里插入图片描述

获取请求报文

在这里插入图片描述

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, {//200为状态码
     //内容类型
        'Content-Type': 'text/html;charset=utf8‘//utf8为编码类型
     });
 });

在这里插入图片描述

4. HTTP请求与响应处理

4.1 请求参数

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

在这里插入图片描述

4.2 GET请求参数

获取get请求内容:由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。node.js 中 url 模块中的 parse 函数提供了这个功能
●参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20
●参数获取需要借助系统模块url,url模块用来处理url地址url.parse 方法来解析 url中的参数

//app.js
//引入创建网站服务器的模块
const http = require('http');
// 引入处理url地址模块
const url = require('url');
//创建网站服务器
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
	// 获取请求方式
	// req.method
	// console.log(req.method);
	// 获取请求地址
	// req.url
	// console.log(req.url);
	// 获取请求报文信息
	// req.headers
	// console.log(req.headers['accept']);
	res.writeHead(200, {
		'content-type': 'text/html;charset=utf8'
	})
	console.log(req.url);
  //url.parse(req.url, true); url.parse 方法来解析 URL 中的参数
  // 1) 第一个参数为要解析的url地址
  // 2) 第二个参数true代表将参数解析为对象格式
  //pathname存储客户端的地址
	let { query, pathname } = url.parse(req.url, true);
	console.log(query.name)//zhangsan
	console.log(query.age)//20
	if (pathname == '/index' || pathname == '/') {
		res.end('<h2>欢迎来到首页</h2>');
	}else if (pathname == '/list') {
		res.end('welcome to listpage');
	}else {
		res.end('not found');
	}
	if (req.method == 'POST') {
		res.end('post')
	} else if (req.method == 'GET') {
		res.end('get')
	}
	// res.end('<h2>hello user</h2>');
});
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

在这里插入图片描述

4.3 POST请求参数

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

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

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

//post.js
//1.引入系统模块
//用于创建网站服务器的模块  
const http = require('http');
//2.创建web服务器
const app = http.createServer()
//处理请求参数模块querystring用于将HTTP参数转换为对象格式
const querystring = require('querystring')
//3.当客户端发送请求的时候
app.on('request', (req, res) => {
  //post参数是通过事件的方式接受的
  //data 当请求参数传递的时候触发data事件
  //end 当参数传递完成的时候触发end事件
  let postParams = '';
  req.on('data', params => {
    postParams += params;
  })
  req.on('end', () => {
    //querystring中的parse方法把字符串参数处理成对象格式
    console.log(querystring.parse(postParams));
  })
  res.end('ok')
});
//4.监听3000端口
app.listen(3000);
console.log('网站服务器启动成功');

在这里插入图片描述

4.4 路由

路由是指客户端请求地址服务器端程序代码的对应关系。简单的说,就是请求什么响应什么
路由决定了由谁(指定脚本)去响应客户端请求
告诉你去哪对于前端,主要是导向告诉浏览器应该去哪,对于后端,可以理解为一个子服务,一个路由就是一个小的服务(server/app),处理一个接口

在这里插入图片描述

//app.js
//1.引入创建网站服务器模块http
const  http =require('http');
const url =require('url')
//2.创建网站服务器
const app =http.createServer();
//3.为网站服务器对象添加请求事件
app.on('request',(req,res)=>{
//4.实现路由功能
//1)获取客户端的请求方式
//2)获取客户端的请求地址
//获取请求方式
const method= req.method.toLowerCase();//转化为小写
//获取请求地址
const pathname = url.parse(req.url).pathname;
res.writeHead(200,{
  'content-type':'text/html;charset=utf8'
})
if(method == 'get'){
  if(pathname =='/'||pathname =='/index'){
  res.end('欢迎来到首页')
  }else if(pathname =='/list'){
  res.end('欢迎来到列表页')
  }else{
  res.end('您访问的页面不存在')
  }
}else if(method =='post'){

}
});
app.listen(3000);
console.log('服务器启动成功');

在这里插入图片描述

4.5 静态资源

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

http://www.itcast.cn/images/logo.png

在这里插入图片描述

4.6 动态资源

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

http://www.itcast.cn/article?id=1

http://www.itcast.cn/article?id=2

在这里插入图片描述

//app.js
const http = require('http');
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime') //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, (error, result) => {
    //如果文件读取失败
    if (error != null) {
      res.writeHead(404, {
        'content-type': 'text/html;charset=utf-8'
      });
      res.end('文件读取失败');
      return;
    }
    res.writeHead(200, {
      'content-type': type
    })
    res.end(result)
  });
});
app.listen(3000);
console.log('服务器启动成功')

在这里插入图片描述

4.7 客户端请求途径

1.GET方式

●浏览器地址栏

●link标签的href属性

●script标签的src属性

●img标签的src属性

●Form表单提交

2.POST方式

●Form表单提交
在这里插入图片描述

5. Node.js异步编程

Node.js 异步编程的直接体现就是回调,回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

5.1 同步API, 异步API

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

//先打印before 在打印after
console.log('before'); 
console.log('after');

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

//先打印before 在打印after 2秒后打印last
console.log('before');
setTimeout(function () {
  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);//30
//异步API不可以从返回值中拿到API执行的结果
function getMsg() {
  setTimeout(function () {
    return {
      msg: 'hello node.js'
    }
  }, 2000)
    //没有指定return内容  默认return undefined
}
const msg = getMsg();
console.log(msg);//undefined  
//异步api 我跳过了定时函数,先执行了return undefined 两秒后才拿到return {msg: 'hello node.js'},此时控制台已经拿到了undefined

5.3 回调函数

回调函数是一个函数,将在另一个函数完成执行后立即执行。回调函数是一个作为参数传递给另一个JavaScript函数的函数。该回调函数在传递给它的函数内部执行。回调函数一般作为函数的最后一个参数出现

  // getData函数定义
 function getData (callback) {}//callback为形参
  // getData函数调用
 getData (() => {});//() => {}这个回调函数为callback对应的实参
function getData(callback) {//callback为形参
  callback(123); //callback函数被调用了 123  function(n){}这个回调函数在getData函数内部执行
//使用callback调用下面的回调函数,n为形参,123为实参
// 改写成
//function (n) {
//   console.log('callback函数被调用了');
//   console.log(n);
// }
// callback(123); callback函数被调用了 123
}
getData(function (n) {//function(n){}这个回调函数为实参
  console.log('callback函数被调用了');
  console.log(n);
})

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

function getMsg(callback) {
  setTimeout(function () {
    callback({
      msg: 'hello node.js'
    })
  }, 2000)
}
const msg = getMsg(function (data) {//{ msg: 'hello node.js'}作为实参传给了形参data
  console.log(data); //{msg: 'hello node.js'}
});

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

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

//同步API从上到下依次执行,前面代码会阻塞后面代码的执行,for循环执行完之后在执行log打印
for (var i = 0; i < 1000; i++) {
  console.log(i);
}
console.log('for循环后面的代码');

异步API不会等待API执行完成后再向下执行代码

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

在这里插入图片描述

代码执行顺序分析:代码从上到下依次执行,遇到同步API放到同步代码执行区执行,遇到异步API放到异步代码执行区,当同步代码执行区执行完毕,然后执行异步代码,当异步代码区执行完毕,系统会到回调函数队列中找异步API对应的回调函数,然后把回调函数放到同步代码执行区执行

在这里插入图片描述

5.5 Node.js中的异步API

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

依次读取 1 2 3.txt文件 方案一:嵌套

//依次读取 1 2 3.txt文件(分别写入的1 2 3),采用嵌套的方式 不可维护 回调地狱
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);
    })
  })
})
//结果依次输出 1 2 3 

5.6 Promise

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

const fs = require('fs');
let promise = new Promise((resolve, reject) => {
  fs.readFile('./1.txt', 'utf8', (err, result) => {
    if (err != null) { //如果文件读取失败,要把文件失败的信息传递到promise外面 
      reject(err)
    } else {
      resolve(result)//如果文件读取成功,要把文件成功的信息传递到promise外面 
    }
  });
})
promise.then((result) => {//then中传递匿名函数,形参result是接受上面resolve中的result
    console.log(result);
  })
  .catch((err) => { //catch中传递匿名函数,形参err是接受上面reject中的err
    console.log(err);
  })

5.7 异步函数

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

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

依次读取 1 2 3.txt文件 方案二:异步函数

const fs = require('fs');

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

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

function p3() {
  return new Promise((resolve, reject) => {
    fs.readFile('./3.txt', 'utf8', (err, result) => {
      resolve(result)
    })
  })
}
p1().then((r1) => {
    console.log(r1);
    return p2();
  })
  .then((r2) => {
    console.log(r2);
    return p3();
  })
  .then((r3) => {
    console.log(r3);
  })

async关键字

1.普通函数定义前加async关键字 普通函数变成异步函数

2.异步函数默认返回promise对象

3.在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法

4.在异步函数内部使用throw关键字抛出程序异常

5.调用异步函数再链式调用then方法获取异步函数执行结果

6.调用异步函数再链式调用catch方法获取异步函数执行的错误信息

await关键字

1.await关键字只能出现在异步函数中

2.await后面只能写promise对象 写其他类型的API是不可以的

3.await关键字可是暂停异步函数向下执行 直到promise返回结果

同步函数:具有顺序性,当调用函数未返回值前不能执行后续行为;
异步函数:无顺序性,调用的函数会立即返回,不影响执行后续的行为,其中调用函数的真实执行情况是在另一个线程中发生;

//1.在普通函数定义的前面加上async关键字,普通函数变成异步函数
//2.异步函数默认的返回值是promise对象
//3.在异步函数内部使用throw关键字进行错误的抛出
//4.await关键字 
//1)它只能出现在异步函数中
//2)await+promise 它可以暂停异步函数的执行 等待promise对象返回结果在向下执行

// async function fn() {
//return new Promise  省略这一步
//   throw '发生了一些错误' //throw关键字: 抛出异常,throw一旦执行,后面的代码就不会执行
//   return 123;
// }
// console.log(fn()); //Promise{123}
// fn().then(function (data) {
//   console.log(data); //123
// }).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函数没有结果,可以暂停异步函数的执行 等待promise对象返回结果在向下执行
  let r2 = await p2()
  let r3 = await p3()
  console.log(r1);
  console.log(r2);
  console.log(r3);
}
run()

依次读取 1 2 3.txt文件 方案三:await+promise

const fs = require('fs');
//promisify 改造现有异步函数api,让其返回promise对象,从而支持异步函数语法
const promisify = require('util').promisify; //获取该方法
//调用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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值