GXUIでチャットワークの特定ルームにつぶやくやつ

Golangを勉強したいんだけど、特に作りたいものはない。
あるにはけど、WebアプリとかiOSなので、正直RailsやSwiftでやればいい。

並行処理が得意ということだけど、それ系のニーズも今はあまりない。
自分のWebアプリを監視する劣化NewRelicみたいなのは欲しいけど、やる気が起きない。
あと、自分用iGoogleみたいなのは欲しいかもしれない。

どうするかなぁ、とかうだうだしてたら、

Go用のGoogle製のGUIツールキットgxuiのインストール - Qiita

という記事を見つけた。

GUI!
これもMacに関してはSwiftで書けばいいんだけど、iOSほど慣れてないので時間がかかる。
ならGoで作っちゃおう!

仕事中、チャットワークに一人ずつスレを作って、Twitter代わりにつぶやいている。す、ら…っく……?
で、たまに誤爆することがあるので、固定でつぶやくツールが欲しい、と思っていたので食ってみる。

GXUIのインストール

https://github.com/google/gxui#common

# インストール
$ go get code.google.com/p/freetype-go/freetype
go: missing Mercurial command. See http://golang.org/s/gogetcmd
package code.google.com/p/freetype-go/freetype: exec: "hg": executable file not found in $PATH

$ brew install mercurial
$ go get code.google.com/p/freetype-go/freetype
$ go get github.com/go-gl/gl/v2.1/gl
$ go get github.com/go-gl/glfw/v3.1/glfw
$ go get github.com/google/gxui

# これで依存も一発で入るかもと思ったけどダメだった。
$ go get -u github.com/google/gxui

サンプルの実行

# サンプル
$ go install github.com/google/gxui/samples/...

# https://github.com/google/gxui/tree/master/samples にあるものが$GOPATH/binの下に入る
$ hello_world

作ったもの

https://github.com/tnantoka/mygo/blob/master/gxui_chatwork/main.go

よし、完成!と思ったが、重大な問題が…日本語入力できない……。
というわけでお蔵入りになりました。
まぁGoに慣れるという目的は少し達成できたので良かった。

参考

コマンドラインでの入力(gets的なやつ)

チャットワークAPIにPOSTする

インスタンス変数と同名のローカル変数を定義しようとしてハマる

Rubyで、今まで2度ぐらいこんな感じの処理を書いてしまったことがある。

class Foo
  attr_accessor :attr
  def initialize
    self.attr = 'default'
  end
  def func(arg = nil)
    attr = arg.presence || attr
  end
end

foo = Foo.new
puts foo.func('arg') # arg
puts foo.func # nil

最後の行のfoo.funcではargを渡していないので、defaultが出力されることを期待している。

    attr = arg.presence || self.attr

こう書かないとダメ。(selfが必要)

そして本当はこんなコード書いちゃダメ。
戒めとして残しておく。

grep, git grepで引っ掛けたファイルをvimのタブで一気に開く

$ vim -p `git grep "QUERY" | awk -F: '{ print $1 }' | sort -u`
$ vim -p `grep "QUERY" . -r | awk -F: '{ print $1 }' | sort -u`

awkの使い方全然覚えてないけど、これは使いすぎてさすがに覚えた。

git grep -lを知った。

$ vim -p `git grep -l "QUERY"`

でいいのか…。

RailsアプリのフォームをGoogle翻訳から使えるようにする

という要件があった。

バグがドラゴンなら、要件は神だろうか…。

ハードルは以下の2つ。

  1. CSRF Protectionで弾かれる(おなじみのActionController::InvalidAuthenticityTokenで500エラー画面。)
  2. iframeで弾かれる(画面遷移しない。Refused to display 'https://example.com/path' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'.というエラーがChromeのコンソールに出るはず。)

リファラーがGoogle Translateの場合に、

  1. protect_from_forgeryフィルターを無効化
  2. X-Frame-Optionsヘッダーを削除

となるようにApplicationControllerを変更。

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception, if: :csrf_protected?
  after_action :allow_iframe, if: :iframe_allowed?

  private  
    # For Google Translate
    def from_google_translate?
      request.referer =~ %r{\Ahttps://translate.googleusercontent.com/}
    end

    def csrf_protected?
      !from_google_translate?
    end

    def iframe_allowed?
      from_google_translate?
    end

    def allow_iframe
      response.headers.except! 'X-Frame-Options'
    end
end

確認画面

上記でうまく行くFormもあったんだけど、確認画面があるFormがまたiframeのエラーで失敗。

初回の画面は、Google翻訳のサーバーが返してる(/translate_p?hl=ja&sl=en&tl=ja&u=https://example.com/のようなURL)けど、遷移後は普通に自分のアプリが返してる画面なので、リファラーがGoogleのものにならない。

これに対応するため、リファラが自分のアプリの時もiframeを許可するように変更。

    def from_same_origin?
      request.referer =~ /\A#{main.root_url.gsub(/http/, 'https?')}/
    end

    def iframe_allowed?
      from_google_translate? || from_same_origin?
    end

一部がエンジン化してあるプロジェクトで、エンジン側のテストがActionController::UrlGenerationErrorでこけてたので、main_app.root_urlを使ってるけど、普通はroot_urlでよいと思う。

ApplicationControllerでやってるけど、影響範囲大きすぎるので、特定のコントローラーでやるようにした方がよさそう。

テスト

Controller Spec書いたので、載せておく。

require 'rails_helper'

RSpec.describe ApplicationController do
  controller do
    def index
      render nothing: true
    end 
  end 

  describe 'CSRF protection' do
    before do
      ActionController::Base.allow_forgery_protection = true
    end
    after do
      ActionController::Base.allow_forgery_protection = false
    end
    context 'referer is google translate' do
      it 'skips CRSF protection' do
        request.env["HTTP_REFERER"] = 'https://translate.googleusercontent.com/'
        expect {
          post :index
        }.to_not raise_error
      end
    end
    context 'referer is same origin' do
      it 'does not skip CRSF protection' do
        request.env["HTTP_REFERER"] = root_url
        expect {
          post :index
        }.to raise_error(ActionController::InvalidAuthenticityToken)
      end
    end
    context 'referer is example.com' do
      it 'does not skip CRSF protection' do
        request.env["HTTP_REFERER"] = 'http://example.com/'
        expect {
          post :index
        }.to raise_error(ActionController::InvalidAuthenticityToken)
      end
    end
  end  

  describe 'iframe protection' do
    context 'referer is google translate' do
      it 'skips iframe protection' do
        request.env["HTTP_REFERER"] = 'https://translate.googleusercontent.com/'
        post :index
        expect(response.headers['X-Frame-Options']).to eq(nil)
      end
    end
    context 'referer is same origin' do
      it 'skips iframe protection' do
        request.env["HTTP_REFERER"] = root_url
        post :index
        expect(response.headers['X-Frame-Options']).to eq(nil)
      end
    end
    context 'referer is example.com' do
      it 'does not skip iframe protection' do
        request.env["HTTP_REFERER"] = 'http://example.com/'
        post :index
        expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
      end
    end
  end
end

ちなみに、Google翻訳、httpとhttpsでなんか挙動が違う感じだった。(セーフモードと関係ある?)今回はhttpsしか実際の挙動は確認していない。

参考

Mysql2::Error: Illegal mix of collations

慣れないシステム(Rails製。旧システムのDBを利用してリプレース中なのでいろいろとややこしい)を触ってたら、

Mysql2::Error: Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT) for operation '=':

という初めてのエラーに出くわした。

database.ymlに

collation: utf8_general_ci

を指定せずに進めてたからだった。
が、指定した後、テーブルを作り直しても何故か直らない…。

mysql> ALTER TABLE テーブル COLLATE utf8_general_ci;
mysql>  show table status like "テーブル" \G;
      Collation: utf8_general_ci

で手動で変更するもまだダメ。

mysql> show create table "テーブル";
  `カラム` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
mysql> ALTER TABLE テーブル MODIFY COLUMN  `カラム` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL;

カラムも変更したら行けた。
なぜ作り直してもダメだったのかは不明。