总结-ajax和跨域方案

246 阅读7分钟

报文

HTTP协议

超文本传输协议(Hypertext Transfer Protocol,简称HTTP)是应用层协议。HTTP 是一种请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求;服务器接到请求后,给予相应的响应信息

HTTP请求报文

HTTP 请求报文由请求行请求头部空行请求包体 4 个部分组成

请求行

请求行由请求方法字段URL字段HTTP协议版本字段3个字段组成,它们用空格分隔如:

GET /index.html HTTP/1.1

请求方法

常用的 HTTP 请求方法有 GETPOSTHEADPUTDELETEOPTIONSTRACECONNECT

请求头部

请求头部由(关键字:<空格>值)对组成,每行一对,关键字和值用英文冒号“:<空格>”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:

  • Accept-Charset:可接受的应答的字符集
  • Host:请求的主机名,允许多个域名同处一个IP 地址,即虚拟主机
  • Cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie

空行

最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头

请求包体

请求包体不在 GET 方法中使用,而是在POST 方法中使用。POST 方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型 Content-Type 和包体长度 Content-Length

HTTP 响应报文

HTTP 响应报文由状态行响应头部空行响应包体 4 个部分组成

状态行

状态行由 HTTP 协议版本字段状态码状态码的描述文本 3 个部分组成,他们之间使用空格隔开,例如

HTTP/1.1 200 OK

状态码

状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:

  • 2xx:表示服务器已成功接收到请求并进行处理;
  • 3xx:表示服务器要求客户端重定向;
  • 4xx:表示客户端的请求有非法内容;
  • 5xx:表示服务器未能正常处理客户端的请求而出现意外错误;

常见的状态码务必要熟悉:

  • 200 OK:表示客户端请求成功;
  • 400 Bad Request:表示客户端请求有语法错误,不能被服务器所理解;
  • 401 Unauthonzed:表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用;
  • 403 Forbidden:表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因;
  • 404 Not Found:请求的资源不存在,例如,输入了错误的URL;
  • 500 Internal Server Error:表示服务器发生不可预期的错误,导致无法完成客户端的请求;
  • 503 Service Unavailable:表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常;

响应头部

响应头可能包括:

  • Location:Location响应报头域用于重定向接受者到一个新的位置。例如:客户端所请求的页面已不存在原先的位置,为了让客户端重定向到这个页面新的位置,服务器端可以发回Location响应报头后使用重定向语句,让客户端去访问新的域名所对应的服务器上的资源。这个头通常配合302重定向状态码使用。浏览器接收到这样的响应信息后,通常会立刻访问Location头所指向的页面
  • Set-Cookie:向客户端设置Cookie。与Cookie请求头相互对应。Set-Cookie头是服务器向客户端设置Cookie,Cookie头是客户端向服务器传客户端已经保存的Cookie信息

空行

最后一个响应头部之后是一个空行,发送回车符和换行符,通知服务器以下不再有响应头部

响应包体

服务器返回给客户端的文本信息

示例

ajax原理

//创建ajax对象
var xhr = new XMLHttpRequest();
//告诉 Ajax 请求地址以及请求方式
xhr.open('get', 'http://www.example.com');
//发送请求
xhr.send();
//获取服务器端给与客户端的响应数据
xhr.onload = function () {
    console.log(xhr.responseText);
}

post提交

格式1-urlencoded

数据格式为字符串,属性名1=属性值1&属性名2=属性值2

原生

// 通过请求头告诉后端数据格式
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// 发送数据
xhr.send('uname=zs&age=20');

jQuery

$.ajax({
    type: 'post',
    url: 'http://www.example.com',
    data: {
        uname: 'zs',
        age: 20
    },
    contentType: "application/x-www-form-urlencoded", //默认的提交方式,当不写contentType时即是此种方式
    success(data){
    }
})

axios

const data = new URLSearchParams();
data.append('uname', 'zs');
data.append('age', 20);
axios({
    url: 'http://www.example.com',
    method: 'post',
    data
})

格式2-json

数据格式为字符串,{"属性名1":"数字","属性名2":数字}

原生

// 通过请求头告诉后端数据格式
xhr.xhr.setRequestHeader("Content-Type", "application/json")
// 发送数据
xhr.send('{"uname": "zs", "age": 20}');

jQuery

$.ajax({
    type: 'post',
    url: 'http://www.example.com',
    data: JSON.stringify({
        uname: 'zs',
        age: 20
    }),
    contentType: "application/json", //默认的提交方式,当不写contentType时即是此种方式
    success(data){
    }
})

axios

axios({
    url: 'http://www.example.com',
    method: 'post',
    data: {
        uname: 'zs',
        age: 20
    }
})

格式3-form-data

数据格式为表单提交对象

原生

// 用FormData对象存储数据
var formData = new FormData()
formData.append("uname", "zs")
formData.append("age": 20)
// 发送数据
xhr.send(formData);

jQuery

var formData = new FormData()
formData.append("uname", "zs")
formData.append("age": 20)
$.ajax({
    type: 'post',
    url: 'http://www.example.com',
    data: formData,
    contentType: false,// 当有文件要上传时,此项是必须的,否则后台无法识别文件流的起始位置
    processData: false,// 是否序列化data属性,默认true
    success(data){
    }
})

axios

var formData = new FormData()
formData.append("uname", "zs")
formData.append("age": 20)
axios({
    url: 'http://www.example.com',
    method: 'post',
    data: formData
})

get提交

格式1-urlencoded

数据通过地址栏发送,数据格式为字符串,属性名1=属性值1&属性名2=属性值2

原生

xhr.open('get', 'http://www.example.com?uname=zs&age=20');

jQuery

$.ajax({
    type: 'get',
    url: 'http://www.example.com',
    data: {
        uname: 'zs',
        age: 20
    },
    success(data){
    }
})

axios

axios({
    url: 'http://www.example.com',
    method: 'get',
    params: {
        uname: 'zs',
        age: 20
    }
})

格式2-restful

原生

xhr.open('get', 'http://www.example.com/user/1');

jQuery

$.ajax({
    type: 'get',
    url: 'http://www.example.com/user/1',
    success(data){
    }
})

axios

axios({
    url: 'http://www.example.com/user/1',
    method: 'get'
})

vue当中使用axios

简化写法

例如: 在main.js

import axios from 'axios'
import Vue from 'vue'
import router from './router/index.js'
// axios的基准地址
axios.defaults.baseURL = 'http://www.example.com'
// axios的请求拦截器添加token
axios.interceptors.request.use(function (config) {
    // 如果有token,就发送
    let token = localStorage.getItem('token')
    if(token){
        config.headers.Authorization = token
    }
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// axios的响应拦截器判断token是否失效
axios.interceptors.response.use(function (response) {
    // 通过响应体返回token失效的状态码
    if(response && response.data && response.data.code === 401){
        router.push('/login')
    	return Promise.reject(new Error('token失效'));
    }    
    return response;
}, function (error) {
    // 通过响应头返回token失效的状态
    if(error.response && error.response.status === 401){
        router.push('/login')
    } 
    return Promise.reject(error);
});
//将axios挂载到Vue的原型链上
Vue.prototype.$http = axios

在组件中

<template>
    <div></div>
</template>
<script>
export default {
    async create(){
        let res = await this.$http.post('/user', {
            uname: 'zs',
            age: 20
        })
    }
}
</script>

模块化写法

例如: 在util/request.js

import axios from 'axios'
const service = axios.create({
    baseURL: 'http://www.example.com'
})
// service的请求拦截器添加token
service.interceptors.request.use(function (config) {
    // 如果有token,就发送
    let token = localStorage.getItem('token')
    if(token){
        config.headers.Authorization = token
    }
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// service的响应拦截器判断token是否失效
service.interceptors.response.use(function (response) {
    // 通过响应体返回token失效的状态码
    if(response && response.data && response.data.code === 401){
        router.push('/login')
    	return Promise.reject(new Error('token失效'));
    }    
    return response;
}, function (error) {
    // 通过响应头返回token失效的状态
    if(error.response && error.response.status === 401){
        router.push('/login')
    } 
    return Promise.reject(error);
});
export default service

api/xxx.js

import request from '@/util/request.js'
export function addUser(data){
    return request({
        url: '/user',
        method: 'post',
        data
    })
}

组件中

<template>
    <div></div>
</template>
<script>
import {addUser} from '@/api/xxx.js'
export default {
    async create(){
        let res = await addUser({
            uname: 'zs',
            age: 20
        })
    }
}
</script>

跨域

什么是同源政策

如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源

同源政策的目的

同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的Cookie,B网站是不能访问的。

随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax 请求,如果请求,浏览器就会报错。

方案1-jsonp

前端

import jsonp from 'jsonp'
import querystring  from 'querystring'
jsonp('http://www.example.com/user?'+querystring.encode({
        uname: 'zs',
        age: 20
    }),
    function (err, data) {
        
    })

后端

import express from 'express'
const app = express()
app.get('/user',(req, res)=>{
    // 获取数据
    req.query
    // 返回数据
    res.jsonp({data: 'ok'})
})
app.lisiten(80,()=>{})

方案2-cros

CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。

前端

import axios from 'axios'
axios({
    url: 'http://www.example.com/user',
    method: 'post',
    data: {
        uname: 'zs',
        age: 20
    }
})

后端

import express from 'express'
import cros from 'cros'

const app = express()
//通过json中间实现接受数据
app.use(express.json())
//通过cros中间件实现跨域
app.use(cros())
/*
//上面语句等同于下面语句
app.use((req,res)=>{
    // 通过响应头返回
    // (1)请求的来源的网址为任意网址
    res.header('Access-Control-Allow-Origin', '*')
    // (2)请求头里可以传的数据
    res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    // (3)请求可以用的请求方式
    res.header('Access-Control-Allow-Methods', 'PUT,DELETE,POST,GET')
})
*/
app.get('/user',(req, res)=>{
    // 获取数据
    req.body
    // 返回数据
    res.send({data: 'ok'})
})
app.lisiten(80,()=>{})

服务器代理

在vue脚手架项目中

util/request.js中,例如:

import axios from 'axios'
const service = axios.create({
    baseURL: '/api'
})
export default service

api/xxx.js中,例如:

import request from '@/util/request.js'
export function addUser(data){
    return request({
        url: '/user',
        method: 'post',
        data
    })
}

vue.config.js中,例如:

module.exports = {
    devServer: {
        proxy: {
            //拦截地址
            '/api': {
                //目标地址
                target: 'http://www.example.com',
                //改变请求头中的origin为target目录
                changeOrigin: true,
                //路径重写
                pathRewrite: {
                    //替换路径中的/api的为''
                    '^/api' : ''
                }
            }
        }
    }
}