js 前后端交互ajax(http)

互联网产品的前后端数据交互是通过一个通讯协议完成。前后台交互的协议主要包括HTTP,FTP,SMTP,TELNET,POP3…本文仅总结HTTP协议。
简单请求get 、post 、head ,简单请求与复杂请求的主要区别是:是否会触发cors预检请求。
简单请求的head不会超出一下几种字段
1、Accept
2、Accept-Language
3、Content-Language
4、Last-Event-ID
5、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

复杂请求都会发预检请求(OPTIONS请求),用于向服务器请求权限信息的(会先向服务器发送一条预检请求,根据服务器返回的内容浏览器判断服务器是否允许该请求访问。如果web服务器采用cors的方式支持跨域访问,在处理复杂请求时这个预检请求是不可避免的),XMLHttpRequest会遵守同源策略(same-origin policy). 也即脚本只能访问相同协议/相同主机名/相同端口的资源, 如果要突破这个限制, 那就是所谓的跨域, 此时需要遵守CORS(Cross-Origin Resource Sharing)机制。可以是后端服务器设置Access-Control-Allow-Origin: * * ,表示是否允许跨域,还有一种是preflighted request在发送真正的请求前, 会先发送一个方法为OPTIONS的预请求(preflight request), 用于试探服务端是否能接受真正的请求,如果options获得的回应是拒绝性质的,比如404\403\500等http状态,就会停止post、put等请求的发出

一、基础知识
HTTP协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议,是一个基于TCP/IP通信协议来传递数据,也是互联网上应用最为广泛的一种网络协议。
HTTP的主要特点

(1)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快;
(2)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记;
(3)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间;
(4)无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快;
(5)支持B/S及C/S模式

无连接、无状态的理解
TCP协议对应于传输层,而HTTP协议对应于应用层。

Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。所谓的无状态,是指浏览器每次向服务器发起请求的时候,不是通过一个连接,而是每次都建立一个新的连接。如果是一个连接的话,服务器进程中就能保持住这个连接并且在内存中记住一些信息状态。而每次请求结束后,连接就关闭,相关的内容就释放了,所以记不住任何状态,成为无状态连接。(3次握手,四次挥手实现连接的建立与断开)

总结
数据交互的过程可简单理解为,前端想要获得某些数据,将传入参数通过URL接口地址,传递给服务器,服务器根据传入的参数了解到前台要获得什么数据,去数据库查询获取数据,然后将所需数据返回给前台,前台拿到数据做相应的页面展示。
前端<----------> 后端<------------->数据库

二、常用的接口请求方法(如何在项目中实现前后端交互 即http协议)

1、 ajax 方法
ajax是利用js创建的异步对象来实现前后端交互的http异步请求交互(它是一个异步操作,可以在不重新加载整个网页的情况下,对网页的某部分进行更新。目前所有浏览器都支持XMLHttpRequest)

// 如何项目中实现 ajax 5步走
// (1) 创建异步对象
const ajaxObj = new XMLHttpRequest()
// (2) 设置请求url等参数,使用异步对象的open方法
ajaxObj.open('get', 'url') // 若是get请求直接在url后拼接查询参数?name=xuheua&age=3
// (3) 发送请求 。使用异步对象的send方法
ajaxObj.send() // 若是post请求,此方法中放请求的参数,可以是字符串拼接或对象  此后可设置请求头等信息ajaxObj.setRequestHeader()
// (4) 注册事件,可监控请求及返回的进度(状态改变时调用的方法)
ajaxObj.onreadystatechange = function () {
  if (ajaxObj.readyState === 4 && ajaxObj.status === 200) { // status表示请求的状态码  readyState表示是否相应就绪可在用户端使用
    // 0 未初始化,还未send请求 1正在载入,还在发 2 载入完成即send完成 3 正在解析想用内容  4 完成 可在用户端使用
    console.log(ajaxObj.responseText)// 返回的数据保存在异步对象的属性中
    // (5) 此时可进行页面的渲染逻辑
  }
}

// 如何设置头部信息。W3C标准文档规定,部分请求头由浏览器直接控制,开发者不可设置,设置会提示。错误。地址:https://www.cnblogs.com/cdwp8/p/5157377.html
// 放置在open方法之后
ajaxObj.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("User-Agent", "test");
// 设置多个请求头属性,最终会合并到一个请求头信息中

2、. jQuery(它是封装的js库,里面封装了ajax,直接利用jquery封装的ajax方法,实现前后端的异步交互 http 请求)

// 如何利用jquery方法进行前后端交互(利用jquery 提供的ajax9()方法)
// 首先项目中引入 jquery 
//方法1:
//方法2:
//(1)首先在package.json里的dependencies加入jquery
"dependencies": {
    "axios": "^0.19.2",
    "element-ui": "^2.13.1",
    "jquery": "^3.5.1",
    "ol": "^5.2.0",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "vuex": "^3.2.0",
    "xlsx": "^0.16.0"
  },
// (2)终端下载jQuery 包 npm install  jquery
// (3)在build/webpack.base.conf.js文件module.exports中添加plugins属性如下代码
module.exports = {
  resolve:{}
  plugins: [      
      new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery"
      })
  ],
  module:{}
//(4) 在main.js中引入
import $ from 'jquery'

//(5) 进行前后端交互

$.ajax({
    url: "fa.php",  // 发送请求的地址
    type: "GET",   // 设置请求的方法,请求方式"POST"或"GET", 默认为"GET"。
    data: "",  // data主要方式有三种,html拼接的,json数组,form表单经serialize()序列化的;通过dataType指定,不指定智能判断。
    // 1、json格式
    //data:{    
    //         selRollBack : selRollBack,    
    //        selOperatorsCode : selOperatorsCode,    
    //        PROVINCECODE : PROVINCECODE,    
    //        pass2 : pass2    
    //},    
    // 2、form表单经serialize()序列化的
    //data:$("#form1").serialize();//序列化表格内容为字符串
    // 3、html拼接的      https://blog.csdn.net/mingliangniwo/article/details/45533201 
    dataType: "json",  // 服务器返回的数据类型   "json"   "jsonp"   "text" 等
    success: function(data) { 请求成功后的回调函数。
        console.log(data);
    },
    
    error: function(xhr,error,obj) { // 请求失败的回调函数  有以下三个参数:XMLHttpRequest 对象、错误信息、(可选)捕获的异常对象。
     console.log(error);
    }
    complete: function() { // 请求成功或失败的回调函数 ,不管成功还是失败都会执行此函数
     // 函数内直接写请求完成后需要处理的事。
    }
 
    beforeSend: function(xhr) { //发送请求前可修改 XMLHttpRequest 对象的函数,如添加自定义 HTTP 头
       xhr.setRequestHeader ("content-type", "application/x-www-form-urlencoded" ) // 后端让我们传参数的格式。 传输数据类型
    }

3、. axios(封装的js库,基于promise的http库,专门用于前后端交互(超文本传输))

// 项目中可自己封装方法名,设置超时时间及baseurl等,对各种请求错误时的处理。例如发完请求后为let req = axios(fetchOpt);

// 处理错误的请求
 req = req.catch(handleError).then((res) => {
      if (res && res.status === 200 && res.data) {
        const resData = res.data;
        const { status, ret, msg } = resData;
        if (ret || status === 0) {
          return res;
        }
        // 处理业务处理失败的场景,多为后端异常啦!
        if (msg) {
          Notification.error(msg);
        } else {
          Notification.error('服务器返回未知错误');
        }
        throw new Error(msg);  // 业务代码里,也可以收到
      }
      return true;
    });
const handleError = (err) => {
  let mess = err.message;

  if (err.response) {
    switch (err.response.status) {
      case 401:
        mess = 'API未认证';
        break;
      case 403:
        mess = 'API未授权';
        break;
      case 404:
        mess = 'API未找到';
        break;
      case 500:
        mess = 'API服务器错误';
        break;
      default:
        mess = '发生未知错误';
        break;
    }
  }

  Notification.error(mess); // 统一给出错误
  throw new Error(err);  // 各页面也可以catch到错误,从而详细记录错误埋点
};
// 处理成功的请求
 req = req.then((res) => {
      const resData = res.data;
      const { status, ret } = resData;
      if (ret || status === 0) Notification.success(config.withSuccessNotice);
      return res;
    });
// 简单实用介绍
// get 方法
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

// 上面的请求也可以这样做,注意写法,第二参数是config对象,属性params是一个对象参数,还可以设置别的参数
axios.get('/user', {
    params: {
      ID: 12345
    },
    baseURL: 'https://some-domain.com/api/',
    headers: {'X-Requested-With': 'XMLHttpRequest'},// 设置头部信息,属性值的形式,一般不单独一个一个请求的设置,会封装一开始create中设置
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

// post 方法
axios.post('/user', { // 第二参数直接是传的参数data
    firstName: 'Fred',
    lastName: 'Flintstone'
  },{ // 第三参数是一个config对象,可以设请求的各个属性
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
// 注意:可以利用default对象设置请求的一些属性,也可以直接在create方法上一开始设置
axios.defaults.baseURL = 'https://api.example.com';  // 全局axios默认值
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// 自定义默认值
var instance = axios.create();

// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 2500;
// 为已知需要花费很长时间的请求覆写超时设置
instance.get('/longRequest', {
  timeout: 5000
});

// 注意在请求或响应到对应的then或catch前想处理一些操作,实用拦截器处理,类似上方处理错误的请求与成功的请求
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

4、. fetch方法(web api ,跨网络异步获取资源,返回promise,fetch请求默认不带cookie的,需要设置fetch(url, {credentials: ‘include’}) )

// get 和post都是类似,只是设置的方法和传参数的形式不同
// get 请求的简单使用 ,第一参数为url,get请求直接将参数拼接url后?a=1&b=1
 fetch(uri, {
 method: 'GET', 
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', //传参数格式,即参数内容类型  ?序列化的拼接形式
'X-Requested-With': 'XMLHttpRequest',
 timeout:10000,  // 超时时间

 }).then((res)=>{
 }).catch((error)=>{
 })

// post请求的简单使用
 fetch(uri, {
 method: 'POST',
 'Content-Type': 'application/json; charset=UTF-8',
  'X-Requested-With': 'XMLHttpRequest',
 timeout:10000,  // 超时时间
 body: form ? stringify(data) : (JSON.stringify(data) || {}),  // body体传的参数。看是健值对形式(name=hehe&age=10)还是json形式({"a":"hehe","age":10})   import qs from 'qs'; 专门用于对象与序列化的转换的js函数库

 }).then((res)=>{
 }).catch((error)=>{
 })


// 封装时的常用思路
// 1、设置一个对象,里面设置get和post时请求头的设置
const HEADERS = {
  get: { // 
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    ...MOCK_HEADERS, // 一些别的设置
  },
  post: {
    'Content-Type': 'application/json; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    ...MOCK_HEADERS, // 一些别的设置
  },
};
// 2、定义暴露全局的get post请求方法
    // get 方法 全局调用时传参数{data:{//请求参数},headers:{// 可以单独设置请求头},再一些别的参数}
    function get(url, param = {}, withUUID) {
	  const {
	    // 是否显示默认错误提示
	    showError = true,
	    // 超时时间
	    timeout = 10000,
	    // 成功提示
	    successTip = false,
	    onlyOnce = true,
	  } = param;
	
	  let { data = {} } = param;  //要传给后端的查询参数
	  const headers = { ...HEADERS.get, ...param.headers }; // 请求头信息
	  const newParam = { method: 'GET', headers, timeout, successTip };
	
	  // 拼接请求参数类似 ?a=1&b=2
	  if (data) {
	    const trimData = trimSearchData(data, 0);
	
	    const urlParam = encodeURIComponent(serialize(trimData))
	      .replace(/%26/g, '&').replace(/%3D/g, '=');
	    url += `${url.indexOf('?') > -1 ? '&' : '?'}${urlParam}`;
	  }
	  const uniqueData = url;
	  // 添加时间戳
	  url += `${url.indexOf('?') > -1 ? '&' : '?'}_time=${new Date() - 0}`;
	  return newFetch(url, newParam, showError, uniqueData, onlyOnce);  // 封装的整体处理fetch请求成功与失败的处理逻辑
	  // 前两个参数即最重要的url(包含参数),头部设置信息等,其他的是根据业务写的逻辑
}




    // post 方法
    function post(url, param = {}, withUUID) {
	  const {
	    // 是否显示默认错误提示
	    showError = true,
	    // 超时时间
	    timeout = 10000,
	    // 成功提示
	    successTip = false,
	    form = false,
	    onlyOnce = true,
	  } = param;
	
	  let { data = {} } = param;  // 传参对象
	
	  const headers = { ...HEADERS.post, ...param.headers };
	  if (form) {// 默认是json格式的传参,若后端要求是form表单的健值对格式则重新设置请求头的传参数格式
	    headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
	  }

	  data = trimSearchData(data, 0);
	
	  return newFetch(url, {
	    method: 'POST',
	    headers,
	    timeout,
	    body: form ? stringify(data) : (JSON.stringify(data) || {}), // post的参数是body中,并设置传参数格式
	    successTip,
	  }, showError, uniqueData, onlyOnce);
}
    
// 3、封装请求的整体方法以及处理请求
const errPromise = {
  then() {
    return errPromise;
  },
  catch() {
    return errPromise;
  },
};


export const errorHook = (response, uri) => { // 返回不是200 的状态码时统一抛错让用户及开发知晓
  // 统一处理埋点
  metric('request.error')
    .tags({ status: response.status })
    .detail({ uri })
    .dot();

  const handler = ERROR_MAP[response.status];

  if (typeof handler === 'function') {
    return handler(uri);
  }

  throw new Error(`${response.status} ${response.statusText}`);
};

function newFetch(url, param, showError, uniqueData, onlyOnce) {
  if (onlyOnce) { // 若设置只能请求一次,此处根据项目需求来,大家可以略过
    return errPromise;
  }
  const starTime = +new Date();
  // 开始处理请求 第一个参数是请求的url,若get时已拼接请求参数及时间戳
  return fetch(uri, param)
    .then((response) => { // 请求成功的处理
      if (response.status >= 200 && response.status < 300) { // 200-300这是响应成功时正常返回后端的响应。
        if (!window.SHOP_CODE) {
          window.SHOP_CODE = response.headers.get('shopCode') || '';
        }
        return response;
      }
      return errorHook(response, uri); // 当是别的状态码时添加处理逻辑
    })
    .then(response => response.json()) // 当上一层then没有抛异常,即200时,走到这层then。 请求成功的处理
    .then((data) => {// 前面处理都正常,请求成功的处理
      if (data.status !== 0) {// 若请求200,但返回的状态不是0时,即数据不是成功拿到,此时抛异常信息
        let error = new Error(`请求异常 ${url}`);
        if (data.msg) {
          error = new Error(`${data.msg} ${url}`);
        }
        error.response = data;
        throw error
      } else {
        if (param.successTip) { // 前端默认设置的请求成功的提示文案,调用接口时可设置不展示此块
          notification.success(typeof param.successTip === 'string' ? param.successTip : '操作成功', 1.5);
        }
        return data; // 请求成功200且状态也是0。这是调用成功最终拿到的的 数据结构{}
      }
    })
    .catch((error) => { // 请求失败的处理(状态码不是200也走此块,状态码不是0也走此块
      throw error; // 失败时拿到的错误信息error:{response:{}}   // 自定义处理成的结构
    });
}

// 4、导出供全局使用(以上自定义一个Request中封装)
Request.get = get;
Request.post = post;

export default Request;

// 5、组件中如何调用
import Request from '......./Request';
	 Request.get(url, {
	      data: {
	        operateNum: this.operateNum,
	      },
	      // 还可以定义封装时兼容的其他参数
	    }).then(({ data }) => { // 拿到处理成功的data数据结构
	     // 处理逻辑
	    }).catch((error)=>{
	     const { status } = error.response;
	    })
    
      Request.post('/seccenter/wdes/all/decryptOrEncrypt/v1', {
        data: {
         // 参数
        },
        // 别的参数
      }).then(({ status, data }) => {
        if (status === 0 && data) {
        // 处理逻辑
        }
      }).catch((error)=>{
	     const { status } = error.response;
	    })

⚠️ 请求中唯一标识符的使用
(1)采用时间戳的 ${+new Date()}`; // “1568689340401” 唯一性较差
(2)引入uuid import uuid from ‘uuid’;
uuid的v1,v3,v4,v5分别是什么意思?
v1 timestamp(时间戳). v3 namespace(命名空间) v4 random number(随机数) uuid.v4()
项目中使用案例

import uuidV4 from 'uuid';
const uuid = uuidV4();
// 设置请求的head参数
headers: {
    uuid,
  },

⚠️ cookie 的使用及学习:如何在前端和后端创建,使用 HTTP cookie
cookie 是后端可以存储在用户浏览器中的小块数据。 Cookie 最常见用例包括用户跟踪,个性化以及身份验证,存储登陆人信息
一般后端在前端请求前设置cookie,展示在请求中,通过document.cookie获取,会拿到一个字符串类型的数据
js 前后端交互ajax(http)_第1张图片

// js创建cookie document.cookie="username=John Doe";
// js获取cookie document.cookie
// 使用案例  import Cookie from 'js-cookie'; 封装的js库,有方便的方法供我们操作cookie
// 写入cookie
Cookies.set('name', 'value')
// 读取
Cookies.get('name') // => 'value'
Cookies.get('nothing') // => undefined
// 读取所有可见的cookie
Cookies.get()
// 删除某项cookie值
Cookies.remove('name')

三、交互方法中传参格式的设置
1、get 请求 ‘Content-Type’: 'application/x-www-form-urlencoded(健值对的形式,一般不用设置,get默认)
2、post请求(参数在body中)

  • json格式的参数:json格式,此时请求的Content-Type头的值必须为application/json。
  • form表单格式的参数(表单编码格式即a=1&b=4这种格式),此时需要做的是将请求的Content-Type头的值必须为application/x-www-form-urlencoded(只能是健值对) multipart/form-data是既可以上传文件类型的二进制也可以是表单类型的健值对 用qs.stringity来处理
    简单请求方法:get post head 即他们头部信息的设置只能是一部分(具体用时查)

四、接口响应数据类型

  1. JSON格式。多数接口应当使用JSON格式的响应,此时响应的Content-Type头的值必须为application/json。
  2. JSONP格式。当跨系统调用API时,会需要使用JSONP接口,此时响应的Content-Type头的值必须为application/x-javascript,使用请求的URL中的callback字段的值为函数名返回相应的JSONP片段。
  3. HTML格式。部分接口,如文件上传,由于技术的限制必须使用元素完成,返回的响应为一个HTML片段,此时响应的Content-Type头的值必须为text/html,且响应状态码必须为200,具体可参考下文的文件上传相关接口规范。

// 备注:陈列、shop-admin、store-roster-web等使用的axios, 订货order-support、策略使用的fetch方法

你可能感兴趣的:(基础,javascript,交互,网络协议)