LCL Engineers' Blog

夜行バス比較なび(高速バス比較)・格安移動・バスとりっぷを運営する LCLの開発者ブログ

プレミアムフライデーにヨガLINE botを作った話

フロントエンドエンジニアの岡田です。 先週の金曜日は、プレミアムフライデーでしたね。 LCLでも従業員全員が15時に退社しました。 premium-friday.go.jp

私は今回はどこへも出かけられなかったのですが、せっかく時間ができたので、LINE botをつくってみました。 先日こちらでご紹介したとおり、LCLにはヨガ部があるので、 今回はおすすめのヨガ動画を返してくれるbotを作ることにしました。 f:id:lcl-engineer:20170227051900p:plain

機能

現時点のbotの機能は以下のとおりです。

  • ユーザーがキーワードを送る
  • botはそのキーワードを含む動画をYoutubeから検索して返す
  • 動画は特定のチャンネル内から検索して返す。いつもヨガ部で見ている以下のチャンネル内から返すようになっています。 www.youtube.com

環境

Node.js + Heroku + LINE BUSINESS アカウント

以下のチュートリアルにならって作りました。Part3までで環境は整います。 qiita.com 今回はAIは入ってないですが、AI入りのbotもおもしろいです。こちらの記事はとてもわかりやすいのでおすすめです。

Google Developers Console

Youtube APIに使います。以下の記事が参考になりそうです。
YouTube APIとは:Data API v3を使って動画情報を取得してみた。 | D2Cスマイル

実際のコード

環境ができたら、index.jsを以下のように書き換えると完成です。

// -----------------------------------------------------------------------------
// 定数の設定
const LINE_CHANNEL_ACCESS_TOKEN = 'あなたのChannl Access Token';
const GOOGLE_API_KEY = 'あなたのGOOGLE API KEY';
const DOMAIN = 'あなたのサーバーのドメイン: https://xxxxxxxx.herokuapp.com/';

// -----------------------------------------------------------------------------
// モジュールのインポート
var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();

// -----------------------------------------------------------------------------
// ミドルウェア設定
app.use(bodyParser.json());

// -----------------------------------------------------------------------------
// Webサーバー設定
var port = (process.env.PORT || 3000);
var server = app.listen(port, function() {
  console.log('Node is running on port ' + port);
});

// -----------------------------------------------------------------------------
// ルーター設定
app.get('/', function(req, res, next) {
  res.send('Node is running on port ' + port);
});

app.post('/webhook', function(req, res, next) {
  res.status(200).end();
  for (var event of req.body.events) {
    if (event.type == 'message') {
      var requestHeaders = {
        'Content-Type': 'application/json',
        'referer': DOMAIN
      }
      //オプションを定義
      var options = {
        url: 'https://www.googleapis.com/youtube/v3/search?key=' + GOOGLE_API_KEY + '&part=snippet&channelId=UCd0pUnH7i5CM-Y8xRe7cZVg&q=' + encodeURI(event.message.text),
        method: 'GET',
        headers: requestHeaders,
        json: true
      }
      console.log(options);

      var youtubeMovieUrl;
      //リクエスト送信
      request(options, function(error, response, responseBody) {
        //コールバックで色々な処理
        youtubeMovieUrl = 'https://www.youtube.com/watch?v=' + responseBody.items[0].id.videoId;
        console.log(youtubeMovieUrl);
        var body = {
          replyToken: event.replyToken,
          messages: [{
            type: 'text',
            text: youtubeMovieUrl
          }]
        }
        var url = 'https://api.line.me/v2/bot/message/reply';
        var headers = {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
        }
        request({
          url: url,
          method: 'POST',
          headers: headers,
          body: body,
          json: true
        });
      })
    }
  }
});

2017/03/19 追記 Google Cloud PlatformのAPIキーの制限に「HTTP リファラー(ウェブサイト)」を使えるように、上記ソースコードのrequest時にrefererを追加しました。

youtubeの動画取得については、以下の記事を参考にさせていただきました。 http://xoyip.hatenablog.com/entry/2014/01/24/212903xoyip.hatenablog.com

はまったところ

Youtubeのキーワードをエンコード(encodeURI)しないとうまく結果が表示されません。 以下のような状態だったため解決に時間がかかってしまいました。

  • ブラウザからリクエストした場合はエンコードしなくても正しい結果が返る
  • キーワードによってはそれらしい結果が返ってくる

まとめ

3時間でできるところはこのへんまででしたが、まだまだ改良できるので育てていこうと思います。

BugsnagでRailsのエラーを漏れなく補足する

弊社では、以下のエントリで紹介したように、Bugsnagを利用してエラー監視をしています。

techblog.lclco.com

前回は、JavaScriptでの利用について紹介しましたが、今回はRailsでどのように利用しているのかを紹介します。

Bugsnagの選定理由

Railsのエラー検知サービスとしては、airbrakeやsentry等が有名ですが、Bugsnagは他と比較して費用が手頃でかつUIが見やすかったため選定しました。(それほど時間をかけて、比較検討したわけではありません)

導入手順

Gemfileに下記を追加して、bundle installを実行します。

gem 'bugsnag'

gemを追加すると、以下のコマンドでbugsnagのconfigファイルが生成できます。API KEYはbugsnagの管理画面から取得できます。

bundle exec rails generate bugsnag YOUR_API_KEY_HERE

これだけで、自動的に発生した例外を補足して、Bugsnagへ通知されるようになります。

任意場所に通知を仕込む

通常のケースでは通るはずがないコードにも、Bugsnagを仕込んで検知するようにしています。

例)必ず取得できるはずのデータが取得できなかった場合

Bugsnag.notify(
    RuntimeError.new('not found xxxx '),
    {
        :severity => 'error',
          :errro_detail => 'エラーの詳細・・・',
          :error_data => {:aaa => '・・・', :bbb => '・・・`},
    }
)

errro_detail,error_dataに各種データを設定することで、Bugsnag側で確認できます。

f:id:lcl-engineer:20170226185952p:plain

このように任意場所で通知を仕込むことで、データ不備によるエラーを早期検知することができるようになりました。

ChatWorkへの通知方法

Bugsnagがエラーを検知するとChatWorkへ通知するようにしています。残念ながら、チャットーワークとの連携はデフォルトでは用意されていないので、独自のAPIを用意して実現しています。

NewrelicとChatWorkの連携でも同じことを行っていますので、詳細は下記の記事を御覧ください。

techblog.lclco.com

まとめ

以前は、エラーログを定期的にチェックして、予期していないエラーが発生していないかを確認していましたが、Bugsnagを導入してからは不要になりました。 Bugsnagは導入も簡単で、費用を比較的安いので、非常におすすめのサービスです。

自社サービスの常時SSL(サイト全体HTTPS)対応でやったこと

Webエンジニアの森脇です。

弊社では先日「格安移動」という自社サービスの常時SSL化(サイト全体HTTPS化)へ対応しました。 この記事では、常時SSL対応のためにやったことを紹介したいと思います。

idou.me

なぜHTTPS化したか

GoogleやApple等のHTTPS化への積極的な姿勢を考慮すると、今後HTTPS化対応は避けられないと判断し、早い段階で対応しておいたほうが影響範囲が少ないと考えて対応に踏み切りました。

環境

サイトは以下の環境で構成されています。

  • AWS
  • ALB
  • Varnish
  • Nginx
  • Unicorn(Rails)

ALBまでをHTTPSで通信し、ALBから後ろはHTTPで通信させています。 サイト全体HTTPS化以前も、お問い合わせフォーム等の一部のページはHTTPS通信をしていました。

--- (HTTPS) ---> ALB --- (HTTP) --> Varnish --> Nginx

移行前の事前対応

できるだけ切り替え当日の作業量・影響範囲が少なくなるように、先行して対応できることは対応しておく方針にしました。

サードパーティタグが、HTTPS対応しているかの調査・修正

重要なサードパーティのタグがHTTPSへ対応できていない場合は、サイト全体のHTTPS対応を延期せざるを得ないため、最初に確認をしました。 全て問題ないことが確認できたので、現在httpで指定されているものは、プロトコル省略の形へ書き換えました。

http://
↓
//・・・

内部リンクを絶対から相対へ書き換え

サイトの内部リンクの指定が絶対URLの箇所が存在したため、こちらも事前に相対URLへ変更しました。

http://xxx.xxx.xx/yyy/zzz
↓
/yyy/zzz

キャッシュをHTTPSとHTTPで分ける

格安移動は、Varnishを利用しページをキャッシュしていますが、HTTPS/HTTPでキャッシュが別れておらず、HTTPSでアクセスした場合も、HTTPのキャッシュが返されてしまうという状態でした。そのため、HTTPS/HTTPのアクセスに応じて、キャッシュを別管理にする対応を実施しました。

なお、ALB→Varnishの通信はHTTPSではなくHTTPのため、大元のリクエストがHTTPSであることを判定するにはHTTPヘッダの「X-Forwarded-Proto」を利用して行いました。

sub vcl_hash {

  hash_data(req.url);

  # httpsとhttpでキャッシュを分ける
  if (req.http.X-Forwarded-Proto) {
    hash_data(req.http.X-Forwarded-Proto);
  }

  return (lookup);
}

移行に向けた準備

SSL証明書の導入

先行して一部のページでHTTPS対応を行っていたため、SSL証明書は既に導入済みであり、HTTPS化にともなって特に対応はしていません。

中間証明書の設定が漏れていると、一部のAndroid端末で警告が表示されてしまうため、証明書が正しく設定できているかどうかは、以下のサイトなどで事前チェックしおくことをおすすめします。

https://cryptoreport.geotrust.com/checker/

HTTP→HTTPSへのリダイレクト設定

nginxで301リダイレクトするようにしています。 Varnish→NginxへはHTTPリクエストとなるため、Varnishでの制御同様「http_x_forwarded_proto」で判断をしています。

if ($http_x_forwarded_proto != 'https') {
    return 301 https://$host$request_uri$is_args$args;
}

sitemap.xmlの出力URL変更

googleに送信するための、sitemap.xmlの出力URLをhttp→httpsに変更します。 robots.txtに、sitemap.xmlの配置URLを定義しているため、そちらもhttpsへ変更します。

Sitemap: https://idou.me/sitemap/sitemap.xml.gz
↓
Sitemap: http://idou.me/sitemap/sitemap.xml.gz

Cookieへのsecure属性付与

HTTPSのみCookieを送信するように、secure属性を付与しました。

HSTS対応

HSTSについては、トラブル時の切り戻しを考慮し、まずは対応を見送りましたが、今後対応予定です。

※ HSTSの説明については、下記をご参照ください。

HSTS (HTTP Strict Transport Security) の導入 - Qiita

移行作業

移行は部分的ではなく、一括で行いました。まだまだサイトの規模も小さいため、一括で行ってもそれほどリスクは大きくないと考えました。

大きく以下の流れで移行しています。

HTTP→HTTPSへのリダイレクト設定

設定後に、問題なくリダイレクトされることを確認します。 Google等のクローラーも問題なくHTTPSサイトへアクセスできていることをログで確認します。

Google Search Consoleの設定

HTTPS用に、新規にGoogle Search Consoleへサイトを追加する必要があるため、新たにサイトを追加します。

sitemap.xmlの再作成・送信

sitemap.xmlをHTTPS用のURLで再作成し、Google Search Consoleから送信します。

サイト外のURLの変更

外部のサイトから自サイトへリンクしているものは、自分たちで変更可能なものはHTTP→HTTPSへ変更しました。

  • 各種広告のURL
  • Twitter,Facebook等のURL
  • コーポレートサイトなどの外部サイト
  • 名刺等の紙媒体

移行後のチェック

Googleの順位に変動がないかどうか、HTTPSのサイトが正しくインデックスされるかどうかを重点的にチェックしています。今のところ順位変動はなく、徐々にHTTPSサイトも徐々にインデックスされています。

まとめ

HTTPS化対応は、かなり大変だと考えてましたが、意外と影響範囲は少なく、実質2,3日で対応完了しました。サイトの規模が大きくなるにつれて、対応工数・リスクは当然膨らんでいきますので、できるだけ早めの対応をおすすめいたします。