2015年6月26日金曜日

ssh/configの書き方と踏み台サーバを利用した多段sshログイン,多段scp

いろんなところで書かているんですが、あるサーバにsshで入って、それを踏み台に別のサーバにsshで入る方法。あと、これを調べたときに~/.ssh/configに設定を書くとsshコマンドのホスト名を簡略化できることも知ったので、それについても。 やってること自体は以前に書いたssh転送と同じことですね。

1.~/.ssh/configの書き方
Host fumidai # 自分がわかりやすい好きな名前で
    User         user # ユーザ名
    HostName     host.fumidai.com # ホスト名
    IdentityFile ~/.ssh/id_rsa
    Port     port_num # 利用するポート番号    

このようにすると
ssh fumidai
だけでssh接続ができます。便利! 次に,上のサーバを踏み台に目的のサーバに入る場合
Host fumidai # 自分がわかりやすい好きな名前で
    User         user # ユーザ名
    HostName     host.fumidai.com # ホスト名
    IdentityFile ~/.ssh/id_rsa # 秘密鍵
    Port     port_num # 利用するポート番号  

  Host target # 同上
    User         target_user # 目的のサーバのユーザ名
    HostName     host.target.com #  目的のサーバのホスト名
    IdentityFile ~/.ssh/id_rsa # 上と同じ秘密鍵
    ProxyCommand ssh fumidai -W %h:%p  
注意すべきはIdentifyFileは上記と同じ、手元にあるマシンの秘密鍵を指定すること

ProxyCommandは目的のサーバに接続する際のコマンドで、%hがHostNameに%pがPort(デフォルト22)に変換されます。
-Wオプションについては、軽く調べたのですが多段sshを行う際に使われるコマンドのようだ、としかわかりませんでした・・・。

これで踏み台サーバへの次のコマンドだけで入れちゃいます。

ssh target
多段scpも同様に

scp -r target:/home/user/folder ./

とできちゃいます。

2014年7月3日木曜日

TreasureDataとMySQLからのインポート

Treasure Dataってご存知でしょうか。僕はごく最近まで全く知りませんでした。
Treasure Dataはビッグデータ解析のためのクラウドツールです。これによってビッグデータの収集・保存・解析を一手に担ってくれるのでアクセスログの解析なんかが簡単にできます。何十万何百万のレコードが書き込まれたテーブルへのクエリもそこそこ速く返ってきます。素晴らしい。
もし知らなくて興味があるって方は公式サイトからどうぞ。

今回はTreasureDataでのテーブルの作り方やサーバーからMySQLのデータをTreasureDataにアップロードする方法なんかを紹介します。TreasureDataには既に登録済みであることを前提に話を進めます。

まずTreasureDataコマンドを使うために。
(僕はRailsで開発を行っているのでそしか知らないのですが)tdというgemを入れる必要があります。 Gemfileに記述するか
gem install td
コマンドを打ち込みます。
treasure-data/td のgithubはこちらから

インストール後はtdコマンドでどんなコマンドが使えるか確認ができます。
一番最初は td account でメールアドレスとパスワードを打ちログインします。これで自分が登録しているTreasureDataにDBを作成したりテーブルを作ったりできます。

データベースの作成
td db:create my_db

テーブルの作成
td table:create my_db my_tbl

これでDBと空のテーブルがTreasureData内に作成出来ました。最後に、テーブルをTreasureDataにインポートします。
MySQLからのインポート
td --apikey my_apikey import:auto --auto-create my_db.my_table --format mysql --db-url jdbc:mysql://my_sql_host//my_sql_db --db-user my_user --db-password my_pass --time-column time my_table
そんなに難しくないです。一応気をつけなければいけないのはmy_db,my_tableはTreasureData内に作成したデータベース・テーブルでありmy_sql_host,my_sql_db,my_user,my_passはローカルにあるMySQLのホストやデータベース等になります。
APIKeyに関しては僕は最初抜きでやったら権限がないと怒られてしまったため付けました。そのユーザーの権限によっては必要がないかもしれません。
また --time-columnの欄は時刻を記録するカラムの設定です。TreasureDataは基本的に時刻のカラムを持たなければいけません。インポート元のテーブルに時刻を記録するカラムがあればそのカラム名を、なければ--time-value 0 とかでいいのかと思います。

これでMySQLのテーブルをTreasureDataへインポートできます。
MySQL以外にもTreasureDataにはCSVやjson,PostgreSQLなど様々な形式のデータをインポートできるみたいです。



今回はテーブルのインポートのみを紹介しました。実際にTreasureDataにあるデータにアクセスするさいはHiveやPigといったSQLライクな(でも結構違う)クエリを書いて投げる必要があります。僕が使っているのはHiveQLですがそれについてもまた書きたいと思っています。






久しぶりの投稿

実に4ヶ月ぶりの投稿になります。
さぼってました。その間に主戦場がAndroidからRailsへと変わったりとまた全然別の事を勉強していたのですがいかんせんゼロからのスタートだったもので書くとなると量が多くなりすぎてしまい気が引けていたのが正直なところです。

暫くはRailsやMySQL,Hive当たりを幅広く載せて定期的にアウトプットが行えればと思っています。
ということで早速次の投稿に書いていきますね。

2014年3月16日日曜日

AndroidでのSDカードへの画像保存

Androidでプロジェクト内部の画像をSDカードに保存する方法を書きます。ちなみに今回はdrawableフォルダに置かれたbg_dot.pngという画像をSDカードに保存します。

String saveDir = Environment.getExternalStorageDirectory().getPath()+"/test";
    File file = new File(saveDir);
    if(!file.exists()){
     if(!file.mkdir()){
      Log.e("debug","Make Dir Error");
     }
    }
    //SDカードに画像保存
    String imgPath = saveDir+"/"+"bt_dot.jpg";
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_dot);
    try {
     FileOutputStream output = new FileOutputStream(imgPath);
     bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
     output.close();
    } catch (FileNotFoundException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
    
    //画像をギャラリーに反映
    String[] paths = {imgPath};
    String[] mimeTypes = {"image/png"};
    MediaScannerConnection.scanFile(getApplicationContext(), paths, mimeTypes, new OnScanCompletedListener(){
     @Override
     public void onScanCompleted(String path, Uri uri) {
      Log.d("MediaScannerConnection", "Scanned " + path + ":");
      Log.d("MediaScannerConnection", "-> uri=" + uri);
     }
    });

最初のfile.mkdir()で保存するディレクトリを作成しています。その後、ファイルをBitmapオブジェクトとして取り出して保存します。

FileOutputStreamを利用して画像の保存処理をしただけでは、保存した画像は再起動しないとギャラリーに反映されません。23行目以降のMediaScannerConnection以降の処理を行うことで再起動することなく画像をギャラリーに反映できます。
また、AndroidManifest.xmlには次のパーミッション記述が必要です。
結果はこんな感じ。SDカード内にtestフォルダが作られ、画像が保存されました。

参考:http://northerndarkstg.at.webry.info/201207/article_3.html

2014年3月5日水曜日

リワード広告の仕組み(2)

さて、いよいよ実際にリワードシステムを作ります。

とは言ったものの、セッションやクッキーに対する理解が浅かったので勉強し直してきました。詳しくはGoogle先生が教えてくれるでしょうから簡単に説明します。

クッキーとは、ブラウザ側にデータを保存するものです。一度ログインしたHPに二度目にアクセスするとIDやパスワードが保存されてますね、あれがクッキーです。語源はそのまんまお菓子のクッキーらしいのでブラウザにクッキーを食べさせる、というイメージなのでしょうか。もぐもぐ。
反対に、セッションはサーバー側に保存するデータになります。ブラウザ側はそのセッションIDのみをクッキーに保存しておきます。サイトにアクセスすると、ブラウザに保存されているセッションIDからサーバーに保存されているデータを呼び出します。


ではコードを交えて解説を。
セッションはsession_start()で開始されます。セッションがまだ開始されていない状態でこの関数が呼び出されるとセッションIDが生成されます。すでにセッションが開いている時にこの関数が呼ばれると、前のセッションが呼び出されるようです。
<?php
  session_start();
?>
セッションIDはブラウザにクッキー名PHPSESSIDで保存されます。セッション変数は$_SESSION[変数名]で管理されています。セッションIDを確認するのであれば
  session_start();
  print($_SESSION[PHPSESSID]);
で見ることができます。ページへの訪問回数を数えるのであれば
  session_start();
  if(isset($_SESSION["visited"])){
 $_SESSION["visited"]++;
 print($_SESSION["visited"]."回目の読み込みです");
  }else{
 $_SESSION["visited"] = 0;
 print("初めての読み込みです");
  }
とすればセッションID訪問回数をカウントすることができます。セッションIDを持つクッキーは基本ブラウザが閉じられると破棄されるのですが、クッキーの生存時間を任意に指定することもできます。

基本はこれを利用してできそうです。全部書くとキリがないのもあるので僕が詰まったとこや要点だけ。

まず、GooglePlayへの遷移ですが、リンク先を
https://play.google.com/store/apps/details?id=com.android.chrome
としてしまうとGooglePlayへのアクセス方法をユーザが選べてしまいます。ブラウザを起動するのかGooglePlayのアプリを起動するのかユーザが選べてしまいます。そこまで困りはしないのですが、できれば強制的にGooglePlayアプリへ遷移させたい。
なので、リンク先は
market://details?id=com.android.chrome
としてあげれば問答無用でGooglePlayのアプリがAndroid端末で起動します。もちろんこれはPCブラウザでは反応しません。


それと、サーバー側でのセッションIDの保存方法。僕はここでかなり躓きましたし実際今も完璧に理解できていない気がします。セッションIDはサーバー側で保持するものですが、その保存方法がいくつかあるようです。
これらはphp.iniのsession.save_handlerで設定できます。また保存場所はsession.save_pathに絶対パスを指定することで変更できます。
しかし、共用サーバーなどの場合サーバー全体で設定が変わってしまうため作業フォルダに.htaccessファイルを作成しました。ここに設定を記述することで.htaccessを置いたフォルダ以下のPHPプログラムにその設定が反映されます。

保存方法そのいち
memcached(メモキャッシュディーと読む?)を利用する方法。使ってるサーバーのphp.iniの設定がこれになっていたのですが、memcachedというキャッシュシステムを使ってサーバーに値を保存する方法。
.htaccessで
php_value session.save_handler = memcache
php_value session.save_path = "tcp://host:11211"
と記述することで保存形式をmemcachedにできます。

保存方法そのに
ファイルで保存する方法。session.save_handlerの値をfilesにすることで設定可能です。
php_value session.save_handler = files
php_value session.save_path = "フォルダの絶対パス"

そのさん
データベースを使う方法
データベースに値を保存する方法です。セッションデータの共有などを行うのであればこの方法が便利。session.save_handlerの値をuserにします。session.save_pathは指定しません。
php_value session.save_handler = users

ちなみに、躓いた経緯はこんな感じ。

最初memcacheで深く考えずにセッション関係の関数を使用していたのですが、最初何回かはうまくいくのに途中からセッションが保持されなくなってしまった。memcachedの存在も調べて初めて知ったのでよくわからない。。。
じゃあファイルで保存できるらしいしそうしよう。
そんな気持ちで設定を変え、保存するフォルダパスを指定しました。
実行テスト
一回目

 いい感じ。ちゃんとセッションIDが発行されています。
更新ボタンを押すと、二回目って出るはず。
二回目
アイエエエエエエエ!?ショカイ!?ショカイナンデエエ!?
更新されません。もいっかい押したら
あれ、更新される。。。。

その後も何回か試してみましたが、必ず二回更新しないとセッションの値が更新されないことに。

ログを見てもなにも出てなかったのですが、ひたすらネットで情報を漁っているといくつか似たような事例が。間違っているかもしれませんが、ファイルシステムがロックされていることが関係している模様。一度アクセスすると30秒ほどはファイルにアクセスできないみたい。

実際30秒間隔を空けてあげるときちんと更新されました。しかこんな自由意思に頼ったシステムで納品するわけにもいかず、先ほど調べているうちに知ったDBを使ったセッションの管理を試してみることに。

これには、あらかじめセッション保存用のテーブルを用意してあげて
session_set_save_handler("open","close","read","write","destroy",gc");
としてセッションの保存に関連するメソッド(open(),close(),read(),write(),destroy(),gc())を定めてあげればいいだけです。僕はsession_hanlder.phpという形で一つのファイルにしてしまったのですがだいたいこんな感じになります。
<?php
        //データベース情報
 $dsn='mysql:dbname=xxx;host=xxx.xxx.xxx.xxx';
        $user='user';
        $password='password';


    //データベースへ接続
 function connect_db(){
 global $dsn,$user,$password;
  try{
   $dbh = new PDO($dsn,$user,$password);
   //print('接続に成功しました
');
  }catch(PDOException $e){
   //print('Error:'.$e->getMessage());
  }
  return $dbh;
 }

 function open(){
  //データベース接続

                return true;
 }
 
 function close(){
  return true;
 }

 function read($id){
  //データベース読み込み
 
  return $data;
 } 
 
 function write($id,$data){
  //データベースへの書き込み・あるいは更新
 }

 function destroy($id){
  //データベースからデータを削除
 }

 function gc($maxlifetime){
  //ガーベッジコレクションの処理
 }

 session_set_save_handler("open","close","read","write","destroy","gc");
  
?>

これでようやく問題なくセッションの管理ができるようになりました。そんなに難しいことじゃないんだけど、苦労した。。。

学んだこととしては


  • アプリDLなどを跨いでのユーザの認証にセッションを使うこと
  • セッションファイルはmemcached,ファイル,DBなどで管理ができること

の二点が大きかったです。あとはセキュリティとか、めんどくさry...いろいろですね。なんとかなって良かったです。

2014年3月1日土曜日

リワード広告の仕組み(1)

唐突に、社でリワード広告システムを作ることになりその勉強をしていました。

仕組み自体は最初全く知らなかったため、「つらいにゃん...つらいにゃん..」とか言いながら苦闘していたのですが・・・結果的にいろいろと理解できて、勉強になったためここにまとめたいと思います。


 そもそも、リワード広告システムとはなにか。
ここで登場するのはユーザ・広告掲載社・広告提供社の三役です。 掲載社をA社・提供社をB社とします。 僕が関わったのはBの立場からです。 

リワード広告システムとは、近年増えてきた所謂ポイントアプリ・お小遣い稼ぎアプリなどで多く利用されているシステムです。
A社のポイントアプリAにおいて、ユーザがB社の出しているアプリBの広告をクリック・それをダウンロード後起動するとアプリAでポイントが貯まる、といった仕組みになります。
最終的にアプリAで貯まったポイントはAmazonやらiTunesのギフト券に換金できる、と。

アプリAを出している会社にしてみれば広告料をもらえるし、B社にしてみれば自社アプリの宣伝になるし、ユーザにしてみればアプリをダウンロードだけでお金に変わるポイントが手に入るし...と一見みんな幸せに見れるような仕組みになっています。



 こっからがシステムの話。 そのときどんな動きをしているのか

ユーザがアプリAの広告をクリックした際にA社からは管理用(キックバック用)のidが付与されます。つまりB社のCGIプログラムにGETでidが送られます。http://B社のページ?id=xxxという感じですね。
idは後で成果報酬を返すための値で、クリックしたユーザの端末情報などではないです。
そのidを受け取った上でB社はユーザをアプリBのDLページ(今回はGooglePlay)へ誘導します。ユーザがアプリBをダウンロード・起動したらその成果をA社に返すことになります。
つまり,広告をクリックしたユーザとアプリBをDLしたユーザが一緒かどうか判別して、確かに同じユーザだと特定できたら成果をA社に返せればいいわけです。

はぁ

最初聞いて疑問に思ったのはユーザの認証方法。
ユーザNが広告をクリックするとA社からid=nnnが送られてくるのですが、Nがアプリをダウンロード、起動した後でそのアプリを起動したユーザがid=nnnのユーザと同一だと認証するにはどうすれば良いのか。だってid一連の流れの中でずっと保持できないし。
解決方法として真っ先に思い浮かんだのは端末の固有情報を利用すること。Androidでは端末からそこそこ情報を抜き取れるのでそれと照らし合せられないかなーと思ったのです。
【参考:TechBooster Androidのシステム情報を取得する】
ただ、サーバ側にidを持ってアクセスしてきた時点での端末情報の取得が難しいようで。ガラケーだったらアクセス情報からキャリア・端末情報を抜き取ることができたみたいなんですけど、スマホから取れたのはAndroidのバージョンくらいでした。これじゃ判別はできない。
そもそも、端末の固有情報を抜き取るのは個人情報保護法の観点からもよろしくないようです。(そりゃそーだ)
iOSではそういった端末情報を取得することができないように規制をかけてるみたいですね。

「じゃあユーザ特定できないじゃん、無理じゃん。無理ゲーじゃん。」なんて悲しみに沈みながら尚ネットの海を泳いでたら、とある記事が目にとまりました。
【アフィリエイト広告業界におけるシステム間の連携ってなんだろう(仕組み編)】
・・・これならでき、そう?

ということでブラウザのセッションを利用するという方向で調べていくと、なんかできそう。
具体的には、
(1)ユーザが広告をクリックしてB社へ転送された時にセッションIDを発行し
(2)ユーザがアプリをダウンロード、起動した後にブラウザを起動させ、セッションIDで認証

という方式をとればうまくいけそうです。上手くいかせなければいけないのです。

ということで次の投稿でクッキーとセッションの話、PHPを使ってどのように実装するか、を簡単に書いていきます。








2014年2月22日土曜日

Android:SeekBarの扱い方

こんにちは

前回enchant.jsの記事を書いておいてなんですが今回はAndroidの話
あっちいったりこっちいったりです。ふらふらです。

では、タイトルの通りSeekBarの扱い方について、今までSeekBarは使ったことがなかったので基本的な使い方になります。

やりたかったことは、フォントの調節をSeekBarで行う感じ。


とりあえず,xmlのレイアウトファイルは次のように

<seekbar 
  android:id="@+id/fontSeekBar" 
  android:layout_height="wrap_content" 
  android:layout_margintop="50dp" 
  android:layout_width="match_parent" 
  android:max="10" android:progress="10">
</seekbar>

<textview 
  android:id="@+id/fontSize" 
  android:layout_gravity="center_horizontal" 
  android:layout_height="wrap_content" 
  android:layout_width="wrap_content" 
  android:text="フォント:○○" 
  android:textsize="18sp">
</textview>

フォントの調節に使うので確認用にTextViewも載せます。
で,ソースコードはこんな感じ

SeekBar seekBar;
TextView textView;
seekBar = (SeekBar)dialog.findViewById(R.id.fontSeekBar);
textView = (TextView)dialog.findViewById(R.id.fontSize);

seekBar.setOnSeekBarChangeListener(new SeekBarChangeListener());  

SeekBarChangeListenerはこちら
class SeekBarChangeListener implements OnSeekBarChangeListener{
  @Override
  public void onProgressChanged(SeekBar seekBar, int progress,
    boolean fromUser) {
   //つまみをドラッグしたとき
   textView.setText("フォント:"+seekBar.getProgress());
  }

  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
   //つまみに触れたとき
  }

  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
   //つまみを離したとき
  }
 }

onProgressChangedの中でSeekBarの値を取得しTextViewに反映させてあげればseekBarの変化とtextViewが連動して見栄えがよくなります。 基本的にseekBar.getProgress()で現在の値を,setProgress()で値をセットしています。そんな難しくない。 気になったのは,値の設定方法。
xmlのandroid:max
とい項目はあってこれで最大値は決められるらしいのだが
最小値を設定するパラメータはない・・・・
え?なんで?って思ったんだけどなんかなさそう。
SeekBarの最低値は0なんだけどフォントサイズ0で設定するわけにもいかないので配列使って変換することにしました。
こんな感じに


//フォントで設定できる値
 private static int Font[] = {8,12,14,16,18,20,22,24,28,32,36};
 

これでSeekBarを10段階に設定(max=10)して対応させてあげました。
最小値と最大値は下の画像のようになりました。