狛ログ

2016年7月31日日曜日

AndroidでIncompatibleClassChangeErrorが発生した場合の対処。

7月 31, 2016
オフィス狛 技術部です。

Android StudioのGradle設定で、dependencies・compileに設定している
外部プラグインのバージョンを最新にして、ビルド後にアプリを起動したら、以下のエラーが発生しました。

Caused by: java.lang.IncompatibleClassChangeError: The method 'java.io.File android.support.v4.content.ContextCompat.getNoBackupFilesDir(android.content.Context)' was expected to be of type virtual but instead was found to be of type direct (declaration of 'java.lang.reflect.ArtMethod' appears in /system/framework/core-libart.jar)
   at com.google.android.gms.iid.zzd.zzeb(Unknown Source)
   at com.google.android.gms.iid.zzd.(Unknown Source)
   at com.google.android.gms.iid.zzd.(Unknown Source)
   at com.google.android.gms.iid.InstanceID.zza(Unknown Source)
   at com.google.android.gms.iid.InstanceID.getInstance(Unknown Source)

「クラスの変換に互換性が無い」、「android.support.v4」というヒントがあります。
何より直前に行った「外部プラグインのバージョンを最新に」という作業が原因なのは間違いありません。

幸いにも、外部プラグインはソースも提供している物だったので、
早速、外部プラグイン側のGradleを見てみると・・・
dependencies {
    compile 'com.android.support:support-v4:24.0.0'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.android.support:recyclerview-v7:24.0.0'
    compile 'com.android.support:support-annotations:24.0.0'
}

となっています。
一方、本体のプロジェクトの方のSupport Libraryのバージョンは・・・
dependencies {
    compile 'com.android.support:support-v4:23.1.1'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
    compile 'com.android.support:support-annotations:23.1.1'
}

となっています。これが原因ですね。

プラグインの Support Library バージョンと、
本体のプロジェクトの Support Library バージョンが異なっていると、
今回のようなエラーが発生する可能性があります。

本体の方のバージョンを上げれば解決なのですが、
ちょっと影響範囲が不明なので、今回は、外部プラグインのバージョンを元に戻す、という事で対応しました。

一気にバージョン上げると、動かなくなる可能性があるので、
Support Library の更新は、定期的にやりましょう、という事ですね。

2016年7月30日土曜日

URLConnection(HttpURLConnection)と向き合おう~POSTメソッドでフォーム情報(application/x-www-form-urlencoded)を送信する~

7月 30, 2016
オフィス狛 技術部です。

URLConnection(HttpURLConnection)と向き合うために、今回はPOSTメソッドを使います。
POSTはPOSTでも特にHTMLのフォームタグでPOSTを行ったときと同様のデータを送信します。

今回の目標

  • HttpURLConnectionを使ってPOSTメソッドを行う。
  • ContentTypeはapplication/x-www-form-urlencodedの形式にする。
  • POSTメソッドのレスポンスデータを取得する。

HttpURLConnectionを使ったPOSTのやり方

HttpURLConnection はGET/POSTに関係なくメソッドの呼び出し順序は大体決まっています。
順序は下記のとおりです。
  • ステップ1 :接続URLを決める。
  • ステップ2 :URLへのコネクションを取得する。
  • ステップ3 :接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
  • ステップ4 :コネクションを開く
  • ステップ5 :リクエストボディの書き込みを行う。(POSTのみ行う)
  • ステップ6 :レスポンスの読み出しを行う。
  • ステップ7 :コネクションを閉じる。

ステップ1:接続URLを決める。

接続先URLを決めるには java.net.URL を使います。
URL url = new URL("[接続先のURLを設定してください。]");

ステップ2:URLへのコネクションを取得する。

接続URLのコネクションを取得することができます。
URL url = [ステップ1:接続URLを決めるを参照してください];
HttpURLConnection urlConnection = null;
try {
    urlConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
    e.printStackTrace();
}

ステップ3:接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。

今回はPOSTを使ったデータの送受信を行うので特に下記の設定を行う必要があります。
  • setRequestMethod を使いPOSTを使う設定をする
  • リクエストボディの Content-Type を application/x-www-form-urlencoded に設定する。
  • リクエストボディにデータを書き込むため、setDoOutput を使い許可をする。
  • レスポンスボディのデータを読み出しするため、setDoInput を使い許可をする。
//接続タイムアウトを設定する。
urlConnection.setConnectTimeout(100000);
//レスポンスデータ読み取りタイムアウトを設定する。
urlConnection.setReadTimeout(100000);
//ヘッダーにUser-Agentを設定する。
urlConnection.setRequestProperty("User-Agent", "Android");
//ヘッダーにAccept-Languageを設定する。
urlConnection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
//ヘッダーにContent-Typeを設定する
urlConnection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
//HTTPのメソッドをPOSTに設定する。
urlConnection.setRequestMethod("POST");
//リクエストのボディ送信を許可する
urlConnection.setDoOutput(true);
//レスポンスのボディ受信を許可する
urlConnection.setDoInput(true);

ステップ4:コネクションを開く

コネクションを設定した内容で接続し、実際にデータの送受信ができる状態にします。
このメソッド以降は「接続先にデータを送信する OutputStream 」と「接続先からデータを受信する InputStream 」を使うことができるようになります。
//ステップ4.コネクションを開く
urlConnection.connect();

ステップ5:リクエストボディの書き込みを行う。

「application/x-www-form-urlencoded」の送信データは「key1=value&key2=value...」形式の文字列を送信する必要があります。
また、key と value はそれぞれURLエンコードする必要があります。そのため Uri.Builder クラスを使い送信文字列を作成します。
書き込みが終わったら必ずストリームを閉じましょう。
//ステップ5:リクエストボディの書き出しを行う。
OutputStream outputStream = urlConnection.getOutputStream();
HashMap keyValues = new HashMap<>();
keyValues.put("key" , "value");
if (keyValues.size() > 0) {
    Uri.Builder builder = new Uri.Builder();
    //HashMapを[key=value]形式の文字列に変換する
    Set keys = keyValues.keySet();
    for (String key : keys) {
        //[key=value]形式の文字列に変換する。
        builder.appendQueryParameter(key , keyValues.get(key);
    }
    //[key=value&key=value…]形式の文字列に変換する。
    String join = builder.build().getEncodedQuery();
    PrintStream ps = new PrintStream(outputStream);
    ps.print(join);
    ps.close();
}
outputStream.close();

ステップ6:レスポンスボディの読み出しを行う。

主なレスポンスデータは下記のメソッドを使って取得します。
  • getResponseCode を使うことでHTTPステータスコードを取得できます。
  • getInputStream を使うことレスポンスボディのデータストリームを取得できます。

//ステップ6:レスポンスボディの読み出しを行う。
int statusCode = urlConnection.getResponseCode();

String responseData = "";
InputStream stream = urlConnection.getInputStream();
StringBuffer sb = new StringBuffer();
String line = "";
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
while ((line = br.readLine()) != null) {
    sb.append(line);
}
try {
    stream.close();
} catch (Exception e) {
    e.printStackTrace();
}
responseData = sb.toString();
今回はレスポンスボディのデータストリームを文字列に変換しています。

ステップ7:コネクションを閉じる。

使ったコネクションはしっかりと閉じましょう。
//ステップ7:コネクションを閉じる。
urlConnection.disconnect();

サンプルコード

今回のサンプルコードの仕様は下記のとおりです。

  • URLとハッシュマップを引数で渡す。
  • 指定したURLにPOSTメソッドで接続する。
  • Content-Typeはapplication/x-www-form-urlencodeを使用する。
  • リクエストボディにハッシュマップからapplication/x-www-form-urlencodeに適合する文字列を生成し送信する。
  • レスポンスボディデータは文字列として扱う。
  • 非同期処理から呼び出されることを想定している。
  • 呼び出し側には文字列を返却する。
    public String execute(String argStrApiUrl, HashMap<String,string> formItems) {
        String ret = "";
        HttpURLConnection urlConnection = null;
        try {
            //ステップ1.接続URLを決める。
            URL url = new URL(argStrApiUrl);

            //ステップ2.URLへのコネクションを取得する。
            urlConnection = (HttpURLConnection) url.openConnection();

            //ステップ3.接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
            //接続タイムアウトを設定する。
            urlConnection.setConnectTimeout(100000);
            //レスポンスデータ読み取りタイムアウトを設定する。
            urlConnection.setReadTimeout(100000);
            //ヘッダーにUser-Agentを設定する。
            urlConnection.setRequestProperty("User-Agent", "Android");
            //ヘッダーにAccept-Languageを設定する。
            urlConnection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
            //ヘッダーにContent-Typeを設定する
            urlConnection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            //HTTPのメソッドをPOSTに設定する。
            urlConnection.setRequestMethod("POST");
            //リクエストのボディ送信を許可する
            urlConnection.setDoOutput(true);
            //レスポンスのボディ受信を許可する
            urlConnection.setDoInput(true);

            //ステップ4.コネクションを開く
            urlConnection.connect();
            //ステップ5:リクエストボディの書き出しを行う。
            OutputStream outputStream = urlConnection.getOutputStream();
            if (formItems.size() > 0) {
                Uri.Builder builder = new Uri.Builder();
                Set keys  = formItems.keySet();
                for (String key : keys) {
                    //[key=value&key=value…]形式の文字列に変換する。
                    builder.appendQueryParameter(key , formItems.get(key));
                }
                String join = builder.build().getEncodedQuery();
                PrintStream ps = new PrintStream(outputStream);
                ps.print(join);
                ps.close();
            }
            outputStream.close();

            //ステップ6.レスポンスボディの読み出しを行う。
            int responseCode = urlConnection.getResponseCode();
            ret = convertToString(urlConnection.getInputStream());
            Log.d("execute", "URL:" + argStrApiUrl);
            Log.d("execute", "HttpStatusCode:" + responseCode);
            Log.d("execute", "ResponseData:" + ret);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                //7.コネクションを閉じる。
                urlConnection.disconnect();
            }
        }
        return ret;
    }

    public String convertToString(InputStream stream) throws IOException {
        StringBuffer sb = new StringBuffer();
        String line = "";
        BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        try {
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

今回のまとめ

  • POSTメソッドではリクエストボディを使ったデータ送信を許可するために setDoOutput にtrueを渡すこと。
  • application/x-www-form-urlencoded に適合した送信文字列を作成するときは Uri.Builder クラスを使用すること。
  • テキストとファイルを同時に送信する Content-Type「multipart/form-data」とは異なるので注意すること。

2016年7月27日水曜日

URLConnection(HttpURLConnection)と向き合おう~GETメソッドでデータを取得する~

7月 27, 2016
オフィス狛 技術部です。

URLConnection(HttpURLConnection)と向き合うためには、まずGETメソッドから始めましょう。
「HTTPにおけるGETメソッドとは何か?」はGoogleさんにお任せします。

今回の目標

  • HttpURLConnection を使ってGETメソッドを行う。
  • HTTP通信を行う際にクエリ文字列を使ってデータを付加する。
  • GETメソッドのレスポンスデータを取得する。

HttpURLConnection の使い方

HttpURLConnection は GET/POSTに関係なくメソッドの呼び出し順序は大体決まっています。
順序は下記のとおりです。

  • ステップ1 : 接続URLを決める。
  • ステップ2 : URLへのコネクションを取得する。
  • ステップ3 : 接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
  • ステップ4 : コネクションを開く
  • ステップ5 : リクエストボディの書き込みを行う。(POSTのみ行う)
  • ステップ6 : レスポンスの読み出しを行う。
  • ステップ7 : コネクションを閉じる。

ステップ1~3の操作は設定を行います、ステップ4の操作で実際にコネクションを開き、ステップ5以降は「接続先にデータを送信する OutputStream 」と「接続先からデータを受信する InputStream 」を使いデータの送受信を行います。
早速、順番に見ていきましょう。

ステップ1:接続URLを決める。

接続先URLを決めるにはjava.net.URLを使います。
また、今回はGETメソッドということでURLにクエリ文字列を付加する必要があります。
サンプルでは HashMap<String,String> のkey:value をそのままクエリ文字列に変換するようにしています。

String urlString = "[接続先のURLに書き換えてください。]";
HashMap<String,string> queryParams = new HashMap<>();
queryParams.put("key", "value");
URL url;
try {
    if (queryParams == null) {
        url = new URL(urlString);
    } else {
        Uri.Builder builder = new Uri.Builder();
        Set keys = queryParams.keySet();
        for (String key : keys) {
            builder.appendQueryParameter(key, queryParams.get(key));
        }
        url = new URL(urlString + builder.toString());
    }
} catch (IOException e) {
    e.printStackTrace();
}

ポイントはクエリ文字列の作成にはUri.Builderを使うことです。
このクラスを使うことでクエリ文字列を作ることが簡単になります。

本来ならURLクラスを使わずUri.Builderだけを使ってスキームやドメイン、パス等を指定すべきなのですが目標に関係なくコードが複雑化してしまうため上記の方法をとりました。

ステップ2:URLへのコネクションを取得する。

接続URLのコネクションを取得することができます。
URL url = [ステップ1:接続URLを決めるを参照してください];
HttpURLConnection urlConnection = null;
try {
    urlConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
    e.printStackTrace();
}
URL#openConnection はコネクションを開き通信可能状態になりそうな名前ですが、実際は開いただけで接続されていないため通信はできません。

ステップ3:接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。

接続先URLとのコネクションを取得したので、通信内容について設定を行っていきます。
下記の項目を設定することができます。
  • setConnectTimeout を使うことで接続タイムアウトを設定できます。
  • setReadTimeou を使うことでレスポンスデータ読み取りタイムアウトを設定できます。
  • setRequestProperty を使うことでヘッダーに値を設定できます。
  • setRequestMethod を使うことでGETやPOSTなどのHTTPリクエストメソッドを設定できます。
  • setDoOutput を使うことでコネクション上でリクエストボディ送信の許可/不許可を設定できます。
  • setDoInput を使うことでコネクション上でレスポンスデータ受信の許可/不許可を設定できます。

HttpURLConnection urlConnection = [ステップ2:URLへのコネクションを取得するを参照してください];
//ステップ3:接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
//接続タイムアウトを設定する。
urlConnection.setConnectTimeout(100000);
//レスポンスデータ読み取りタイムアウトを設定する。
urlConnection.setReadTimeout(100000);
//ヘッダーにUser-Agentを設定する。
urlConnection.addRequestProperty("User-Agent", "Android");
//ヘッダーにAccept-Languageを設定する。
urlConnection.addRequestProperty("Accept-Language", Locale.getDefault().toString());
//HTTPのメソッドをGETに設定する。
urlConnection.setRequestMethod("GET");
//リクエストのボディ送信を許可しない
urlConnection.setDoOutput(false);
//レスポンスのボディ受信を許可をする
urlConnection.setDoInput(true);

setRequestMethod、setDoOutput、setDoInput の設定値の関係性について特に注意が必要です。
・デフォルト値について
   setRequestMethod はGET、setDoOutput はfalse、setDoInput はtureが設定されている状態になっています。
・setRequestMethod と setDoOutput について
   setRequestMethodでGETを指定した状態で、このメソッドにtrueを渡すとGETをPOSTに書き換えてしまいます。
確かにGETメソッドでリクエストボディは使わないのでその通りですが、特にメッセージもなく通信が失敗します。
この3つのメソッドは必ず呼ぶようにして、各設定値が矛盾を起こさないようにしましょう。

ステップ4:コネクションを開く

コネクションを設定した内容で接続し、実際にデータの送受信ができる状態にします。
このメソッド以降は「接続先にデータを送信する OutputStream 」と「接続先からデータを受信する InputStream 」を使うことができるようになります。
//ステップ4:コネクションを開く
urlConnection.connect();

ステップ5:リクエストボディの書き込みを行う。

GETメソッドではリクエストボディを送信しません、そのため OutputStream を取得してはいけません。
取得しようとすると例外が発生します。

ステップ6:レスポンスボディの読み出しを行う。

主なレスポンスデータは下記のメソッドを使って取得します。
  • getResponseCode を使うことでHTTPステータスコードを取得できます。
  • getInputStream を使うことレスポンスボディのデータストリームを取得できます。

//ステップ6:レスポンスボディの読み出しを行う。
int statusCode = urlConnection.getResponseCode();

String responseData = "";
InputStream stream = urlConnection.getInputStream();
StringBuffer sb = new StringBuffer();
String line = "";
BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
while ((line = br.readLine()) != null) {
    sb.append(line);
}
try {
    stream.close();
} catch (Exception e) {
    e.printStackTrace();
}
responseData = sb.toString();

今回はレスポンスボディのデータストリームを文字列に変換しています。

ステップ7:コネクションを閉じる。

使ったコネクションはしっかりと閉じましょう。
//ステップ7:コネクションを閉じる。
urlConnection.disconnect();
上記の7ステップで URLConnection を使ったGETメソッドを実装することができます。

サンプルコード

今回のサンプルコードの仕様は下記のとおりです。

  • URLとクエリ文字列を引数で渡し、クエリ文字列付きのURLでGETメソッドを実行する。
  • レスポンスボディデータは文字列として扱う。
  • 非同期処理から呼び出されることを想定している。
  • 呼び出し側には文字列を返却する。

    /**
     * GETメソッドで文字列を取得する。
     *
     * @param urlString   アクセスするURL
     * @param queryParams クエリパラメーター
     * @return レスポンス
     */
    public String execute(String urlString, HashMap<String,string> queryParams) {
        HttpURLConnection urlConnection = null;
        int responseCode = 0;
        String responseData = "";
        try {
            //ステップ1:接続URLを決める。
            URL url;
            if (queryParams == null) {
                url = new URL(urlString);
            } else {
                //クエリ文字列を付加する。
                Uri.Builder builder = new Uri.Builder();
                Set keys = queryParams.keySet();
                for (String key : keys) {
                    builder.appendQueryParameter(key, queryParams.get(key));
                }
                url = new URL(urlString + builder.toString());
            }
            //ステップ2:URLへのコネクションを取得する。
            urlConnection = (HttpURLConnection) url.openConnection();

            //ステップ3:接続設定(メソッドの決定,タイムアウト値,ヘッダー値等)を行う。
            //接続タイムアウトを設定する。
            urlConnection.setConnectTimeout(100000);
            //レスポンスデータ読み取りタイムアウトを設定する。
            urlConnection.setReadTimeout(100000);
            //ヘッダーにUser-Agentを設定する。
            urlConnection.setRequestProperty("User-Agent", "Android");
            //ヘッダーにAccept-Languageを設定する。
            urlConnection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
            //HTTPのメソッドをGETに設定する。
            urlConnection.setRequestMethod("GET");
            //リクエストのボディ送信を許可しない
            urlConnection.setDoOutput(false);
             //レスポンスのボディ受信を許可する
            urlConnection.setDoInput(true);
            //ステップ4:コネクションを開く
            urlConnection.connect();

            //ステップ6:レスポンスボディの読み出しを行う。
            responseCode = urlConnection.getResponseCode();
            responseData = convertToString(urlConnection.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                //ステップ7:コネクションを閉じる。
                urlConnection.disconnect();
            }
        }
        Log.d("execute", "URL:" + urlString);
        Log.d("execute", "HttpStatusCode:" + responseCode);
        Log.d("execute", "ResponseData:" + responseData);
        return responseData;
    }

    public String convertToString(InputStream stream) throws IOException {
        StringBuffer sb = new StringBuffer();
        String line = "";
        BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        try {
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

今回のまとめ

  • HttpURLConnection を使った場合、HTTPメソッドに関係なく7ステップを実装すること。
  • HTTPメソッドに合わせてsetDoOutputとsetDoInputを設定すること。

URLConnection(HttpURLConnection)と向き合おう

7月 27, 2016
オフィス狛 技術部です。

 ApacheHTTPClient がAndroid 6.0で廃止されてからもう少しで1年ですね。

「useLibrary 'org.apache.http.legacy'」がいまだに残る Gradle。
取り消し線を表示し「早くこのコードを消してくれ」と訴えてくる AndroidStudio。

Android 7.0も出そうなので、そろそろ向き合おうと思います。
ApacheHTTPClitentを使ったHTTP通信」から「URLConnection(HttpURLConnection)を使ったHTTP通信」への乗り換えについて複数回に分けて書きます。

今回は ApacheHTTPClitent がしていてくれたことの確認を行います。
そもそも、ApacheHTTPClitent はHTTP通信を行う上で必要な機能を提供してくれるクラスです。
下記はその機能の一部です。
  • HTTPリクエストのリクエストデータ(ヘッダーやボディ)の設定方法の提供
  • HTTPリクエストの実行
  • レスポンスの取り扱い
  • クッキーの管理
  • 認証
  • コネクションの管理

一部機能を挙げましたが、多機能すぎるため保守性・拡張性・互換性に問題が出始めたようです。
これらの問題を解決するために軽量で柔軟な URLConnection が登場したわけです。

では HttpURLConnection は何をしてくれるのかというと、「HTTP通信に必要最低限の機能」を提供してくれます。
  • HTTPリクエストのリクエストデータ(ヘッダー)の設定方法の提供
  • HTTPリクエストの実行
  • 認証
  • コネクションの管理

一部機能を挙げましたが、特に「リクエストデータ(ボディ)の設定」と「レスポンスデータの解析」は自前で実装することになります。
 ApacheHTTPClitent が覆い隠していたリクエストとレスポンスの実装がプログラマーに提供されたような印象を受けます。

次回以降はサンプルコードを提示し、要点を説明していきます。
目次は下記のとおりです。
  • GETメソッドでデータを取得する。
  • POSTメソッドでフォーム情報(application/x-www-form-urlencoded)を送信する。
  • POSTメソッドでJSONを送信する。
  • POSTメソッドでファイルを送信する。
  • POSTメソッドでマルチパート情報(multipart/form-data)を送信する。
  • レスポンスの解析
  • Cookieの管理方法

今回のまとめ

  • ApacheHTTPClitent から URLConnectionへの乗り換えは必須です。
  • URLConnection ではリクエストデータとレスポンスデータに対する操作は、自前で実装する必要があります。

2016年7月23日土曜日

小石につまずくとけっこう痛い Codeigniter3 ファイル命名規約

7月 23, 2016
オフィス狛 技術部です。

ブログになかなか手がつけられないなと思うこと早1年。。。
久々の投稿です。

弊社では、Android,iOS,PHP( Codeigniter ), Java , Node.js を用いて開発を行っていますが、
今回は PHP ( Codeigniter3 )で地味につまずいたことを書こうと思います。

まあそもそも

 Codeigniter は日本語の情報が少ない!!!

ということでよくつまずきます。
なので、自分の調べた備忘録も兼ねて、ここにつまずきろく(記録)をためていきます。

今回の地味につまづいたことは、

『ファイル名は大文字から始める。』

ということ。

 Codeigniter3 からの規約で本家サイトを見れば当たり前のように書いてあるのですが、
なぜか本番にデプロイするときまで気が付かず。

原因は、

Windowsはファイル名の大文字小文字を無視する(Macも)

という点。

 Codeigniter2 だとファイル名の規約はなかったので、
昔使っていたファイルをまんま利用するとファイル名の頭が小文字のままのものもあり、、、

Windowsでは何事もなく動く。
本番環境はLinux(AWS EC2 Amazon Linux)へデプロイ、
URLへアクセス。。。できずエラー!!!となります。(そんなファイルねぇよと怒られます)

↓のようなチェックツールもあるので、デプロイ前に一度チェックしてみると良いです。
https://github.com/kenjis/codeigniter3-filename-checker

足元の確認は大事ですね。

2015年6月5日金曜日

モバイル開発 で AWS をどう使う? 【 AWS Summit Tokyo2015 参加報告①】

6月 05, 2015

オフィス狛 インフラチームです。



AWS Summit Tokyo2015 に行ってきました。




写真のグッズは行った際にもらったものです。他にマグカップやクッキーなどもありました。



(ちなみに写真にはありませんが、まい泉のカツサンドも全員に配っていました。。。さすが Amazon )


少しの時間しかいられなかったのですが、
2日目のデベロッパーカンファレンス(DevCon)の中で
「 モバイル開発 におけるデータストアの選び方」というセミナーに
参加してきたので少し報告します。




「 3-Tierアーキテクチャ から 2-Tierアーキテクチャ へ」


【 3-Tierアーキテクチャ とは?】
クライアントから EC2 サーバを介して AWS サービスを利用する仕組み

【 2-Tierアーキテクチャ とは?】
クライアントから直接 AWS のサービスを利用する仕組み


アクセスキー と シークレットキー を モバイル端末 に組み込むわけにはいかず
( アプリ 配布の際に漏れてしまうリスクなど)
従来は 3-Tierアーキテクチャ が必要とされていましたが、
AWS のサービスを活用することで
2-Tierアーキテクチャへと開発の仕組みが変わってきているとのことです。



紹介されていたサービスとしては、

Congnito
S3
DynamoDB
Kinesis
Lambda

がありましたが、
その中でも Cognito と Lambda に関しては
特に注力してお話されていたなと感じました。


Amazon Cognito とは?】
ざっくりいうと「認証」( Amazon Cognito Identity )と「同期」( Amazon Cognito Sync )の機能を提供するサービスです。

○認証( Amazon Cognito Identity )
Google や Facebook などの別サービスの認証情報を利用したり、
独自の認証機能を設定することで、
一時的な認証を安全で簡単にクライアントに与えて AWS のサービスを利用可能にします。


例えば、ゲストユーザー用にも認証を行うことができるので、
登録前のアプリの体験利用などにも活用できそうですね。


○同期機能( Amazon Cognito Sync )
複数のモバイル機器( スマホ 、タブレット 、など)で同じユーザーを利用する場合、
それぞれで異なる操作をしたときにデータの同期が必要になりますが、
Cognito SyncのAPIを利用することで同期を簡単に行えます。


例えば、、、

・オフライン時の動作としてまずローカルストレージにデータを保存してくれる。

・ローカルとクラウド上のデータのバージョンを比較してデータをプッシュ/プルしてくれる。

・複数の機器でデータ変更があった際、最後の書き込みのみを有効にしてデータの同期を取ってくれる。

・ゲストユーザーにも一意な認証を行っているので使っていたデータを会員登録後も引き継いでくれる。

ゲストユーザーでアプリを体験してもらったデータを
会員登録後もそのまま引き継いでサービス利用できるのは作る側にとっても使う側にとっても便利だなと思いました。




「もはや EC2 いらないのでは?」
とふと感じましたが、
容量制限の問題(Cognitoに1ユーザ-の保存できるデータ量が 20MB まで)や
仮に DynamoDB や S3 を使うにしてもモバイル機器は電源の問題などもあるので、
EC2 と併用していく中でどう活用していくのかを考えていくことが大切なのかなと思いました。

今後モバイル開発をする際の AWS の活用法の選択肢として非常に勉強になりました。


ちなみにAndroidで使う場合は



CognitoSyncManagerの初期化
cognito = new CognitoSyncManager (context, Regions.US_EAST_1, provider);



同期するデータ用を入れるDataSetの作成とデータの追加
Dataset dataset = cognito.openOrCreateDataset(datasetName);
dataset.put(key, value);



同期の実行
dataset.syncronize(new SyncCallback(){..});


のような形で利用します。さらっと同期できてしまいますね。



Amazon Lambda とは?】
これもざっくり言うと AWS 上にプログラムをアップロードし、イベント発生に応じて実行することができるサービスです。


例えば、、、

・ S3 に画像をアップロード

       ↓

Lambda のプログラムを起動し、DBにメタデータを保存 + サムネイルを作成し、 S3 へアップロード




・ Cognito Sync でデータの同期を実行

       ↓

Lambda のプログラムで Amazon SNS (モバイル通知)から各モバイル機器にデータ変更を通知



・Amazon Kinesis(リアルタイムデータのストリーミング処理)にデータを送信

       ↓

Lambda のプログラムで集計処理後DBに登録、データに応じて SNS でモバイル通知


など、何かしらのイベントに応じて次の処理を AWS 上で行ってくれます。

今までアプリケーション上で行っていたことが
本当の意味でバックグラウンド処理できるようになったということですね。


Lambda は相当広い範囲で活用することができ、
セミナー内で紹介されていたサービス全てに対して Lambda の活用例 を紹介していました。

※そういえば、 Lambda の言語として Python が使用されていましたが
Python がデフォルトなのでしょうか?
勝手にnodejsかなと思っていましたが。。。



他のサービスに関しても色々とご紹介して頂いたのですが、
最後のまとめのメッセージとしては、


「アプリ開発以外の課題(データ分析や同期、スケーラビリティの確保、メディアの管理など)は AWS に任せて、アプリ開発に集中できる環境を作りましょう」

ということでした。

確かに開発側としてデータの収集や分析、活用といった部分を考える機会が増えていると感じていたので
改めてクラウドサービスの活用方法を考えていく必要があると思いました。

短い時間しかいられませんでしたが、
単純に勉強になった点と今後のモバイル開発の仕組みを作る上での新しい視点を得られた点で
非常に有意義な時間となりました。


あとこのセミナーに参加する前に Amazon Machine Learning のセミナーにも実は参加してきました。

新しいサービスということで、満員状態で立ち見でした。。。

機械学習というと統計学やR言語などかなり専門的な分野だと感じていただけに
それを簡単に使えるようになるというのは非常に興味深かったです。

まとめる時間がなかったので改めて報告したいと思います。

2015年2月17日火曜日

AWSの支払いをドルから円に変えてみた。

2月 17, 2015
オフィス狛 インフラチームです。

AWS(Amazon Web Services)が2015年2月16日(現地時間)、
クラウドサービスの支払い通貨を拡大したと発表しました。

【AWS発表】 AWSアカウントへの支払い通貨の設定機能

これでついに円での支払いが可能になりました。

オフィス狛では米ドル(USD)支払でも何の問題もないのですが、折角なので円支払に変えてみました。

まずはAWSにログイン後、「Billing & Cost Management」の「Dashboard」を表示させます。


上記のように、デフォルトは米ドル(USD)支払になっています。

そこで、左側のメニューから「Account Settings」へ移動し、


「Local Currency Preference」で、通貨を「JPY - Japanese Yen 」に変更します。


これで「Dashboard」を再度表示すると、「JPY」に・・・・・なっていない。
ただ、表示が少し変わっています。


何度か再表示させてみたら、以下のように「JPY」に換算されました。


今まで、円で支払う事が出来ない為に、お客様の決済上の問題で
AWSの使用を諦める事になったプロジェクトもあったので、これはかなり朗報だと思います。

ただ、決済代行サービスを行っている企業はどうするのだろう・・・・などと、心配をしてしまいます。

ちなみに、円で支払う為には、VisaもしくはMasterCardで無いと駄目なようです。