Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【Docker】イメージビルド時にキャッシュを使用しない

f:id:rennnosukesann:20190108224132p:plain

メモ。

Dockerイメージ作成時、デフォルトだと過去のビルドキャッシュが存在する場合そちらを使用してしまいます。

$ ls
Dockerfile
$ docker build -t hoge .
Step 2/20 : COPY entrypoint.sh /sbin/entrypoint.sh
 ---> Using cache
 ---> 5214f6bedefd

これにより、コマンドの実行結果やファイル内容の反映が上手くいかない場合があります。 --no-cache を指定することで、キャッシュが存在する場合も無視してビルドを実行できます。

$ docker build -t hoge . --no-cache
 ---> 5d870d6990ba
Step 2/20 : COPY entrypoint.sh /sbin/entrypoint.sh

以上です。

【Kotlin】Java(8) -> Kotlinやってみて「良い」と思ったことリスト

f:id:rennnosukesann:20190106130012p:plain:w500

Kotlinをやっていて、Java(8)と比較して良いな、と思った部分を書きました。

null許容型

Swiftなどにもありますが、Kotlinではnull許容型が定義されており、それと対を成すようにデフォルトの型宣言ではnullが許容されないようになっています。

val hoge: String = null // Error:(9, 23) Null can not be a value of a non-null type String
val hoge: String? = null // OK 

エルビス演算子(?:)

オペランド(被演算子)がnullのときのデフォルト値を設定できる

String fuga = hoge == null ? "hoge is null" : hoge;

val fuga = hoge ?: "hoge is null"

第一級関数

Kotlinは関数を言語仕様に持ち、かつそれらを第一級関数として扱えます(値として扱える)。

fun hoge(arg1: String) {
    println(arg1)
}

fun main(args: Array<String>) {
    val func = ::hoge // Javaのメソッド参照のように渡す
    func("hoge")
}

if式

Scalapythonのように、ifが式なので返り値を持ちます。

val isHoge = true;
val hoge = if (isHoge) "hoge" else "not hoge"

文字列テンプレート

JavaScript言語仕様が持つ文字列テンプレートをKotlinでも使用できます。 文字列リテラル内で $ を使って変数を文字列として埋め込みます。

val message = "Hello, Kotlin!"
println("Welcome to Kotlin world : $message")

${} を使用すると、カッコ内に式を使用できます。

val kotlin = "Kotlin!"
val java = "Java"
val isKotlin = true
println("Hello ${if (isKotlin) kotlin else java}")

クラスフィールドにデフォルトでアクセサがつく

immutableフィールド val で宣言した場合はgetterのみが、 mutableフィールド var で宣言した場合はsetter+getterが生成されます。 Kotlinでは暗黙的にgetter/setterが呼ばれます。

class Gorilla(
    val name: String,
    var isAdult: Boolean // getter + setter
)


fun main(args: Array<String>) {
    val gorilla = Gorilla("ゴリラ", true)
    
    // 暗黙的なgetter呼び出し
    println(gorilla.name)  // ゴリラ
    println(gorilla.isAdult) // true
    
    // 暗黙的なsetter呼び出し
    gorilla.isAdult = false
    println(gorilla.isAdult) // false
}

値forループの廃止

言語仕様で for (int i = 0; i < 10; i++) のような繰り返し変数をインクリメントしていく for 文が廃止されており、基本的に拡張forループのみを使用することになります。

Kotlinでfor文による値のインクリメントは .. 演算子によるレンジによって実現できます。

for (i in 1..10) {
    println(i) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}

レンジは逆順・数値飛ばしが可能です。

for(i in 10 downTo 1) {
    println(i) // 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
}
for(i in 1..10 step 2) {
    println(i) // 1, 3, 5, 7, 9
}

レンジは閉区間であり、.. 演算子の右側の値を数列に含みます。 開区間のレンジを作る場合、 until を利用します。

for(i in 1 until 10) {
    println(i)  // 1, 2, 3, 4, 5, 6, 7, 8, 9
}

分割代入

オブジェクトプロパティを複数の変数に代入することができます。

fun divMod(a: Int, b: Int) : Pair<Int, Int> {
    return Pair(a / b, a % b)
}

fun main(args: Array<String>) {
    val (div, mod) = divMod(10, 3)
    println("div: $div , mod: $mod")
}

when式

Javaではif/switch文で処理できた複数の分岐処理が、Kotlinではwhen式と呼ばれる言語仕様で実装できます。 when式は式として評価されるので分岐先で結果を返す・変数に入れるなどの操作ができます。 whenはJavaのswitch文のように並列に記述できます。

fun main(args: Array<String>) {
    val num = 3;
    println(fizzbuzz123(num)) // 
}

fun fizzbuzz123(num: Int): String{
    return when (num) {
        1 -> "fizzbuzz"
        2 -> "fizz"
        3 -> "buzz"
        else -> "$num"
    }
}

また引数を省略することで、値マッチングの代わりに条件式による分岐が可能になります。

fun main(args: Array<String>) {
    val num = 3;
    println(fizzbuzz(num))
}

fun fizzbuzz(num: Int): String{
    return when {
        num % 15 == 0 -> "fizzbuzz"
        num % 3 == 0 -> "fizz"
        num % 5 == 0 -> "buzz"
        else -> "$num"
    }
}

名前付き引数/デフォルト引数

名前付き引数・デフォルト引数もサポートされています。

fun add(a: Int, b: Int = 1) :Int {
    return a + b;
}

fun main(args: Array<String>) {
    println(add(1,2)) //3
    println(add(1)) //2
    println(add(a = 4,b = 2)) // 6
}

全般的に他言語で実装済みのモダンで使いやすい言語仕様が入っている印象でした。 まだまだ良いところがありそうなので、追記するかもしれません。

【Kotlin】Kotlin CLIでKotlinをJavaバイトコードにコンパイル/実行する

f:id:rennnosukesann:20190106130012p:plain:w500

今日はじめてKotlinを触ってみたのですが、その際に使用したKotlin CLIによるコンパイルと実行のメモです。

Kotlin CLIのインストール

実施環境:Mac OS X Mojave 10. 14.2

brew でインストールできます。

$ brew update
$ brew install kotlin

コンパイル

適当な .kt ファイルを用意します。

hoge.kt
fun main(args: Array<String>) {
    println(args)
}

kotlinc コマンドでコンパイルします(オプションでKotlinランタイムを含めたjarを生成)。

$ kotlinc hoge.kt -include-runtime -d hoge.jar

jar ファイルが生成されるので、Java同様 java コマンドで実行します。

$ ls
hoge.kt hoge.jar
$ java -jar hoge.jar
[Ljava.lang.String;@1b6d3586

コンパイル時に -include-runtime でKotlinランタイムを含めているのは、Kotlinコンパイラコンパイルされて生成されたバイトコードがKotlinランタイムのライブラリに依存しているためです。ランタイムを含めずにそのままコンパイルすると、実行時に kotlin/jvm/internal/Intrinsics クラスが見つからず ClassNotFoundException 例外を送出します。

$ kotlinc hoge.kt
$ kotlinc _hoge.class
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
    at _hoge.main(1_5_1_compile.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

ちなみに kotlinc コマンドを引数なしで実行すると、インタラクティブシェル(REPL)が起動されます。

$ kotlinc
Welcome to Kotlin version 1.3.11 (JRE 1.8.0_131-b11)
Type :help for help, :quit for quit
>>> println("Hello, Kotlin CLI !")
Hello, Kotlin CLI !
>>> :help 
Available commands:
:help                   show this help
:quit                   exit the interpreter
:dump bytecode          dump classes to terminal
:load <file>            load script from specified file

参考文献

kotlinlang.org

【Twitter】Twitter OAuthでメールアドレスの第三者共有を許可する設定

f:id:rennnosukesann:20190105182527p:plain:w300

メモ。

Twitter OAuth1.0a認証にて、メールアドレス認可を第三者に行いたい場合、下記のような設定が必要になります。

1. OAuth認証ページURLリクエストに include_email を含める

https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true

2. Developerアカウントダッシュボード App detailsTerms of ServiceURL Privacy policy URL を設定する

f:id:rennnosukesann:20190105182045p:plain

f:id:rennnosukesann:20190105182119p:plain

3. Developerアカウントダッシュボード PermissionAdditional permissionsRequest email address from users を有効にする

f:id:rennnosukesann:20190105182301p:plain

f:id:rennnosukesann:20190105182313p:plain


以上です。

【npm/yarn】パッケージをダウングレードする

f:id:rennnosukesann:20181223181823p:plain:w500

メモ。

npm では、パッケージのダウングレードを行う場合、一旦パッケージを削除してから再度インストールし直す必要があります。

$ npm remove cowsay
$ npm install --save cowsay@1.0.0

yarn であれば、 yarn upgrade [パッケージ名]@[version] で指定したバージョンにそのままダウングレードしてくれます。もちろんアップグレードも可能。

$ yarn upgrade cowsay@1.0.0

yarn は楽でいいですね。個人的には npm と違ってデフォルトでローカルパッケージインストールなのが安心です。

【React Native/Expo】Google Oauth認証で認証後リダイレクトを行う

f:id:rennnosukesann:20190101154829p:plain:w500

前回の記事では、Expoを使用して作成したReact NativeアプリにGoogleOAuth認証を実装しました。

rennnosukesann.hatenablog.com

今回はその認証後にリダイレクト先URLを設定し、リダイレクト先にログインユーザの情報にアクセスするためのトークンを渡す手順について書いていきます。

WebクライアントIDの作成

認証後にリダイレクト可能なエンドポイントを設定するため、WebクライアントIDを作成します。

下記Google認証設定のページに遷移し、「認証情報の作成」ボタンをクリックします。

console.developers.google.com

f:id:rennnosukesann:20190102181936p:plain

ボタンをクリックすると作成したい認証情報のメニューが表示されるので「OAuth クライアントID」をクリック。

f:id:rennnosukesann:20190102182035p:plain

作成するクライアントIDの種類を選択します。今回はWebクライアントIDが欲しいので「Webアプリケーション」を選択。

f:id:rennnosukesann:20190102182418p:plain

すると詳細設定用のフォームが出現するのでIDの名称を入力し、リダイレクトしたいURLのドメインを「承認済みのJavaScript生成元」に、「承認済みのリダイレクトURL」にURLをそれぞれ入力します。入力が完了したら「作成」をクリック。

f:id:rennnosukesann:20190102182730p:plain

するとIDが作成されます。同時にクライアントIDを掲載したダイアログが表示されるので、IDをコピーしましょう。以降の工程ではこのIDをクライアントIDとして使用します。

f:id:rennnosukesann:20190102183120p:plain

アプリケーション側の実装

アプリケーション側の実装についても触れていきます。 以降の実装では、下記記事のアプリケーション作成を前提としています。

rennnosukesann.hatenablog.com

App.js

前回の記事 で作成した App.js 中の signInWithGoogle を以下のように変更します。

authUrl のパラメータである client_id には 先程取得したWebクライアントIDを入力してください。また redirect_url にはリダイレクト先のURLを入力してください。

  async signInWithGoogle() {
    try {
      const result = await Expo.AuthSession.startAsync({
        authUrl:
          `https://accounts.google.com/o/oauth2/v2/auth?` +
          `&client_id={WebクライアントID}` +
          `&redirect_uri={リダイレクトURL}` +
          `&response_type=code` +
          `&access_type=offline` +
          `&scope=profile`,
      });
     if (result.type === 'success') {
        console.log('Google Access Token: ' + result.accessToken);
      }
    } catch (e) {
      console.log(e);
    }
  }

Backend

今回は認証後のリダイレクト先として自前のバックエンドAPIサーバを指定すします。例として以下のようなSpring実装のAPIエンドポイントを用意し、Google OAuth認証後はこのエンドポイントに対してリクエストが飛びます。

下記実装ではAPIリクエストが届くとバックエンド側のコンソールにトークンIDが表示されるようにしています。実際はトークンIDを利用して、Google認可ユーザの情報を取り扱ったりすることが多いです。

@RestController
public class OAuthController {

@RequestMapping(value = "/oauth/google", method = RequestMethod.GET)
  public void signInWithGoogle(@RequestParam String code) {
      System.out.println(code);
  }

}

Demo

それでは、実際にアプリを動かしてみます。

npm start でExpo Developer toolsを起動し、QRコードをクライアント端末でスキャンしインストールを開始します。詳細な手順は下記記事に載っているので、適宜参照していただければ幸いです。

rennnosukesann.hatenablog.com

アプリを端末にインストールして起動したら、中央の「Sign in with Google」 ボタンを押します。

f:id:rennnosukesann:20190102130012p:plain:w300

すると 「Expoがサインインのために"expo.io"を使用しようとしています`」と書かれたダイアログが出現するので、「続ける」をタップします。

f:id:rennnosukesann:20190102201704p:plain:w300

続けて起動したアプリが別のサービス上でログインすることへの許可を求めてくるので、「Yes」をタップ。

f:id:rennnosukesann:20190102201749p:plain:w300

するとGoogleの認証画面に遷移します。すでに端末上でGoogleログイン済みのアカウントがある場合、アカウント一覧からログインしたいアカウントを選択します。

f:id:rennnosukesann:20190102130236p:plain:w300

認証に成功するとリダイレクトが実行され、バックエンドログにトークンIDが表示されているのがわかります。

2019-01-02T20:13:33.22+0900 [APP/PROC/WEB/0] OUT aBcd...(トークンIDが続く)

これでリダイレクト成功です!


OAuth認証は自前のサービスに組み込んで認証に使う場合が多く、リダイレクトでGoogleなどのSNS上にあるユーザ情報にサービスがアクセスさせなければいけない場面も割と多いので、今後も頻繁にメモしていきたいですね。

参考文献

Google - Expo Documentation

【ReactNative/Expo】Google OAuth認証をReact Native(Expo)アプリに実装する

f:id:rennnosukesann:20190101154829p:plain:w300

前回はExpo CLIでReact Nativeプロジェクトを作成しました。今回は同じくExpoを使って、Google OAuth認証を実装するネイティブアプリをReact Nativeで実装してみます。

OAuthとは

OAuth、特に2.0については下記の記事参照。

rennnosukesann.hatenablog.com

今回作成するアプリは上記記事例のクライアントに相当し、Google API バックエンドがリソースサーバ・認可サーバを兼任します(内部で別れているのかもしれませんが)。リソースオーナーはクライアントIDなど、APIリクエストに必要な情報を発行します。

ExpoアプリでGoogle OAuth認証を実装する

それでは早速作っていきましょう。

なおクライアント端末はiOSの前提で進めています。

リソースオーナー(Googleアカウント)側の設定

アカウントの作成

実装に入る前に、Google OAuth に利用する開発者用アカウントを用意します。

Googleアカウントを持っていない方はアカウントを作成してください。

ログイン - Google アカウント

GCPプロジェクトの作成

アカウントを作成したら、GCP内にアプリ専用のプロジェクトを作成します。このプロジェクト単位に認証情報や使用ライブラリを管理できます。

まず、Google Developer Credentialsにアクセスします。

console.developers.google.com

ロゴ右隣のプロジェクト名欄をクリック。

f:id:rennnosukesann:20190101165923p:plain

プロジェクト選択ダイアログが表示されるので、「新しいプロジェクト」をクリック。

f:id:rennnosukesann:20190101170026p:plain

プロジェクト作成フォームが表示されます。プロジェクト名を入力し、「作成」ボタンをクリック。

f:id:rennnosukesann:20190102130355p:plain

これでアプリプロジェクトがGoogle API Console上に作成されました。

iOSクライアントIDを作成

次に、OAuth APIに必要なクライアントIDを作成していきます。

Google Developer Credentialsの「認証情報」タブの画面中央の「認証情報の作成」をクリックします。

f:id:rennnosukesann:20190101160444p:plain

クリックするといくつかの選択肢が出るので、「OAuth クライアント キー」をクリック。

f:id:rennnosukesann:20190101161850p:plain

キーの作成画面に遷移します。Google APIを呼び出すアプリケーションの種類について聞かれるので、クライアント端末の機種がiOSの場合は「iOS」を、Androidの場合は「Android」を選択します。

f:id:rennnosukesann:20190101161935p:plain

ラジオボタンの一つを選択すると、詳細設定のための入力欄が表示されます。クライアントIDの名前と、バンドルIDを入力します。バンドルIDはアプリケーションを一意に識別する名前のため、一意になるような名前を設定します(com.hoge.fuga.piyoなど)。

今回はExpoアプリで実行するため、 host.exp.exponent と設定します。(バンドルIDがこれと異なると、Expoアプリ上でGoogle OAuthができません)

※参考

github.com

f:id:rennnosukesann:20190101162550p:plain

入力を確定すると、クライアントIDが発行されます。 このクライアントIDは後ほどExpoアプリ内で使用します。

f:id:rennnosukesann:20190101163120p:plain

クライアントアプリの実装

いよいよ実装に入ります。

プロジェクトの作成

その前にアプリケーションの開発環境を作成しなければいけません。なので下記記事に従い、Expo CLIでReact Nativeプロジェクトを作成していきます。

rennnosukesann.hatenablog.com

app.json の設定

次に、クライアント端末側の app.json にプロパティを追加します。 app.jsonJSONios プロパティを追加し、その直下に bundleIdentifier config.googleSignin.reservedClientId を追加してください。bundleIdentifierhost.exp.exponent に、 reservedClientIdiOSクライアントIDを逆にしたものを設定してください。

{
...
    "ios": {
      "bundleIdentifier": "host.exp.exponent",
      "config": {
        "googleSignIn": {
          "reservedClientId": "{iOSクライアントIDを逆にしたもの : com.hoge.fugaだったらfuga.hoge.com}"
        }
      }
    }
  }

}

App.js

フロントビューと、ビューロジックの実装になります。 画面には認証用のボタンをひとつ配置しており、そのボタンを押すことでGoogleOAuth認証が起動するようになっています。OAuthの起動は expo.Google.logInAsync によって行われます。

Google - Expo Documentation

import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import expo from 'expo';

export default class App extends React.Component {

  render() {
    return (
      <View style={styles.container}>
        <Button
          onPress={this.signInWithGoogle}
          title="Sign in with Google"
        />
      </View>
    );
  }

  // Google OAuth認証メソッド
  async signInWithGoogle() {
    try {
      const result = await expo.Google.logInAsync({
        behavior: 'web',
        iosClientId: '{iOSクライアントID}',
        scopes: ['profile', 'email'],
      });
      // 認証に成功したら、アクセストークンが標準出力される
      if (result.type === 'success') {
        console.log('Google Access Token: ' + result.accessToken);
      }
    } catch (e) {
      console.log(e);
    }
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

今回はブラウザアプリを開く形でGoogle OAuth認証画面を表示しています。 認証に成功すると、コンソール上に認可アクセストークンが表示されます。このアクセストークンを第三者が使用することで、限定された範囲で認証ユーザの情報にアクセスできるようになります。

これでアプリ起動の準備が整いました!

Demo

実際にアプリを起動して、挙動を確かめてみます。 npm run start を実行し、起動時に開いたブラウザ画面、またはコンソール上のQRコードを端末でスキャンし、アプリをインストールします(Expo Clientアプリが必要です)。

詳しいローカル起動方法は下記記事を参照。

rennnosukesann.hatenablog.com

起動が成功すると、下記のような画面が表示されます。「Sign in with Google」ボタンを押すと、OAuth認証の画面へ遷移します。

f:id:rennnosukesann:20190102130012p:plain:w300

iOSの場合、Expoアプリがgoogle.comをブラウザ開くことを許可するかどうか確認するダイアログが表示されるので「続ける」をクリック。

f:id:rennnosukesann:20190102130123p:plain:w300

Googleログインが未実行の場合、Googleアカウントのメールアドレスとパスワードを聞かれます。所有するGoogleアカウントのメールアドレス・パスワードを入力し、ログインを実行してください。

f:id:rennnosukesann:20190102130236p:plain:w300

ログインが成功すると、もとのアプリ画面に戻ります。コンソール上にアクセストークンが表示されれば、認証成功です。

f:id:rennnosukesann:20190102131905p:plain

余談

Expoの expo.Google.loginAsync ではスタンドアロンアプリとしての認証オプションも用意してあるのですが、端末のOSごとに細かい設定が必要みたいなので今回は behavior = webを使用しての実装を採用しました。

github.com

参考文献

Google - Expo Documentation

OAuth 2.0 for Mobile & Desktop Apps  |  Google Identity Platform  |  Google Developers