GANCHIKU.com

バーチャールドメインでメール受信からスクリプトを実行

2012年2月25日

サーバがメールを受け取ったら、スクリプトを実行する処理を作成していたときのメモです。なんかいつもここはまるので。。。

はまるのは、バーチャルドメインを使用しているからで、使用していなければ、素直な設定でうまくいきます。と言いつつも、1つのサービス、1つのサーバで賄えるほど経済的な余裕はありませんので、複数のサービスを1つのサーバで動かします。となると、必要になるのがバーチャルドメインですね。ウェブでは、Apache などの設定が簡単でいいのですが、Postfixだと少々はまります。しかも、メール受信のタイミングでスクリプトを起動させる際には、特に注意が必要です。

なぜかと言うと、バーチャルドメインを使用していると、そのまま受け取ったメールをスクリプトに流しこむことができないからです。なので、ワンクッション置いてあげる必要があります。このワンクッションの方法は2つあって、1つは、.forwardを使う方法。もう1つは、aliasesを使う方法です。
私自身は、昔は.forwardを使う方法でやっていたのですが、最近はaliasesを使うようにしています。よく.forwardを書くのを忘れてしまうので。

まず、/etc/postfix/main.cf
一番最後にvirtual_alias_domains と virtual_alias_map を追加してあげます。

virtual_alias_domains = example.com, example.net, example.org, ganchiku.com, scubaeye.com, 5kinjo3.com
virtual_alias_maps = regexp:/etc/postfix/virtual

のような感じです。virtual_alias_mapsでvirtual の転送先でも指定してみましょう。/etc/postfix/vitual

/^info@example.com$/ example@gmail.com
/^info@example.net$/ example@gmail.com
/^info@example.org$/ example@gmail.com
/^aaa-[0-9]{9}(@.*)example.com$/ example, example@gmail.com

ここでは、info@example.(com|net|org)に来たメールは、全部example@gmail.com に転送しています。また、aaa-030494944@example.com 等aaa-以降のランダムに来た数字9つに届くメールは全部 example ユーザと example@gmail.com に転送します。ところで、exampleユーザっていましたか?そんなユーザはいないですよね。

ここで、example ユーザへの転送ではなく、スクリプトを直接指定できたらいいのですが、これができないんですね。なので、上に書いたようにワンクッション置いてげて、aliases でスクリプトを指定してあげる必要があるんですね。というわけで、/etc/aliases を修正してあげます。

postmaster    root
example |"/home/shin/prod/example/symfony mail:receive-mail"

なんかコロンがあるとフォーマットが違う!と怒られることがあるので、怒られたら取っておきます。見ればわかりますが、example がメールを受信すると symfony コマンドが実行されます。また、標準入力としてメールを受け取っています。あとは、このタスクの中でゴニョゴニョすればいいんですね。

なお、/etc/postfix/virtual, /etc/aliases は両方ともpostmap してvirtual.db, aliases.db を作成しておきましょう。なお、 /etc/aliaes を postmap して aliases.db を作成しておきましょう。virtual に関しては、postmap はいらないという指摘をいただきました。あと、postfixの再起動も。

整理すると、次のような感じです。
1./etc/postfix/main.cf でバーチャルホストを使うように指定してあげる。
2./etc/postfix/virtual で受け取ったメールを調べて、スクリプトに渡すものがあれば、ユーザ(エイリアス)にそのまま渡してあげる。postmap を忘れずに。 /etc/aliases に postmap をするのを忘れずに
3./etc/aliases で2で指定したユーザ(エイリアス)で受け取り、スクリプトに渡す。

以上です。整理して考えるとわかるんだけど、virualからaliasesに渡すのって、最初はなんで?って思うんだよなー。

Google In-App Payment API を使ってみた

2012年1月12日

In-App Payments expands its borders – The official Google Code blog にて12月15日より日本でGoogle In-App Paymentが使えるようになりました。そこで是非早いことやってみようということで早速、私の開発しているサイト、ルームシェア・ジャパンに組み込んでみました。各投稿を少額払うことによって、注目させることができます。
「注目機能」追加のお知らせ – ルームシェアジャパン

Google In-App Paymentは以下のような説明になっています。

In-App Payments API により、ウェブ アプリケーション内での支払いを受け付けることができます。アプリケーション内での購入はスピーディーで購入者にも自然な印象を与え、頻繁に購入してすぐに使用する仮想商品やデジタル商品の販売に理想的です。
既に Android アプリケーション、Chrome ウェブストア アプリケーション、または Picasa ストレージを購入しているユーザーは、エクスペリエンスがさらに向上します。既に Google での購入経験があるため、支払うときに請求情報を入力し直す必要がありません。
わずか数行のコードで、アプリケーションにアプリ内ペイメントを追加できます。その結果、Google の強力な支払いインフラストラクチャ、PCI への準拠、リスク管理など、多数のメリットを活用できます。この API の利用料金は、取引あたり 5% のみです。

というわけで、Googleを通して決済を代行してくれるサービスなんですね。実際は5%を利用料金として払うということなのですが、まぁ、とても良心的なサービスなのではないでしょうか?
ただ、結局このサービスではGoogleにクレジットカードの番号を伝える必要があるので、少々敷居が高いのではないのかな、と思っています。この辺はPaypalが普及しきれていない原因の1つだと思います。こういうのは、Amazonにやってもらいたかったですね。おそらくクレジットカードの番号を一番持っている会社だと思うので。すでに登録されているのであれば、敷居も低そうですし。というか、Paypalでry

ルームシェア・ジャパンでは、投稿の注目をこの決済でやってみましたが、物を売るときにもこの決済は使えるのではないでしょうか?動作を確認したい人は以下の画面から私に振り込んでくださいwww ある程度振込があるようでしたら、そのエントリを書いてみようと思います。最近、お金がないので、なんとかマネタイズの方法を考えないと思っているので、これ何かに使えないかな。。。

そして、実際に振込があると Google Check のサイトで以下のように確認ができます。Google Check 自体は Google Walletに統合されるようですが、現段階では、売り手の方は、Google Check Merchant を使って管理するようです。ちなみに私自身が$1を購入してみて、1つをRefundしてみました。Refundもできるみたいです。

Payoutの方を見ると、こんな感じになっています。

nginx(reverse proxy cached) + nginx(backend) + PHP-fpm の設定メモ

既にたくさんの記事でこの設定がありましたが、どれを参考にしてもなかなかうまく動かなかったのですが、自分のためのメモということで、ここに書いておきます。前までは、 nginx(reverse proxy cached) + apache-cgi(backend)でやっていたのですが、nginxがときどき暴走してしまっていたので、ちょっと見直してみました。

うまく動かなかったのは、私の設定がいけてないのがもちろん一番の理由なのですが、もう一つあげるとするのであれば、 wordpress のインストールがドメイン直下ではなく、サブディレクトリ以下だったことです。マルチブログでは、まだ動作を確認していないのですが、近々見てみます。あと、書き込みとかは、全部wordpress_masterサーバで行わせるが、読み込みは、どっちでも行わせるという設定にしています。
wordpressは、 /home/app/ganchiku.com/blog 以下にインストールされていることを前提にして書いています。/home/app/ganchiku.com/ も一応見えるのですが、こっちには他のファイル等を入れていることを前提にしています。

環境:php5-fpm(5.3.5) nginx (1.0.11-1) ubuntu(10.04)
nginx は本家のパッケージを、php5-fpm は launchpadのパッケージをsources.list に加えて適当にインストールします。

nginx(reverse proxy cached)は、ポート80で動かし、リバースプロクシの役割をしてもらいます。また、nginx(backend)は、ポート9001で動かし、ウェブサーバとするのですが、さらにPHPの処理は、バックエンドのPHP-fpmに投げて行います。PHP-fpmはデフォルトでは、ポート9000で動くのですが、unixソケットに変更します。

というわけで、以下ベタ貼り。というか、サイト固有のものだけ変えます。よくここでミスるので、実際に貼りつけてもうまくいかない、なんてことになるんだよなぁ。

まずリバースプロクシ側

proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=czone:180m max_size=512m inactive=120m;
proxy_temp_path   /var/tmp/nginx;
proxy_cache_key   "$scheme://$host$request_uri";
proxy_set_header  Host               $host;
proxy_set_header  X-Real-IP          $remote_addr;
proxy_set_header  X-Forwarded-Host   $host;
proxy_set_header  X-Forwarded-Server $host;
proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_buffers     32 16k;

upstream wordpress_master {
  server 192.168.1.5:9001 fail_timeout=120s;
}

upstream wordpress_slave {
  server 127.0.0.1:9001 fail_timeout=120s;
}

server {
  listen 80;

  server_name dev.ganchiku.com;

  proxy_cache_valid 200 20m;

  expires off;

  set $do_not_cache 0;

  location / {
    root /home/app/ganchiku.com;
    rewrite ^/$ /blog/ permanent;
  }

  location /blog/wp-admin {
    proxy_pass http://wordpress_master;
  }
  location /blog/wp-login.php {
    proxy_pass  http://wordpress_master;
  }
  location ~ .*\.php {
    proxy_pass http://wordpress_master;
  }

  location /blog {

    set $mobile 0;
    if ($http_user_agent ~* '(DoCoMo|J-PHONE|Vodafone|MOT-|UP\.Browser|DDIPOCKET|ASTEL|PDXGW|Palmscape|Xiino|sharp pda browser|Windows CE|L-mode|WILLCOM|SoftBank|Semulator|Vemulator|J-EMULATOR|emobile|mixi-mobi
le-converter)') {
      set $mobile 1;
    }
    if ($http_user_agent ~* '(iPhone|iPod|Opera Mini|Android.*Mobile|NetFront|PSP|BlackBerry)') {
      set $mobile 2;
    }

    # If logged in, don't cache.
    if ($http_cookie ~* "comment_author_[^=]*=([^%]+)%7C|wordpress_logged_in_[^=]*=([^%]+)%7C") {
      set $do_not_cache 1;
    }
    if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
      set $do_not_cache 1;
    }

    proxy_no_cache     $do_not_cache;
    proxy_cache_bypass $do_not_cache;
    proxy_cache        czone;
    proxy_cache_key    "$scheme://$host$request_uri$is_args$args$mobile";
    proxy_cache_valid  200 20m;
    proxy_cache_valid  404 5m;
    proxy_pass         http://wordpress_slave;
  }

  location ~* \.(jpg|png|gif|jpeg|css|js|swf|pdf|ppt|pptx)$ {
    proxy_cache_valid  200 120m;
    expires            864000;
    proxy_cache        czone;
    proxy_pass         http://wordpress_master;
  }

  location ~* wp\-.*\.php|wp\-admin {
                  # Don't static file cache admin-looking things.
                  proxy_pass http://wordpress_slave;
  }

  location  ~* \/[^\/]+\/(feed|\.xml)\/? {
    if ($http_cookie ~* "comment_author_[^=]*=([^%]+)%7C|wordpress_logged_in_[^=]*=([^%]+)%7C") {
      set $do_not_cache 1;
    }
    proxy_no_cache     $do_not_cache;
    proxy_cache_bypass $do_not_cache;
    proxy_cache        czone;
    proxy_cache_valid  200 60m;
    proxy_pass         http://wordpress_master;
  }
  location ~* \.(jpg|png|gif|jpeg|css|js|mp3|wav|swf|mov|doc|pdf|xls|ppt|docx|pptx|xlsx)$ {
    # Cache static-looking files for 120 minutes, setting a 10 day expiry time in the HTTP header,
    # whether logged in or not (may be too heavy-handed).
    proxy_cache_valid 200 120m;
    expires 864000;
    proxy_pass http://wordpress_slave;
    proxy_cache czone;
  }

  location = /50x.html {
    root   /var/www/nginx-default;
  }

  #AWS uses Elastic Load Balancer.
  set_real_ip_from        10.0.0.0/8;
  real_ip_header  X-FORWARDED-FOR;
  add_header X-Forwarded-For $http_x_forwarded_for;

   log_format htest '$http_x_forwarded_for - $remote_addr - $remote_user [$time_local]  '
      '"$request" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent"';

   #Custom Error Page
   error_page   404          /404.html;
   error_page   502 503 504  /50x.html;

   access_log /home/app/log/ganchiku.proxy.log;
   error_log /home/app/log/ganchiku.proxy.log;

   # Set the real IP.
   proxy_set_header X-Real-IP  $remote_addr;

   # Set the hostname
   proxy_set_header Host $host;

   #Set the forwarded-for header.
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

上記で書きましたが、upstreamに wordpress_masterとwordpress_slaveを2つ用意しています。192.168.1.5を書き込みを行うサーバのIPアドレスと固定します。そして、読み込みは、どのサーバで動かしてもローカルのIPとします。これは実は肝で、この設定でオートスケールしても、そのまま使えるようにしているんですね。あと AWS の Elastic Load Balancerを使用しているので、その辺の設定も適当に付けておきました。

次にバックエンド側

server {
  listen 9001;

  server_name dev.ganchiku.com;
  root /home/app/ganchiku.com;

  access_log /home/app/log/ganchiku.backend.log;
  error_log /home/app/log/ganchiku.backend.log;

  index index.php;
  try_files $uri $uri/ /blog/index.php?q=$uri;

  location ~ \.php$ {
    include       fastcgi_params;
    fastcgi_pass  unix:/tmp/php.socket;
#    fastcgi_pass  127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SERVER_PORT 80;
    fastcgi_param SERVER_NAME dev.ganchiku.com;
    expires 2h;
  }
}

肝は、try_files と indexの指定かな。indexの指定がないと、index.phpがディレクトリインデックスだと認識してくれないので、ちゃんと書きます。デフォルトでなっているだろう、と思ってここを書かずに、うまくいかずに数時間費やしてしまいました。とほほ。
fastcgi_passには、PHP-fpmを指定します。ポートで動かしているのであれば、コメントアウトしている方を使ってください。UNIX ソケットで使用しているのであれば、UNIXソケットのファイルを指してください。

PHP-fpmをUNIXソケットで動かす

Ubuuntuの場合は、 /etc/php5/fpm/pool.d/www.confを変更すればいいかな。独自にファイルを作ってもいいですが、基本一行のみの変更なので、修正しました。

;listen = 127.0.0.1:9000
listen = /tmp/php.socket

listenしている行を変更します。127.0.0.1:9000をコメントアウトして、 /tmp/php.socket に変更してphp-fpmを再起動すれば、ソケットで通信をすることがでできます。いくつかのブログでソケット通信の方が速いと書いてありましたので、それに倣ってみました。

実際は、この設定をサブドメインのマルチブログに適用していないのですが、しなければいけないので、その内容を見てみます。
まぁ、いつものように間違いがあれば、指摘してくださいな。

wordpress のアップロード画像を S3 に置いて、アクセス制御も行う

ブログを書こう書こうと思いつつ、すっかり書かなくってなってしまいましたshinです。最近は、自分のブログを充実させるくらいなら、Symfonyのドキュメントを充実した方が、もっと読まれる内容になるだろうなぁ、と思っています。とは言っても、Symfony と関係のないネタなどは、やはり書く場所もないので、ブログに戻ってきてしまいます。

さて、今回のネタはwordpressです。現在このブログが走っているサーバもAmazon Web Service のEC2 なのですが、画像の置き場所について調べてみました。実際は、お手伝いしているブログの画像の管理についてだったのですが、実験用として自分のブログサーバでやってみました。内容はタイトルにあるように「wordpress のアップロード画像を S3 に置いて、アクセス制御も行う」です。

まず、なぜwordpressでアップロードしたファイルをS3に置く必要があるのでしょうか?私のこのブログのような個人ブログではあまり必要はないのですが、次の三点からその必要があると思います。

  • S3はバックアップ用途として使える。
  • S3の信頼度は、New Amazon S3 Reduced Redundancy Storage (RRS)によると、99.999999999%らしいです。つまりバックアップストレージとして最適です。自分のサーバに入れておいて、クラッシュする可能性も考えておくと、なんかしらのバックアップの仕組みが必要になると思います。その際に、画像をローカルに置いておいてもいいのですが、S3のような信頼性のあるストレージに置けるのであれば、それはそれで素晴らしいバックアップ戦略だと思います。

  • スケールアウトした際に、複数のサーバから同じリソースを見ることができる
  • アクセスの少ないこのブログでは、特に関係はないのですが、スケールアウトした際にアップロードしたファイルを外部の同じ場所に置いておけるのは素晴らしいです。もし、外部に置かなければ、ディスクをマウントして使い回したり、複数のサーバ間でsyncする必要があります。外部に置いておけば、複数のウェブサーバから同じファイルを指すだけで可能になり、Wordpressの運営で素晴らしいスケールアウト戦略だと思います。

  • S3に置いておいて、将来クラウドフロントに乗り換える
  • これは、Wordpressに限った話ではありませんが、さらなるスケールアウトに便利です。S3に置いておけば、将来 Amazon Cloud Front を使用して、リソースをCDNサービスで配信することができます。アップロードファイルは、往々にしてファイルサイズが大きいので、これらのファイルを CDN で配信できれば、良いスケールアウト戦略です。

さて、Wordpressでこの機能を実現するためには、次のプラグインが必要になります。

このプラグインを使用して、適切に指定を行うとアップロードをS3にしてくれます。私の使用している WordPress 3.2.1 で、このプラグインのバージョン0.4.1.1では、アップロードに関しては動作を確認しています。実際は次のような問題がありますが、それほど大きな問題ではありません。まず、削除に関しては動作しませんし、ACLに関しては public-read でS3にアップロードしてしまいます。Wordpress をマルチブログ(ネットワーク)で使用している際には、config.php.sample をconfig.phpにコピーして、使用します。あとは、素直に設定をするだけで基本は動くようになります。普通にアップロードを行うと、S3の指定したバケットにアップロードしてくれます。
S3では、CNAMEを指定することができますので、指定しました。私の場合であったら、media.ganchiku.com というバケットを作成しておき、次のようにDNSで指定してあげます。

media CNAME media.ganchiku.com.s3.amazonaws.com

しかし、アクセスが多いブログはコピーされてしまったり、アップロードした画像を直接リンクをされてしまうことがありますよね。S3は確かに格安のサービスなのですが、大きな画像や映像らが頻繁にダウンロードされるような状況になると、それでもお金がかかってしまいます。そこで、アクセス制御を行います。具体的には、自分のサイト、Google, FacebookがRefererであった際には、画像を表示するが、他のドメインであった際には画像を表示させない、というようにします。また、直接画像に対してリクエストがあった際にも画像を表示させません。これは、S3のbucket policy をすることで可能になります。bucket policy は、AWSのコンソールから、左のバケットを選択肢、右クリックで、プロパティを編集しましょう。その後、「Edit bucket policy」というリンクがあるので、ここで指定してあげます。そうすると次のような画像なります。

ちなみに、この画像自体、S3にアップロードされており、bucket policyを指定してありますので、直接 http://media.ganchiku.com/wp-content/uploads/2011/11/ishot-17-1024×703.jpg のURLをコピーアンドペーストで 見ることはできません。実際は、キャッシュが有効になっているので、このページを見ているだけで、一度目は見えるのですが、再読み込みをすると、XMLのエラーコードが返ってきます。

さて、bucket policyですが、私の場合は次のようになりました。

{
	"Version": "2008-10-17",
	"Statement": [
		{
			"Sid": "GIve asccess if refere is set",
			"Effect": "Allow",
			"Principal": "*",
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::media.ganchiku.com/*",
			"Condition": {
				"StringLike": {
					"aws:Referer": [
						"http://*.google.co.*/*",
						"http://*.facebook.com/*",
						"http://*.google.com/*",
						"http://ganchiku.com/*",
						"http://*.ganchiku.com/*",
					]
				}
			}
		}
	]
}

このままでは、Amazon Cloud Front から使うことはできないのですが、そこまで調べていないのですが、できるかもしれません。 http://blog.cloudberrylab.com/2010/09/how-to-grant-permissions-to-cloudfront.html この辺とか https://forums.aws.amazon.com/thread.jspa?threadID=74717 とか見ると、完全に同じことはできないのですが、オリジナルのサイトからのみのアクセスということならばできそうです。しかし、そうすると、feed readerとかでは画像が表示できなくなってしまいますね。悩ましいなぁ。

I'vRead launched

2009年9月1日

Akky‘s yonda4 has a sensational debut last couple of months in Japan. “Yonda” means “I have read” in Japanese. If you are not familiar with Japanese, you have no idea why there is “4″, but in Japanese “4″ can be pronounced as “yo”, so literally, it is “yondayo”. Actually “yonda” and “yondayo” is a quite same meaning, but latter is more friendly and it has meaning to tell people. You are not saying to yourself, but to tell somebody.

Since yonda4 has launched successfully, akky was thinking for English version of yonda4. Yep, it is ivread, and has just launched a few days ago! Actually, I also helped this project. :D Since we launched narabe / narabete 2 years ago, we have developed web applications with symfony. Yes, both yonda4 and ivread are developed with symfony framework.

So, akky asked me for help to i18n of yonda4 last week, and setting up the environment. At that time, I was working on his other projects, but I thought ivread is a good challenge, and I also liked his idea. Since I’m using symfony in last two years, it didn’t take any time to join this project. I was just ready. :) Again, I’m happy with symfony for easy development and its i18n support.

So, I helped set up product environment in ec2, and automatically backup feature for s3 storage. Yep, we are using ec2 now to start new service. I like these services, it is easy to set up environment, and once you set it up, you can store private AMI to your s3 storage. So, we use s3 for back up for both AMI and database dump. Some might says ec2 is too expensive, but you can pay per hour. I hope it won’t happen, but if service you are running on ec2 did not work well, you can always stop the server. Just after you stopped, you are not going to be changed.

We’re still working on ivread to parse tweet. There are some rule to follow in usage page, but it seems that not many people read it, and tweet whatever they like. So, we will try to cover as much as we can.

BTW, in this weekend, symfony’s core developer, Fabien is coming to Tokyo!!!! I live at Kyoto where is a bit far from Tokyo. BUT, I’m going to see him in Tokyo this weekend. :D

All right, tech part is done. The reason I like about ivread is not only symfony base application and I could use ec2. I liked akky’s idea very much. Both ivread and yonda4 provide users the way to affiliate their books. I do not like ads most of time, because it does not relate with the contents in many cases. I like ads when you really recommend it. At least for me, I do not want any ads for products or services I have never used. If I have used it, and liked it, then I would be happy to add ads of them. So, ivread is real users’ voice. You can add a short comment with tweeting to ivread as well.

As I mentioned above, and Cool Cat Teacher Blog mentioned, if you tweet their amazon affiliate id to ivread, you have chances to get some cash back. I think this motivates people to tweets to ivread. Akky also said Twitter can be platform for our life. I strongly agree with his idea.

Well, we are still on the way to make ivread better, but I believe ivread has a bright future! :)

Shin Ohno 2003-2012