精华内容
下载资源
问答
  • 阅读本文约需要9分钟大家好,我是你们的导师,我每天...1 Vue 之前后端分离的跨域webpack这个工具,已经是各大主流框架、项目毕不可少的了,也确实大大方便、简化了开发人员的日常工作。在vue-cli、angular-cli、c...

    8e8c7d0ee7371665ea284dd43cd09c89.png

    abceeb6176ed7508f775563b06695236.gif 阅读本文约需要9分钟

    大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈)。上次老师跟大家分享了JS 之继承的知识,今天跟大家分享下Vue 之前后端分离的跨域的知识。

    1 Vue 之前后端分离的跨域 webpack这个工具,已经是各大主流框架、项目毕不可少的了,也确实大大方便、简化了开发人员的日常工作。在vue-cli、angular-cli、create-react-app等等一些脚手架中也会常常遇到。

    使用webpack来构建、打包前端项目,尤其是在SPA(single-page application)的场景,已经成为了主流,其中附带的webpack-dev-server也是非常强大的功能。

    但是在nodejs后端项目的构建、打包方面,我也看到了有的社区中的一些讨论,大多数持有的态度为是不需要的。其实从我个人的角度来看,我觉得是非常必要的,我认同大家所说的只是脚本项目、本来基于nodejs就都是支持的,没必要打包,我认为打包是必要的,主要指的是交付、部署方面。

    如果只是站在前端的角度看待问题,webpack为项目提供了语法降级兼容、CSS预编译、JS合并压缩、公共代码抽离、图片转码等等,也确实在浏览器兼容性、网页优化等方面拥有非常强大、完善的插件。

    在nodejs的后端项目上,确实是没有这么多事儿的。只有脚本,聚焦的也就是脚本语法转换、打包这些简单的功能,就类似于我们在其他语言开发完模块功能后,都是会打包为动态链接库一样,来进行发布、部署。

    具体需不需要在nodejs的后端项目中使用webpack,仁者见仁智者见智,更多的根据实际场景来考虑考虑,也不能一味的追求。

    刚好最近为公司产品做了个B/S的小工具,也将自己的一些思考、做法,尽量详细的整理出来,供大家以作参考。

    0x00 从应用场景倒推设计

    个人的看法,在开始一个小项目的时候,不但要关注功能与需求,而且还要考虑好最终的部署、应用场景,尤其是小工具之类的。技术实现业务功能的方案非常多,我们往往就是要挑出比较契合的方案。比如:在线使用还是本地使用、移动端还是PC端、需不需要跨平台等等。

    就比如这次想要做的小工具,只是一个工具应用,但是在部署应用上,希望的是能做到低配置、低资源,不需要很重量级的,最好是一个绿色版程序包,随起即用,随删即卸。

    最后暂定的部署、运行目录为:

    /bin/node_modules/node/app.js/www/package.json
    • /bin 基础的应用脚本,启停服务

    • /node nodejs运行环境,如果系统自带则可以移除

    • /node_modules 应用依赖的模块

    • /www web静态资源目录,放置前端静态资源

    • /app.js 主程序文件

    • /package.json nodejs项目描述文件

    由于是B/S的方式,前端考虑使用Vue,所以页面相关的事儿都交由浏览器完成,这样前端项目的交付物就是一堆静态的资源文件,这也完全符合当前基于vue-cli开发的结果物。

    后端的话徘徊于Go和nodejs,其实我更喜欢Go,尤其是交叉编译,之前也有基于Go做过简单的B/S工具,非常的舒服,以后找时间分享出来。这次,考虑到时间紧迫,就暂定了nodejs。主要是会与mysql、redis、dubbo等服务交互,相对熟悉一些。

    这样的话,项目的整体结构为:

    7d624020bc81b3a6807d665e8f9d40e9.png项目整体结构图

    很普通的一个通用结构,在其他的语言框架中也都是通用的,有经验的开发人员,一眼就能明白。

    基于这样的结构,为了方便理解,可以从一个简单的数据流程来看一下,可以帮助大家理解每个组件所处的位置以及负责的事儿。

    aced61288d59cbed198618d38ebf830a.png数据流程以及各组件职责

    在项目的结构上,虽然都是基于nodejs,又由于是前后端分离开发,为了项目的整洁且互不干涉,也可以由前后端人员分别独立开发,所以就简单的拆分为两个项目。考虑到需要统一的编译构建,所以再添加一个主项目,负责管理构建子项目,生成可以直接交付的部署包。

    所以,从git的项目目录结构看,是这样的:

    56890213b28594703fdba59dfba6031b.pnggit项目目录结构图

    基于vue和nodejs的项目开发,没有什么很大的难度,所以在业务功能开发方面就不去深入介绍。比如一个字典功能的代码片段:

    5f600b2bc12a0d5f4f719ef59476ba02.pngVuex21cb3c805d382ae2b1bfe230ecdcff1a.pngController9845b619d7c5d409052534c1235679e3.pngService

    0x01 webpack的多环境需求

    本次主要关注点在webpack,所以,接下来主要讨论的一个就是,如何开发模式、生产模式的多环境运行与打包。

    为什么要考虑多环境?可以先从这几方面思考下

    项目在开发环境、测试环境、生产环境等,及多端中,相同的变量,需要定义为不同的属性值(非配置文件方式,直接替换编入代码中)。

    比如有代码const name = ''

    在开发模式生成结果为:const name = '/api'

    在生成模式生成结果为:const name = ''后端服务提供的接口格式为/:module/:action格式,module名字随意定义。

    从生产环境考虑,后端是不需要CORS(跨域资源共享)方式来处理跨域的。

    从开发环境考虑,由于是分离式的,如果直接请求后端接口,则需要解决跨域问题。

    所以,开发环境中需要在前端方来解决跨域,可以使用webpack来完成请求代理。这个时候如何使用统一的代理规则,而不是使用每个module名称配置规则?

    后端服务在开发调试阶段,配置项使用开发环境,部署时需要使用生产环境的配置信息,且不想使用独立的配置文件,希望能压缩为一个可执行脚本。

    其实,都不难,就跟maven中resource的filter功能一样,在编译时对配置文件中的属性进行替换。

    javascript脚本语言也一样,并没有所谓的编译过程,都是纯文本的,只能看做是源代码的预处理、合并、混淆、压缩,也正是在这些操作的过程中完成属性的替换。

    4a4032303debc1fad7eca9ffd35dcc26.pngWebpack

    接下来就主要从示例来看下如何使用webpack来实现多环境的配置与应用

    0x02 前端项目中的跨域和多环境问题

    在基于vue-cli的开发模式中,已经很好的配置了webpack来支持开发环境和生产环境。

    分别执行npm run serve和npm run build,将会使用不同的混淆压缩方式。

    我之所以想在前端来实现多环境的属性替换,是想做这么一个事儿。

    在未分离之前开发模式是这样的,我们常常会基于一个web服务来进行开发。比如使用jsp、aspx、php等来开发页面,然后在同一个项目中开发接口服务。
    最终的部署方式也都是一个包含了接口代码和页面静态资源的部署包,而我也想在这个小工具中使用这样的方式,我们可以在文章开头设想的运行目录可以看出,就是同时包含了接口服务和页面静态资源。
    整体的运行模式可以这样来描述。

    06888f5a65306191a14e94bf568aead1.png生成环境运行方式

    一个独立的程序包,在本地3000端口提供http服务,包含静态资源文件模块和业务接口模块,这样是B/S结构的基础需求。

    静态资源模块也就是前端静态资源,这些资源主要是提供给浏览器来进行页面的渲染、展示,是提供给用户操作的界面。

    业务接口模块就是俗称的后端逻辑,主要对浏览器端的一些请求做出响应处理。比如动态页面的渲染、数据接口的请求等等。

    从上图中我们可以整理出这样的信息:

    浏览器发起GET请求,地址为:http://chonger.org:3000/index.html

    服务端收到请求,获取相对路径/index.html,优先查找静态资源(static目录中)是否有配合,找到后,使用文件接口读取静态文件内容,响应给浏览器

    浏览器执行js逻辑,遇到这样一段代码,发起一个Ajax请求,地址为:/dict/all

    this.$ajax.get('/dict/all')

    而浏览器在最终发起请求的时候,会加上当前的访问域,补全请求地址为:http://chonger.org:3000/dict/all

    服务端收到请求后,同样的先匹配静态资源,匹配不上后,再匹配是否有相应注册的路由,匹配成功后,则由对应的业务逻辑完成处理,返回响应。

    上面的这些东西,对于大家应该都是老生常谈了,为什么还要不厌其烦的大费篇幅呢?一个主要是为了让还未能掌握的朋友能借鉴学习,另一个就是为了和后面的内容形成对比。

    接着,咱们来看下,在前后端分离方式中,基于webpack-dev-server的开发环境中,变成了什么样子了。先看图

    4d6e84a0f852421de0e3d818ad791550.png开发环境运行方式

    前后端分离,形如字义,被拆分为两个独立的服务,一个依旧是在3000端口坚守的后端服务,另一个就是前端项目的服务了,图中配置的端口为8080。

    分别启动两个服务,后端服务与前面描述的方式一致,前端服务运行后,会先根据webpack的配置,扫描加载前端的“源码”文件,合并打包为静态资源。同时会监控前端“源码”目录的文件变更,如果发现有文件被修改,则触发合并打包。

    同样的,从图中整理信息如下:

    浏览器发起GET请求,地址为:http://localhost:8080/index.html,访问的是前端开发服务(Webpack Dev Server)

    前端服务收到请求,获取相对路径/index.html,会在启动时合并打包的静态资源中匹配,找到后,读取文件内容,响应给浏览器

    浏览器执行js逻辑,遇到这样一段代码,发起一个Ajax请求,地址为:/dict/all

    而浏览器在最终发起请求的时候,会加上当前的访问域,补全请求地址为:http://localhost:8080/dict/all

    this.$ajax.get('/dict/all')

    前端服务收到请求后,匹配不到静态资源,转而匹配路由,同样匹配不到,响应404

    先暂停流程,来看一下前后端分离中最常遇到的这个404问题,当前这种情况下,解决方式不少,先简单的看看

    8e8c7d0ee7371665ea284dd43cd09c89.png

    方案一:修改ajax请求,直接访问后端服务

    //修改代码逻辑this.$ajax.get('http://localhost:3000/dict/all')
    • 浏览器在最终发起请求时,判断将要请求的地址域和当前的访问域不同,触发跨域请求问题。对应的解决办法就是在后端服务开启CORS来处理。从文章最前面的介绍中已经聊到过,最终的运行方式,并不会存在跨域问题,所以,实际场景并不会跨域,而在后端开启跨域逻辑处理,可以认定为是多余、且不安全的。

    在方案一中,直接将后端接口地址这样硬编码是不可取的,所以,通常会使用全局的变量来配合

    //全局的后端服务接口地址const API_URL = 'http://localhost:3000'//修改代码逻辑this.$ajax.get(API_URL + '/dict/all')

    这样来编码,实现功能是ok的,但我一般不会建议采用这种方式,因为这种方式,还需要让前端开发人员在调用接口时,得不停想着进行拼接。所以,一般会改为这样,示意如下:

    //使用代理模式来处理ajax请求,可以进行地址、参数转换和响应数据初步处理this.$ajax = {  API_URL: 'http://localhost:3000',  //get请求  get: function(url) {    axios.get(this.API_URL + url)  }}//修改调用代码逻辑,最终回被转换为 http://localhost:3000/dict/all//对于业务开发人员,仅需要关心接口文档中的地址的path部分,对于开发,屏蔽了一些不重要的信息干扰this.$ajax.get('/dict/all')

    方案二:使用代理,反向代理后端服务到当前访问域
    如果我们如果想让上面的第4步不出现404,那么就可以在webpack-dev-server中配置代理规则

    //将开发服务接收到的/dict开头请求,转发到http://localhost:3000服务上devServer: {  proxy: {    '/dict': {      target: 'http://localhost:3000'    }  }}

    这样,我们就可以继续来看下处理流程

    4. 前端服务收到请求后,匹配不到静态资源,转而匹配路由,这时候匹配到代理规则/dict,然后生成访问地址:http://localhost:3000/dict/all,由前端服务直接发起请求
    5. 后端服务收到请求后,同样的先匹配静态资源,匹配不上后,再匹配是否有相应注册的路由,匹配成功后,则由对应的业务逻辑完成处理,返回响应。
    6. 前端服务收到后端服务的响应后,代理请求完成,然后将接受到的响应内容,直接响应给浏览器

    使用代理的方式,对于浏览器端是感知不到的,所有的请求都在当前访问域中,只是一些请求最终会被打到后端服务上。

    • 这样的方式,关注点就在代理规则的配置上了,前端开发人员可就得盯着这里了,随着后端接口越来越多,比如/rule/search、/user/search、/order/:id等等,就得配置多个代理规则,这样也是行的通的,只是,总感觉不是有点多余。

    • 解决N多个代理规则配置问题,第一个是可以让后端统一下接口规则,比如都写为/api/rule/search、/api/user/search、/api/order/:id等,这样前端就只需要配置一个统一的代理规则

    //修改请求前缀this.$ajax = {  API_URL: '/api',  //get请求  get: function(url) {    axios.get(this.API_URL + url)  }}//调用逻辑this.$ajax.get('/dict/all')//配置代理proxy: {  '/api': {    target: 'http://localhost:3000'  }}

    这种方式也能解决问题,前端同学简单省事儿了,后端同学郁闷了,写接口注册到路由的时候为什么非要加上个没意义的/api,就为了区分静态资源和接口逻辑么,感觉也是有点多余。曾经说好的不分批次,结果你却悄悄躲到了/api后面。对于后端同学的地址就会变成这样

    http://localhost:3000/index.htmlhttp://localhost:3000/js/index.jshttp://localhost:3000/api/dict/allhttp://localhost:3000/api/rule/search
    • 所以在统一代理规则上,还可以这样做,后端同学还是使用默认的方式,定义接口为/rule/search、/user/search、/order/:id,修改前端的代理配置为

    //修改请求前缀this.$ajax = {  API_URL: '/api',  //get请求  get: function(url) {    axios.get(this.API_URL + url)  }}//调用逻辑this.$ajax.get('/dict/all')//配置代理proxy: {  '/api': {    target: 'http://localhost:3000',    //重写路径,抹掉/api,     //e.g. http://localhost:8080/api/dict/all => http://localhost:3000/dict/all    pathRewrite: {      "^/api": ""    }  }}

    这样,前后端都可以按照自己舒服的方式进行开发了。

    开发模式下,一切都会相安无事,一旦到了集成打包发布后,运行起来就会出问题了。因为实际运行中,并没有了代理服务,所以前端ajax的请求就会是http://localhost:3000/api/dict/all。这个时候,就需要在发布打包的时候修改逻辑为

    //修改请求前缀this.$ajax = {  //开发模式  //API_URL: '/api',  //发布模式  API_URL: '',  //get请求  get: function(url) {    axios.get(this.API_URL + url)  }}

    这样,就需要前端同学打包的时候,来回的切换这里的代码,一个不留神,就会出错。这种重复性的替换工作,也实时的会让打包的时候强迫关注这里,更期望的方式是类似于mvn package -Pxx这样的方式来实现自动切换。

    也就是核心的主角DefinePlugin,

    DefinePlugin允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和生产模式的构建允许不同的行为非常有用。如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是DefinePlugin的用处,设置它,就可以忘记开发环境和生产环境构建的规则。

    Link: https://webpack.docschina.org/plugins/define-plugin/

    在vue-cli中的vue-cli-service,已经对DefinePlugin进行了封装,可以通过mode和.env[.mode]配合来实现。具体的资料可以参考官方文档:在客户端侧代码中使用环境变量

    前端项目中,添加环境变量配置

    VUE_APP_customPrefix=/api
    VUE_APP_customPrefix=

    同时修改项目中使用的地方,示意如下

    //修改请求前缀this.$ajax = {  API_URL: process.env.VUE_APP_customPrefix,  //get请求  get: function(url) {    axios.get(this.API_URL + url)  }}

    这样,就可以彻底放手不用再去关注不同环境需要变更的地方了,只需要关注业务,完成开发,然后执行npm run serve或者npm run build就ok了。

    在开发模式生成的包中,请求的地址就是:http://localhost:8080/api/dict/all
    发布包中,请求的地址就是:http://chonger.org/dict/all

    0x03 后端项目中的多环境问题

    后端项目考虑多环境问题,不像前端那样必须得替换,因为前端毕竟只是纯纯的静态资源。而后端常用的做法就是添加启动参数或者使用配置文件

    • 添加启动参数设置些简单的数据类型还ok,复杂或量大的情况下,一般都是使用配置文件,然后在启动参数中指定使用的配置文件。类似于java -jar spring-boot.jar --spring.profiles.active=prod,然后就会加载application-prod.yml配置文件。这种方式在内部使用是没有问题的,如果是作为小工具类的,提供的运行文件中,往往就会暴露其他环境的配置信息。

    • 使用配置文件的另一种方式就是资源文件替换,在项目中会按照不同的环境名称设立文件夹,然后分别放不同的环境配置文件。打包的时候通过resource指定配置目录。这种方式也是ok的,只是在小工具的范畴内,更希望的就类似一个.exe文件,直接双击使用,也不用附带什么配置文件之类的。

    在nodejs后端项目中,添加webpack的相关依赖

    "devDependencies": {  "clean-webpack-plugin": "^3.0.0",  "copy-webpack-plugin": "^5.0.3",  "webpack": "^4.32.2",  "webpack-cli": "^3.3.2",  "webpack-merge": "^4.2.1"}

    创建一个build目录,来放置跟构建相关的配置,由于是要考虑多环境,所以按照webpack.config.js、webpack.dev.js、webpack.test.js等,来拆分配置。

    • webpack.config.js存放公共的配置信息

    • webpack.dev.js、webpack.test.js分别存放不同环境的配置信息

    公共的webpack配置信息如下

    const path = require("path")const CopyPlugin = require('copy-webpack-plugin')module.exports = { entry: {    app: './src/main.js'  },  target: 'node',  output: {    filename: "[name].js",    //输出到主项目目录    path: path.resolve(process.cwd(), "../dist")  },  //排除项目依赖,不进行打包,由运行时安装提供  externals: externals(),  plugins: [    //附带项目描述文件,主要进行依赖安装    new CopyPlugin(["./package.json"])  ]}function externals() {  let manifest = require('../package.json');  let dependencies = manifest.dependencies;  let externals = {};  for (let p in dependencies) {      externals[p] = 'commonjs ' + p;  }  return externals;}

    由于在不同环境中需要使用不同的数据库配置,可以在build目录中创建profile文件夹,存放不同环境的配置信息

    开发环境配置:

    module.exports = {  database: {    user: {      host: "127.0.0.1",       port: "8066",     }  }}

    测试环境配置:

    module.exports = {  database: {    user: {      host: "192.168.1.100",       port: "3306",     }  }}

    接着将在不同的环境中使用DefinePlugin来替换掉值,编写不同环境的打包配置

    开发环境配置:

    const webpack = require('webpack')const merge = require("webpack-merge")const config = require("./webpack.config.js")const profile = require("./profile/profile.dev.js")//参数值转换为字符串 Object.keys(profile).map(key => {  profile[key] = JSON.stringify(profile[key])}) //合并基础配置 module.exports = merge(config, {  mode: 'development',  devtool: 'eval',  plugins: [    //添加替换插件    new webpack.DefinePlugin(profile),  ]})

    测试环境配置:

    const webpack = require('webpack')const merge = require("webpack-merge")const TerserPlugin = require('terser-webpack-plugin')const config = require("./webpack.config.js")const profile = require("./profile/profile.test.js")//参数值转换为字符串 Object.keys(profile).map(key => {  profile[key] = JSON.stringify(profile[key])}) //合并基础配置 module.exports = merge(config, {  mode: 'production',  optimization: {    //配置压缩参数    minimizer: [      new TerserPlugin({        terserOptions: {          ecma: 5        }      })    ]  },  plugins: [    //添加替换插件    new webpack.DefinePlugin(profile),  ]})

    完成多环境的配置后,就可以直接在代码中使用应用变量标记

    const databaseConfig = database

    这样,代码在合并压缩后,就会将不同profile中定义的值,替换进去,打包后的结果如图

    4ebbf7fbb6dc5c76b708a551ca9efde4.png后端项目合并压缩结果

    以为到这里就功成身退了么?其实,有个深坑在那里等着。

    前面的一波操作,可以看到主要关注的是发布时候的打包模式。回过头,来看看开发模式。按照后端项目的方式,启动项目

    > node ./src/main.js

    会发现如下提示:

    //文件名省略~XXXXXXX.js:29const databaseConfig = database                       ^ReferenceError: database is not defined

    开发模式已经不能直接运行的了,因为这个database变量只有在webpack打包后才会有具体的值,直接启动会提示未定义。

    总不能每次代码修改都要执行webpack --config ./build/webpack.dev.js打出测试环境的包,然后node ./app.js来启动吧,虽然目的是一致的,但是这种操作,总是有点让人感觉到抓狂,就不能像前端项目那样,直接源文件变更自动编译、还能替换变量适应多环境么?答案是: 还真的可以,只是需要实现一个简易版的类webpack-dev-server

    经过多环境的修改,由于源代码中放入了替换标记,导致源代码不能直接执行,必须通过webpack的打包才能获得完整的执行文件。就是上面说的手动执行的过程,而要实现的简易版开发服务,也正是帮助自动来完成这两步。

    先来讨论下思路,回过头来看前端项目的开发模式,启动开发服务后,就可以直接访问打包后的文件,那么,这些文件在哪里?webpack把打包后的文件放哪里了?答案是:内存文件系统

    Memory-fs一个简易的内存文件系统,提供了标准的文件操作接口,访问项目主页。

    而webpack也提供了inputFileSystem和outputFileSystem接口来可以扩展不同的文件系统。

    先完成第一步,启动webpack打包,并将结果写入内存文件系统(不落地)

    在项目中添加memory-fs依赖

    "devDependencies": {  ……  "memory-fs": "^0.4.1",  ……}

    接着,参看webpack-dev-server的方式来简单实现一个开发服务。

    a4cfbb8379cdb474838f9d63df531624.png前端简易版开发服务

    在build目录中创建一个start.js来作为开发服务的启动文件

    const webpack = require("webpack")const MemoryFS = require('memory-fs')//使用开发模式的webpack配置 const config = require('./webpack.dev')//创建文件服务 const fs = new MemoryFS()//创建webpack打包器const compiler = webpack(config); //更改webpack打包器的输出文件系统为内存模式compiler.outputFileSystem = fs//开始执行打包compiler.run((err, stats) => {  //输出日志  console.log(stats.toString({    chunks: false,    colors: true  }))  //第二步});

    96b8c25569a7473235cae20d3138756b.png打包结果

    接下来就是第二步,执行打包后的脚本文件

    执行脚本就简单了,可以从memory-fs中读出文件内容后来进行执行。

    • 一种执行方式,使用熟悉的eval()函数来执行代码字符串。

    • 另一种就是使用nodejs的vm模块来执行。

    2.1 读取编译后的脚本文件
    由于后端项目打包成了一个app.js,所以直接从webpack的stats中获取打包后的文件路径,然后读取出来

    //开始执行打包compiler.run((err, stats) => {  //输出日志  console.log(stats.toString({    chunks: false,    colors: true  }))  //获取打包文件输出路径  const file = stats.compilation.outputOptions.path + '/' + stats.compilation.chunks[0].files[0]  //读出文件内容  const content = fs.readFileSync(file)});

    2.2 执行脚本字符串

    • 使用eval执行

    eval(content.toString())
    • 使用vm执行

    const vm = require('vm')const NativeModule = require('module')//将代码字符串包装为nodejs模块,function (exports, require, module, __filename, __dirname) {}const wrapper = NativeModule.wrap(content)//执行函数const result = new vm.Script(wrapper).runInThisContext()const m = { exports: {} }result.call(m, m.exports, require, m)

    相关参考:http://nodejs.cn/api/vm.html

    好了,这次就先聊这些,非常感谢大家耐心看完。

    刚开始从期望的一个小工具使用形态来完成一个简易版的系统设计,并简单的阐述了系统各模块功能以及交互流程,然后到项目的组织搭建。
    在开发中又简单的聊了下前后端分离的跨域问题,以及前端在多环境中的打包。
    最后,又介绍了在nodejs后端项目使用webpack打包、nodejs后端项目的多环境问题以及使用webpack完成nodejs后端项目的开发模式服务。

    参考来源:http://chonger.org/2019/06/03/20190603231900.html
    今天 就分享这么多, Vue 之前后端分离的跨域 学会了多少欢迎在留言区评论,对于有价值的留言,我们都会一一回复的。如果觉得文章对你有一丢 丢帮助 ,请点右下角【在看】,让更多人看到该文章。

    8e8c7d0ee7371665ea284dd43cd09c89.png

    3ac3fa31efd9003659931079fc30aab8.gif
    展开全文
  • 笔者被朋友推荐,于是采用这个作为后端接口的测试工具,以下是postwoman的web界面内容 ## 测试django接口 由于是本地的小项目,于是直接使用http://127.0.0.1:8000/xxx以get请求进行测试,然而明明这么简单的东西...

    postwoman

    Postwoman是一个用于替代Postman,免费开源、轻量级、快速且美观的API调试工具。笔者被朋友推荐,于是采用这个作为后端接口的测试工具,以下是postwoman的web界面内容
    在这里插入图片描述## 测试django接口
    由于是本地的小项目,于是直接使用http://127.0.0.1:8000/xxx以get请求进行测试,然而明明这么简单的东西居然没有正常返回。于是在浏览其中按F12进入开发者模式,进入Network栏查看发现,当前请求好像除了点问题:
    在这里插入图片描述大概内容就是status=blocked,transferred=CORS missing Allow Origin,然后在开发者模式查看了下响应,发现前端的确收到了后端传来的文件,但是页面当中却一直呈现network error错误,找了前端同学请教一番,原来是跨域请求的问题。

    配置django处理跨域问题

    笔者查看网上资源,发现了比较经典的解决方法

    • 安装第三方插件,然后配置django项目中的setting.py
      pip install django-cors-headers
    • 添加到已安装的应用程序中
    INSTALLED_APPS  =(
         ... 
        ' corsheaders ',
         ... 
    )
    
    • 添加中间件类来收听响应
    MIDDLEWARE  = [
        ... 
        # 跨域请求中间件
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware',
         ... 
    ]
    
    • 然后在setting.py文件后添加
    # 跨域允许的请求方式,可以使用默认值,默认的请求方式为:
    # from corsheaders.defaults import default_methods
    CORS_ALLOW_METHODS = (
        'GET',
        'POST',
        'PUT',
        'PATCH',
        'DELETE',
        'OPTIONS'
    )
    
    # 允许跨域的请求头,可以使用默认值,默认的请求头为:
    # from corsheaders.defaults import default_headers
    # CORS_ALLOW_HEADERS = default_headers
    
    CORS_ALLOW_HEADERS = (
        'XMLHttpRequest',
        'X_FILENAME',
        'accept-encoding',
        'authorization',
        'content-type',
        'dnt',
        'origin',
        'user-agent',
        'x-csrftoken',
        'x-requested-with',
        'Pragma',
    )
    
    # 跨域请求时,是否运行携带cookie,默认为False
    CORS_ALLOW_CREDENTIALS = True
    # 允许所有主机执行跨站点请求,默认为False
    # 如果没设置该参数,则必须设置白名单,运行部分白名单的主机才能执行跨站点请求
    CORS_ORIGIN_ALLOW_ALL = True
    
    • 重新启动django服务器
      python manager.py runserver
      配置完成后,然后又测试了一下,发现还是一样的问题。可能是djangodjango-cors-headers版本的问题,笔者用2.0版,由于一些原因,将不做版本的更换。那得想想其他办法啊,想到之前做视频推流用到nginx,可以尝试用nginx配置转发,然后在nginx端来处理跨域问题(因为项目中需要部署多个服务器,如果每个django服务器都要作者么多工作,就有点麻烦了)。

    nginx配置跨域问题

    此文不涉及nginx安装及部署工作,只在配置文件nginx.conf上作相关工作,在nginx中,可以通过在httphttps中添加server来实现转发

    http{
      server{
        listen    8081;
        location    /  {
          proxy_pass http://127.0.0.1:8000;
        }
      }
    }
    

    如上配置就可以实现将ngnix_ip:8081的所有请求发送到http://127.0.0.1:8000中,为了解决跨域问题,在server中添加

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    

    nginx.conf的对应部分变化如下

    http {
         server {
             listen      8081;
         add_header Access-Control-Allow-Origin *;
         add_header Access-Control-Allow-Headers X-Requested-With;
         add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    
            location / {
                  proxy_pass http://127.0.0.1:8000;
           }
    
        }
     }
    
    

    配置完成后重新运行nginx,然后再在postwoman当中以nginx地址进行请求时,便可以得到测试结果了

    展开全文
  • 一、WebMvcConfigurer 中添加跨域配置 注意: addExposedHeader() 一定要配置, 否则前端将获取不到响应头中的 TOKEN /Authorization 参数 @Configuration public class MvcConfig implements WebMvcConfigurer { ...

    一、WebMvcConfigurer 中添加跨域配置

    注意: addExposedHeader() 一定要配置, 否则前端将获取不到响应头中的 TOKEN /Authorization 参数

    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
    
        @Bean
        public CorsFilter corsFilter() {
            final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
            final CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.setAllowCredentials(true); /*是否允许请求带有验证信息*/
            corsConfiguration.addAllowedOrigin("*");/*允许访问的客户端域名*/
            corsConfiguration.addAllowedHeader("*");/*允许服务端访问的客户端请求头*/
            corsConfiguration.addAllowedMethod("*"); /*允许访问的方法名,GET POST等*/
            corsConfiguration.addExposedHeader("token");/*暴露哪些头部信息 不能用*因为跨域访问默认不能获取全部头部信息*/
            corsConfiguration.addExposedHeader("TOKEN");
            corsConfiguration.addExposedHeader("Authorization");
            urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
            return new CorsFilter(urlBasedCorsConfigurationSource);
        }
    }
    

    二、SecurityConfig中的配置修改

    添加一

    配置允许跨域访问 ,不要加 .disable(); ,否则一样无法访问

    http.cors();    //.disable();
    

    添加二

         // 当出现跨域的OPTIONS请求时,发现被拦截,加入下面设置可实现对OPTIONS请求的放行。
        http.authorizeRequests().
               requestMatchers(CorsUtils::isPreFlightRequest).
               permitAll();
        }
    

    当前完整授权代码如下

        /**
         *  授权
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            // 只拦截需要拦截的所有接口, 拦截数据库权限表中的所有接口
            List<AdminAuthority> authoritys = adminAuthorityMapper.selectList(null);
            ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry eiur = http.authorizeRequests();
            authoritys.forEach((auth) -> {
                eiur.antMatchers(auth.getUrl()).hasAnyAuthority(auth.getUrl());
            });
            // 配置token验证及登录认证,过滤器
            eiur
                    // 登录接口不需要权限控制,可删除,目前该接口不在权限列表中
                    .antMatchers("/auth/login", "POST").permitAll()
                    // 设置JWT过滤器
                    .and()
                    .addFilter(new JWTValidFilter(authenticationManager(), resolver))
                    .addFilter(new JWTLoginFilter(authenticationManager(), resolver)).csrf().disable()
                    // 剔除session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
            // 开启跨域访问
            http.cors(); //.disable();
            // 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
            http.csrf().disable();
            // iframe 跳转错误处理 Refused to display 'url' in a frame because it set 'X-Frame-Options' to 'deny'
            http.headers().frameOptions().disable();
            // 当出现跨域的OPTIONS请求时,发现被拦截,加入下面设置可实现对OPTIONS请求的放行。
            http.authorizeRequests().
                    requestMatchers(CorsUtils::isPreFlightRequest).
                    permitAll();
        }
    

    个人开源项目(通用后台管理系统)–> https://gitee.com/wslxm/spring-boot-plus2 , 喜欢的可以看看
    本文到此结束,如果觉得有用,动动小手点赞或关注一下呗,将不定时持续更新更多的内容…,感谢大家的观看!

    展开全文
  • 一、什么是跨域访问 举个栗子:在A网站中,我们希望使用Ajax来获得B网站中的特定内容。如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题。你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,...

    一、什么是跨域访问

    举个栗子:在A网站中,我们希望使用Ajax来获得B网站中的特定内容。如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题。你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,否则就是不安全的。跨域访问违反了同源策略,同源策略的详细信息可以点击如下链接:Same-origin_policy; 
    总而言之,同源策略规定,浏览器的ajax只能访问跟它的HTML页面同源(相同域名或IP)的资源。

    二、什么是JSONP

    JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。

    由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用<script>元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。更具体的原理需要更多篇幅的讲解,小伙伴可以自行去百度。

    三、JSONP的使用

    前端的使用示例

    JQuery Ajax对JSONP进行了很好的封装,我们使用起来很方便。前端示例:

        $.ajax({
                type:"GET",
                url:"http://www.deardull.com:9090/getMySeat", //访问的链接
                dataType:"jsonp",  //数据格式设置为jsonp
                jsonp:"callback",  //Jquery生成验证参数的名称
                success:function(data){  //成功的回调函数
                    alert(data);
                },
                error: function (e) {
                    alert("error");
                }
            });
    

    需要注意的地方是:

    • dataType,该参数必须要设置成jsonp
    • jsonp,该参数的值需要与服务器端约定,详细情况下面介绍。(约定俗成的默认值为callback)

    后端的配合示例

    JQuery Ajax Jsonp原理

    后端要配合使用jsonp,那么首先得了解Jquery Ajax jsonp的一个特点: 
    Jquery在发送一个Ajax jsonp请求时,会在访问链接的后面自动加上一个验证参数,这个参数是Jquery随机生成的,例如链接 
    http://www.deardull.com:9090/getMySeat?callback=jQuery31106628680598769732_1512186387045&_=1512186387046 
    中,参数callback=jQuery31106628680598769732_1512186387045&_=1512186387046就是jquery自动添加的。 
    添加这个参数的目的是唯一标识这次请求。当服务器端接收到该请求时,需要将该参数的值与实际要返回的json值进行构造(如何构造下面讲解),并且返回,而前端会验证这个参数,如果是它之前发出的参数,那么就会接收并解析数据,如果不是这个参数,那么就拒绝接受。 
    需要特别注意的是这个验证参数的名字(我在这个坑上浪费了2小时),这个名字来源于前端的jsonp参数的值。如果把前端jsonp参数的值改为“aaa”,那么相应的参数就应该是 
    aaa=jQuery31106628680598769732_1512186387045&_=1512186387046

    后端接收与处理

    知道了Jquery Ajax Jsonp的原理,也知道了需要接受的参数,我们就可以来编写服务器端程序了。 
    为了配合json,服务器端需要做的事情可以概括为两步:

    第一步、接收验证参数

    根据与前端Ajax约定的jsonp参数名来接收验证参数,示例如下(使用SpringMVC,其他语言及框架原理类似)

        @ResponseBody
        @RequestMapping("/getJsonp")
        public String getMySeatSuccess(@RequestParam("callback") String callback){

    第二步、构造参数并返回

    将接收的的验证参数callback与实际要返回的json数据按“callback(json)”的方式构造:

         @ResponseBody
        @RequestMapping("/getMySeat")
        public String getMySeatSuccess(@RequestParam("callback") String callback){
            Gson gson=new Gson();   //google的一个json工具库
            Map<String,String> map=new HashMap<>();
            map.put("seat","1_2_06_12");
            return callback+"("+gson.toJson(map)+")";   //构造返回值
        }

    四、总结

    最终,前后端的相应代码应该是这样的: 
    前端

        $.ajax({
                type:"GET",
                url:"http://www.deardull.com:9090/getMySeat", //访问的链接
                dataType:"jsonp",  //数据格式设置为jsonp
                jsonp:"callback",  //Jquery生成验证参数的名称
                success:function(data){  //成功的回调函数
                    alert(data);
                },
                error: function (e) {
                    alert("error");
                }
            });

    后端

        @ResponseBody
        @RequestMapping("/getMySeat")
        public String getMySeatSuccess(@RequestParam("callback") String callback){
            Gson gson=new Gson();
            Map<String,String> map=new HashMap<>();
            map.put("seat","1_2_06_12");
            logger.info(callback);
            return callback+"("+gson.toJson(map)+")";
        }

    需要注意的是:

    • 前端注意与后端沟通约定jsonp的值,通常默认都是用callback。
    • 后端根据jsonp参数名获取到参数后要与本来要返回的json数据按“callback(json)”的方式构造。
    • 如果要测试的话记得在跨域环境(两台机器)下进行。

    转自原博客地址:https://blog.csdn.net/ltmtianming/article/details/75269773 

    展开全文
  • - 创建后端项目 - 搭建前端项目 - 跨域CORS
  • 跨域

    2019-03-20 14:10:27
    要掌握跨域,首先要知道为什么会有跨域这个问题出现 确实,我们这种搬砖工人就是为了混口饭吃嘛,好好的调个接口告诉我跨域了,这种阻碍我们轻松搬砖的事情真恶心!为什么会跨域?是谁在搞事情?为了找到这个问题的...
  • 一、什么是跨域访问 举个栗子:在A网站中,我们希望使用Ajax来获得B网站中的特定内容。如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题。你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,...
  • Vue.js 跨域请求 .net core 3.0后端API 简介 .net core 3.0部分 1.Startup.cs的配置 2.Controller部分 Vue.js 部分 总结 简介 最近想把公司的一套订单系统(winform版本的),按照大致相同逻辑自己做成网页版,...
  • 无非就是“后端加一下跨域的相关响应头即可”。 但是在前后联调过程中,发现跨域并没有那么容易解决。 后端环境是springboot,一开始用的重写WebMvcConfigurer里面的addCorsMappings,添加相应的响应头,但是发现...
  • 前后端分离项目快速搭建数据库准备后端搭建1、快速创建个SpringBoot项目2、...测试数据库是否连接正确8、编写后端工具类代码(封装结果集、日期处理、解决跨域请求)9、编写后端的增删改查代码前端搭建由于篇幅有限...
  •  训练营中,为扩展学员们的编程能力,我们给他们介绍了一些工具和库。目前有位JavaScript学员Kalina,他汇总了这些工具的清单,以分享给其他的代码爱好者。 基于Kalina的清单,我们JavaScript训练营的辅导员Ivan ...
  • ajax跨域

    2018-12-14 16:36:57
    一、 什么是跨域 前台调用后台接口时如果不在同一个域就会出现跨域问题,这里的域包括端口和域名。 二、跨域问题出现的条件 1、浏览器限制 浏览器发送请求时会进行安全校验 ,...打开chrome调试工具,切换到netwo...
  • 功能描述: 使用模型编辑工具编辑模型,每个模型对应数据库一张表,内含多个字段。 从模型生成数据库(现支持sqlite)。 从模型生成golang微服务代码,并... 从模型生成vue项目,并可自动安装依赖,启动开发测试...
  • 3,测试环境开发过程中,需要H5和后端测试服务器频繁交互,并随时查看请求和返回数据。 问题: H5直接连接开发服务器有跨域问题。 跨域解决方案(今天主要说简单粗暴的第5种): //一般情况下还是应该让后端配合用...
  • 到main.js中注册store 测试vuex是否成功拿到数据 接着很容易的做法去检测你的数据有没有拿到,在App.vue中写,需要引入mapAction import {mapActions} from 'vuex' export default { mounted(){ this.getTexts(1) },...
  • 前言 由于自己平时只做做demo,并没有遇到太多跨域问题,今天通过几个样例模拟...测试步骤如下: 1.为了实现跨域访问的效果,需要下载http-server 作为一个服务器 npm install http-server。用来挂载静态页面 inde...
  • 解决前端跨域记录

    2019-12-07 10:41:47
    博主开发前端不是很多,每次开发前端测试都会跨域,然后浪费不少时间, 今天记录一下省的下次再浪费时间 开发工具hbuilderx 后端python,现在有后端接口 127.0.0.1:5000/api/getinfo 前端ip 127.0.0.1:8082 这个只是...
  • 学习Java后端有一段时间了,最近实验室的官网需要重构,我搭了一个springboot+maven架子给给新入坑的同学使用,包括常见的shiro权限控制,验证码生成与使用,异常处理。跨域处理)。 1创建一个springboot项目 创建一...
  • thinkphp5.1解决跨域问题

    千次阅读 2020-02-16 16:06:29
    最近项目开发中,前后端是分离的,各自有自己的接口,一般情况下,后端程序员开发环境中,通过postman或其他测试工具调用接口都没有什么问题。等到前端程序员静态页面完成后,和后端程序员调试接口的时候,最大的...
  • 跨域问题

    2019-03-19 22:22:31
    要掌握跨域,首先要知道为什么会有跨域这个问题出现 确实,我们这种搬砖工人就是为了混口饭吃嘛,好好的调个接口告诉我跨域了,这种阻碍我们轻松搬砖的事情真恶心!为什么会跨域?是谁在搞事情?为了找到这个问题的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,395
精华内容 3,358
关键字:

后端测试工具跨域