在前端开发过程当中,开发者一般都会遇到前端数据不能正常获取的问题,这就须要开发者之间’想办法‘搞到这些数据;开发过程当中咱们可能遇到的场景:javascript
后端接口数据开发中暂时不可用,须要前端在本身本地mock接口数据进行开发html
重构一个已有的前端功能,在测试环境开发功能,这时可能须要使用测试环境提供的数据来进行开发前端
解决线上问题,须要本地开启服务访问线上数据java
访问某个服务资源时,用另外一个服务器上的资源提供服务node
本地服务访问某个具体环境的数据时须要带上某些具体认证信息,如cookie信息等webpack
.....git
相似这样的场景可能还有其余的状况,其实他们归结到一个问题就是:http代理。咱们可使用http代理来解决前端开发过程当中数据获取的问题,下面就来说讲各个工具中http代理的动态实现,其实原理都是同样的。github
http代理的具体原理就不在本文中讲述了,具体能够参考这篇文章HTTP 代理原理及实现(一)。web
http代理能够分为 普通代理和隧道代理。首先说明一下,咱们这里只讲述http普通代理。npm
何为普通代理?
http客户端向代理服务器发送http报文,代理服务器作一个中间的处理,好比处理一下请求或者连接,而后向服务器发送请求,并将收到的响应转发给客户端。
其实,普通的http代理更多扮演’中间人‘的角色,对于客户端来讲,它是服务端;对于真正要连接服务端来讲它是客户端,它负责在客户端和服务器两端来回传送http报文。能够借用上文中的一幅图来讲明:
普通代理其实又能够分为两种状况:
正向代理 :
正向代理通俗的说就是客户端要访问真正的服务器A,代理在中间进行请求响应的转发,对服务器A来讲,代理隐藏了客户端的具体信息,客户端对服务器A来讲是透明的,不过代理能够设置
X-Forwarded-IP
来告诉服务器A真正的客户端IP
反向代理:
与正向代理相反的是反向代理代理真正的服务器。 例如客户端访问服务器A时,实际上访问的是代理服务器,代理服务器收到请求后而后再向真正提供服务的服务器发送请求,并将响应转发给客户端,这样对客户端来讲隐藏了真正提供服务的服务器的IP和端口;
通常使用反向代理时,须要修改DNS让域名解析到代理服务器IP。最多见的反向代理就是Nginx服务器,经过它的proxy_pass
来将请求转发到真正的提供服务的服务器。
就前端在本地开发过程当中涉及的代理通常都是正向代理,反向代理用的比较少;具体的作法是:
代理服务器经过nodejs经过`http.request(options, callback)`建立一个新的request请求来与服务器通讯,从而实现代理服务器向服务器发送请求,而后服务器返回的响应经过代理服务器response来转发服务器的响应。
下面就以几种前端经常使用的工具为例中来描述动态数据代理的实现。
fis不管是fis2仍是fis3都是支持设置动态代理,工具设计之初都有考虑支持数据mock代理的功能的,具体能够参考Mock假数据模拟都有详细的介绍。
不知用过fis的同窗注意到没有,在fis本地的服务器工程目录(mac下默认是/Users/当前用户/.fis-tmp/www
)下有一个server.js
文件,其就是用来支持动态代理前端数据用的。
经过server.js
代码,能够看出fis支持mock前端数据须要提供一个server.conf
文件(其目录默认是在当前项目根目录的config目录下),经过三种指令rewrite、redirect和proxy来完成前端不一样要求的数据mock代理;其实这三种指令是fis提供的相似语法糖的概念。
rewrite
:因为某些缘由,如验证问题或者cookie问题须要重写原有基础上的请求响应redirect
:重定向到一个新的页面网址proxy
:用其余服务器上的api地址响应当前api接口下面就描述一下fis的动态数据代理,这须要rewrite
指令;
server.conf
文件中定义rewrite规则。rewrite ^\/api /mock/mock.js
上面rewrite规则表面,当前本地服务的全部以/api开头的接口pathname都会通过根目录的mock目录下的mock.js进行重写。
这一步能够完成不少重要的做用,例如一个场景就是本地开启的服务想访问测试环境或者线上环境同pathname的api接口,这些环境的各类api接口服务须要经过cookie携带的登陆信息认证才可使用,这时因为跨域没法携带本地cookie到指定的环境致使mock数据不能成功;
固然还有其余不少场景如跨域、或者带有某些逻辑的返回指定响应的状况登登;解决这些问题通常经常使用的作法是:
用
http.request
新建立一个http. ClientRequest实例,用新建立的请求响应实例来完成真正意义上的与接口服务器进行数据请求与响应通讯;由本地的请求响应实例来与本地客户端通讯,接受客户端的请求并将代理获取的数据响应给本地客户端。
利用http.request
实现前端数据mock代理,主要利用其提供的相关事件完成,好比data
、end
和error
事件等,下面mock.js中代码展现了重写本地服务的请求与响应使其带上cookie认证信息,可以mock测试环境的api接口数据。
var http = require('http'); module.exports = function(req, res, next) { res.charset = 'utf8'; res.setHeader('Content-Type', "application/json;charset=utf8"); var buf = ''; req.on('data', function(chunk){ buf += chunk; }); req.on('end', function(){ //proxy var beta = 'betaa.qunar.com'; var options = { hostname: beta, port: 80, path: req.originalUrl, method: req.method, headers: Object.assign({}, req.headers, { 'host':beta, 'Origin':beta, 'referer':beta, 'cookie': 'xxxx' // your login cookie info here }) }; //在本地请求内容接受完毕后,新建一个http.request来负责与真正提供api服务数据的服务器通讯 var _req = http.request(options, function(_res){ var data = ""; _res.setEncoding('utf8'); _res.on('data', function(chunk){//代理响应接受到服务器数据返回 data += chunk ; }) .on('end', function(){//提供数据服务的数据接受完毕 res.end(data); // 由本地的响应实例来响应代理服务器接受到的数据内容 }) }).on('error', function(error){ res.end(); //本地响应实例返回空内容 }); _req.write(buf); //由http.request生成的请求实例来完成请求真正的提供数据服务的服务器 _req.end(); }) }
咱们的后台系统使用dva + antd来搭建,使用过 dva的同窗应该知道,官方推荐使用dora来搭建本地开发环境,包括本地开发服务器、webpack编译、hmr以及数据代理proxy等等。
dora
使用代理时,须要在项目根目录下默认提供一个proxy.config.js
文件,在该文件中配置前端数据代理的一些静态和动态的数据代理,如:
'/api/user': require('./mock/user.json'), 'POST /api/login/info: {username: 'test', ret: true} '/api/*': function(req, res){...}
具体了解请到dora-plugin-proxy查看,里面由对配置规则的详解。
dora中使用的proxy代理插件,其内部是使用阿里开源的一个代理服务器新轮子anyproxy,其提供了3类的接口能够参考anyproxy规则接口查看。在dora-plugin-proxy内部实现中覆盖了一些接口用于代理本地响应。
具体细节能够看dora-plugin-proxy的源码,下面就看一下dora代理的动态代理实现以下,仍是借上面代理的功能:
var http = require('http'); module.exports = { '/api/*': function(req, res){ res.charset = 'utf8'; var buf = req.body; //dora-plugin-proxy对req、res进行了封装 var beta = 'betaa.qunar.com'; var options = { hostname: beta, port: 80, path: req.originalUrl, method: req.method, headers: Object.assign({}, req.headers, { 'host':beta, 'Origin':beta, 'referer':beta, 'cookie': 'xxxx' // your login cookie info here }) }; //新建一个http.request来负责与真正提供api服务数据的服务器通讯 var _req = http.request(options, function(_res){ var data = ""; _res.setEncoding('utf8'); _res.on('data', function(chunk){//代理响应接受到服务器数据返回 data += chunk ; }) .on('end', function(){//提供数据服务的数据接受完毕 res.end(data); // 由本地的响应实例来响应代理服务器接受到的数据内容 }) }).on('error', function(error){ res.end(); //本地响应实例返回空内容 }); _req.write(buf); //由http.request生成的请求实例来完成请求真正的提供数据服务的服务器 _req.end(); } }
细心的同窗可能从上面代码中看出了其代理实现与fis动态代理的区别:获取本地服务器的请求内容的方式不太同样,直接使用req.body
来获取请求内容而不是利用事件实现。why ?
这是由于anyproxy的内部实现中,对http请求响应进行了封装,具体说对request实例添加了**params**、**query**和**body**属性,重写了response使其只有5个方法的对象:
Content-Type
属性的值这样,dora中动态代理就能够直接经过访问request中的body属性就能够轻松获取请求的内容了。
webpack-dev-server
是与webpack配套的搭建本地轻量级服务器的,内部使用webpack-dev-middlemare
来提供webpack的bundle,以此提供能够访问webpack打包生成的静态资源的web服务。详细的webpack-dev-server介绍能够参考webpack dev server.cn,也能够参考其官网。 本节就讲讲webpack-dev-server的前端数据代理实现。
webpack-dev-server
在设计的时候就充分考虑了数据代理的实现,内部使用http-proxy-middleware
来实现数据代理;http-proxy-middleware
提供了不少配置项,经过提供的简单配置就能完成几乎大多数状况下的数据代理。
webpack-dev-server中代理的使用方式有两种,这跟webpack-dev-server使用是同样的:
此形式是在命令行中执行webpack-dev-server命令,能够添加各类配置项,如
webpack-dev-server --inline --hot --config webpack.config.dev.js
固然它还有其余一些配置项,具体能够到官网上查看;固然也能够在webpack的配置文件webpack.config.js
中配置devServer配置项,用于表示webpack-dev-server的配置,其优先级比命令行低,也就是说命令行CLI和webpack.config.js中同时配置,命令行CLI形式会覆盖它。 webpack中的devServer配置以下:
... module: {...}, plugin: [...], devServer: { hot: true, inline: true, config: 'webpack.config.dev.js', proxy: { target: 'http://beta.qunar.com', secure: false, changeOrigin: true ... } ... }
这样能够在项目根目录下package.json
配置以下, 而后在命令行执行npm start
命令就能够启动webpack-dev-server服务了,配置的代理也可使用了。
"scripts": { "start": "webpack-dev-server --inline --hot --config webpack.config.js" }
这种形式就是使用webpack-dev-server当成npm包同样,使用其提供的node api形式来建立一个web服务,具体能够参考官网的一个例子:
var WebpackDevServer = require("webpack-dev-server"); var webpack = require("webpack"); var webpackCfg = require('./webpack.config.js'); var compiler = webpack(webpackCfg); var server = new WebpackDevServer(compiler, { // webpack-dev-server options contentBase: "/path/to/directory", hot: true, historyApiFallback: false, compress: true, proxy: { "**": "http://localhost:8080" }, clientLogLevel: "info", // webpack-dev-middleware options quiet: false, noInfo: false, lazy: true, filename: "bundle.js", watchOptions: { aggregateTimeout: 300, poll: 1000 }, // It's a required option. publicPath: "/assets/", headers: { "X-Custom-Header": "yes" }, stats: { colors: true } }); server.listen(8080, "localhost", function() {});
可将上面代码置于一个js文件中如devServer.js,那么在package.json中像下面配置一下,而后经过npm start
就能够其中服务了。
"scripts": { "start": "node devServer.js" }
那么话说回来了,相似上面fis与dora中为当前请求添加有关登陆信息cookie从而使用测试环境的数据,在webpack-dev-server中如何实现呢?
既然webpack-dev-server对数据代理有充分的支持,因此相似上面的功能在webpack-dev-server中很容易实现,经过简单的配置便可:
devServer: { ... proxy: {//代理相关的配置 '/api/**': { target: 'http://beta.qunar.com', changeOrigin: true, secure: false, headers: { "Cookie": '...' // your login cookie info here } } } }
webpack-dev-server能够很轻松的经过配置能完成相关数据代理,那么问题来了,有些场景可能须要一些额外的处理逻辑,须要配置动态代理,在其中处理相关业务逻辑;
那么webpack-dev-server能像fis和dora那样配置动态的代理么?
刚开始,查看http-proxy-middleware
相关配置项,没有发现有专门知足的配置项。无心间看到了bypass
这个配置项,其配置的function它能够访问请求的request和response对象;可是bypass
这个属性的意义是配置一些请求跳过代理,貌似与咱们要求不太符合。
最后看了webpack-dev-server内部bypass实现的源码:
options.proxy.forEach(function(proxyConfig) { var bypass = typeof proxyConfig.bypass === 'function'; var context = proxyConfig.context || proxyConfig.path; var proxyMiddleware; // It is possible to use the `bypass` method without a `target`. // However, the proxy middleware has no use in this case, and will fail to instantiate. if(proxyConfig.target) { proxyMiddleware = httpProxyMiddleware(context, proxyConfig); } app.use(function(req, res, next) { var bypassUrl = bypass && proxyConfig.bypass(req, res, proxyConfig) || false; if(bypassUrl) { req.url = bypassUrl; next(); } else if(proxyMiddleware) { return proxyMiddleware(req, res, next); } }); });
从其源码实现中,咱们能够得出一个结论:
webpack-dev-server的proxy代理配置项中若没有配置
target
属性,而且bypass
对应的属性值不返回值或者返回false,那么就不会走http-proxy-middleware代理中间件,也就是说没有走webpack-dev-server真正的代理。
鉴于上面这一结论,由于bypass配置的函数是会执行一遍的,那么咱们能够在bypass
配置项的内容中用http.request来生成新的http request对象来完成动态的数据代理,从而能够实现一些场景逻辑。例如相似fis功能代码逻辑以下:
devServer: { ... proxy: { "/api/**": { secure: false, changeOrigin: true, bypass: function(req, res) { res.charset = 'utf8'; var buf = ''; req.on("data", function(thunk){ buf += thunk; }) .on("end", function(){ var http = require('http'); var testHost = 'beta.qunar.com'; var options = { hostname: testHost, port: 80, path: req.originalUrl, method: req.method, headers: Object.assign({}, req.headers, { 'host': testHost, 'origin': testHost, 'referer': testHost, 'Cookie': "" //your login cookie here }) }; var _req = http.request(options, function(_res) { var body = ""; _res.on("data", function(chunk){ body += chunk; }) .on("end", function(){ res.end(body); }) }).on("error", function(){ res.end(); }); _req.write(buf); _req.end(); }); } } }
上面不一样工具下的动态数据代理可能存在必定的问题,就是在提供数据服务的响应实例返回的响应头后被丢弃了,代理服务器生成的响应reponse直接将内容返回而没有返回响应头;通常状况下都能知足要求,不能知足的能够根据具体使用场景来具体修改。
上面讲述的内容有什么不妥之处,还请各位斧正!!!