EC2+Squidでプロキシサーバを立てて、複数IPでアクセス元を分散させる

スクレイピングをする場合、同じアクセス元だとBanされたりすることがあるようで、なるべくコストを抑えつつプロキシサーバを運用したいと相談されたので、AWSで試してみることにしました。

前提

VPCとサブネットがすでに作成されている前提で書いていきます。 もし、この2つを作成していない場合は作成してください。

手順

EC2

まず、EC2作成します。AMIは Amazon Linux 2 AMI (HVM), SSD Volume Type - ami-00d101850e971728dで、インスタンスタイプは t2.medium を使いました。

理由は、インスタンスタイプによって、インターフェースあたりのIPv4アドレスを追加できる数に制限があるためです。 t2.mediumだと6つ追加できるようです。

※ 自動割り当てパブリックIPを有効にしてください

Elastic Network Interface

ネットワークインターフェースを作成します。 サブネットには既存のサブネットを指定し、セキュリティグループも指定します。作成していなければ、作成してください。

作成したネットワークインターフェースを先ほど作成したEC2にアタッチします。

eth1に新しいIPを割り当てる

またインスタンスの一覧に戻って、作成したインスタンスを指定した後、 アクション > ネットワーキング > IPアドレスの管理を開きます。

f:id:ryskit:20190601211900j:plain

そのあと、eth1側に新しいIPを5つ追加します。

f:id:ryskit:20190601212311j:plain

追加したら更新します。

Elastic IPを割り当てる

Elastic IPを5つ割り当てます。 スコープはVPCで構いません。

Elastic IPを関連付ける

先ほど、eth1に新しいIPを割り当てたかと思いますが、 そのIPに対してElastic IPを関連づけていきます。

Elastic IPを選択し、アクション > アドレスの関連付け をクリックします。

f:id:ryskit:20190601220307j:plain

リソースタイプはネットワークインターフェースを選択し、 ネットワークインターフェースは、作成したものを選択します。

プライベートIPは、eth1で新しく割り当てたセカンダリプライベートIPを選んで、関連付けを5つ行ってください。

f:id:ryskit:20190601221518j:plain

セキュリティグループのインバウンドのルールを追加する

Squid(プロキシサーバー)にアクセスする際のポートをインバウンドのルールに追加しておきます。 今回、タイプはカスタムTCP、ポートは 4578、ソースは カスタム 0.0.0.0/0 を設定しました。

Squidをインストールする

EC2にSSHで接続して以下のコマンドでSquidをインストールします。

sudo yum install -y squid

これでSquidがインストールされます。

Squidの設定ファイルを書き換える

アクセス元を分散するように設定ファイルを書き換えます。

sudo vim /etc/squid/squid.conf

以下のように書き換えました。

#
# Recommended minimum configuration:
#

# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 10.0.0.0/8 # RFC1918 possible internal network
acl localnet src 172.16.0.0/12  # RFC1918 possible internal network
acl localnet src 192.168.0.0/16 # RFC1918 possible internal network
acl localnet src fc00::/7       # RFC 4193 local private network range
acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines

acl SSL_ports port 443
acl Safe_ports port 80      # http
acl Safe_ports port 21      # ftp
acl Safe_ports port 443     # https
acl Safe_ports port 70      # gopher
acl Safe_ports port 210     # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280     # http-mgmt
acl Safe_ports port 488     # gss-http
acl Safe_ports port 591     # filemaker
acl Safe_ports port 777     # multiling http
acl CONNECT method CONNECT

#
# Recommended minimum Access Permission configuration:
#
# Deny requests to certain unsafe ports
http_access deny !Safe_ports

# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports

# Only allow cachemgr access from localhost
http_access allow localhost manager
http_access deny manager

# We strongly recommend the following be uncommented to protect innocent
# web applications running on the proxy server who think the only
# one who can access services on "localhost" is a local user
#http_access deny to_localhost

#
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
#

# Example rule allowing access from your local networks.
# Adapt localnet in the ACL section to list your (internal) IP networks
# from where browsing should be allowed
http_access allow localnet
http_access allow localhost

# And finally deny all other access to this proxy
#http_access deny all

# Squid normally listens to port 3128
#http_port 3128

# Uncomment and adjust the following to add a disk cache directory.
#cache_dir ufs /var/spool/squid 100 16 256

# Leave coredumps in the first cache dir
coredump_dir /var/spool/squid

#
# Add any of your own refresh_pattern entries above these.
#
refresh_pattern ^ftp:       1440    20% 10080
refresh_pattern ^gopher:    1440    0%  1440
refresh_pattern -i (/cgi-bin/|\?) 0 0%  0
refresh_pattern .       0   20% 4320

http_access allow all

client_persistent_connections off
server_persistent_connections off

acl balance random 1/5
balance_on_multiple_ip on

http_port 4578

visible_hostname unknown
forwarded_for off
request_header_access X-Forwarded-For deny all
request_header_access Via deny all
request_header_access Cache-Control deny all
reply_header_access X-Forwarded-For deny all
reply_header_access Via deny all
reply_header_access Cache-Control deny all

max_filedesc 65535

tcp_outgoing_address 192.168.xx.xxx balance
tcp_outgoing_address 192.168.xx.xxx balance
tcp_outgoing_address 192.168.xx.xxx balance
tcp_outgoing_address 192.168.xx.xxx balance
tcp_outgoing_address 192.168.xx.xxx balance

tcp_outgoing_address 192.168.xx.xxx balance で書いているIP部分は、EC2のセカンダリプライベートIPの5つ書いてください。

f:id:ryskit:20190601214628j:plain

※注意: http_access allow all でアクセスを許可するクライアントをすべて許可しているため、ここはよしなに変更してください。

Squidを起動する

以下のコマンドで起動します。

sudo systemctl start squid

自動起動の設定もするのであれば、以下も実行してください。

sudo systemctl enable squid

試してみる

実行するたびに あなたのIPアドレス(IPv4) の部分がランダムに切り替わっていたら成功です🎉

curl --proxy http://EC2の自動割り当てパブリックIP:4578 http://www.ugtop.com/spill.shtml

最後に

いかがでしたでしょうか?

これらの作業を自動化すれば、必要なときにプロキシーサーバーを立てて、 必要ないときはすべてのリソースを解放しておけばお金もかからないので、 VPSでプロキシサーバーを立てて置いとくより安くなるかもしれません。

自動化までやったら、またこれ関連の記事でも書こうと思います。

P.S. 作成したリソースはすべて削除しましょう! お金かかっちゃうので!

メタプログラミングRuby第2版 Ⅰ部を読んだ

最近、友達と毎週メタプログラミングRubyの読書会をやっています。

というのも、友達が転職して🎉、業務でRubyを使い始めることになり、自分も業務で少し使っているので、お互い知識を共有しつつ学びがあれば良いなと思って始めました。

序盤の4章ぐらいまでは特に難しいとは思わなかったんですが、define_methodやevalなどを使って動的にメソッドを定義したり、クラスマクロを作ったりするところはやはりメタプロ脳が足りないのか、少し難しく感じました。

普段、JavaやScalaを使っているので、ここまで自由に書けるのも面白いけど、業務のコードでメタプログラミングしまくると保守が大変そう...というイメージ。

でも、ライブラリに手を入れたり、少しだけここのコードを書き換えたいみたいなときは便利なんだろうな。

まさに、「大いなる力には大いなる責任が伴う」ですね。

内容について

手書きの図を使って、継承チェーンやメソッド探索の動きを説明してくれていたり、出てくるキーワードは理解しやすい言葉で説明されています。

ところどころクイズもあって、解きながら読み進めると理解も深まるし、本としてはおもしろく読めると思います。

Rubyの言語仕様をざっくり理解したあとに読むと良いんじゃないかな。

www.amazon.co.jp

ノート

2章 月曜日: オブジェクトモデル

  • 共通のクラスを持つオブジェクトは、メソッドも共通している。つまり、メソッドはオブジェクトではなく、クラスに存在する
  • レシーバー
  • 継承チェーン Class -> Module -> Object -> Kernel -> BasicObject
  • ancestors メソッド
  • include, extend, prepend
  • included, extended, prepended
  • Object クラスが Kernelモジュールをインクルードしているので、すべてのオブジェクトの継承チェーンにKernelモジュールが挿入されている
  • https://github.com/awesome-print/awesome_print
  • self
  • クラスやモジュールの定義の内側(メソッドの外側)では、selfの役割はクラスやモジュールそのものになる。
  • Refinements
    • モジュール を書いてから、モジュール定義のなかで refine を呼び出す
    • 変更を反映にするには、using メソッドを使って明示的に有効にする必要がある。
    • Refinements が有効になるのは 2 箇所だけ
      • refine ブロックそのものと
      • using を呼び出し た場所からモジュールの終わりまで(モジュール定義にいる場合)またはファイルの終わりまで(トップレベルにいる場合)た
  • Kernel.private_instance_methods.grep(/^print/) // => [:printf, :print]

3章 火曜日:メソッド

  • メソッドを呼び出すということは、オブジェクトにメッセージを送っていること。
  • sendメソッド
    • どんなメソッドも呼び出せて、privateでさえも呼び出せる
  • define_method
    • 動的にメソッドを定義できる
  • data_source.methods.grep(/^get(.*)info$/) { Computer.define_component $1 }
  • メソッド呼び出しを method_missing に集中させ、ラップしたオブジェクトに転送する
  • respond_to_missing?
    • respond_to? は、respond_to_missing? というメソッドを呼び出している
  • const_missing
  • ゴーストメソッドの名前と、継承した本物のメソッドの名前が衝突すると、後者が勝ってしまう
  • こうした最小限のメソッドし かない状態のクラスをブランクスレートと呼ぶ。Ruby には、最初からこのブランクスレートが用意されている。
  • undef_method
    • メソッド呼び出しへのレスポンスを止める
  • remove_method
    • 現在のクラスからメソッドを削除する
      • 継承元にある場合は呼ばれる

4章 水曜日: ブロック

  • ブロックはメソッドに渡され、メソッドは yield キーワードを使ってブロックをコールバックする。
  • メソッドの内部では、Kernel#block_given? メソッドを使ってブロックの有無を確認できる。
  • スコープゲート
    • def, class, module
    • Class => Class.new
      • 変数を共有できるように => フラットスコープ
  • define_method
def define_methods
  shared = 0
  Kernel.send :define_method, :counter do
    shared
  end
  
  Kernel.send :define_method, :inc do |x|
    shared += x
  end
end

define_methods
counter # => 0 inc(4)
counter # => 4
  • instance_eval
  • instance_eval に渡したブロックは、レシーバを self にしてから評価されるので、レシーバの private メソッドや @v などのインスタンス変数にもアクセスできる。また、他のブロックと同じよ うに、instance_eval を定義したときの束縛も見える。
  • ブロックを評価するためだけにオブジェクトを生成することもある。このようなオブジェクトは、クリーンルームと呼ばれる。
    • クリーンルームにはメソッドもインスタンス変数もあまり増やさないほうがいい。ブロックに伴う環境のメソッドやインスタンス変数と名前が衝突する可能性があるからだ。クリーンルームとして使うには、BasicObject のインスタンスが最適である。
  • コードを保管できるところは少なくとも3つある
    • Proc <- ブロックオブジェクト
    • lambda <- Procの変形
    • メソッドのなか

Procオブジェクト

inc = Proc.new {|x| x + 1}
inc.call(2)

この技法は、あとで評価と呼ばれるもの。
  • ブロックをProcに変換する2つのカーネルメソッド
    • lambda
    • proc
dec = lambda {|x| x - 1}
dec.class # => Proc
dec.call(2) # => 1
p = ->(x){ x - 1} # => lambda {|x| x - 1} と同じ

「矢印ラムダ」演算子で lambda を生成する方法
  • yieldでは足りないケース
    • 他のメソッドにブロックを渡したいとき
    • ブロックをProcに変換したいとき
  • &をつけると「メソッドに渡されたブロックを受け取って、それをProcに変換したい」という意味になる
    • &をつけなければ、Prcoのままになる
  • Proc をブロックに戻したいときも & を使う
  • Proc と lambda には 2 つの違いがある。
    • ひとつは、return キーワードに関すること
    • もうひとつは、引数のチェックに関すること

return キーワードの違い

def double(callable_object)
  callable_object.call * 2
end

l = lambda { return 10 }

double(l) # => 20
def another_double
  p = Proc.new { return 10 }
  result = p.call
  return result * 2 # ここまで来ない!
end

another_double # => 10

引数のチェックの違い

  • 一般的に lambda のほうが Proc(や普通のブロック)よりも引数の扱いに厳しい。違った項数で lambda を呼び出すと、ArgumentError になる。Procは引数列を期待に合わせてくれる。
p = Proc.new {|a, b| [a, b]}
p.call(1, 2, 3) # => [1, 2]
p.call(1) # => [1, nil]

UnboundMethod

  • UnboundMethod は、元のクラスやモジュールから引き離されたメソッドのようなものである。Method を UnboundMethod に変換するには、Method#unbind を呼び出す。

5章 木曜日: クラス定義

  • 特異クラス(別名: シングルトンクラス)
  • Rubyのプログラムは、常にカレントオブジェクトselfを持っている。それと同様に、常にカレントクラス(あるいはカレントモジュール)を持っている
  • カレントクラスを変更するには、classキーワード以外の方法が必要
  • Module#class_eval メソッド
  • Module#class_evalBasicObject#instance_eval とはまったく別物
    • instance_eval はselfを変更するだけだが、class_evalはselfとカレントクラスを変更する
  • クラスインスタンス変数
    • アクセスできるのはクラスだけであり、クラスのインスタンスやサブクラスからはアクセスできない
class MyClass
  @my_var
end
  • クラス変数
    • サブクラスや通常のインスタンスメソッドからもアクセスできる
class Loan
  def initialize(book)
    @book = book
    @time = Loan.time_class.now
  end
  
  def self.time_class
    @time || Time
  end
  
  def to_s
    ...
  end
end

特異メソッド

  • 単一のオブジェクトに特化したメソッドのことを 特異メソッド という
  • クラスメソッドはクラスの特異メソッド
def object.method
  # メソッドの中身
end

上記のobjectの部分は、オブジェクトの参照、クラス名の定数、selfのいずれかが使える

クラスマクロ

  • attr_*族メソッドはModuleクラスで定義されているので、selfがモジュールであっても、クラスであっても使える。
  • attr_accessorのようなメソッドはクラスマクロと呼ぶ
class Book
  def title #...
  def subtitle #...
  def lend_to(user)
    puts "Lending to  #{user}"
    # ...
  end
  
  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      warn "Warning: #{old_method}() is deprecated. Use #{new_method}()."
      send(new_method, *args, &block)
    end
  end
  
  deprecate :GetTitle, :title
  deprecate :LEND_TO_USER, :lend_to
  deprecate :title2, :subtitle

特異クラス

  • あなたが見ているクラスとは別に、オブジェクトは裏に特別なクラスを持っている
    • それが特異クラスと呼ばれるもの
class << an_object
  # << あなたのコードをここに
end

特異クラスの参照を取得したければ、スコープの外にselfを返せばよい

obj = Object.new
singleton_class = class << obj
  self
end

singleton_class.class # => Class

"abc".singleton_class # => #<Class::#<String:0x331df0>
  • インスタンスを一つしか持てない(だから、シングルトンクラスと呼ばれる)
  • 継承ができない
  • オブジェクトが特異メソッドを持っていれば、特異クラスのメソッドから探索をはじめる

Rubyオブジェクトモデルの7つのルール

  1. オブジェクトは 1 種類しかない。それが通常のオブジェクトかモジュールになる。
  2. モジュールは 1 種類しかない。それが通常のモジュール、クラス、特異クラスのいずれかになる。
  3. メソッドは 1 種類しかない。メソッドはモジュール(大半はクラス)に住んでいる。
  4. すべてのオブジェクトは(クラスも含めて)「本物のクラス」を持っている。それが通常のクラスか特異クラスである。
  5. すべてのクラスは(BasicObject を除いて)ひとつの祖先(スーパークラスかモジュール)を持っている。つまり、あらゆるクラスが BasicObject に向かって 1 本の継承チェーンを持っている。
  6. オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラスである。クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラスである(3回唱えてみよう。もっと早く。図 5-5「特異クラスと継承」を見返せば、納得できるはずだ)。
  7. メソッドを呼び出すときは、Ruby はレシーバの本物のクラスに向かって「右へ」進み、継承チェーンを「上へ」進む。Ruby のメソッド探索について知るべきことは以上だ。

クラス拡張

  • Object#extend
    • レシーバの特異クラスにモジュールをインクルードするためのショートカット
module MyModule
  def my_method; 'hello'; end
end

obj = Object.new
obj.extend MyModule
obj.my_method # => 'hello'

class MyClass
  entend MyModule
end

MyClass.my_method # => 'hello'

メソッドラッパー

  • エイリアス
    • alias_method :m, :my_method
    • 新しいメソッド名を先に、古いメソッドをあとに書く
  • メソッドの再定義
    • 元のメソッドを変更することではない。新しいメソッドを定義して、元のメソッドの名前をつけること
class Integer
  alias_method :old_plus, :+
  
  def +(value) self.old_plus(value).old_plus(1)
end end

第6章 金曜日: コードを記述するコード

  • instance_eval
  • class_eval
  • Kernel#eval
  • Bindingオブジェクト
    • Bindingオブジェクトにはスコープは含まれているが、コードは含まれていないため、ブロックよりも「純粋」なクロージャーと考えることができる
    • 取得したスコープで評価するには、evalの引数にBindingを渡せばいい
class MyClass
  def my_method
    @x = 1
    binding
  end
end

b = MyClass.new.my_method
eval "@x", b # => 1
  • 定数: TOPLEVEL_BINDING
    • トップレベルのスコープのBinding
    • これを使えば、トップレベルのスコープにプログラムのどこからでもアクセスできる
class AnotherClass
  def my_method
    eval "self", TOPLEVEL_BINDING
  end
end

AnothierClass.new.my_method ## => main
  • gem: Pry
    • Bindingをうまく活用している
    • binding.pryを呼び出すと、現在のbindingでRubyのインタプリタが開かれる
      • つまり、実行中のプロセスの中
  • instance_eval, class_evalはコード文字列またはブロックのいずれかを
  • evalでコードインジェクションに気をつける
    • 自分で書いた文字列のみevalを使うように制限すれば良い
  • オブジェクトが汚染されているか
    • tainted? を使う
  • Rubyにはファイル名を受け取り、そのファイルのコードを実行するメソッドが用意されている
    • Kernel#load
    • Kernel#require

docs.ruby-lang.org

クイズ: アトリビュートのチェック(手順2)

  • クラスメソッドを定義するには、クラスのスコープに入る必要がある
    • クラスのスコープに入るには、class_evalを使えば良い

クイズ: アトリビュートのチェック(手順4)

  • attr_checkedをあらゆるクラス定義で使うには、Class, またはModuleのインスタンスメソッドにすればよい
  • self.inherited
  • include
  • prepended
  • Module#extendedをオーバーライドすれば、モジュールがオブジェクトを拡張したときにコードを実行できる
    • 以下のフックは、オブジェクトのクラスに住むインスタンスメソッドにしか使えない
      • Module#method_added
      • Module#method_removed
      • Module#method_undefined
    • 特異メソッドのイベントをキャッチするには以下を使う
      • Kernel#singleton_method_added
      • Kernel#singleton_method_removed
      • Kernel#singleton_method_undefined

クイズ: アトリビュートのチェック(手順5)

module CheckedAttributes
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    def attr_checked(attribute, &validation)
      define_method "#{attribute}=" do |value|
        raise 'Invalid attribute' unless validation.call(value)
        instance_varialbe_set("@#{attribute}", value)
      end
      
      define_method attribute do
        instance_variable_get "@#{attribute}"
      end
    end
  end
end

MySQLのJSON型の値をGenerated Columnsを使ってカラムにデータを追加してみる

MySQLのGenerated Columnsを使う必要があり、動作が気になったので検証してみました。

Generated Columns(生成カラム)とは

Values of a generated column are computed from an expression included in the column definition. [翻訳] 生成された列の値は、列定義に含まれている式から計算されます。

つまり、Generated Columnsの値には、そのカラムを定義したときに定義した関数や式の計算結果の値が入るということ。

MySQL :: MySQL 5.7 Reference Manual :: 13.1.18.8 CREATE TABLE and Generated Columns

なるほど、もう少し手を動かして掴んでみましょう。

作業

MySQLに接続するまで

試すには、Generated Columnsが追加されたバージョン 5.7.6 以上のMySQLをインストールします。

Dockerでサクッとやる。

docker pull mysql

これで最新のMySQLのイメージを取得。

docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 mysql

これでMySQLを起動。 rootのパスワードはmysql

次は、MySQLのコンテナに入る。

docker exec -it `CONTAINER ID` /bin/bash

MySQLに接続する。 パスワードはさっき書いたものを入力する。

mysql -u root -p

データベースを作成する

以下のコマンドでデータベースを作成する。

create database gc_db;

テーブルを作成する

以下のコマンドでテーブルを作成する。

create table gc_test(id int unsigned auto_increment not null primary key, json_data json);

カラムは id , json_data のみでひとまず作成する。

データを追加する

以下のコマンドでデータを追加する。

insert into gc_test(json_data) values ('{"demo": {"id": 1}}'), ('{"demo": {"id": 2}}'), ('{"demo": {"id": 3}}');

これでJSONデータを含むテーブルができました。

mysql> select * from gc_test;
+----+---------------------+
| id | json_data           |
+----+---------------------+
|  1 | {"demo": {"id": 1}} |
|  2 | {"demo": {"id": 2}} |
|  3 | {"demo": {"id": 3}} |
+----+---------------------+

次は、Generated Columnsを定義します。

Generated Columnsを定義する

今回、Generated Columnsを定義するとき、 json_data カラムの demo オブジェクト中の id を取り出すように定義します。

次のコマンドでテーブルにGenerated Columnsを追加します。

alter table gc_test add column demo_id varchar(255) GENERATED ALWAYS AS (json_extract(`json_data`, '$.demo.id')) STORED;

STORED というキーワードを書いているが、他にもVIRTUALが存在します。 デフォルトでは、VIRTUALが選択されます。

このキーワードで、生成された値をストレージに格納するかを決めることができます。

VIRTUAL

VIRTUAL の場合は、ストレージには保存されず、BEFOREトリガーの後に行が読まれたときに評価されます。

VIRTUAL: Column values are not stored, but are evaluated when rows are read, immediately after any BEFORE triggers. A virtual column takes no storage.

引用 MySQL :: MySQL 5.7 Reference Manual :: 13.1.18.8 CREATE TABLE and Generated Columns

行が読み込まれたときに評価だと、Generated Columnsの値で検索を絞り込むときにパフォーマンスは悪いのかな? そこらへんはデータを大量に用意して後日試してみたい。

STORED

STOREDの場合は、行がINSERTまたはUPDATEされたときに評価されて値が格納されます。

STORED: Column values are evaluated and stored when rows are inserted or updated. A stored column does require storage space and can be indexed.

引用 MySQL :: MySQL 5.7 Reference Manual :: 13.1.18.8 CREATE TABLE and Generated Columns

Generated Columnsを定義した後はこんな感じになります。

mysql> select * from gc_test;
+----+---------------------+---------+
| id | json_data           | demo_id |
+----+---------------------+---------+
|  1 | {"demo": {"id": 1}} | 1       |
|  2 | {"demo": {"id": 2}} | 2       |
|  3 | {"demo": {"id": 3}} | 3       |
+----+---------------------+---------+

json_dataのdemoオブジェクトの中のidが、demo_id として取り出されてカラムに格納されているのが分かりますね。

検索のパフォーマンスがどうなるのかがモヤモヤしてきたので、 そこらへんはドキュメント読むなり実際に試してみて計測したほうが良さそうだな。

以上です。

[Android] [Kotlin] Android開発でRoomとKoinの設定

今、UdemyのAndroid開発コースをやっていて、そのコースではRoomとToothPickというライブラリを使って開発が進むのですが、個人的にKoinを使いたくてドキュメント見ながらやると見事に設定の記述漏れでハマったので、備忘録として書いておきます。

Kotlinのバージョンは、1.3.20 を使用。

Module: appbuild.gradle に以下の記述を追加する。

// Room
def room_version = "2.1.0-alpha05"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"

// Koin
def koin_version = "1.0.2"
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version"
implementation "org.koin:koin-android-viewmodel:$koin_version"

今回、以下の設定が抜けていたためビルドは通る?けど、アプリが起動しないため原因が最初どこかわからなくて1時間ぐらい時間溶かしてしまいました。

// Room
kapt "androidx.room:room-compiler:$room_version"

ちなみに、kapt を利用するには以下の記述が必要です。

apply plugin: 'kotlin-kapt'

kaptは Kotlin Annotation Processing Tool(であってる?)で、 Java6から導入された Pluggable Annotation Processing API を Kotlinからも利用できるようにしたものです。

Koinは Applicationクラスを継承したAppクラスに設定を記述しました。

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        startKoin(this,
            listOf(AppModule.module())
        )
    }

    companion object {
        lateinit var instance: Application
    }

    init {
        instance = this
    }
}

AppModulemodule() 関数内にDIの設定を記述しました。

object AppModule {
    fun module() = module {
        single<ITaskModel> { TaskLocalModel() }
        single<INoteModel> { NoteLocalModel() }
        single {
            RoomDatabaseClient.getInstance(App.instance.applicationContext)
        }
    }
}

これで合っているのかよく分からないので、間違っていたらコメントとかもらえると嬉しいです。

以上です。

UdemyのKotlin for Android O Developmentというコースが良かった!

普段はWeb開発しかしていないのだけれど、モバイルアプリを開発する必要性が出てきたので最近いろいろサンプルアプリを作ったり、ライブラリを試しに動かしたりしている。

そのいった中で、Udemyの「Kotlin for Android O Development」というコースが個人的に総合して良いと思ったので、良かった点・気になった点に分けて感想をまとめてみる。

良かった点

Kotlinを使ったAndroidの開発コースであること

このコースはKotlinを使って1からサンプルアプリを開発していくため、KotlinでAndroidアプリを開発していきたいと思っていた僕にはちょうどよかった。

Kotlinで書かれた書籍はいつくかあるもののまだまだJavaで説明されているものも多いので、Udemyのような動画で学ぶことには意味がある。

サンプル数が多い

なにか新しい言語や仕組みを学ぶときは本を読むのも良いが、実際にものを作って動かしてから書籍を読んだ方が個人的には理解力が高まると思っているので、とにかくサンプルアプリを作りまくりたかった。

このコースでは、10個以上のサンプルアプリを作ることが可能なので、とりあえず手を動かして覚えたい人におすすめしたい。

DBを使ったサンプルがある

モバイルアプリを開発するにあたってDBを使うことは多々あるのではないかと思う。

このコースでは簡単ではあるがSQLiteを使ってアプリを作ることもできるので良かった。

Firebaseを使ったアプリも作れる

コースの総まとめとして、今まで作ってきたサンプルアプリの機能を駆使して、そしてFirebaseも利用してアプリを作ることができる。

実際に使ってみると分かるが、Firebase簡単すぎっ!!ってなる。

少し気になったのは、コースで使っているFirebaseのライブラリのバージョンが古いので、 deprecated になっているメソッドを呼び出していたりしていた。

僕がアプリを作ったときは最新バージョンを使うようにしていたので、値がうまく取れないなと思ったら上記のような理由が原因だった。Firebaseドキュメントや他の方の記事を参考にすれば解決できるはずだ。

Android でファイルをアップロードする  |  Firebase

気になった点

ListViewのみを使っていた

おそらくBeginner向けのコースであるからだと思うが、RecyclerViewもコースに盛り込めばいいのにと思った。 おそらく普段Androidアプリを開発されている方はListViewとか使うのか?と思ったりしたのでここらへんは気になった。

Fragmentは扱われていない

普通はAcitivityのみを使って開発は行わないと思うのでFragmentについてのチャプターもあってもいいんじゃないかと思った。 このコースではFragmentは学べないため、記事を見るなり本を読むなりして学ぶのが良いだろう。

最後に

Kotlin for Android O Development: From Beginner to Advanced

このコースは講師の方が英語で説明するため、英語字幕を出しながら動画を視聴していた。話すスピードはゆっくりで難しいことは話さないので、そんなに英語が得意でない僕でも理解できたのでおすすめである。

RecyclerViewやCardViewはAndroid Developersの記事を読むのが良さげ。

developer.android.com

まだまだ知らないといけない部分があるので、このまま継続してAndroid開発の学習・開発を進めていく。

おすすめの記事やこれだけはやっておけみたいなのがあれば、 ブログのコメントかTwitterに投げつけてくださると嬉しいです!

では!

AWS CloudFrontのキャッシュを削除する方法

静的ページをCloudFront + S3でホストしていて画像を更新したけど、キャッシュのせいで反映されないということがありそうです。

そんなときにキャッシュの有効期限まで待つのではなく、自分でキャッシュを削除して反映させたいはず。

この記事では、AWSのCloudFrontでどうやってキャッシュを削除するかを知ることができます。

では、いってみましょう。

ファイルを無効化

CloudFrontにはInvalidationというものがあり、これをCreateすることでキャッシュを削除することができます。 Edgeサーバーにあるファイルを無効化するからInvalidation。

手順

1. コンソールで [CloudFront Distributes]を開く。

f:id:ryskit:20190209171501j:plain

2. [ディストリビューション] を選択する。

選択したディストリビューションの概要が表示されます。

f:id:ryskit:20190209171840j:plain

3. [Invalidations] タブを選択する。

f:id:ryskit:20190209171915j:plain

4. [Create Invalidation] ボタンをクリックする。

f:id:ryskit:20190209172213j:plain

ボックスの中にキャッシュを削除したいファイルのパスを入力してください。

画像のように記述すると、 /images/xxxx.jpg のキャッシュを削除することになります。

まとめてキャッシュを削除したいときは、 /* のような指定をするとすべてキャッシュを削除できます。

5. [Invalidate] ボタンをクリックする。

選択すると、CloudFrontのEdgeサーバーのキャッシュ削除処理が開始されます。 Status が In Progress の場合はまだ削除処理中で、 Complete になれば完了です。

こちらにAWSの公式ドキュメントがあるので、詳細を知りたい場合は参考にしてみてください。

Databindingを利用したアプリをビルドするとUnresolved reference: BR エラーになった

Androidアプリの開発を始めて、Databindingが便利そうというかMVVMなどのアーキテクチャで実装しようとすると必須?みたいなので、とりあえず簡単なサンプルアプリを実装して、どんなものか簡単に実装を理解しようとした。

アプリを書き終わったのでビルドしようとすると Unresolved reference: BR というエラーに出くわしてしまった。

結論から言うと、

(Module: app) build.gradle に apply plugin: 'kotlin-kapt' の記載を書いたら解決した。

経緯

Googleのドキュメントを見る限り、 (Module: app) build.gradleに、

android {
    ....
    dataBinding {
        enabled = true
    }
}

を書くだけでデータバインディングの準備はOKなのかなと思っていた。

そのため、以下のようにViewHolderクラスで ViewDataBinding に変数をセットするコードを書いてビルドしてみるとエラーになった。

class WeatherViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(data: Any) {
        binding.setVariable(BR.data, data) // ここで kotlin compiler にUnresolved reference: BR と表示された
        binding.executePendingBindings()     
    }
}

どうやら、BRクラスファイルの参照解決ができてないということで、 うーんとうなりながらぐぐってみると以下の記事に行き着きました。

medium.com

どうやらこいつが必要らしい。

apply plugin: 'kotlin-kapt'

ということで、build.gradleに記載してビルドしてみるとすんなりエラーなくビルドできました。

いろいろ昔の記事を見ていると、

kapt 'com.android.databinding:compiler:x.x.x'

を書いてる記事とかあるんだけど、これはもう書かなくて良いっぽい。

最後に

不慣れなことをすると、すぐバグ踏んだり躓いたりしやすいから、早く慣れていきたい。 友達とサービス作るときに、キダくんAndroidアプリ開発で参加してよ!って声がいっぱいかかるぐらいにはスキルアップしていくぞ!

Rustをインストールして始めてみる

会社の人から、Rustはすごくいいぞ〜と言われ、O'Reillyから出てるプログラミングRustを読んだりしている。

Rustをインストールしたとき、すごく簡単だったので手順を記しておく。 といっても、以下のコマンド叩くだけ。

curl https://sh.rustup.rs -sSf | sh

すごく簡単なので、すぐにRustを始められるのが良かったと思う。

あとRustをインストールすると、CargoというRustのビルドシステム兼パッケージマネージャが一緒にインストールされるけど、これがいろいろ面倒なことを代わりにやってくれるので、プロジェクトのセットアップに躓くことがなかったのは嬉しい。

案件のどこかで使えるようにしておきたい。