MENU

スティード400を復活!!〜キャブ洗浄編〜

はじめに

最近またバイクが乗りたくなってきたので、
高校時代に乗っていたスティード400を修理することにしました。
大学3年ぐらいの時に軽く乗って以来実家の倉庫に眠りっぱなしなので、かれこれ8年ぶり?ぐらいになります。
今年のお盆に作業して、車のバッテリーと接続してエンジンをかけることは出来たのでまとめます。

洗浄方法

自分ではバイクの整備はよく分からないので詳しい友人に聞きながら作業を進めました。
まずはキャブをオーバーホールしなきゃならないだろうなーと思っていたのですが、 中のパーツ交換するではなく洗浄するだけならもっと簡単な方法があるとの事で、その方法を実践しました。

なにやらスティードはタンクからのガソリンを一旦シート下のポンプに流し、そのポンプからキャブへ燃料を送っている構造になっており、
ポンプのチューブからクリーナーを噴射すれば簡単にキャブ内の洗浄が出来るらしいです。
今回は友人お勧めのワコーズのクリーナーを使いました。

いざ実践!

こちらが僕のスティード400です。
高校の頃に乗っていたままで実家の車庫に眠っていました。
バッテリーも上がっておりエンジンもかかりません。
f:id:ti_taka:20181117212708j:plain

まずはシートを外します。ここはシート下のボルトを外すだけなので簡単ですね。
f:id:ti_taka:20181117212730j:plain

次はタンクを外しにかかりますが、スティードの構造上燃料コックを外さないとタンクが外せません。
燃料コックの奥にあるネジを回し、外していきます。必ずOFFにしてガソリンの流れを止めておくように。
(かなり奥にあるので鈍らないように注意!!)
f:id:ti_taka:20181117212758j:plain

こんな感じで外せます。
f:id:ti_taka:20181117212820j:plain

燃料コックが外せると、タンクを少し上に浮かせることができるので、チューブに繋がってるボルトを外します。
f:id:ti_taka:20181117212845j:plainf:id:ti_taka:20181117212856j:plainf:id:ti_taka:20181117212906j:plain

また、タンク端のネジを外します。
f:id:ti_taka:20181117212934j:plain

外せました!
f:id:ti_taka:20181117213023j:plainf:id:ti_taka:20181117213035j:plain

次に、まずキャブのドレンボルトを外しておきます。
f:id:ti_taka:20181117213101j:plain

ここを外しておくことでポンプ側からクリーナーを入れた時に汚れが外に流れ出てきます。
キャブの中にガソリンを残したまま長期間放置してしまうとキャブの中に汚れが溜まり、
クリーナーを入れる前からドロっとした汚れが出てくるので、
あらかじめ紙などを敷いておきましょう。
右側と左側のキャブそれぞれにあるのでどちらも忘れずに開けましょう。
洗浄途中の写真ですが、こんな感じに外れます。
f:id:ti_taka:20181117213131j:plain

次にようやくポンプ側のチューブを取り外しにかかります。
ガソリンを送るポンプはシート下にあります。
f:id:ti_taka:20181117213153j:plain

こちらのチューブがキャブに繋がっているのでこれを外します。
f:id:ti_taka:20181117213236j:plainf:id:ti_taka:20181117213241j:plain

ここが外せればあとはこちらのクリーナーを入れるだけです。

  • 注入口をしっかりと塞いで5秒ほど噴射して注入
  • 汚れがだらだらとドレンボルトから流れ出てくるのでしばらく待つ

というのを繰り返します。
f:id:ti_taka:20181117213301j:plain

するとこんな感じに徐々に出てくる汚れが薄くなっていきます。
f:id:ti_taka:20181117213323j:plain

最終的にはこれぐらいに薄まるまで上記の手順を繰り返します。
f:id:ti_taka:20181117213346j:plainf:id:ti_taka:20181117213400j:plain

これで概ねキャブの洗浄は完了です。
後はこのままポンプやチューブをつなぎ直してエンジンをかけてみます。
ドレンボルトから流れ出た分以外のクリーナーが少しキャブの中に残ってしまいますが、ガソリンで流されるので問題ないそうです。

とりあえず車のバッテリーと接続してエンジンをかけてみます。
車側のプラス端子とバイク側のバッテリープラス端子を接続し、車側のマイナス端子はどこか導電体の金属の箇所に接続し、アースします。
f:id:ti_taka:20181117213437j:plainf:id:ti_taka:20181117213450j:plainf:id:ti_taka:20181117213501j:plain

これでセルを回すと無事エンジンがかかりました!!
かなり久しぶりにエンジンの音を聞いたので感動です!!

終わりに

今回はとりあえず簡易的な方法でキャブを洗浄し、エンジンをかけるところまで実践しました。
が、実際に乗るためには整備しなくてはならない箇所がまだまだ残っています。 ぱっと思いつく限りでこんなに。。

  • エアクリーナーのゴムパッキン交換
  • エンジンオイル交換
  • チェーン交換
  • サイドカバー交換
  • シフトレバー交換
  • フロントフォークのオイル漏れ修理
  • シート交換
  • ウィンカー交換
  • マフラー交換
  • ディスクブレーキ専用液補充
  • タイヤ、ホイール交換
  • ラジエーター修理
  • 全体、サビ落とし、塗装

来年、遅くとも再来年にはもう一度乗れるよう細々と修理していきます!

Vue.jsとFlaskでフルスタックなWebアプリの開発環境を構築 その2〜〜投稿画面作成〜〜

はじめに

前回作ったこちらの環境を用いて、タスク管理アプリ的なものを作成していきたいと思います。

kittagon.hateblo.jp

タスクのモデルを作成(Flask)

まずはこちらのサイトを参考に、タスクのクラスを作成し一通りFlask側のみで動作するところまでを作成いたします。
2. Flaskチュートリアル

前回からのファイル構成変更点

その前に、前回作成した構成を若干修正します。

前回は下記の構成だったかと思いますが、今後の扱いやすさを考慮しファイル名とその中の記載内容を変更します。

app_dir
 ┣appserver.py → manage.pyにファイル名変更
 ┣frontend/
 ┗backend/
   ┣api.py
   ┣application.py → __init__.pyにファイル名変更
   ┗config.py

それぞれファイル名を変更したものの中身は以下です。

# __init__.py(←application.py)

from flask import Flask
from flask_cors import CORS

app = Flask('FLASK-VUE',
            static_folder = "./dist/static",
            template_folder = "./dist")
app.config.from_object('backend.config.BaseConfig')

from backend.api import api
app.register_blueprint(api, url_prefix="/api")
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")
# maange.py (←appserver.py)

from backend import app

if __name__ == '__main__':
    app.run()

sqliteを使用する設定

チュートリアルに倣い、sqliteはSQLAlchemyで操作します。
まずは以下コマンドでインストールします。

Flask-SQLAlchemyのインストール

$ pip install Flask-SQLAlchemy

次にconfig.pyを編集します.

# config.py

import os
class BaseConfig(object):
    DEBUG = True
    ## 以下を追記
    SQLALCHEMY_DATABASE_URI = 'sqlite:///backend.db'
    # cookieを暗号化する秘密鍵
    SECRET_KEY = os.urandom(24)
# __init__py

from flask import Flask
from flask_cors import CORS
import requests
from flask_sqlalchemy import SQLAlchemy # ここを追記

app = Flask('FLASK-VUE',
            static_folder = "./dist/static",
            template_folder = "./dist")
app.config.from_object('backend.config.BaseConfig')

db = SQLAlchemy(app) # ここを追記

from backend.api import api
app.register_blueprint(api, url_prefix="/api")
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")

models.pyの作成

チュートリアルに倣い、以下のように記述します。

# backend/models.py

from backend import db

class Task(db.Model):
    __tablename__ = 'tasks'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.Text)
    text = db.Column(db.Text)

    def to_dict(self):
    return dict(
        id=self.id,
        title=self.title,
        text=self.text
    )

    def __repr__(self):
        return '<Task id={id} title={title!r}>'.format(
            id=self.id, title=self.title)

def init():
    db.create_all()

コマンドラインからデータベースを初期化します。

$ python
>>> from backend.models import init
>>> init()

テータを登録してみます。

$ python
>>> from backend.models import Task, db
>>> Task.query.all()
[]
>>> task = Task(title='title', text='text')
>>> db.session.add(task)
>>> db.session.commit()
>>> Task.query.all()
[<Task id=1 title=u'title'>]

>>> task = Task.query.get(1)
>>> task
<Task id=1 title=u'title'>
>>> task.title = 'hello'
>>> task.text = 'Hello world'
>>> db.session.add(task)
>>> db.session.commit()
>>> Task.query.all()
[<Task id=1 title=u'hello'>]

APIの作成(Flask)

まずはFlask側で登録のAPIを作成します。
基本は先程のチュートリアルを参考にしつつ、以下のように記述します。

# backend/api.py

from flask import Blueprint, jsonify, request, url_for, make_response
from random import *
from flask_cors import CORS

from backend import app, db
from backend.models import Task

api = Blueprint('api', __name__)

@api.route('/hello/<string:name>/')
def say_hello(name):
    response = { 'msg': "Hello {}".format(name) }
    return jsonify(response)

@api.route('/random')
def random_number():
    response = {
        'randomNumber': randint(1, 100)
    }
    return jsonify(response)

@api.route('/get', methods=['GET'])
def get_taks():
    taks = Task.query.order_by(Task.id.desc()).all()
    taks_dict = [task.to_dict() for task in taks]
    return jsonify(taks_dict)

@api.route('/add', methods=['POST'])
def add_task():
    task = Task(
            title=request.form['title'],
            text=request.form['text']
            )
    db.session.add(task)
    db.session.commit()
    task = Task.query.order_by(Task.id.desc()).first()
    id = str(task.id)
    r = make_response(id)
    return r

@api.route('/delete', methods=['POST'])
def delete_task():
    id=request.form['id']
    task = Task.query.get(id)
    db.session.delete(task)
    db.session.commit()
    r = make_response(id)
    return r

http://127.0.0.1:5000/api/getにアクセスして、以下のようにレスポンスが返却されればOKです。

[
    {
        "id": 1,
        "text": "Hello world",
        "title": "hello"
    }
]

登録画面の作成(Vue.js)

ルーティングの追加

続いて、Vue.jsのSPA側で、タスクの登録と閲覧をする画面を作成します。

$ cd frontend/src/component
$ cp Home.vue Tasks.vue

また、SPA側のルーティングにもタスク画面を表示するように記述します。

// frontend/src/router/index.js


/////// 省略 ///////
const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' },
  { path: '/tasks', component: 'Tasks' },//ここを追記
  { path: '*', component: 'NotFound' }
]
/////// 省略 ///////

bootstrap-vueの導入

登録画面を作成するにあたってはbootstrap-vueを使用したいと思います。

公式サイトに記載されている通り、以下コマンドでインストールします。

$ npm install jquery
$ npm install bootstrap-vue

また、こちらも記載されている通り、frontend/src/main.jsfrontend/src/App.vueに以下の記述を記載します。

// frontend/src/main.js

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue' //ここを追記
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

Vue.use(BootstrapVue) //ここを追記
// frontend/src/App.vue

/////// 省略 ///////

<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
  name: 'App'
}
</script>

/////// 省略 ///////

画面の作成

そして、Tasks.vueにて先程作成したバックエンド側のAPIを叩き、UIに表示するviewを作成します。
デザインは適当です笑。

// frontend/src/component/Task.vue

<template>
  <div class="container">
    <div align="center">
      <div class="col-sm-8 col-md-6 col-lg-6">
      <p>タスクの登録</p>
        <b-form v-if="show">
          <b-form-group label="Title:"
                        label-for="title">
            <b-form-input id="title"
                          type="text"
                          required
                          placeholder="Enter title">
            </b-form-input>
          </b-form-group>
          <b-form-group label="Text:"
                        label-for="title">
            <b-form-textarea id="text"
                             placeholder="Enter something"
                             :rows="3"
                             :max-rows="6">
            </b-form-textarea>
          </b-form-group>
          <div align="center">
            <div class="col-sm-4 col-md-2 col-lg-2">
              <b-button block @click="addTask" variant="success">Add</b-button>
            </div>
          </div>
        </b-form>
      </div>
    </div>

    <b-list-group v-for="(task, index) in tasks" :key='index'>
      <b-list-group-item>
        {{ task.title }}<br>
        {{ task.text }}
        <b-button v-bind:src="task.id" block @click="deleteTasks(index, task.id)">削除</b-button>
      </b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data () {
    return {
      randomNumber: 0,
      tasks: [],
      show: true
    }
  },
  methods: {
    getTasks () {
      const path = 'http://localhost:5000/api/get'
      axios.get(path)
        .then(response => {
          this.tasks = response.data
        })
        .catch(error => {
          console.log(error)
        })
    },
    addTask () {
      const path = 'http://localhost:5000/api/add'
      var title = document.getElementById('title')
      var text = document.getElementById('text')
      let params = new URLSearchParams()
      params.append('title', title.value)
      params.append('text', text.value)
      var titleValue = title.value
      var textValue = text.value
      title.value = ''
      text.value = ''
      axios.post(path, params)
        .then(response => {
          var id = response.data
          var task = {'id': id, 'text': textValue, 'title': titleValue}
          this.tasks.unshift(task)
          console.log(response)
        })
        .catch(error => {
          console.log(error)
        })
    },
    deleteTasks (taskIndex, taskId) {
      console.log(taskIndex)
      console.log(taskId)
      const path = 'http://localhost:5000/api/delete'
      let params = new URLSearchParams()
      params.append('id', taskId)
      this.tasks.splice(taskIndex, 1)
      axios.post(path, params)
        .then(response => {
          console.log(response)
        })
        .catch(error => {
          console.log(error)
        })
    }
  },
  created () {
    this.getTasks()
  }
}
</script>

http://127.0.0.1:5000/tasksを開けば以下のように登録と削除ができる画面が動作すると思います。
f:id:ti_taka:20181113213852g:plain

あまりタスク管理っぽくないですが。笑

終わりに

これで、バックエンドとフロントエンドの役割が明確なWebアプリを作成出来ました。
あとはバックエンドのFlaskでAPIを作成し、フロントエンドのVue.jsでUIを描画すればいろんなWebアプリが作れるようになります。
次回はこのWebアプリにログイン機能をつけたいと思います。

↓続き kittagon.hateblo.jp

参考サイト

Vue.jsとFlaskでフルスタックなWebアプリの開発環境を構築 その1〜〜環境構築〜〜

はじめに

最近のWebアプリはReact.jsやVue.jsを使用したSPAが主流かと思いますが、データ取得のAPIを実装したりなど、サーバサイドの実装も必要になります。

自分はJavascriptに不慣れなのでサーバサイドはJavascriptではなくPythonを使いたかったので、Flaskと共存できる構成にしたかったのがきっかけです。

いろいろと探してみたらすでに先人がやっていました。

Full-stack single page application with Vue.js and Flask
Single Page Apps with Vue.js and Flask

以下のような構成で開発できるようにします。

app_dir
  ┗frontend  // Vue.jsのプログラム
  ┗backend   // Flaskのプログラム

英語記事なのと、それぞれの記事で若干やっていることが異なるため
それぞれを混ぜる形で環境を構築したので、備忘として自分の言葉でまとめておこうと思います。

vue-cliで雛形のインストール(フロントエンド)

以下コマンドでvue-cliをインストールできます。

$ npm install -g vue-cli

アプリの雛形をインストールします。

$ vue init webpack frontend

? Project name (frontend)[Enter]
? Project description (A Vue.js project)[Enter]
? Author (hogehoge <hogehogehogehoge@gmail.com>)[Enter]
? Vue build (Use arrow keys)[Enter]
❯ Runtime + Compiler: recommended for most users
  Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed
 in .vue files - render functions are required elsewhere
? Install vue-router? (Y/n)[Enter]
? Use ESLint to lint your code? (Y/n)[Enter]
? Pick an ESLint preset (Use arrow keys)
❯ Standard (https://github.com/standard/standard)
  Airbnb (https://github.com/airbnb/javascript)
  none (configure it yourself)
? Set up unit tests (Y/n)[Enter]
? Pick a test runner (Use arrow keys)[Enter]
❯ Jest
  Karma and Mocha
  none (configure it yourself)
? Setup e2e tests with Nightwatch? (Y/n)[Enter]
? Should we run `npm install` for you after the project has been created? (recommended) (Use arrow keys)
❯ Yes, use NPM
  Yes, use Yarn
  No, I will handle that myself

  vue-cli · Generated "frontend".


# Installing project dependencies ...
# ========================

(略)

# Project initialization finished!
# ========================

To get started:

  cd frontend
  npm run dev

Documentation can be found at https://vuejs-templates.github.io/webpack
$ cd frontend
$ npm install

# after installation
$ npm run dev

DONE  Compiled successfully in 21497ms                                                      12:42:34

I  Your application is running here: http://localhost:8080

ブラウザでhttp://localhost:8080を開いて以下の画面が表示されれば、アプリの雛形インストール完了です。

サンプルページの追加

Vue.jsの構成や説明はここでは記載しませんが、練習用にページを追加してみましょう。 frontend/src/componentsHome.vueAbout.vueを追加します。

// Home.vue
<template>
  <div>
    <p>Home page</p>
  </div>
</template>
// About.vue
<template>
  <div>
    <p>About</p>
  </div>
</template>

また、frontend/src/router/index.jsを以下のように編集します。

import Vue from 'vue'
import Router from 'vue-router'
// もともと記載されていたものは削除orコメントアウト
// import HelloWorld from '@/components/HelloWorld'

// Vue.use(Router)

// export default new Router({
//   routes: [
//     {
//       path: '/',
//       name: 'HelloWorld',
//       component: HelloWorld
//     }
//   ]
// })

const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' }
]

const routes = routerOptions.map(route => {
  return {
    ...route,
    component: () => import(`@/components/${route.component}.vue`)
  }
})

Vue.use(Router)

export default new Router({
  routes,
  mode: 'history'
})

このように編集したら再度ブラウザでlocalhost:8080localhost:8080/aboutを開いてページが表示されたらOKです。

ビルドディレクトリの変更

npm run buildコマンドで立ち上がるのは開発用のサーバなので、実際にサービス提供する際はブラウザが解釈できる形にビルドする必要があります。
そのビルドした結果がどこに出力されるかはfrontend/config/index.jsに記載があります。
もともとの記載は、

build: {
  // Template for index.html
  index: path.resolve(__dirname, '../dist/index.html'),

  // Paths
  assetsRoot: path.resolve(__dirname, '../dist'),
  //////以下略//////
}

となっており、frontend/dist配下にビルドされたソースコードが出力される設定となっております。
このままだとFlaskでindex.htmlにアクセスするときにfrontend配下を参照しなくてはならなくなってしまうので、管理上明確に分離するためにfrontendの上の階層に出力されるようにします。

build: {
  // Template for index.html
  index: path.resolve(__dirname, '../../dist/index.html'),

  // Paths
  assetsRoot: path.resolve(__dirname, '../../dist'),
  //////以下略//////
}

以下コマンドでビルドします。

$ npm run build

以下のような構成になります。

app_dir
  ┗frontend  // Vue.jsのプログラム
  ┗dist   // コンパイルされたVue.jsのプログラム
$ ls ../dist/
index.html static

Flaskのインストール(バックエンド)

バックエンドのプログラムには今回はFlaskを使用します。
元記事にはPythonの環境を整えるのにvirtualenvを使用していますが、僕はpyenv(pyenv-virtualenv)のほうが使い慣れているのでこちらを使います。
pyenvは以下コマンドでインストールできます。

$ brew install pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

以下コマンドで本アプリ用のPython3.6.1の環境を作ります。

$ pyenv install --list
$ pyenv install 3.6.1
$ pyenv virtualenv 3.6.1 vue-flask
$ pyenv versionos
system
3.6.1
3.6.1/envs/vue-flask
vue-flask

app_dirに行き、以下のコマンドを実行します。

$ pyenv local vue-flask
(vue-flask) $ # このようにプロンプトが変わったらOK

これで、app_dir内に閉じたPython3.6.1の環境が作れました。
(この中でパッケージのインストールを行っても他の環境には影響ありません。)

以下コマンドでFlaskをインストールします。

$ pip install Flask
$ mkdir backend

Flaskのサンプル作成

いよいよFlaskでバックエンドのプログラムを作成していきます。
今回は以下のようなファイル構成にしようと思います。

app_dir
 ┗appserver.py
 ┗frontend/  
 ┗backend/
   ┗api.py
   ┗application.py
   ┗config.py
   ┗router.py

それぞれのファイルの中身は以下です。

# appserver.py
from backend.application import create_app
from flask import render_template
app = create_app()

@app.route('/')
def index():
    return render_template("index.html")

if __name__ == '__main__':
    app.run()
# application.py
from flask import Flask

def create_app(app_name='FLASK-VUE'):
    app = Flask(app_name,
                static_folder = "./dist/static",
                template_folder = "./dist")
    app.config.from_object('backend.config.BaseConfig')

    return app
# config.py
class BaseConfig(object):
    DEBUG = True

ポイントは、application.pyにおいて、

app = Flask(app_name,
            static_folder = "./dist/static",
            template_folder = "./dist")

を指定することです。
これを指定することで、Flaskのテンプレート参照先が./dist配下になり、Vue.jsでビルドしたindex.htmlが参照されることになります。

以下コマンドで、Flaskの開発用サーバが立ち上がります。

$ python appserver.py
* Serving Flask app "FLASK-VUE" (lazy loading)
* Environment: production
  WARNING: Do not use the development server in a production environment.
  Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 977-628-540

http://localhost:5000/にアクセスすると、先程vue.jsで作成したホーム画面が表示されます。
しかし、vue.js側でルーティングを実装したはずの、http://localhost:5000/aboutにアクセスしても、Not Foundとなってしまいます。
これは、Flask側ではルーティングをしていないにもかかわらず、Flask側で処理しようとしてしまっているためです。
そこで以下をappserver.pyに追記して、/以下のパスをvue.jsでビルドしたindex.htmlにリダイレクトするようにします。

# appserver.py
from backend.application import create_app
from flask import render_template
app = create_app()

#### 以下を追記 ###
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template("index.html")
#################

if __name__ == '__main__':
    app.run()

再度、http://localhost:5000/aboutにアクセスするとAboutページが表示されます。

404ページの追加

これで、ブラウザ上のルーティングがvue.jsでビルドしたindex.htmlにリダイレクトされるので、vue.js側で404ページも追加しておかなくてはなりません。
frontend/src/components配下に以下NotFound.vueを作成します。

// NotFound.vue
<template>
  <div>
    <p>404 - Not Found</p>
  </div>
</template>

また、frontend/src/router/index.jsに以下の通り、ページがない場合のパスを追記します。

const routerOptions = [
  { path: '/', component: 'Home' },
  { path: '/about', component: 'About' },
  { path: '*', component: 'NotFound' }
]

http://localhost:8080/配下の適当なパスにアクセスしてNotFoundのページが表示されればOKです。
npm run buildでビルドすればFlask側のサーバhttp://localhost:5000/でも同様にNotFoundのページが表示されます。

APIの追加

さて、基本的な環境は整ってきたので、バックエンド側にAPIエンドポイントを追加していきたいと思います。

バックエンド(Flask)→APIエンドポイントの追加
フロントエンド(Vue.js)→APIアクセスし結果を描画

まずはFlask側にAPIに対するルーティングを追加します。
index.htmlに対するルーティングと同様に、そのままappserver.pyに記載しても良いのですが、APIのルーティングは別ファイルで管理したいので、FlaskのBluprintを使用します。

backend配下にapi.pyというファイルを作成します。

from flask import Blueprint, jsonify, request
from random import *

api = Blueprint('api', __name__)

@api.route('/hello/<string:name>/')
def say_hello(name):
    response = { 'msg': "Hello {}".format(name) }
    return jsonify(response)

@api.route('/random')
def random_number():
    response = {
        'randomNumber': randint(1, 100)
    }
    return jsonify(response)

今回は試しに名前に対してHelloのメッセージを返すAPIと乱数を返すAPIを作成しました。
backend/application.pyにてこのAPIのルーティングを記載したBlueprintファイルをimportします。

# application.py
from flask import Flask

def create_app(app_name='FLASK-VUE'):
    app = Flask(app_name,
                static_folder = "./dist/static",
                template_folder = "./dist")
    app.config.from_object('backend.config.BaseConfig')

    ##### ここを追記 #####
    from backend.api import api
    app.register_blueprint(api, url_prefix="/api")
    ####################

    return app

url_prefix="/api"を指定しているので、ここで記載するルーティングはすべて/api/配下のルーティングとなります。

http://localhost:5000/api/randomにアクセスすると以下の結果

{
"randomNumber": 38
}

http://localhost:5000/api/hello/hogeにアクセスすると以下の結果

{
"msg": "Hello hoge"
}

上記のような結果になればOKです。

次にfrontend配下のVue.jsのプログラム側でこのAPIにリクエストをし、画面表示させたいと思います。

まずは、Vue.jsでajaxを利用するために、axiosをインストールします。

$ cd frontend
$ npm install --save axios

frontend/src/components/Home.vueを以下のように書き換えます。

// Home.vue
<template>
  <div>
    <p>Home page</p>
    <p>Random number from backend: {{ randomNumber }}</p>
    <button @click="getRandom">New random number</button>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data () {
    return {
      randomNumber: 0
    }
  },
  methods: {
    getRandom () {
      const path = 'http://localhost:5000/api/random'
      axios.get(path)
        .then(response => {
          this.randomNumber = response.data.randomNumber
        })
        .catch(error => {
          console.log(error)
        })
    }
  },
  created () {
    this.getRandom()
  }
}
</script>

バックエンドのFlask側では、デフォルトでは他のサーバからのリクエストを受け付ける設定になっていないため、フロントエンドのVue.jsからのAjaxのリクエストを受け入れるようにします。

まずは以下コマンドでCORSをインストールします。

$ pip install -U flask-cors

backend/application.pyを以下のように書き換えます。

# application.py
from flask import Flask
from flask_cors import CORS # ここを追記

def create_app(app_name='FLASK-VUE'):
    app = Flask(app_name,
                static_folder = "./dist/static",
                template_folder = "./dist")
    app.config.from_object('backend.config.BaseConfig')

    from backend.api import api
    app.register_blueprint(api, url_prefix="/api")
    # ここを追記
    cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

    return app

これでhttp://localhost:8080/アクセスしたときにAPIから取得した乱数を表示することができるようになったのですが、
もし静的なファイルをFlask経由で取得する必要が無いのであれば、COREの機能を使う必要はありません。
その場合、backend/appserver/pyを次のように書き換えればOKです。

# appserver.py
from backend.application import create_app
from flask import render_template
import requests # ここを追記
app = create_app()

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    #### 以下を追記 ###
    if app.debug:
        return requests.get('http://localhost:8080/{}'.format(path)).text
    return render_template("index.html")
    #################

if __name__ == '__main__':
    app.run()

必要に応じて以下コマンドを実行。

$ pip install requests

これで、開発モード(DEBUG=1)の時は、http://localhost:5000にアクセスした時はhttp://localhost:8080にリダイレクトされるようになり、Vue.js側の画面でちゃんと乱数が取得できるようになります。

Flask側のサーバhttp://localhost:5000とVue.js側のサーバhttp://localhost:8080どちらにアクセスしても同様の結果が得られます。

終わりに

これで、バックエンドのAPIエンドポイントはFlaskで実装し、フロントエンドのUIはVue.jsで実装するフルスタックなアプリケーションが構築できました。
Flask側の開発用サーバとVue.jsの開発用サーバどちらも起動しておけば、それぞれ配下のプログラムを編集した際にホットリロードでリアルタイムに反映されるので、とても開発がしやすいです。
FlaskとVue.jsの詳細な使い方は今後もっと勉強していきたいと思います。

↓続編 kittagon.hateblo.jp

kittagon.hateblo.jp

dropbox-sdk-jsを使ってフォルダ内アイテムの共有リンクを取得

はじめに

最近フロントエンドの勉強をしています。 今作っているWebアプリで、Dropboxに保存してある写真を表示するという機能を作りたかったのですが、サーバサイドでAPIを叩き個別に取得して表示させるととても画面レスポンスが遅くなってしまいました。
そこでJavascriptで非同期に取得し、順次画面表示させてユーザーの体感速度を早くしようとしたのですが、そこで少しハマってしまったので解決方法を備忘として残しておきます。

dropbox-sdk-jsの導入

まずはdropbox-sdk-jsを導入します。 今回はnpmではなくCDN版のものを使用します。
Getting started | Dropbox JavaScript SDK

以下をhtmlに記載します。

<script src="https://unpkg.com/dropbox/dist/Dropbox-sdk.min.js"></script>
<script src="https://unpkg.com/dropbox/dist/DropboxTeam-sdk.min.js"></script>

フォルダ内アイテム一覧を取得

まずは共有リンクを取得したい画像アイテムのリストを取得します。 filesListFolderメソッドにフォルダのパスを渡せば取得出来るみたいです。

var dropbox_api_token = "((dropbox_api_token))";
var dbx = new Dropbox.Dropbox({ accessToken: dropbox_api_token });

var dir_path = "path/to/items"
dbx.filesListFolder({path: dir_path})
  .then(function(response) {
    console.log(response.entries)
  })
  .catch(function(error) {
    console.error(error);
  });

これはチュートリアルに載っているコードそのままで取得できます。 これでresponse.entriesにアイテム一覧が配列で取得できました。

forループでフォルダ内のアイテムそれぞれの共有リンクを取得

一覧が取得できたので、それぞれのアイテムの共有リンクを取得しようと思います。 本来なら、共有リンクを作成dbx.sharingCreateSharedLinkWithSettingsの後に共有リンクを取得dbx.sharingListSharedLinksする流れですが、自分の環境だとサーバサイドで共有リンクの作成は完了しているので、リンク取得のみを記述します。

こんな感じです。

var dropbox_api_token = "((dropbox_api_token))";
var dbx = new Dropbox.Dropbox({ accessToken: dropbox_api_token });
var entries;
var urls = [] //共有リンクの配列

var dir_path = "path/to/items"
dbx.filesListFolder({path: dir_path})
  .then(function(response) {
    console.log(response.entries)
    entries = response.entries;
    for(var i = 0;i<entries.length;i++) {
      dbx.sharingListSharedLinks({path: entries[i].path_display})
      .then(function(response) {
        urls.push(response.links[0].url);
      })
    }
  })
  .catch(function(error) {
    console.error(error);
  });

///////////////////////////////////////
//以下に共有リンクの配列(urls)を取得した後の処理を記載
///////////////////////////////////////

JavaScript初心者だった自分は、なんとなくこれで動作しそうだなーと思って、こんなコードを書いたのですが、これが全然うまくいきませんでした。
共有リンクが取得できている前提で後の処理を書いても「urlsの中身が空だよー」とエラーになってしまいます。

再帰的に関数を呼び出す

非同期処理が入っているため順番がめちゃくちゃになってしまうようなので、きっちりと順番順番に処理をしてもらうために、以下のように関数を再帰的に呼び出して処理することにしました。

var dropbox_api_token = "((dropbox_api_token))";
var dbx = new Dropbox.Dropbox({ accessToken: dropbox_api_token });
var urls = [] //共有リンクの配列

var dir_path = "path/to/items"
dbx.filesListFolder({path: path})
  .then(function(response) {
    // 関数の呼び出し
    getSharingLinks(0, urls, response.entries);
    $("#loading-img").fadeOut('slow');
  })
  .catch(function(error) {
    console.error(error);
  });

// 関数を定義
function getSharingLinks(i, urls, entries) {
  dbx.sharingListSharedLinks({path: entries[i].path_display})
    .then(function(response) {
      var url = response.links[0].url.replace("www.dropbox.com","dl.dropboxusercontent.com").replace("?dl=0","");
      urls.push(url);
      i++;
      if ( i == entries.length) {
        return console.log(url);
      } else {
        // 再帰的に関数を呼び出し
        getSharingLinks(i, urls, entries);
      }
    })
    .catch(function(error) {
      console.error(error);
    });
}

まとめ

Javascriptで非同期の処理を扱う際は、Promiseやasyncなどを使うのが基本みたいですが、ちょっと調べただけではよく理解できなかったので、今回はきれいではないですがとりあえずこんな感じで解決しました。 いつか必要になると思うのでasyncについてもいつか勉強しようと思います。

heroku container:push でエラー

先日からherokuCLIをアップデートしたあたりからherokuにデプロイしようとする度に以下のエラーが出るようになってしまいました。

(node:26614) Error Plugin: heroku-container-tools: files attribute must be specified in /Users/username/.local/share/heroku/node_modules/heroku-container-tools/package.json
module: @oclif/config@1.6.27
plugin: heroku-container-tools
root: /Users/username/.local/share/heroku/node_modules/heroku-container-tools
See more details with DEBUG=*
(node:26614) Error Plugin: heroku-container-tools: files attribute must be specified in /Users/username/.local/share/heroku/node_modules/heroku-container-tools/package.json
module: @oclif/plugin-legacy@1.0.15
plugin: heroku-container-tools
root: /Users/username/.local/share/heroku/node_modules/heroku-container-tools
See more details with DEBUG=*
Uninstalling heroku-container-tools... done

エラーメッセージを読んでもよくわからなかったのでいろいろ検索したところ以下の記事がヒットしました。
Latest Docker update broken Heroku cli?
症状は違いますがheroku-container-toolsが悪さをしているようだったので、以下コマンドでアンインストールし、heroku-container-registryをインストールし直したところ、エラーは出なくなりました。

$ heroku plugins:uninstall heroku-container-tools
$ heroku plugins:install heroku-container-registry

Node.jsの環境をDockerで構築&herokuにデプロイ

はじめに

それぞれ他サイトを参照して作ったのみですが、自身の備忘までに。

会社でSkyway使うかも?という話が出てきたため、検証環境を作るのが目的です。 * 他にもいろいろパッケージ入れて検証するかもーついでにnodejsとかの勉強もしなきゃ * 最近Docker触ってないしDockerで環境つくりたい * というかherokuにDockerのコンテナをデプロイできるようになったみたいだし使ってみたい ということで、node.jsの環境をDocker上に構築し、それをHerokuにデプロイするまでを試してみたいと思います。

Skywayとは?

NTT Comunicationsが開発している、WebRTCのためのプラットフォームです。  

公式サイト
Enterprise Cloud Skyway

SkyWayとは

ビデオ会議やコンタクトセンター、遠隔作業支援、オンライン教育、ライブ配信など、さまざまな機会において、オンラインでのリアルタイムコミュニケーションのニーズが高まっています。 ビデオ・音声通話、データ通信といったリアルタイムコミュニケーションの標準技術である「WebRTC」が登場し、リアルタイムコミュニケーションがより実現しやすくなってきました。 SkyWayを利用すれば、WebRTCに必要なサーバを構築・運用することなく、手軽にビデオ・音声通話、データ通信を利用できます。 自社サービスの開発・提供に専念して、イノベーションに集中することができます。

要は、Skywayを使えばWebRTCのアプリが簡単に開発できるというものです。
WebRTCはクライアント間で直接通信する方法ですが、そのためにはシグナリングサーバーと呼ばれる、クライアント間の接続する仲介をするサーバーを準備する必要があるのですが(これがまた複雑、らしい)、Skywayではそういったシグナリング等の、WebRTCに必要なサーバサイドの機能をPFとして公開してくれているため、我々開発者はクライアント側の実装に集中できるのです。
自分もまだそんなに詳しくないのでこの辺で。。

Node.jsのDocker環境

まずはDocker上の環境を作っていきます。

とはいえこれまでほとんどNode.jsなんて触ったことがなく全く知識が無いため、 とりあえず先人の行いに習って作ってみます。
以下を参考にしました。
【初心者向け】Dockerで手軽にNode.js開発環境構築 (2)
参考というかほとんど完コピだけど。。
以下のDockerfiledocker-compose.ymlを作成

FROM node:8.9.4-alpine

ENV NODE_ENV=development

RUN npm install -g express-generator@4.15.0

WORKDIR /app

EXPOSE 3000
version: '3'
services:
  webserver:
    build: ./
    image: node-express-dev:1.0
    container_name: node
    tty: true
    volumes:
      - ./:/app
    ports:
      - "8080:3000"

ファイルの配置は記事とは変えてすべて同じディレクトリにしています。

MyApp
 ├─ Dockerfile
 └─ docker-compose.yml

そしてビルドと起動

$ docker-compose build
$ docker-compose up -d

コンテナに接続し、express-generatorでアプリの雛形を作成します。
参照先サイトでは、オプションにpugを指定しています。
pugはテンプレートエンジンですが、構文が馴染めなそうだったので、今回はhtmlライクに書けそうなejsを指定しました。

$ docker exec -it node /bin/sh
# express -f --view=ejs /app
# npm install

雛形が生成され、こんな構成になりました。

MyApp
├── Dockerfile
├── app.js
├── bin
│   └── www
├── docker-compose.yml
├── node_modules
├── package-lock.json
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.ejs
    └── index.ejs

以下コマンドで開発用サーバを起動します。

# npm start

http://localhost:8080をブラウザで開いてExpressのスタートページが表示されれば成功です。
Dockerコンテナ上ではポート3000番で立ち上がっていますが、ブラウザの8080番ポートが3000番に転送されています。

Skywayのチュートリアル

さて、とりあえずnode.jsとWebサーバが立ち上がる環境はDocker上に構築出来たので、次はいよいよskywayのチュートリアルアプリを立ち上げていきます。
Javascript SDKの概要は以下公式ページに記載があります。
Skyway JavaScript SDK チュートリアル

とりあえず、今回はまず動くものを動作させたいので、チュートリアルのコードをそのまま使います。
Github上に公開されているサンプルコードを使用します。
今回はP2Pの1対多通信のものを使用しました。
github : skyway/skyway-js-sdk/examples/p2p-broadcast

先程構築したnode環境ではview/index.ejsがホームにて表示されるページのテンプレートになっています。
↑のgithubに上がっていたindex.htmlの内容をそのままvies/index.ejsに貼り付けます。
script.jsstyle.cssはそれぞれ、public/javascripts/script.jspubic/stylesheets/style.cssに配置します。  

また、APIキーをSkywayのダッシュボードから取得し、public/javascripts/key.jsに記載します。

javascript window.__SKYWAY_KEY__ = '<YOUR_KEY_HERE>'

index.ejsの読み込み先のパスも変更

<head>
  <meta charset="utf-8">
  <title>SkyWay - Broadcast example</title>
  <link rel="stylesheet" href="/stylesheets/style.css">
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script type="text/javascript" src="//cdn.webrtc.ecl.ntt.com/skyway-latest.js"></script>
  <script type="text/javascript" src="/javascripts/key.js"></script>
  <script type="text/javascript" src="/javascripts/script.js"></script>
</head>

ここまで出来れば、http://localhost:8080にてSkywayのサンプルが動作するはずです。

browserifyの導入

一旦ここまでで、Skywayの動作確認は出来ますが、jQueryやSkywayのJavascript SDKCDNから読み込んでいます。
ここままでも問題ないのですが、後々いろんなパッケージを導入することを考えると、(今回もWebRTCでの配信→音声認識とかを試したいなーと思ってるので)
パッケージのバージョン管理が大変になったり、何よりも今回はnode.jsの環境構築の勉強も兼ねていたので、 * npmでパッケージインストール * browserifyでコンパイル

という流れでパッケージ管理ができるよう変更したいと思います。

まずはnpmでjQueryとskyway-jsをインストールします。

# npm install jquery --save
# npm install -D browserify

パッケージをインストールしたら、script.js内でそれらのライブラリをrequireする分を記載します。

'use strict';
// Imports jQuery
const $ = require('jQuery');
$(function() {
  // Imports the skyway library
  const Peer = require('skyway-js');

読み込みも簡単ですね。
ただしこれだけだとrequireをブラウザが解釈できないので、動作しません。
browserifyコンパイルする必要があります。
一旦上記script.jsscript/ディレクトリを作成し、その配下で管理、開発、コンパイルしたらpublic/javascripts/script.jsが生成されるようにします。
コマンドは以下。

# $(npm bin)/browserify script/script.js -o public/javascripts/script.js

これでpublic/javascripts/script.jsコンパイルされた形で生成されました。 再びnpm startコマンドでサーバを立ち上げ、http://localhost:8080をブラウザで開いてExpressのスタートページが表示されれば成功です。

Herokuへのデプロイ

ひとまず、Docker上に環境を作ることができたので、いよいよHerokuの環境にデプロイをしていきます。

以下のサイトを参考にしました。 * Container Registry & Runtime (Docker Deploys) * Heroku で Docker を使う場合の諸注意

herokuにDockerコンテナをデプロイするためには以下の注意点があるみたいです。

  1. PORT 環境変数で Listen する Webアプリケーションであること
  2. Network link は未サポート
  3. Default working directory = '/'。変更するときは WORKDIR を指定する。
  4. CMDが必須。ENTRYPOINTはオプション。
  5. VOLUME、EXPOSE、STOPSIGNAL、SHELL、HEALTHCHECKは未サポート

今回のサンプルはPORTが3000で動作し、起動コマンドはnpm startなので、以下をDockerfileに追記します。

ENV PORT 3000
CMD ["npm", "start"]

あとは以下コマンドでHerokuにデプロイします。

$ heroku login # herokuにログイン
$ heroku container:login # Heroku 上の Container Registry へログイン
$ heroku create # Heorkuアプリの作成
$ heroku container:push web # コンテナをにContainer RegistryにPush
# 2018/5/15の変更にて以下のコマンドが必要になった
$ heroku container:release web # heroku アプリをデプロイ

heroku openでherokuアプリがブラウザで開けば成功です。

※5/15時点で変更があり、heroku container:push webだけではherokuアプリにはデプロイされなくなったようです。実際にアプリにデプロイするにはheroku container:release webを実行する必要があります。

Pushing images to Container Registry, either via the heroku container:push CLI command or using docker push, no longer releases those images to your application. To create a new release using the images pushed to Container Registry, run heroku container:release (specifying the process types you would like to release). Separating push and release allows you to:

  1. Push several images and then release them all at the same time.
  2. Use the release phase feature (run tasks before a new release of your app is deployed).
  3. Push one image and release it to multiple process types with different CMDs, via heroku.yml or API.

Pushing images to Container Registry no longer creates a release

まとめ

Herokuはこれまでもとても便利だったので大変重宝しておりましたが、自分自身余りgitを使わないし、グローバル環境で動作試験をしたいときもわざわざgit commitしてログに残るのが嫌だったので、Dockerコンテナを気軽にデプロイ出来るのはとても便利だなーと思いました。

参考サイト