積極的後進

後ろ向きに全力ダッシュ

セッション認証付きAPIサーバーをnode.js(express)で作る

f:id:heimusu:20180518115114j:plain プライベートプロジェクトのサーバー実装を一新するべく,セッション認証とパスワードハッシュ化を付けたのでメモ.

本当はユーザー情報をDBに持たないと意味が無いけど,ユーザーは僕しか居ないからまあいいやと妥協した.

実際のコード

const express = require('express')
const oauth = require('oauth')
const http = require('http')
const path = require('path')
const crypto = require('crypto') // 暗号化
const bodyParser = require('body-parser')
const session = require('express-session')

const app = express();
app.use(express.static(__dirname));
app.set('port', process.env.PORT || 3000);
[f:id:heimusu:20180518115114j:plain]

// Cross Originを有効化
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
  next();
});

// Optionsも
app.options('*', (req, res) => {
  res.sendStatus(200);
});


// urlencodeとjsonの初期化
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());


// セッションの生存時間(分で指定)
const maxage = 1;

// セッション管理設定
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: maxage * 60000 }}))



// セッション管理関数
const sessionCheck = (req, res, next) => {
  if (req.session.email) {
    next();
  } else {
    res.status(440).json({message: 'セッション切れです'})
  }
}


const email = 'email@email.com'
const password = 'hogehoge'

// sha-512で暗号化
const hashed = password => {
  let hash = crypto.createHmac('sha512', password)
  hash.update(password)
  const value = hash.digest('hex')
  return value;
}

// ログイン処理
app.post('/login', (req, res, next) => {
  const reqEmail = req.body.email
  const reqPass = req.body.password
  try {
    if(email === reqEmail && hashed(password) === hashed(reqPass)) {
      req.session.email = {name: req.body.email};
      res.send(200)
    }
    else {
      res.status(401).json({message: 'メールアドレス/パスワードが一致しません'})
    }
  }
  catch (error){
    res.status(500).json({message: 'error'})
  }
});

app.get('/', (req, res, next) => {
  sessionCheck(req, res, next)
  res.sendStatus(200)
})

app.listen(3000, function(){
  console.log('working');
});

リポジトリ

github.com

React.jsのsetStateが遅い問題

f:id:heimusu:20180510105852j:plain

散々言われ続けてきたことだが,自分の備忘録として残しておく.

this.stateを参照してDOMを書き換える

失敗する例

例えば下記のような場合

  constructor(props) {
    super(props);
    this.state = {
      isOpen: false
    }
  }

  componentDidMount() {
    this.setState({
      isOpen: true
    })
  }

  click(flg) {
    alert(flg)
  }


  render() {
    return({
      <div onClick={()=>this.click(this.state.isOpen)}>
    })
  }

DOMをクリックした場合に,1回目のクリックではfalse,2回目以降でtrueが表示される. 本当は1回目のクリックでtrueが表示されてほしいが…

成功する例

正しい対処法なのかどうかはさておいて,上記のコードを次のように変更すると良い.

  constructor(props) {
    super(props);
    this.state = {
      isOpen: false
    }
  }

  componentDidMount() {
    this.setState({
      isOpen: true
    })
  }

  click(flg) {
    alert(flg)
  }


  render() {
    const isOpen = this.state.isOpen

    return({
      <div onClick={()=>this.click(isOpen)}>
    })
  }

render()内でthis.state.isOpenを変数に外出しにして,その変数をDOMに渡す. こうすることで一回目のクリックでもtrueが表示されるはずだ.

私見

業務でReact.jsを扱う中でハマったので試行錯誤した結果この形になったが,原因や良い対処法についてはっきりしていない…

stateの変更タイミングが意図的に制御されているのだと思うが, そもそもreduxを使っていれば心配いらないのかもしれない.

原因やよりよい方法についてご存知であれば是非コメントにて.

Rx.jsでTinder風UIを実装する

f:id:heimusu:20180502125757j:plain

業務でTinder風UIを作るオーダーが来たので,勉強も兼ねてRx.jsで作ることにした.

コードと処理の流れ

下記のコードはRx.js ver5.8.8で動作を確認しています. v6.0.0では動作しないので適宜読み替えてください.

また,モバイルでの動作を想定して記述しています.

const tolerance = 200;
const windowWidth = window.innerWidth
const imageElem = document.querySelector('.characterImage') // dom

// 現在のスワイプ位置を取得
const touchstart$ = Rx.Observable.fromEvent(imageElem, 'touchstart')
  .switchMap(startEvent => 
    Rx.Observable.fromEvent(imageElem, "touchend")
    .map(e => e.changedTouches[0].pageX)
  )

touchstart$
  .do(val => {
    // スワイプ位置を判断して画像を振り分ける
    if(val > windowWidth - tolerance) {
      imageElem.classList.add('rotate-right')
    }
    else if(val < windowWidth / 2.0 - tolerance) {
      imageElem.classList.add('rotate-left')
    }
  })
  .delay(1000)
  .subscribe(val => {
    imageElem.classList.remove('rotate-right')
    imageElem.classList.remove('rotate-left')
  })

対象のDOMをfromEventで監視して,touchend時の座標を基に判定する. torelanceをいじることで動作をもう少し過敏にしたりできる.

subscribeではサンプル用に画像の復帰処理をしているが,画像の追加変更も任意で行える.

所感

まだRx.jsの勉強中なので掴めていないところが多々あるが,一旦は目標のものが出来上がった. Rx.jsは出来ることがたくさんあって使い所が分かっていなかったり,そもそも書き方がまだおぼつかなかったり… 使いながら覚えていくしかなさそう.

上記コードの指摘等あればプルリクを投げてもらえると嬉しいです.

サンプルプロジェクト

https://github.com/heimusu/rxtinder

参考

GulpでCloudFront + S3環境へのフロントエンドデプロイを自動化する

CloudFrontに紐付いたS3に静的ファイル一式を置いた構成はテッパンだと思う. この場合,静的ファイルを更新するためには 1. S3にファイルをアップロードする 2. CFをInvalidateする の手順が必要となる. 滅多に更新が発生しないならともかく,絶賛開発中のプロジェクトの場合にこの操作を何度も手動で繰り返したくない.

本来ならばCIを設定したりwebpackでbuildついでに実行するなどありそうだが,オーソドックスにgulpタスクを書いてみた.

パッケージインストール

$ yarn add gulp gulp-awspublish gulp-cloudfront-invalidate

gulpタスクの記述

const gulp = require('gulp');
const awspublish = require('gulp-awspublish');
const cloudfront = require('gulp-cloudfront-invalidate');

// S3にデプロイするタスク
// dev環境にデプロイする
gulp.task('deploy', function() {
  const key = {
    accessKeyId: '...',
    secretAccessKey: '...',
    region: "ap-northeast-1",
    params: {
      "Bucket": "bucket-name",
    }
  }
  const publisher = awspublish.create(key);

  // deployするもの
  gulp.src(['**/*', '!node_modules/**/*', '!gulpfile.js',  '!package.json', '!README.md', '!yarn.lock', '!.babelrc' ])
    .pipe(publisher.publish())
    .pipe(awspublish.reporter());
});

// CFをInvalidateする
const cfInvalidateSettings = {
  distribution: '...',
  paths: ['/*'], 
  accessKeyId: '...', 
  secretAccessKey: '...',
  wait: true   
}


gulp.task('invalidate', function() {
  return gulp.src('*')
    .pipe(cloudfront(cfInvalidateSettings));
})

実行

$ gulp deploy && gulp invalidate

これで,指定したS3に静的ファイルを設置(存在しないファイルは新規に設置,存在するファイルは差分があれば更新)した後に指定したCFをinvalidateしてくれる. あとは個々の案件に併せてタスクをカスタマイズして,package.jsonのscriptsに良い感じで書いておく. 上記の設定だとinvalidateの完了までwaitする設定になっているが,外すことも出来る.個人的にはしっかりinvalidateが完了したかを見届けたいのでwaitすることにしている.

VSCodeでRiot.jsのtagファイルのシンタックス・コメントアウトを有効化する

VSCodeを使っていてなおかつRiot.jsを扱おうとすると,シンタックスが死んだりコメントアウトが上手くできなかったりしたので対応備忘録.

プラグインインストー

Riot-Tagを入れる.

settings.jsonを編集する

このエントリを参考にする.

すると,HTMLやJS,CSSが混在するtagファイルでも問題なくシンタックスコメントアウトが出来るようになる.

艦これをelectronでwrapしてアプリめいて起動する

Windowsがメイン環境な提督たちは,提督業も忙しい!を使うと快適にプレイできる. 一方でMacな提督たちは,(僕が知らないだけだと思うけど)基本的にブラウザ上 or Extensionな環境でプレイしていると思う.

ガチ勢ではない僕にとってはそれほど不便ではないのだけれど,Macを使っていてCommand + Tabでアプリケーションを切り替えたりすると, 艦これだけが立ち上がっているWindowも一緒に最前面に出てきてしまう. じゃあSafariとか使えよ.という話ではあるが.

せっかくなのでElectronでWrapして,アプリめいて起動させてやろうかなと思ってやってみた. Electron使いたかっただけ.Electronで単純にWrapしてやるだけ.

基本的なことは下記Qiitaに全てまとまっていたので,なぞっていけば楽勝. ただし,FlashPlayerを使うためのPepperFlashPlayer.pluginの箇所は僕の環境だと下記になった.

/Users/user-name/Library/Application Support/Google/Chrome/PepperFlash/25.0.0.127/PepperFlashPlayer.plugin

なんだかんだこのへんに注意しながらやるとあっさりWrapできた. 自ドメインに置いてみたので,雑に使ってみたい人はためしてください. 気が向いたら改良していきます.

Electronも随分便利だなあ.

参考

vue-cliで生成したVue.js 2.0 なアプリケーションをherokuにデプロイする

Vue.js 2.0

最近のWebフロント界隈といえばReact + Redux一強だが,Vue.js 2.0もなかなか使いやすい. 公式が作成しているジェネレータvue-cliを用いてプロジェクトを生成すると,環境構築を考えることなく一通り試すことができる.

Vue.js 2.0はReact.jsほどガチガチのコンポーネント指向ではなく,Angular.js 1.x系のバインディングコンポーネント指向を合わせたイメージ. vue-loaderが強力なので,html, js(es5), sassを一つのvueファイルにコンポーネントとして持つことが出来る. また,vue-routerも結構直感的なので本当にあっさりSPAを構築できる. webpack, webpack-dev-serverと合わせると爆速でページが量産されていく.

しばらくはVue 2.0で作っていきたい.

herokuへデプロイ

さて,Vue 2.0でページをサクサク作ったらどこかで公開しておきたくなる. herokuへのデプロイをしたいが,手順で若干詰まったのでメモ.

ローカルでの準備

本番用ファイルの準備

% npm run build

すると,本番用のファイルがdist以下に生成される. デフォルトだと.gitignoreによってリポジトリに乗らないようになっているので,これを削除してリポジトリに乗るようにしておく.

サーバーファイルの準備

heroku上で動作するサーバーファイル(node.js)を作っておく. 下記をserver.jsとして, dist以下に作成する.

var express = require('express');
var path = require('path');
var serveStatic = require('serve-static');
app = express();
app.use(serveStatic(__dirname));
var port = process.env.PORT || 5000;
app.listen(port);
console.log('server started '+ port);

ためしに

% node server.js

すると,localhost:5000で作成したページが表示されるはずだ.

package.jsonの準備

heroku用にpackage.jsonを作成する.とはいえ,元々ルートディレクトリにあったpackage.jsonをコピーして手を加えるだけだ.

% cp package.json dist/

した後,下記をコピー先のpackage.jsonに追記する.

scripts: {
    ...
    "postinstall": "npm install express",
    "start": "node server.js"
}

ここまで準備できたら,一度gitにpushしておこう(heroku以外でも管理している場合).

herokuの準備

herokuのアプリを新規で作成しておく. 作成して,herokuのremoteを登録しておく. また,buildpacksのnodejsを指定する.

% heroku git:remote -a vue-application
% heroku buildpacks:set heroku/nodejs

pushする

いよいよpushとなる. ここで重要なのは,distディレクトリのみをpushすることだ. subtreeを用いて,下記のように叩く.

% git subtree push --prefix dist heroku master

heroku openする

出来上がり.

あとがき

手順を確立してしまうと簡単だが,少しはまってしまった. このように本番ビルドの結果 + 動作用のサーバーアプリケーションのみをデプロイする方法はvue以外でも活用できそうなので,覚えておきたい.

参考

ほとんどのやり方はこちらを参考にした.ありがとうございます. https://medium.com/@sagarjauhari/quick-n-clean-way-to-deploy-vue-webpack-apps-on-heroku-b522d3904bc8#.752sfjdmv