创建阶段
新创建一个 ProxyAgent 文件夹
React脚手架创建
- 在
ProxyAgent文件夹路径中运行npx create-react-app proxy-agent-app生成一个 react 脚手架。 - 修改其中
public文件夹下的index.html文件,删除src文件夹 下的所有文件,新创建index.js和App.jsx文件。 - React脚手架路径中输入
npm i axios安装axios - 如果要运行该脚手架,开启一个
terminal,命令行输入cd proxy-agent-app进入当前脚手架路径,输入npm start运行脚手架。 - 脚手架运行在
http://localhost:3000/,也就是3000端口。
Express服务器创建
ProxyAgent文件夹路径中创建一个 server文件夹, 文件夹路径中运行npm i express安装express框架.- 内部创建
server1.js和server2.js - 如果要运行该服务器,开启另一个
terminal,命令行输入cd server到server文件夹路径中,输入node server1.js命令运行server1服务器 server1运行在http://localhost:8000/,也就是8000端口。
出现跨域问题阶段
保持服务器server1.js运行,再打开第二个terminal运行脚手架,当脚手架从端口3000请求端口8000中的数据时,就发生了跨域,浏览器会报错:
注意:3000端口的请求已经发送给了8000端口,因为server1 的 ternimal 在 react这边点击发送请求按钮后输出了有人请求服务器1了...,表明请求已经成功被8000端口接收到了。
当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>
)
}
}
单个服务器 代理配置阶段
- 打开 react 脚手架文件夹中的
package.json文件,在最后添加一个"proxy": "http://localhost:8000"。加上后,所有发给3000的请求就都发给8000了。 - 打开 react脚手架中的
App.jsx文件,把axios.get("http://localhost:8000/students")改成axios.get("http://localhost:3000/students") - 重新运行 react 脚手架,发现可以获取到数据
注意:
不要写成"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"只能实现加入一个服务器,当有第二个服务器加入时就需要发生改变。所以需要找到其他实现方法:
servers文件夹中再创建一个server2.js,内容类似于server1.js,但是端口号改成8001。App.jsx文件添加一个新的button用于获取teachers的信息- 把在配置单个服务器时在
package.json文件中添加的值**proxy删除,复原package.json文件。 - 在 React 脚手架中的
src文件夹中添加一个新的文件setupProxy.js。这个文件不能用ES6语法去写,而是用CJS(CommonJS)的语法去写,因为这个文件不是给前端去执行的,React脚手架会找到这个文件,并把这个文件加到Webpack的配置里面,Webpack里面用的都是Node的语法,也就是CJS的语法。配置好该文件。 - 修改
App.jsx文件 里的两个请求为axios.get("http://localhost:3000/api1/students")和axios.get("http://localhost:3000/api2/teachers") - 打开三个
terminal,分别运行server1.js,server2.js和 React脚手架,3000端口的浏览器就会出现下面结果。
具体代码实现
// 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");
}
})