1. 简介
请求转发,其实是使用 webpack-dev-server 的代理功能来实现的,本节为大家介绍 webpack-dev-server 的代理功能和主要使用场景。
2. 正向代理与反向代理
在进入正题之前,先简单地先介绍一下什么是代理,字面意义上理解就是委托第三方处理有关事务。网络代理分为正向代理和反向代理,所谓正向代理就是顺着请求的方向进行的代理,即代理服务器他是由你配置为你服务,去请求目标服务器地址。反向代理正好与正向代理相反,代理服务器是为目标服务器服务的。虽然整体的请求返回路线都是一样的都是 Client 到 Proxy 到 Server。
webpack-dev-server 的代理功能更偏向于正向代理,即是为前端开发者服务的。
3. 页面准备和接口请求
我们在项目中,新建如下文件:
// webpack.common.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
var path = require('path');
module.exports = {
entry: {
index: "./src/index.jsx",
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node-modules/,
use: 'babel-loader'
},
{
test: /\.(jpg|jpeg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
limit: 2048
}
}
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
]
},
{
test: /\.(eot|svg|ttf|woff)$/,
use: 'file-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new CleanWebpackPlugin()
]
};
// webpack.dev.js
var path = require('path');
var webpack = require('webpack');
var merge = require('webpack-merge');
var commonConfig = require('./webpack.common');
var devConfig = {
mode: 'development',
devtool: "cheap-module-eval-source-map",
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
open: true,
port: 3000,
hot: true // 开启热更新
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
module.exports = merge(commonConfig, devConfig);
// webpack.prod.js
var merge = require('webpack-merge');
var commonConfig = require('./webpack.common');
var prodConfig = {
mode: 'production',
devtool: "cheap-module-source-map",
};
module.exports = merge(commonConfig, prodConfig);
// src/index.js
// src/index.js
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
axios.get('http://127.0.0.1:3600/api/hello.json').then(res => {
console.log(res)
this.setState({
msg: res.data.msg
})
}).catch(e => {
console.error(e);
})
}
render() {
return <div>{this.state.msg}</div>
}
}
ReactDom.render(<App />, document.getElementById('root'));
<!--src/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>esmodule-oop</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
.babelrc:
{
"presets": [
[
"@babel/preset-env",
{
"corejs": 2,
"useBuiltIns": "usage"
}
],
"@babel/preset-react"
]
}
"scripts": {
"dev": "webpack --config ./build/webpack.dev.js --watch",
"dev-analyse": "webpack --config ./build/webpack.dev.js --profile --json > stas.json",
"build-analyse": "webpack --config ./build/webpack.prod.js --profile --json > stas.json",
"dev-server": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
3. 接口准备
这里就不用 node 写 server 了,直接 http-server 起一个简单的服务。运行 npm run build,然后在 dist 下新建如下文件: api/hello.json
{
"msg": "hello world"
}
进入 dist,使用 http-server -p 3600 开启服务,访问 http://127.0.0.1:3600
image.png
4. 代理请求
但是我们部署的服务可能会改变地址(先上来讲是域名),另外,在开发环境的时候,我们的后台接口可能还没有开发完成,需要我们访问其他的开发地址或者测试地址。那该怎么做呢?一个最容易想到的方案就是将域名配置到统一的地方,一处更改,多处生效。比如我们封装一层请求,request,为其配置 host,每次请求的时候自动加上 host。我们的代码中只要写相对路径即可:
request.get('/api/hello.json')
但其实 webpack dev-server 为我们提供了方便地配置。一般为了防止跨域,我们会将静态资源和接口资源部署在同一个服务下,比如上面的 dist 下面加一个 api 目录,当然实际可能并不是这样,比如使用了反向代理等。在代码中我们写相对地址即可:
axios.get('/api/hello.json')
如果仅仅这样写,那么代码请求的始终是当前服务下的 api/hello,每次修改代码,需要部署之后才能生效。这显然是不可能的。我们关闭之前的服务,新建一个文件:server/api/hello.json,进入 server 使用 3000 端口重新开启服务。
然后我们使用 dev-server 开启服务:npm run dev-server
image.png
可以看到, 请求的是 3600 端口下的接口,但是我们这里的 dev-server 仅提供了页面资源,并没有接口资源,接口资源在线上(这里用 3000 端口代替)。
这时候就要使用上述我们提到的代理了:
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
open: true,
port: 3000,
hot: true,// 开启热更新
proxy: {
'/api': 'http://127.0.0.1:3000'
}
},
重新运行 npm run dev-server,如下:
image.png
4. 跨域
有的人会想,那这样做其实和在源码中通过配置去写也是一样的呀,只要最终达到以下效果就可以了:
axios.get('http://127.0.0.1:3600/api/hello.json').then(res => {
console.log(res)
this.setState({
msg: res.data.msg
})
}).catch(e => {
console.error(e);
})
那么我们代码作如上改写,关闭代理后,npm run dev-server 看看:
image.png
打开 console:
image.png
可以看到,报了跨域。这是因为浏览器现代浏览器的同源安全策略,禁止跨域发送请求。而 proxy 是通过一个代理服务器帮我们转发请求,不受浏览器的跨域限制。但其实对于很多后端服务,出于安全考虑,我们也会做跨域限制,这时候接口就无法正常返回数据呢。对于这种情况,我们可以使用 changeOrigin 来解决:
我们把请求地址改回相对地址,然后修改 proxy 配置如下:
proxy: {
'/api': {
target: 'http://127.0.0.1:3600',
changeOrigin: true
}
}
就可以帮我们解决接口跨域问题了。
5. 重写路径
有时候,我们会遇到路径不一致的场景,比如我们本来是请求 hello 接口的,但这个接口正在开发中,后端可能丢了一个 demo 接口让我们先用,还有的时候我们的生产接口可能放在 api 下面,但是测试接口并没有这一层路径,这时候我们就可以通过重写路径来保证访问地址的正确性:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3600',
pathRewrite: {'^/api' : ''}
}
}
}
};
6. 过滤
有时你不想代理所有的请求。可以基于一个函数的返回值绕过代理。
在函数中你可以访问请求体、响应体和代理选项。必须返回 false 或路径,来跳过代理请求。
例如:对于浏览器请求,你想要提供一个 HTML 页面,但是对于 API 请求则保持代理。你可以这样做:
proxy: {
"/api": {
target: "http://localhost:3000",
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf("html") !== -1) {
console.log("Skipping proxy for browser request.");
return "/index.html";
}
}
}
}
- 代理多个路径
如果你想要代码多个路径代理到同一个target下, 你可以使用由一个或多个「具有 context 属性的对象」构成的数组:
proxy: [{
context: ["/auth", "/api"],
target: "http://localhost:3000",
}]
8. 使用 https
默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,修改配置如下:
proxy: {
"/api": {
target: "https://other-server.example.com",
secure: false
}
}
9. 小结
proxy 的配置相当丰富,甚至还可以帮我们修改 header,携带 cookie 等。这些都让我们能在不修改源码的情况下通过简单的配置即可做到,远远优于直接手动在源码进行修改的方法,极大方便了我们的开发。
参考
正向代理与反向代理的区别
https://webpack.js.org/configuration/dev-server/#devserverproxy
https://www.webpackjs.com/configuration/dev-server/#devserver-proxy
Webpack-dev-server的proxy用法
网友评论