2019年11月18日月曜日

go-workers で失敗したジョブをリトライさせる

概要

go-workers ではデフォルトだと失敗したジョブはリトライされません
意図的にリトライさせるにはエンキュー時にリトライをオンにする必要があります
今回はその方法を紹介します 

環境

  • macOS 10.15
  • golang 1.12.9
  • go-workers dbf81d0b75bbe2fd90ef66a07643dd70cb42a88a

事前準備

  • redis-server

失敗するコード

まずはワーカーが失敗するコードを作成します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true})
}
  • vim worker.go
package main

import (
    "fmt"
    "github.com/jrallison/go-workers"
)

func perform(msg *workers.Msg) {
    fmt.Println("perform")
    fmt.Println(msg.Jid())
    a, _ := msg.Args().String()
    fmt.Println(a[0])
}

func main() {
    workers.Configure(map[string]string{
        "server":  "localhost:6379",
        "process": "1",
    })
    workers.Process("default", perform, 1)
    workers.Run()
}

これでそれぞれ実行してみます

  • go run main.go
  • go run worker.go

するとエラーが表示され失敗したジョブをカウントする stat:failed に格納されます

  • redis-cli keys '*'
stat:failed queues stat:failed:2019-11-18
  • redis-cli get 'stat:failed'
1

リトライさせる

エンキューさせる時にリトライオプションを付与します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true})
}

workers.EnqueueWithOptions を使ってエンキューし workers.EnqueueOptions{Retry: true} を指定します
これで実行してみます

  • go run main.go
  • go run worker.go

すると stat:failed はカウントされるものの失敗したジョブの情報が goretry に格納されるようになります

  • redis-cli keys '*'
queues stat:failed:2019-11-18 stat:failed goretry
  • redis-cli zrange 'goretry' 0 -1
{"args":[1,2],"class":"SampleWorker","enqueued_at":1574046394.6627982,"error_message":"runtime error: index out of range","failed_at":"2019-11-18 03:05:57 UTC","jid":"74ffa4ab4e81a809c9bd7002","queue":"default","retried_at":"2019-11-18 03:06:34 UTC","retry":true,"retry_count":1}

しばらくするとリトライされます
リトライの仕組みは Sidekiq とおそらく同じでバックオフを採用しています (30s -> 46s -> 1m16s …)

デフォルトだと最大で 25 回リトライします

回数を指定してリトライさせる

25 回リトライさせない場合はリトライをカウントするインクリメント数を指定します
例えば 2 回リトライさせたい場合には インクリメントする数字を 13 にします
インクリメントする数字はエンキュー時のオプションに RetryCount を付与します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true, RetryCount: 13})
}

今回はテストなので 13 にしています
この数字分リトライカウントがインクリメントされます
インクリメント後に 25 以上の数字になればリトライは終了します

ただ上記の場合次回実行時は 8 時間後くらいになります
さきほど説明した通り Sidekiq はバックオフなので 13 回リトライしたあとの時間分待つことになります

最後に

go-workers でリトライする方法を紹介しました
基本的には Sidekiq のリトライ方式を踏襲しているので最大 25 回でリトライの待ち時間はバックオフになります

デフォルトではリトライは無効になっているので使用する場合にはオプション付きでエンキューしましょう
またリトライ回数を指定したい場合は RectryCount を調整することでリトライ回数を指定しましょう

参考サイト

2019年11月15日金曜日

Google の OAuth で「このアプリは確認されていません」を表示されないようにするまでの道のり

概要

Google の OAuth を使ったアプリを作成していると以下のような画面を見たことがあると思います
Google の OAuth を使ったアプリは Google に正しいアプリなのか確認をしてもらわないと以下の画面が出続けてしまいます

今回はこの画面を表示させない対応をしてみたのでその方法ややったことを紹介します

環境

  • Google Cloud Console (2019/11/07 時点)

サイトの所有者を確認する

おそらく必要だと思います
自分は所有者を確認済みのサイトをそもそも使っていたので確信はないのですがやっておいて損はないと思います

OAuth確認画面でスコープを正しく設定する

OAuth 確認画面で使用するスコープを割り当てます
有効になってる API は一覧に表示されているので必要なスコープを選択しましょう

以下は Google Photos API のスコープを追加している画面です
スコープが一覧にない場合は以下のように手動でスコープの URL を設定する必要があります

ホームページ/プライバシー ポリシーリンクを正しく設定する

ホームページ及びプライバシーポリシーページの設定は必須になります
またここで指定したページがダウンしていても申請は拒否されてしまうのでちゃんとページにアクセスできる状態でなければいけません

確認のため送信

スコープが設定できたら「確認のため送信」というボタンが活性状態になり押せるようになります
なぜそのスコープを追加したのかを入力します
また確認の結果を受け取るためのアドレスを入力します

これで Google に確認を依頼すれば OK です
が、申請が実際に通るまでいろいろと苦労したのでそれも紹介します

Request Denied

申請時にホームページとプライバシーポリシーの URL が必要なのですがそれがアクセスできない状態だったので Request Denied になりました
ちゃんとアクセスできるようにできれば OK です

Action Needed

ホームページに記載してる情報が少なかったため Action Needed になりました
最低限追加したコンテンツは以下の通りです

  • アプリの概要
  • 機能の一覧
  • アプリのアイコン
  • 使用する権限
  • バグなどがあった場合の連絡先

プライバシーポリシーのページも Action Needed になる可能性があるのでしっかりとコンテンツを作成してから申請しましょう

審査を通すコツ

やはりホームページとプライバシーポリシーのページをしっかり作成することだと思います
基本は公式の FAQ や公式のドキュメントをすることです
が、英語だらけでよくわからないという場合にすでに公開されている OAuth アプリを参考にすると良いです

例えば「Google Auth Library」は以下のようになっています
https://cloud.google.com/docs/authentication/production
こんなにたくさんの情報を記載する必要はないかもしれませんがアプリがどういうものなのかどういう情報を扱うのかなどわかるようにしましょう

確認を待つ

あとはひたすら待ちます
コンソール上だと「数週間」と書いてありました
自分は 2019/11/07 に確認依頼をしていますが上記で紹介したように何度かやり取りをしたので申請が完了するまでに結構かかりました

  • 2019/11/07 1 度目申請
  • 2019/11/08 Request Denied の連絡、対応して再度申請
  • 2019/11/09 Action Needed の連絡 (ホームページ)
  • 2019/11/12 再度 Action Needed の連絡 (プライバシーポリシーページ)

で自分はギブアップしました
諦めずに対応して審査が通れば OK です

この状態で再度 OAuth の確認画面を表示すると「このアプリは確認されていません」が表示されないのが確認できると思います

最後に

そもそもスコープを何も使わないのであれば特に必要ありません
スコープの追加や変更をした場合には確認依頼をする必要があります

また「承認済みドメイン」に登録したドメインも確認を依頼すると黄色い三角マークが表示されました
おそらくドメインの所有者の確認もしているはずなのでそういった意味でも冒頭の「サイトの所有者を確認する」をやっておいたほうが良いと思います
ちなみに「承認済みドメイン」はリダイレクト先などに使います

参考サイト

2019年11月14日木曜日

JavaScript でキーボードイベントを送信する方法

$(document).trigger({type:'keydown',keyCode:228});

Chrome のデベロッパーツールのコンソールからも送信できます

2019年11月13日水曜日

FireTV の Webアプリケーションでは Google の OAuth は使えないのか

概要

FireTVアプリは Android or Webアプリケーションのどちらかで開発することができます
Webアプリケーションとして開発を進めていたのですが Google OAuth をどうしても実装することができなかったので備忘録として残しておきます
解決方法があればご教授いただきたいです

環境

  • Ruby 2.6.2p47
  • JavaScript
  • FireTV 3gen
  • Web App Tester

やりたかったこと

Heroku にデプロイした Web アプリケーションを FireTV 上で動作させます
Web アプリケーションは Google OAuth を使ったアプリになっており認証後 Ruby アプリにアクセスできるようになります

FireTV の Web App Tester を使ってデバッグするのですが「disallowed_useragent」になり OAuth を発動させることができません
普通に Chrome や Firefox のブラウザでテストする分には動作します

どうやら Google の使用で WebView からの OAuth リクエストはすべて拒否されているようです (参考)
FireTV では Web アプリケーションの場合には Amazon ウェブアプリケーションプラットフォームと呼ばれる専用の WebView が立ち上がります
それが原因で disallowed_useragent になってしまうようです

結果的に解決できなかったのですがいろいろと試したことを紹介します

試したこと

Silk で一度ログインしてからアプリを起動する

Google 的にはデバイスに内蔵されているブラウザで一度認証すれば再度アプリで認証する必要がなくなるとのことだったので FireTV の Silk ブラウザを使って一度認証してからアプリを開いてみたのですがダメでした
もしかしたら実装方法が悪いだけかもしれませんが

アプリ内から認証時にだけ Silk を立ち上げる

これもできませんでした
Web アプリケーションなので立ち上げる方法は window.open を使ったのですが新規でブラウザは立ち上がらず先に進めませんでした

google-api-javascript-client を使ってみる

JavaScript のみで Google OAuth するライブラリです
認証時に別ウィンドウを開いて行ってくれるので Silk が起動してくれるかなと思って試したのですがダメでした
WebView 内で使っているので当然ダメでした

UserAgent を書き換えられないか

window.navigator.__defineGetter__ を使ってユーザエージェントを書き換えてアクセスしてみたのですがこれもダメでした
もしかしたらこれも実装方法が悪かっただけかもしれません

考察

FireTV + Webアプリケーションの構成では必ず WebView Google OAuth は現状突破できないんじゃないかなと思います
もし Google OAuth を使っているのであれば Silk を使えということなのだと思います

今回は試さなかったのですが FireTV では Android アプリが動作します
なので Android アプリとして作れば Google の公式の Android SDK があるのでこれを使って FireTV でも Google OAuth が使えるようになるのではないかと思っています

参考サイト

2019年11月12日火曜日

JavaScript で UserAgent を変更する方法

概要

JavaScript のみで UserAgent を変更する方法を紹介します
サーバ側で偽装とされる場合もあるので使用する際にはご注意ください
また動作しないブラウザもあるようです

環境

  • Chrome 78.0.3904.97

サンプルコード

  • vim index.html
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function show_ua() {
        window.navigator.__defineGetter__('userAgent', function() {
          return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 (Dummy)';
        });
        var ua = window.navigator.userAgent;
        document.getElementById("ret").textContent = ua;
      }
      window.onload = show_ua;
    </script>
  </head>
  <!-- <body text="#FFFFFF" style="padding: 30px;"> -->
  <body>
    <h2>User Agent</h2>
    <div id="ret"></div>
  </body>
</html>

window.navigator.__defineGetter__ を使うようです

動作確認

  • open index.html

2019年11月11日月曜日

FireTV の WebView で開いたページから Silk が開けるか検証してみた

概要

FireTV アプリを HTML5+JavaScript の Web アプリ構成で作っている場合そのアプリは FireTV の内部的には Amazon WebView という WebView で開かれます
WebView だけで問題ないアプリであれば良いのですが Google の OAuth などは WebView では使えません
そういった場合はデバイスが持つブラウザを使う必要があります
今回はそういったケースを想定して FireTV の Web アプリケーションから Silk と呼ばれる FireTV のデフォルトブラウザが開けるか試してみました

環境

  • FireTV 3gen
  • Web App Tester
  • macOS 10.15

確認アプリ

  • vim index.html
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function open_url() {
        var nw = window.open('https://kaka-request-dumper.herokuapp.com/');
        document.getElementById("ret").textContent = nw.innerWidth;
      }
      window.onload = open_url;
    </script>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>nw.innerWidth</h2>
    <div id="ret"></div>
  </body>
</html>

ホスティング

  • docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name host nginx

Web App Tester で確認

ホスティングしているマシンの IP を登録して確認します
あとはテストしてみましょう

結論

Silk は開けない

アプリを起動して動作確認しても別のウィンドウが立ち上がったりすることはありませんでした
また FireTV のホーム画面に戻って Silk が起動した形跡があるか確認もしましたが特にありませんでした
もしかしたらどこかでエラーになっている可能性はあるかもしれません

2019年11月10日日曜日

FireTV の WebView のユーザエージェントを調べてみた

概要

公式のドキュメントでは FireTV の WebView は Amazon WebView と呼ばれています
ユーザエージェントのサンプルも記載されているのですが気になったので実際にサーバにリクエストして確認してみました

環境

  • FireTV 3gen
  • macOS 10.15
  • Ruby 2.6.2p47

確認用アプリ

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra/base'
require 'net/https'
require 'json'

class TestWebApp < Sinatra::Base
  get '/' do
    @user_agent1 = request.env['HTTP_USER_AGENT']
    uri = URI.parse 'https://kaka-request-dumper.herokuapp.com/'
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = Net::HTTP::Get.new uri.request_uri
    res = http.request req
    @user_agent2 = JSON.parse(res.body)['headers']['HTTP_USER_AGENT']
    erb :user_agent
  end
end
  • vim config.ru
$stdout.sync = true
require './app'
run TestWebApp
  • mkdir views
  • vim views/user_agent.erb
<html>
  <head>
    <title></title>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>user_agent1</h2>
    <div><%= @user_agent1 %></div>
    <h2>user_agent2</h2>
    <div><%= @user_agent2 %></div>
  </body>
</html>

ホスティング

  • bundle exec rackup config.ru -o 0.0.0.0

Web App Tester から IP でアクセスするためバインドする IP を外部からアクセスできるようにします

Web App Tester で確認

Web App Tester を起動してホスティングしているホストの「IP:9292」を入力して動作確認します
結果は以下の通りです

Mozilla/5.0 (Linux; Android 7.1.2; AFTN Build/NS6268; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36 cordova-amazon-fireos/3.4.0 AmazonWebAppPlatform/3.4.0;2.0

最後の部分が FireTV を特定しそうな文字列でした
WebView -> Ruby アプリのユーザエージェント (user_agent1) が上記になります
user_agent2 は確認用のアプリから外部のサーバにアクセスした際のユーザエージェントなのでそれはサーバのものになりました

おまけ: JavaScript だけで User-Agent を確認する方法

<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function show_ua() {
        var ua = window.navigator.userAgent;
        document.getElementById("ret").textContent = ua;
      }
      window.onload = show_ua;
    </script>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>User Agent</h2>
    <div id="ret"></div>
  </body>
</html>
  • docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name host nginx

最後に

FireTV の WebView のユーザエージェントを調べてみました
実際に外部のサーバから見えたユーザエージェントなので間違いないと思います
FireTV の OS のバージョンによってユーザエージェントの値も変わってくるので参考程度に見てください
実際の自身のデバイスを調べたい場合は同じようにアプリを立ち上げてみるのが良いと思います