webネタ

Webエンジニアが業務に関係することをメモしていく

JavascriptでSingletonパターン

Singletonで実装したい要件があったが、ググッっても良いのがなくて本読んでも残念なものしかなかったので自分で実装してみた。ちなみに本は、JavaScriptパターン ―優れたアプリケーションのための作法

要件

  • newできないようにする。
  • 同一オブジェクトを返す。

クラス名は本にあったものと同じUniverseにしたが別になんでもok。

最初にテストコード

newできないか
try {
    new Universe()
} catch(e) {
    console.info("ok", e);
}

new したら例外が吐かれるようにする。

同一オブジェクトか
var u1 = Universe.getInstance();
var u2 = Universe.getInstance();
console.info(“true?”, u1 === u2);
console.info(“true?”, u2.constructor === Universe);

厳密な等価 + constructorも同じかチェックするる

いつもはjasmineでテスト書くけど今回は準備がめんどうなのでベタ書き。

実装

/**
 * Description of this class.
 * Singleton Class.
 * 
 * @class Universe
 */
var Universe = (function() {
	
	var _fromGetInstance = false;
	var _instance;
	
	function _construct() {
	    if (_fromGetInstance !== true) {
	        throw new Error("must use the getInstance.");
	    }
	    _fromGetInstance = false;
	}
	
	/**
         * @method getInstance
         * @return {Object}
         */
	_construct.getInstance = function() {
	    if (_instance) {
	        return _instance;
	    }
	    _fromGetInstance = true;
	    return _instance = new this();
	}
	return _construct;
})();

/**
 * func1.
 * 
 * @method func1
 */
Universe.prototype.func1 = function() {
    console.log("called func1");
}

使い方

var universe = Universe.getInstanse();
universe.func1();


まず、privateにしたいプロパティがあったのでクロージャにした。
returnされるfunctionをnewできないようにしたいので、getInstanceメソッドからでなければ呼べないようにしてしまう。判定方法は、変数_fromGetInstanceがtrueなのかfalseかなのかというだけ。getInstanceでは一度生成したら次からはそのインスタンスを返すだけ。phpの実装と似た感じ。あとはprotptypeに実装したいメソッドを書いていけばてつもどおりになる。

本の場合

ちなみに本に載ってたsingletonは下記のようにnew Universe()するが、返すものは同じという謎な仕様だった。

var u1 = new Universe();
var u2 = new Universe();
console.info(“true?”, u1 === u2); // true

なんぞこれ。カオスすぎる。


次はfactory書く。

クロスドメインでcookie書き込む方法 +クロスブラウザで

あるサイトから別ドメインのクッキーを書き込む。こういうクッキーは、サードパーティクッキーと呼ばれる。FirefoxChromeはデフォルトでサードパーティクッキーが書き込めるようになっているが、IEとSafariが問題になる。IEはコンパクトポリシーというものを設定すればいけるが、Safariは出来ない。Safariはデフォルトで”知らないとサイトや広告のみCookieをブロック”となっている。でも、GoogleAdsenseとかは書き込めている。なので調べた。

目的

localhostにアクセスしたときsample.comのクッキーを書き込みたい。
もちろんクロスブラウザで。
(sample.comはhosts書き換えやるといい)

ポリシーの設定 (P3P) (以下IE対策用)

webサイトで個人情報などを取り扱う場合、ブラウザで設定されたポリシー設定とアクセスしているサイトのポリシーがマッチするか確認する仕組みがある。詳しくは参考サイトを参照。


Platform for Privacy Preferences 1.0 (P3P1.0) 仕様書
http://www.iajapan.org/trans2japanese/w3c/rec-p3p-20020416j.html

【絵で分かるキーワード】P3P
http://ascii.jp/elem/000/000/338/338065/

P3Pコンパクトポリシーをコピペするのが流行らないことを祈る
http://bakera.jp/ebi/topic/3594


今回は下記をHTTPヘッダーに加える。

header("P3P: CP='UNI CUR OUR'");

phpでセッションに書き込むサンプル (以下Safari対策用)

test-cookie-send.php

<?php
session_start();
header("P3P: CP='UNI CUR OUR'");
var_dump($_SESSION);
$_SESSION['writetest'] = 'waaaa';

できない方法

iframeでクロスドメインのURLにアクセスする。
セッションを張るために、HTTPヘッダーでSetCookie: PHPSESSION=****が返ってくるがクロスドメインなので書き込めない。

test-cookie-sample.html

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cookie write test of Cross Domain.</title>
</head>

<body>
<iframe name="ifr" src="http://sample.com/test-cookie-send.php"></iframe>
</body>
</html>

できる方法

iframeの中にform送信すると、クロスドメインのクッキーも書き込める。詳細は不明だが、GoogleAdsenseがこのロジックで書き込んでいる(実際はもっと複雑な行程だが)。つまりGoogleAdsenseは、Safariで広告のクッキーを書き込まないとしているにも関わらず書き込んでいるのだ。良い子は真似しないように。

test-cookie-sample.html

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cookie write test of Cross Domain.</title>
</head>

<body>
<form id="postform" method="GET" action="http://sample.com/test-cookie-send.php"target="ifr">
<input type="hidden"  name="cookiewrite" value="cookiewritevalue" />
</form>

<iframe name="ifr"></iframe>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
$(function(){
    $("#postform").submit();
});
</script>

</body>
</html>

確認ブラウザ

iPhoneアプリでNode.js

iphoneアプリでnode.jsと通信する。
ネイティブアプリでリアルタイムな通信ができるので幅が広がりそう。
環境はMacOSX10で、今回は全部ローカル作業。


f:id:R-H:20110929084724p:image

objective-cでWebSocket通信

iPhoneアプリとnode.jsで双方向通信するのは、もちろんWebSocketで行う。node.jsをインストールしたことある人はわかると思うが、WebSocket通信はsocket.IOモジュールで行っているのでクライアントサイド(objective-c)もsocket.IOにしたい。ライブラリはsokect.IO-objcを使う。

インストール

xcode

socket.IO-objcの依存ライブラリをインポート

  • cocoa-websocket
  • RegexKitLite
  • json-framework
  • ASIHTTPRequest

cocoa-websocket

https://github.com/erichocean/cocoa-websocket
git cloneでダウンロードし、以下をプロジェクトにインポート。
AsyncSocket/
SocketIO.h
SocketIO.m


RegexKitLite

http://regexkit.sourceforge.net/RegexKitLite/
tarでダウンロードし、以下をプロジェクトにインポート。
RegexKitLite.h
RegexKitLite.m
あとFrameworksに以下をインポート
libicucore.dylib


json-framework

https://github.com/stig/json-framework/
git cloneでダウンロードし、以下をプロジェクトにインポート。
Classes/


ASIHTTPRequest

http://allseeing-i.com/ASIHTTPRequest/
git cloneでダウンロードし、以下をプロジェクトにインポート。
ここに詳しい説明がある
http://allseeing-i.com/ASIHTTPRequest/Setup-instructions


socket.IO-objcをインポート

https://github.com/pkyeck/socket.IO-objc
git cloneでダウンロードし、以下をプロジェクトにインポート。
SocketIO.h
SocketIO.m

node.js(サーバー側)

node.jsとsocket.ioモジュールのインストール

nodeのインストールはnvmコマンドで行う。

git clone https://github.com/creationix/nvm.git ~/.nvm
. ~/.nvm/nvm.sh
nvm install v0.4.7
nvm use stable

npm install socket.io

サンプル

説明

iPhone側から接続し、接続完了後にnode.js側へobjcイベント発火。(もちろんこのobjcという名前はてきとうにつけたイベント名)
node.js側でobjcイベント受信したら3秒後にiPhone側へメッセージを送信。

xcode

まずxcodeで空のプロジェクトを作る。
名前はSocketIoSampleとした。


SocketIoSampleViewController.h

SocketIODelegateをデリゲート。

#import 
#import "SocketIO.h"
@interface SocketIoSampleViewController : UIViewController
@end


SocketIoSampleViewController.m

viewDidLoadメソッドに下記を追記。

 - (void)viewDidLoad
{
    [super viewDidLoad];
    
    // WebSocket接続
    SocketIO *socketIO = [[SocketIO alloc] initWithDelegate:self];
    [socketIO connectToHost:@"localhost" onPort:3000];
}

// WebSocket接続完了した場合
 - (void) socketIODidConnect:(SocketIO *)socket
{
    NSLog(@"-- socketIODidConnect()");

    // objcイベント発火。(node.js側でon('objc', function() {...})でキャッチ)
    [socket sendEvent:@"objc" withData:[NSDictionary dictionaryWithObject:@"hoge" forKey:@"Key"]];
    NSLog(@"-- sendEvent()");
}

// node.jsからWebSocketでメッセージが送られてきた場合
 - (void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet
{
    NSLog(@"-- didReceiveMessage() >>> data: %@", packet.data);
}
node.js側

conn-objc.js

var io = require('socket.io').listen(3000);
var timer = require('timers');

// WebSocket接続時
io.sockets.on('connection', function (socket) {
  console.log('CONNECTION ok');

  // iPhoneからのobjcイベント受信時
  socket.on('objc', function (data) {
    console.log('CATCH EVENT ok', data);

    // iPhoneからのメッセージ受信3秒後に、node.js側からメッセージ送信
    timer.setTimeout(function() {
        socket.emit('messages', { echo: 'from node.js!' });
        console.log('SEND MESSAGE ok');
    }, 3000);
  });
});


node.jsを起動

node conn-objc.js

xcodeでビルド

xcode側のログ
SocketIoSample[19935:b303] Opening ws://localhost:3000/socket.io/1/websocket/222774812681844920
SocketIoSample[19935:b303] Connection opened.
SocketIoSample[19935:b303] onData 1::
SocketIoSample[19935:b303] connect
SocketIoSample[19935:b303] onConnect()
SocketIoSample[19935:b303] -- socketIODidConnect()
SocketIoSample[19935:b303] send()
SocketIoSample[19935:b303] send() >>> 5:::{"args":{"Key":"hoge"},"name":"objc"}
SocketIoSample[19935:b303] -- sendEvent()
SocketIoSample[19935:b303] doQueue() >> 0
SocketIoSample[19935:b303] setTimeout()
SocketIoSample[19935:b303] onData 5:::{"name":"messages","args":[{"echo":"from node.js!"}]}
SocketIoSample[19935:b303] setTimeout()
SocketIoSample[19935:b303] event
SocketIoSample[19935:b303] -- didReceiveMessage() >>> data: {"name":"messages","args":[{"echo":"from node.js!"}]}
node.js側のログ
   info  - handshake authorized 222774812681844920
   debug - setting request GET /socket.io/1/websocket/222774812681844920
   debug - set heartbeat interval for client 222774812681844920
   debug - client authorized for 
   debug - websocket writing 1::
CONNECTION ok
   debug - websocket received data packet 5:::{"args":{"Key":"hoge"},"name":"objc"}
CATCH EVENT ok { Key: 'hoge' }
   debug - websocket writing 5:::{"name":"messages","args":[{"echo":"from node.js!"}]}
SEND MESSAGE ok
   debug - emitting heartbeat for client 222774812681844920
   debug - websocket writing 2::
   debug - set heartbeat timeout for client 222774812681844920
   debug - websocket received data packet 2::

特に問題なく通信できた。
これでリアルタイム操作が可能なアプリ作れますね。(・・`。

さくらVPSでDropbox

個人サーバーのバックアップがめんどうなので、dropboxのアカウント作ってそこに放り込むことにした。
環境はLinux。CentOS5.6(64bit)さくらVPS。

f:id:R-H:20110928074924p:image

必要なものをインストール

python2.6

sudo yum install python26
dropboxコマンドの準備

ソースをダウンロードし、権限を与えパスが通っているディレクトリへ移動。

cd ~/src
wget https://www.dropbox.com/download?dl=packages/dropbox.py
chmod u+x dropbox.py
vi dropbox.py
  一行目、#!/usr/bin/pythonを#!/usr/bin/python26に変更
mv dropbox.py ~/bin/dropbox
dropboxインストール
dropbox start -i
  To link this computer to a dropbox account, visit the following url:
  https://www.dropbox.com/cli_link?host_id=39c6****c3d04b***e6&cl=ja

ブラウザでdropboxにログインし、このurlを叩くとアカウントが紐づく。
~/Dropboxが自動で作成され、同期がはじまる。GUIの時と同じ。
なんて簡単!

確認コマンド
cd ~/Dropbox
ls -lA

ステータス
dropbox status

ヘルプ
dropbox help

プロセス
ps aux | grep dropbox

試しにディレクトリを作ってみる。

cd ~/Dropbox
mkdir test

ブラウザでアクセス
https://www.dropbox.com/home
反映されているか確認。

起動と停止
dropbox start
dropbox stop
自動起動

rootで作業。
サービスファイル作成。
変数のuserexecの値は各々設定する。

vi /etc/init.d/dropbox

                                                                                  • -
#!/bin/sh # chkconfig: 345 99 01 # description: dropbox # processname: dropbox exec="/home/ryo/bin/dropbox" user="ryo" start() { echo -n $"Starting $prog: " su -c "${exec} start" ${user} } stop() { echo -n $"Stopping $prog: " su -c "${exec} stop" ${user} } status() { su -c "${exec} status" ${user} } restart() { stop start } case "$1" in start) $1 ;; stop) $1 ;; restart) $1 ;; status) $1 ;; *) echo $"Usage: $0 {start|stop|status|restart}" exit 2 esac exit $?
                                                                                  • -

権限の付与

chmod u+x /etc/init.d/dropbox

自動起動に登録

chkconfig --add dropbox
chkconfig dropbox on
chkconfig --list dropbox
    dropbox        	0:off	1:off	2:on	3:on	4:on	5:on	6:off

これで再起動しても自動で起動する。

バックアップ

定期的にtarにして~/Dropboxに配置すば自動でバックアップされる。
自分はgitのマスターリポジトリdropboxでbackupしてます。安心。

crontab -e
  0 3 * * * tar zcfp ~/Dropbox/backu/git_master_repo.tar.gz /var/git >> /tmp/cron_dropbox.log 2>&1

gitリポジトリの作り方

gitリポジトリの作り方まとめ。

構成図はこんな感じ。ローカルにxampp置いて開発。
f:id:R-H:20110927231603p:image

マスターリポジトリ(公開リポジトリ)の作成
mkdir -p /var/git/sample.git
cd /var/git/sample.git
git --bare init

マスターリポジトリは--bareをつけてinitする。

/home/user/sampleにあるソースをマスターにするためコミット
cd /home/user/sample
git init
git add .
git commit -m 'import'
push先のリポジトリパスを指定し、push
git remote add origin file://var/git/sample.git
git push origin master

追加したリポジトリパスを見る場合。

git remote -v
pushしたら、cloneでもってくる。
cd /var/www/vhosts
git clone file:///var/git/sample.git sample

最後のsampleはディレクトリ名になる。

ローカル作業

ここはGIT GUIがおすすめ。
ローカルでclone。

git clone ssh://00.00.00.00:22/var/git/sample.git

ローカルでcommit。
ローカルでpush。

マスターリポジトリの変更をHTTP公開用リポジトリに反映
cd /var/www/vhosts/sample
git pull
さいごに

マスターリポジトリは定期的にバックアップすること。
dropboxを使うと便利。

iPhoneプッシュ通知まとめ

2011/11/14 : 追記
moruguさんに指摘頂いてdeviceTokenのPOST処理を追記しました。
連絡手段がなかったのでここにお礼として書いておきます。どもです! m(_ _)m


iPhoneアプリ開発でプッシュ通知を使ったので、まとめ。

仕組み

f:id:R-H:20110923115354j:image

1、APNsにPush通知許可の登録する。
2、APNsからデバイストークンが帰ってくる。
3、そのデバイストークンをサーバー(これは自分で用意します)に送ってDBなりに保存する。
4、サーバーからAPNsにPush通知依頼を出す。
5、APNsは登録済みの指定デバイスにPush通知を出す。
6、受け取ってAlert出すなり色々する。

実装前に準備

Push通知を行うには、iOSDeveloperCenterでPush通知用の証明書をインストールしたりしなければならない。
このフェーズは自分ではやってないので省略orz
参考サイトだけ載せておく。

実装

ここのメソッドは全てプロジェクトのdelegateクラス。

1、APNsにPush通知許可の登録する。

registerForRemoteNotificationTypesを叩くと『プッシュ送信を許可しますか?』のアラートが出て、APNsにリクエストが送られ登録される。

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
    
[[UIApplication sharedApplication] 
     registerForRemoteNotificationTypes:
     (UIRemoteNotificationTypeBadge| 
      UIRemoteNotificationTypeSound|
      UIRemoteNotificationTypeAlert)];
    
return YES;
}
2、APNsからデバイストークンが帰ってくる。

レスポンスが返ってくると、didRegisterForRemoteNotificationsWithDeviceTokenメソッドがコールされる。
引数にデバイストークンがついてくる。

 - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken{ 
    NSLog(@"Success : Regist device token to APNS. (%@)", devToken);
    [self postDeviceToken:deviceTokenString]; // POST
}

APNSへのデバイス登録失敗時

 - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)err{ 
    NSLog(@"Error : Fail Regist to APNS. (%@)", err);
}
3、そのデバイストークンをサーバー(これは自分で用意します)に送ってDBなりに保存する。
 - (void)postDeviceToken:(NSString *)deviceToken {
    
    // prepare url.
    NSString* content = [NSString stringWithFormat:@"deviceToken=%@", deviceToken];
    NSURL* url = [NSURL URLWithString:@"http://192.168.1.5:8080/registDeviceServelt"];
    
    // create instance.
    NSMutableURLRequest* urlRequest = [[NSMutableURLRequest alloc]initWithURL:url];
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setHTTPBody:[content dataUsingEncoding:NSUTF8StringEncoding]];
    
    // post.
    NSURLResponse* response;
    NSError* error = nil;
    NSData* result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
    if(error) NSLog(@"error = %@", error);
    
    // get result.
    NSString* resultString = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
    NSLog(@"%@", resultString);
    
    // release.
    [resultString release];
    [urlRequest release];
}

サーバー側でdeviceTokenを受け取り、DBに保存します。

4、サーバーからAPNsにPush通知依頼を出す。

PHPでやる場合
php-apnsというライブラリを使う。
使う前に証明書を作ったり、ルート証明書をダウンロードしないといけない。ここにやり方が載ってる。
何も考えずにコマンドを叩く^^;

Javaでやる場合
java-apnsというライブラリを使う。
証明書は、キーチェーンからPush通知用にインストールした証明書をエクスポートして使う。
サンプルコード

ApnsService service =
APNS.newService()
.withCert("/Users/*******.p12", "*****")
 .withSandboxDestination()
 .build();

String payload = APNS.newPayload().alertBody("Can't be simpler than this!").build();
String token = "638d09063fe6a73a6cb4*******************ab084e5ea8b0b390d1480f";
service.push(token, payload);
5、APNsは登録済みの指定デバイスにPush通知を出す。

ここはAPNsが勝手にやります。

6、受け取ってAlert出すなり色々する。
 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    for (id key in userInfo) {
        NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);
    }
}

まとめ

Push通知の仕組み自体がシンプルな上に、ライブラリも各種言語あるので簡単。
やっぱりappleはスゴイ。

iPhoneDeveloperに法人登録したまとめ

iPhone開発用に会社のアカウントを取得したときのメモです。

まず初めに

会社で法人として登録する場合、印鑑証明書か登記簿謄本のどっちか必要なので用意が必要です。
さらに審査?に時間がかかるので、今回の場合だと3週間ほどかかりました。
個人登録と同様に年間10800円かかります。

登録の流れ

流れ等は、以下のサイトを参考にして登録しました。とても参考になります。

困ったポイント

Documentation Requested for Your Program Enrollment

このようなタイトルでメールがきました。
登録のためにドキュメントが欲しいらしいのです。
が、何を送れというのが書いてありません。信じられない。。。。
しかたなくサポートにメールして聞いたところ、
申請書と印鑑証明書か登記簿謄本のどっちかを送れということでした。
申請書は、こんな感じで書式はお願いしますとご丁寧に送ってくれましたので、ワードで作って印刷しfaxしました。
faxが届くと、ちゃんと届いたと確認のメールが来るので安心です。

以下はサポートからのメールです。最初からこれ送ってこいや。

この度は iOS Developer Programへお申し込みいただき、ありがとうございます。このEメールは登録手続きに関するご案内です。

Appleでは、Program へお申し込みいただく企業について、実在確認をさせていただいております。 誠にお手数ではございますが、以下『計2点』の書類の写しをご用意いただき、FAXにて弊社までご送付ください。

1)本 Program への申請について、申請者が御社の代表取締役により権限が与えられている事を示す書面(御社本部の所在地と代表電話番号の記載、代表取締役の押印が必要です)1通。文面に関しましては、以下の例をご参照ください。
   
    ----------
    申請証明書

    ○○会社(日本語名称および英語名称)代表取締役である <代表取締役の氏名> は、Apple Developer Program の申請に関する一切について、
    当社の社員である以下の申請者に委任している事を、この書面により証明する。

    申請者氏名(Applicant Name): <申請者の氏名>(ローマ字表記を含む)
    役職(あるいは所属部署): <申請者の役職名あるいは所属部署名>

    日付
    企業本部所在地
    代表電話番号

    <代表取締役の氏名>(ローマ字表記を含む) および捺印
    ----------
    
    申請者が代表取締役である場合には、『○○会社代表取締役としての権限によりApple Developer Program へ申請する事を、この書面により証明する』といった文面で作成してください。

2)登記簿謄本(履歴事項全部証明書、又は現在事項全部証明書)、あるいは社印の印鑑証明書(本店のもの)のいずれか1通。

FAX番号:  +1-408-974-7683
※米国の番号となりますので、国際電話のダイヤル方法をご確認ください。

書類をお送りいただく際には、Enrollment ID: ******** と御社の代表電話番号を書類にお書き加えください。日本語表記のものをそのままお送りいただいて、問題ございません。

どうぞ、よろしくお願いいたします。もしご不明な点などございましたら、当方までご連絡ください。Apple Developer Programへのご参加、ありがとうございます。