代理服务器实现 跨域

741 阅读7分钟

创建阶段

新创建一个 ProxyAgent 文件夹

React脚手架创建

  1. ProxyAgent 文件夹路径中运行 npx create-react-app proxy-agent-app生成一个 react 脚手架。
  2. 修改其中 public 文件夹下的 index.html 文件,删除src文件夹 下的所有文件,新创建index.jsApp.jsx文件。
  3. React脚手架路径中输入npm i axios安装axios
  4. 如果要运行该脚手架,开启一个terminal,命令行输入 cd proxy-agent-app进入当前脚手架路径,输入 npm start运行脚手架。
  5. 脚手架运行在http://localhost:3000/,也就是3000端口

Express服务器创建

  1. ProxyAgent 文件夹路径中创建一个 server文件夹, 文件夹路径中运行 npm i express 安装express框架.
  2. 内部创建 server1.jsserver2.js
  3. 如果要运行该服务器,开启另一个terminal,命令行输入 cd server 到server文件夹路径中,输入 node server1.js 命令运行server1服务器
  4. server1 运行在http://localhost:8000/ ,也就是8000端口

出现跨域问题阶段

保持服务器server1.js运行,再打开第二个terminal运行脚手架,当脚手架从端口3000请求端口8000中的数据时,就发生了跨域,浏览器会报错:

image.png

注意3000端口的请求已经发送给了8000端口,因为server1ternimal 在 react这边点击发送请求按钮后输出了有人请求服务器1了...,表明请求已经成功被8000端口接收到了。

image.png

3000客户端发送请求给8000服务器时,要通过Ajax引擎,发送成功了,但当响应的数据回来时,还要通过AJAX引擎,在这个时候被AJAX引擎拦截了,因为跨域了。 ‌

代理Proxy指的是一个中间人的角色,这个中间人开在3000端口,也就是3000端口不仅开着 React 脚手架,还开着一个代理服务器。当React客户端发送请求时,3000客户端会先把请求发送给代理,代理再把请求发送给8000服务器,然后8000服务器把相应的数据先返还给代理服务器,再从代理服务器把数据传回给React客户端。

这里8000服务器可以把数据返还给3000端口的代理服务器是因为跨域本质上是3000端口的 react 客户端中的AJAX引擎把响应拦截了,但因为代理服务器没有AJAX引擎,所以不存在跨域的问题,同源策略不限制代理,代理只是负责转发。

具体代码

// server文件夹中的 server1.js 文件内容

// 1. 引入 express
const express = require("express");

// 2. 创建应用对象
const app = express();

// 只要有人请求server1,就会输出内部的话
app.use((request, response, next) => {
    console.log("有人请求服务器1了...")
    next()
})

// 3. 创建路由规则 GET 请求
// request 是对请求报文的一个封装
// response 是对响应报文的一个封装
app.get("/students", (request, response) => {
    const students = [
        {id:"001", name:"张三", age:10},
        {id:"002", name:"李四", age:20},
        {id:"003", name:"王五", age:30}
    ]
    // 设置响应
    response.send(students);
})

// 4. 监听端口启动服务

app.listen(8000, (error) => {
    if(!error) {console.log("服务已经启动,请求服务器地址为:http://localhost:8000/students");}
})

// react脚手架中 index.js 文件内容

import React, { Component } from 'react'
import ReactDOM from "react-dom"
import App from "./App.jsx"

export default class Index extends Component {
    render() {
        return (
            <div>
                <App></App>
            </div>
        )
    }
}
ReactDOM.render(<Index />, document.getElementById("root"))
// React 脚手架中 index.js 文件内容

import React, { Component } from 'react'
import axios from "axios"
import "./App.css"

export default class App extends Component {
    // 当点击button按钮后,会给8000端口的server1发送GET请求获取students数据
    getStudentData = () => {
        axios.get("http://localhost:8000/students").then(
            response => {console.log("成功了!", response.data);},
            error => {console.log("失败了!", error);}
        )
    }
    render() {
        return (
            <div>
                <button className="button" onClick={this.getStudentData}>点击获取5000端口服务器上的学生数据</button>
            </div>
        )
    }
}

单个服务器 代理配置阶段

  1. 打开 react 脚手架文件夹中的 package.json 文件,在最后添加一个"proxy": "http://localhost:8000"。加上后,所有发给3000的请求就都发给8000了。
  2. 打开 react脚手架中的 App.jsx文件,把 axios.get("http://localhost:8000/students") 改成axios.get("http://localhost:3000/students")
  3. 重新运行 react 脚手架,发现可以获取到数据

image.png

注意:

不要写成"proxy": "http://localhost:8000/students" ,因为如果8000端口返回的如果不仅有students信息,还有teachers信息和其他信息,这里写上 /students 就表示只找 /students 中的信息。

如果把App.jsx文件中的 axios.get("http://localhost:3000/students")改成axios.get("http://localhost:3000/index.html"),发现服务器没有收到这次请求,但是3000客户端中却输出了内容,表明这次请求成功了,并且这个内容是React脚手架文件中public文件夹下的**index.html文件内部的所有代码。

这表明"http://localhost:3000/"public文件夹的根路径,在3000/ 后面输入的内容会先到public文件夹中查找是否有对应文件存在,只有当Public文件夹下没有找到对应的内容后才会到8000端口的服务器中查找。

具体代码实现

// package.json 文件正确修改后内容(...表示内部省略)

{
    "name": "proxy-agent-app",
    "version": "0.1.0",
    "private": true,
    "dependencies": {...},
    "scripts": {...},
    "eslintConfig": {...},
    "browserslist": {...},
    "proxy": "http://localhost:8000"
}
// App.jsx 文件正确修改后内容

import React, { Component } from 'react'
import axios from "axios"
import "./App.css"

export default class App extends Component {
    getStudentData = () => {
        axios.get("http://localhost:3000/students").then(
            response => {console.log("成功了!", response.data);},
            error => {console.log("失败了!", error);}
        )
    }
    render() {
        return (
            <div>
                <button className="button" onClick={this.getStudentData}>点击获取5000端口服务器上的学生数据</button>
            </div>
        )
    }
}

多个服务器 代理配置阶段

因为在package.json 文件最后添加一个"proxy": "http://localhost:8000"只能实现加入一个服务器,当有第二个服务器加入时就需要发生改变。所以需要找到其他实现方法:

  1. servers文件夹中再创建一个server2.js,内容类似于server1.js,但是端口号改成8001
  2. App.jsx 文件添加一个新的button用于获取teachers的信息
  3. 把在配置单个服务器时在package.json文件中添加的值**proxy删除,复原package.json文件。
  4. 在 React 脚手架中的 src文件夹中添加一个新的文件 setupProxy.js。这个文件不能用ES6语法去写,而是用CJS(CommonJS) 的语法去写,因为这个文件不是给前端去执行的,React脚手架会找到这个文件,并把这个文件加到Webpack的配置里面,Webpack里面用的都是Node的语法,也就是CJS的语法。配置好该文件。
  5. 修改App.jsx文件 里的两个请求为axios.get("http://localhost:3000/api1/students")axios.get("http://localhost:3000/api2/teachers")
  6. 打开三个terminal,分别运行 server1.jsserver2.js 和 React脚手架,3000端口的浏览器就会出现下面结果。

image.png

具体代码实现

// App.jsx 文件正确修改后内容

import React, { Component } from 'react'
import axios from "axios"
import "./App.css"

export default class App extends Component {
    getStudentData = () => {
        axios.get("http://localhost:3000/api1/students").then(
            response => {console.log("成功了!", response.data);},
            error => {console.log("失败了!", error);}
        )
    }
    getTeacherData = () => {
        axios.get("http://localhost:3000/api2/teachers").then(
            response => {console.log("成功了!", response.data);},
            error => {console.log("失败了!", error);}
        )
    }
    render() {
        return (
            <div>
                <button className="button" onClick={this.getStudentData}>点击获取8000端口服务器上的学生数据</button>
                <button className="button" onClick={this.getTeacherData}>点击获取8001端口服务器上的老师数据</button>
            </div>
        )
    }
}
// server文件夹中的 server1.js 文件内容

// 1. 引入 express
const express = require("express");

// 2. 创建应用对象
const app = express();

// 只要有人请求server1,就会输出内部的话
app.use((request, response, next) => {
    console.log("有人请求服务器1了...")
    next()
})

// 3. 创建路由规则 GET 请求
// request 是对请求报文的一个封装
// response 是对响应报文的一个封装
app.get("/students", (request, response) => {
    const students = [
        {id:"001", name:"张三", age:10},
        {id:"002", name:"李四", age:20},
        {id:"003", name:"王五", age:30}
    ]
    // 设置响应
    response.send(students);
})

// 4. 监听端口启动服务
app.listen(8000, (error) => {
    if(!error) {
        console.log("服务1已经启动,请求服务器地址为:http://localhost:8000/students");
    }
})

// server文件夹中的 server2.js 文件内容

// 1. 引入 express
const express = require("express");

// 2. 创建应用对象
const app = express();

// 只要有人请求server2,就会输出内部的话
app.use((request, response, next) => {
    console.log("有人请求服务器2了...")
    next()
})

// 3. 创建路由规则 GET 请求
// request 是对请求报文的一个封装
// response 是对响应报文的一个封装
app.get("/teachers", (request, response) => {
    const students = [
        {id:"001", name:"小a", age:10},
        {id:"002", name:"小b", age:20},
        {id:"003", name:"小c", age:30}
    ]
    // 设置响应
    response.send(students);
})

// 4. 监听端口启动服务
app.listen(8001, (error) => {
    if(!error) {
        console.log("服务2已经启动,请求服务器地址为:http://localhost:8001/teachers");
    }
})