Vue-CliとPythonの連携

# １．目的

・Vue-CliとPythonの連携を図りたい。(Vue-CLiを使ったアプリ上でPythonを動作させたい)

# 2．方法

・フロントエンドはVue-Cliで構成する。Pythonコードはバックエンドに配置する。
・Vue-CliとPythonとのインターフェースは[Python-shell](https://qiita.com/NT1123/items/09aed7b23388190cba23#3-1-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95)を使う。
・フロントエンドとバックエンドはそれぞれ別のポートを割り当て、双方が通信できるようにする。

![4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/24bf5f07-211d-2fcb-3287-e865ca7ee605.jpeg)

# 3．環境設定/準備

使用したパッケージやライブラリのインストール方法やversionを下記に記載しておきます。


| 項目 | version | インストール方法 | 
|:-----------|---------|------:|
| Vue-Cli      |3.9.3|    [LINK先を参照](https://qiita.com/NT1123/items/fda5c66f9ffdba878c8a#12-vue-cli%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) |    
| python-shell     |1.0.8| [LINK先を参照](https://qiita.com/NT1123/items/09aed7b23388190cba23#3-1-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95) |   
| express     |4.17.1 |        |     
| bodyparser  |  | 説明略     |     
| node.js      |12.14.0 | 説明略    |  
| npm   |6.3.14  | 説明略   |  

# 4．作成したアプリ

Webページより、入力した値に+3を加算し、その演算結果を表示します。

![5.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/8a9a8747-cb73-4416-7ec9-bc83b38dad7d.jpeg)

★上記図中の(1)～(6)は、下記に説明する動作(1)～(6)に対応しています。

(1)「デモ画面」数値をテキストボックスに入力します。
(2) 取得ボタンをクリックします。
(3) (1)で入力した数値がindex.js(Python-shellを実行する)を介して、Pythonコード(sample.py)に送られます。
(4) sample.pyは、入力された数値に+3を加算します。
(5) sample.pyは、演算を完了した数値をindex.jsに返却します。
(6) index.jsは、sample.pyから受け取った数値を「デモ画面」に返します。
(7)「デモ画面」は、演算結果を表示します。

## 4-1 システム構成

Vue-Cliはすでにインストールされ、使用できる状態であるという前提です。

## 4-2 ディレクトリ構成

各ファイルのディレクトリ構造は下記のとおり配置されています。

![6.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150223/ef9046a7-f85b-208b-7e01-356e4ceff4a3.jpeg)


## 4-3 コード


```javascript:/src/component/main.js
// eslint-disable-next-line
/* eslint-disable */ 
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'  //axiosを使う場合、main.jsでimportする。

Vue.config.productionTip = false
Vue.prototype.$axios = axios  //axiosを使う場合、main.jsでこの行の追記が必要

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})
```


```vuejs:/src/components/Top.vue
<template>

  <div>
    <h1>デモ画面</h1>
    <input type="button" value="移動" @click="goNewTask()"> <br>
    <input type="number" v-model="message"><input type="button" value="取得" @click="getdata()">
    <p> <font size="2"> 入力データ :{{ $data.message }} </font> </p>
    <p> <font size="2"> 出力データ :{{ $data.result }} </font> </p>
    <p> <font size="2"> 状態 :{{ $data.state }} </font> </p>
  </div>

</template>

<script>
// eslint-disable-next-line
/* eslint-disable */ 
import * as d3 from 'd3'  //有効にする

export default {
  name: 'top',
  data: function(){
    return { 
        message:'',  //入力データを格納する変数。
        result :'',  //演算結果を格納する変数。
        state:"wait" //現在の状況を格納する変数。
    }
  },
  methods: {
    //テキストボックスに入力されたデータをバックエンドに送り、バックエンドから演算結果を受け取り、その結果を表示するメソッド
    getdata:function(){
        this.state="getting data"
        this.$axios.get('http://192.168.0.4:3000/api',{params:{dat:this.message}})
          .then(function(response){
            console.log(response.data.message)  //バックエンドから返却された演算結果をconsole.logしている。
            this.result= response.data.message
            this.state="done"    
            }.bind(this))  //Promise処理を行う場合は.bind(this)が必要
          .catch(function(error){  //バックエンドからエラーが返却された場合に行う処理について
            this.state="ERROR"
            }.bind(this))
          .finally(function(){
            }.bind(this))}
     
  } 
}

</script>

```

```javascript:/bkend/index.js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json())


//CORSポリシーを無効にしている。
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});


app.get('/api', function(req, res) {

  var {PythonShell} = require('python-shell');
  var pyshell = new PythonShell('sample.py');  
  console.log("req")
  console.log(req.query.dat) //フロントエンドから受け取ったデータをconsole.logしている。

  pyshell.send(req.query.dat); //本コードからpythonコードに'req.query.dat'を入力データとして提供する 

  //pythonコード実施後にpythonから本コードにデータが引き渡される。
  pyshell.on('message',  function (data) {
    console.log("return data")
    res.send({
      message: data   //pythonで実施した演算結果をフロントエンドに返している。
    })
  })

})

app.listen(3000)
```

```python:/bkend/sample.py
import sys
data = sys.stdin.readline()  #標準入力からデータを取得する
num=int(data)

def sum(a):
    return a+3

print(sum(num))  #printの内容をpython-shellに返却する
```

## 4-4 実行方法




# 5．ポイント

## 5-1 Pyshon-shellを使用した理由
Vue-cliはwebpack上で動作していますが、python-shellは、webpack上では動作しないようです。よって、新たにvue-cliで動作させていないサーバーを立ち上げその上でpython-shellを動作させるようにしました。



#5-2 CORS問題の回避方法
xxx.xxx.xx.xx:91 から xxx.xxx.xx.xx:3000にアクセスする場合、ドメインが異なるのでセキュリティー上通信がブロックされます[CORS問題]。CORS問題を避けるために、アクセスする側に対策を施す場合と、アクセスされる側に対策を施す場合があるが、本件ではアクセスされる側に対策を施しました。詳しくは[こちら](https://microcms.io/blog/async-request/)をご覧ください。

```javascript:/bkend/index.jsの一部
/bkend/
//CORSポリシーを無効にしている。
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
```