不需要web服务器,如何构建一个可以内部跨域的http服务(Vue+Flask)

Posted 山河已无恙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不需要web服务器,如何构建一个可以内部跨域的http服务(Vue+Flask)相关的知识,希望对你有一定的参考价值。

写在前面


我的需求:

  • 一个很老的项目,中游服务,webservice接口,需要测试,没有页面,需要我写一个小工具来测试,我准备用一个web来实现。

我需要解决的问题:

  • 这个小工具其实类似测试工具,soup UI或者postman,需要实现以下功能:
    • 满足跨域请求,尽可能的轻量。
    • 满足发送xmljson作为报文请求
    • 可以做简单的自动化压力测试
    • 可以存储所有的的接口报文信息作为发送请求
    • 可以修改设置请求url,选择存在的url路径
    • 可以展示少量的请求报文和响应报文历史数据
    • 做好的工具不需要环境可以在机器上直接运行,类似windows上的*.exe

我是是这样解决的:

  • 在技术上,涉及到的技术栈: Vue + Flask,主要是轻量
  • 数据没有持久化,因为也没有多少数据,只是简单的使用
  • 前后端分离的方式开发,打包方式:前端编译好直接放到后端的指定文件夹下,通过pythonPyInstaller打包为exe
  • 直接运行exe就会在window发布为一个服务。不需要部署

需要注意的问题

  • 前后端的整合
  • 使用PyInstaller的打包问题
  • 需要知道一点Vuepython.

人生两苦,想要却不得,拥有却失去。 ----- 烽火戏诸侯《雪中悍刀行》


开发环境准备

这里不多讲,这是我的版本:

前端

PS > npm -v
6.12.1
PS > node -v
v12.13.1
PS > vue  -V
3.7.0

后端

PS > python -V
Python 3.9.0
PS > pip -V       
pip 20.2.3 from d:\\python\\python310\\lib\\site-packages\\pip (python 3.9)
PS > PyInstaller -v
4.7

前端把需要测试的接口地址,报文通过axios 发送给后端Flask服务,Flask服务通过 requests 模块实现测试

测试工具功能:

xml,json 格式的报文发送,支持http,soap协议 的方式
支持请求报文路径自定义及相关配置
支持测试接口历史的查看(少量)
支持简单压力测试,自定义时间间隔,轮询调用接口方式
获取报文

二、编码

后端编码

后端很简单,需要注意的是,设置静态资源的加载路径,以及设置跨域

from flask import Flask, jsonify,request,render_template
from flask_cors import CORS  #跨域问题
import requests
import time


# configuration
DEBUG = True

# instantiate the app
app = Flask(__name__,static_folder = "./dist/static", template_folder = "./dist")
app.config.from_object(__name__)


# enable CORS
CORS(app, resources=r'/*': 'origins': '*')

headersXml = 
    "Content-Type": "Content-Type: text/xml;charset=UTF-8",
    "Connection": "keep-alive",
    
headersJson = 
    "Content-Type": "application/json;charset=UTF-8",
    "Connection": "keep-alive",
        
SendData = []


# sanity check route
@app.route('/test', methods=['POST','GET'])
def uag_test():
    response = ""
    if request.method == 'POST':
        post_data = request.get_json()
        data = post_data.get("content")
        url = post_data.get("url")
        id = post_data.get("id")
        nameCose = post_data.get("nameCose")
        type = post_data.get("type")
        date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        print("============================================",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"================================================")
        print("请求的URL:",url)
        print("请求的报文:",data)
        print("请求的ID:",id)
        print("请求的日期:",date)
        print("请求的报文类型:",type)
        try:
            if type == 1 :
                responseDate=requests.post(url, headers=headersXml, data=data)
                response = responseDate.text
            if type == 2 :
                responseDate=requests.post(url, headers=headersJson, data=data)
                response = responseDate.text
        except:
            return jsonify("服务器异常!")
   
       
        print("============================================",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"================================================")
        
        #response = etree.fromstring(response.encode('utf-8'))
        #htmlelement = etree.XML(etree.tostring(response, pretty_print = True,encoding='utf-8'))
        #print(etree.tostring(htmlelement))
        SendData.insert(0,
            "id":id,
            "url":url,
            "data":data,
            "response":response ,
            "date":date,
            "nameCose":nameCose
        )
        if len(SendData) > 5 :
            SendData.pop()
    return jsonify(response)


# sanity check route
@app.route("/init",methods=['GET','POST'])
def uag_init():
    print("获取全部数据")
    return jsonify(SendData)

@app.route('/', defaults='path': '')
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8085,debug=DEBUG)

前端代码

vie.config.js 代码

  
let proxyObj = ;

proxyObj['/'] = 
    target: 'http://localhost:8086',
    changeOrigin: true,
    pathRewrite: 
        '^/': ''
    

module.exports = 
    devServer: 
        host: '127.0.0.1',
        proxy: proxyObj,
    ,
    lintOnSave:false,//关闭eslintre语法检查
    assetsDir: 'static/',




main.js

import Vue from 'vue'
import App from './App.vue'

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'axios'
import VueAxios from 'vue-axios'





Vue.config.productionTip = false

Vue.use(ElementUI);
Vue.use(VueAxios, axios)

new Vue(
  render: h => h(App),
).$mount('#app')

router.js

import Vue from 'vue';
import Router from 'vue-router';
import Ping from './components/Ping.vue';
import App from './App.vue';

Vue.use(Router);

export default new Router(
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    
      path: '/ping',
      name: 'Ping',
      component: Ping,
    ,
    
      path: '/',
      name: 'App',
      component: App,
    
  ],
);

vue的相关页面代码在最后

三、前后端整合

前后端目录对应

PyInstaller打包,运行测试

这里打包是通过PyInstaller来完成的,如果为windows系统打包,则为一个单独的app.exe文件,windows上运行直接双击。linux的话,是一个可以在机器上直接运行的二进制文件,linux上运行通过./app来运行。

当然,PyInstaller可以直接通过命令行的方式来运行,也可以通过py文件的方式,下面是一个打包的脚本。

from PyInstaller.__main__ import run
#### 打包文件直接执行
if __name__ == '__main__':
    opts = ['app.py',  # 主程序文件
            '-F',  # 打包单文件
            '--icon=favicon.ico',  # 可执行程序图标
            '--add-data=dist;dist',  # 打包包含的html页面
            '--add-data=dist\\\\static;dist\\\\static',  # 打包包含的静态资
            ]

    run(opts)

直接运行就可以打包了。

python package.py

这里要说明一下文件对应的目录位置

对应的打包文件
直接发布一个服务服务
整个文件目录

app.vue 代码

<template>
  <div id="app">
    <el-container>
      <el-header><h1>接口测试小工具</h1></el-header>
      <el-main class="main-class">
        <div class="main-up-class">
          <div class="search-class">
            <!-- 搜索框 -->
            <div class="search-class-up">
              <div>
                <el-select
                  v-model="ipValue"
                  placeholder="IP"
                  style="width: 300px"
                >
                  <el-option
                    v-for="item in optionsIp"
                    :key="item"
                    :label="item"
                    :value="item"
                  >
                  </el-option>
                </el-select>
              </div>
              <div>
                <el-select
                  v-model="hostValue"
                  placeholder="端口"
                  style="width: 200px"
                >
                  <el-option
                    v-for="item in optionsHost "
                    :key="item"
                    :label="item"
                    :value="item"
                  >
                  </el-option>
                </el-select>
              </div>
              <div>
                <el-select
                  v-model="pathValue"
                  placeholder="请求路径"
                  style="width: 480px"
                >
                  <el-option
                    v-for="item in optionsPath"
                    :key="item"
                    :label="item"
                    :value="item"
                  >
                  </el-option>
                </el-select>
              </div>
            </div>
            <div class="search-class-next">
              <el-input placeholder="输入完整路径" clearable v-model="urlValue">
                <el-button slot="append" @click = "testNet">测试网络</el-button>
              </el-input>
            </div>
          </div>
          <div class="active-class">
            <el-collapse v-model="activeNames" @change="handleChange">
              <el-collapse-item title="请求响应报文" name="1">
                <div class="context-class">
                  <el-card
                    v-loading="loading"
                    shadow="never"
                    class="context-card-class"
                  >
                    <div slot="header" class="clearfix">
                      <span>请求报文</span>
                      <el-button
                        style="float: right; padding: 3px 0px 2px"
                        type="text"
                        @click="getPost"
                      >
                        请求
                      </el-button>
                      <el-button
                        style="float: right; padding: 3px "
                        type="text"
                        @click="cleartextareaRequest"
                      >
                        清空
                      </el-button>
                      <el-button
                        style="float: right; padding: 3px 0"
                        type="text"
                        @click="RequestTxt"
                      >
                        获取报文
                      </el-button>
                      <el-radio-group v-model="radio" style="float: right; padding: 3px 0">
                        <el-radio :label="1">text/xml</el-radio>
                        <el-radio :label="2">application/json</el-radio>
                      </el-radio-group>
                    </div>

                    <el-input
                      type="textarea"
                      :autosize=" minRows: 17, maxRows: 17 "
                      placeholder="请输入请求"
                      v-model="textareaRequest"
                      show-word-limit
                      autofocus="true"
                    >
                    </el-input>
                  </el-card>
                  <el-card shadow="never" class="context-card-class">
                    <div slot="header" class="clearfix">
                      <span>响应报文</span>
                      <el-button
                        style="float: right; padding: 3px 0"
                        type="text"
                        @click="clearTextareaResponse以上是关于不需要web服务器,如何构建一个可以内部跨域的http服务(Vue+Flask)的主要内容,如果未能解决你的问题,请参考以下文章

跨域的三种解决方式

如何解决IE8下Ajax调用时跨域的问题

如何解决Vue.js里面noVNC的截图问题——论可以跨域的webSocket

如何解决跨域问题

如何解决跨域问题

如何构建非跨域的 AJAX API?