phpのcurlが動かない。

PHPのcurlでどはまりした。普通に接続するには問題ないが、proxy環境から接続するのが問題。まず、proxyを通さずに普通に接続擦る場合のコードはこんな感じ。

$url="https://www.myexample.com";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$value = curl_exec($ch);
curl_close($ch);
echo $value;

これはまぁ、普通につながるんだよ。で、Proxy経由にすると、よく書いてあるのは、こんな感じ。

$url="https://www.myexample.com";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
curl_setopt($ch, CURLOPT_PROXY, "proxy.example.com");
curl_setopt($ch, CURLOPT_PROXYPORT, 10080);

$value = curl_exec($ch);
curl_close($ch);
echo $value;

みたいな?。
当初これでつながっていた。ところが、下記の様にurlを変えた途端うごかなくなった。

$url="http://www.yahoo.co.jp";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
curl_setopt($ch, CURLOPT_PROXY, "proxy.example.com");
curl_setopt($ch, CURLOPT_PROXYPORT, 10080);

$value = curl_exec($ch);
curl_close($ch);
echo $value;

何が違うのか?。しばらく悩んだ。myexample.comからyahooに変えただけなのに・・。
動かすには?。こうすれば良い。

$url="http://www.yahoo.co.jp";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
curl_setopt($ch, CURLOPT_PROXY, "proxy.example.com");
curl_setopt($ch, CURLOPT_PROXYPORT, 10080);

$value = curl_exec($ch);
curl_close($ch);
echo $value;

つまり、HTTPROXYTUNNELの解釈に間違いがあった。PROXYって書いてあるので、単純にPROXYを使う時はこれを1にするのかと思ってたのだが、それが間違い。なぜ今までつながっていたかというと、”https”だったから。httpsの場合CONNECTメソッドを使うしかないので、普通にTUNNELで良いのだが、”http”の場合、PROXYによってはCONNECTを認めてない。その場合HTTPPROXYTUNNELを1にすると動かなくなると。

ヘッダーを出力して、”CONNECT http://www.yahoo.co.jp”ってなっているのを見て気がついた。日本の会社のPROXYって外からの攻撃より、内部のデータの外部流出を防ぐためにある!。みたいなところがあるので、SOCKとかCONNECTとかPROFIND(だっけ?)とか動かないのよね・・・。

HTTPPROXYTUNNELを1にして動いている人は、本来のPROXY(代理人)の役割として機能しているproxyを使っている人かな。檻に閉じ込めるためのPROXYを通さなきゃ外に出られない人は、HTTPPROXYTUNNELは0にしておかなきゃいけないっぽい。まぁ、TUNNELって書いてあるしな・・。

FuelPHPを使ってみる(その1)

今現在、phpとsmarty、ライブラリとしてPECL,PEAR,Zendを入れて使ってる。モデル要素はLibとしてまとめ、public以下にAppディレクトリ、その中にコントローラーとなるmain.php、ViewとなるSmartyのtemplateを配置し、アプリの開発はPublic以下のアプリと、その動作モデルであるLib以下のAppフォルダに入れる。lib/AppとPublic/App以下を一人が担当し、どちらかというとパフォーマンスと自由度重視でやってきた。でも、XPのように担当を入れ替えようとした時にルールがいい加減過ぎて破綻しそう。。ルールもある程度決めて、常識の範囲で。とやろうとしたのだが、その通りに人は動かない。また、次作ろうと思っているのはそれほどパフォーマンスが必要ないので、フレームワークを使って楽ちんにやろうかと。。

検討したのはCakePHP,CodeIgniter,ZendFramework,FuelPHPである。まずZendはLibraryだけ使ってても重いのでパス。CakePHPは有名(よく聞く)だが遅そうなのでパス。CodeIgniterが良さそうだったら、日本国内ではライセンスの問題が発生しているとかの話をみたので、発展中でちょっと危険だが新しめのFuelPHPを試す。

本家はここ。
fuelphp
現在の最新バージョンは1.4。
解凍すると、

> ls -al
合計 88
drwxr-xr-x 5 fff users 4096 1月 7 11:00 .
drwxr-xr-x 3 fff users 4096 1月 7 11:00 ..
-rw-rw-r-- 1 fff users 170 9月 9 20:04 .gitignore
-rw-rw-r-- 1 fff users 643 9月 9 20:01 .gitmodules
-rw-rw-r-- 1 fff users 35655 11月 10 04:07 CHANGELOG.md
-rw-rw-r-- 1 fff users 6526 9月 9 20:04 CONTRIBUTING.md
-rw-rw-r-- 1 fff users 1817 10月 27 06:37 README.md
-rw-rw-r-- 1 fff users 3243 9月 9 20:01 TESTING.md
-rwxrwxr-x 1 fff users 2318 9月 9 20:01 build.xml
drwxr-xr-x 9 fff users 4096 1月 7 11:00 docs
drwxr-xr-x 5 fff users 4096 1月 7 11:00 fuel
-rwxrwxr-x 1 fff users 1188 9月 9 20:01 oil
drwxr-xr-x 3 fff users 4096 1月 7 11:00 public

こんな感じ。ぱっと見public以下がメインで本体はfuelの中かな?。
Documentを見ると、

> curl get.fuel.com/oil | sh

とやれと。中身を見ると、スクリプトをダウンロードして、起動、そのままgitで落としてくるようになってるっぽい。けど、gitで外部に繋げられる環境限定だよね。そんなわけでマニュアルインストールと。。

なになに?とりあえず、パーミッションをセットしろと。

/fuel/app > chmod 766 cache/ logs/ tmp/ config/

で「app/config/config.phpを好きなように編集しろ?。」だと?。ファイルを見ると、デフォルトをオーバーライトするっぽいので、とりあえず放置で。

一応これでインストール完了。
public/index.phpにアクセスしたら何か出るかな?。と思って繋いでみたけど、落ちるな。。

Fatal error: Exception thrown without a stack frame in Unknown on line 0

php.iniのdate.timezoneを設定してない、もしくはUTCだと落ちるらしい。これを設定してあげれば、うご・・・404かよ。

http://localhost/fuelphp-1.4/public/welcome
welcomeページが動いた。
http://localhost/fuelphp-1.4/public/welcome/hello
こうすると、hello worldが出る。

なるほど。app/controllerが作られてそれが呼ばれるのか。
ruby on railに影響受けてるのか?。
ここでrewriteエンジン使われるのか。まぁいいか。

イントラ内でのRubyGems

イントラ内でRubyGemsでインストールするのに一苦労。。

> sudo gem install パッケージ名

ではインストール出来ない。sudoにしたときにproxyを設定していないから。

> su
> export http_proxy=http://proxy.example.com:8080
> gem install パッケージ名

suに成れない場合は・・・どうするんだっけ。

> sudo gem install パッケージ名 -p http://proxy.example.com:8080

こうね。

アンインストールする場合は、

> sudo gem uninstall パッケージ名

バージョンを指定してインストールする場合は、

> sudo gem install パッケージ名 --version "=1.5.0"

ライブラリに依って、バージョンが指定されてしまう場合がある。

今回、exlsのファイルを開こうとして、rooっていうライブラリを使おうと思ったが、Google Spreadsheetを使用するのに、nokogiriというライブラリの指定をさせられた。

/usr/lib/ruby/vendor_ruby/1.8
/rubygems.rb:274:in `activate': can't activate nokogiri (>= 1.4.4, != 1.5.1, != 1.5.2, runtime) for 
["google-spreadsheet-ruby-0.2.1", "roo-1.10.1"], already activated nokogiri-1.5.2 for ["roo-1.10.1"] (Gem::LoadError)

こんな感じで、1.4.4以上で、1.5.1,1.5.2では動かないと。なので直接1.5.0を指定したが、

> sudo gem install パッケージ名 --version ">=1.5.0"

のように、1.5.0以上と指定することもできるらしい。じゃ、出てきたまま書けばよいのか?と。

> sudo gem install パッケージ名 --version ">=1.4.4, !=1.5.1, !=1.5.2"

と書けばインストール出きるのかと思ったが、出来なかった。。いとかなし。

javascriptを一つにまとめる

色々javascriptで作っているうちに、どんどんファイルが増えてくる。今現在でajvascriptファイルだけで30ファイル。ファイルが分かれる分オーバーヘッドが増えるので、一つにできないかと、色々物色。良いのがなければ作る覚悟で。

で、見つけたのがこれ→phpflair.phpってやつ。ライセンスはNew BSDなので導入はしやすい。というか、全部で500行程度のプログラム。gzip圧縮にも対応している。cssとajvascriptをまとめて、圧縮できる。

カスタマイズ方法はというと、ソースを書き換えろと。あっそう。。

if (isset($_REQUEST['js'])) {
	$config['type'] = 'js';

	if($_REQUEST['js'] == 'jquery'){
		$config['sources'] = array(
				"../../common/js/jquery-1.6.2.min.js",
				"../../common/js/jquery.json-2.3.min.js",
				"../../common/js/ui/jquery.ui.core.js",
				"../../common/js/ui/jquery.ui.widget.js",
				"../../common/js/ui/jquery.ui.accordion.js",
				"../../common/js/ui/jquery.ui.mouse.js",
				"../../common/js/ui/jquery.ui.draggable.js",
				"../../common/js/jquery.cookie.js",
				"../../common/js/jquery.mousewheel.js",
								   );
・
・

ということで、こんな感じで、jqueryをまとめて、出力できるようにした。9個のファイルが1つになるので、サーバーと回線にやさしいはず。

で、ここで問題が起こった。今使ってるサーバーはhtmlファイルを外部に出力する際、クライアントが対応していればgzip圧縮して出すような設定になってる。具体的にはhttpd.confに’php_value zlib.output_compression = 1’と設定してある。この設定を外すと動く。なんだよ、htmlをgzip圧縮するか、javascriptをまとめるかの2択か?。ってそんなわけ無いだろ。

結局、phpflair.phpの方はgzip圧縮がかからないように設定する。設定というか、ソースを編集して、compress_methodをnoneにしてやる。

// compression method based on browser support; gzip, deflate, or none
if (!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
	$config['compress_method'] = 'none';
} else if (stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
	$config['compress_method'] = 'gzip';
} else if (stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate')) {
	$config['compress_method'] = 'deflate';
} else {
	$config['compress_method'] = 'none';
}
// $config['compress_method'] = 'gzip'; // override

$config['compress_method'] = 'none';  ← オーバーライトする。

でも、動かない。ブラウザでみると、Encodingはgzipで、content-typeはtext/javascriptでちゃんと来てるんだけど、javascriptが途中でエラーになる。出てきたjavascriptをデバッグしてみると、途中で切れてるっぽい。。なんかおかしい・・と、header()の部分をコメントにして動かしてみると、content-typeはtext/htmlになっちゃうが一応最後まで受信できる。。うーーん?。
phpのzlib.output_compressionの設定は何で判断しているんだ?。拡張子で判断しているわけでもない、content-typeで判断しているわけでもない。と、header()をひとつずつコメントアウトしながら動かしてみた。

header('Content-Type: '.$config['content_type']);
//header('Content-Length: '.$combined_filesize);   ←こいつ
if ($config['compress_method'] == 'gzip') {
	header('Content-Encoding: gzip');
} else if ($config['compress_method'] == 'deflate') {
	header('Content-Encoding: deflate');
}

なるほど・・。Content-Lengthを指定しちゃうと、phpのzlib.output_compressionで指定したものと食い違うのでおかしくなる。の、かな?。

ってことで、phpのzlib.output_compressionを1にしたままjavascriptをまとめるphpplair.phpをどうじに使えるようになった。当然、htmlもcssもjavascriptもgzipで出力されるようになった。

効果はというと、うーん、体感速度はあがってないけど、リクエスト数が減ってるのでまぁ気休めというか、そんな感じかね。cacheも出きるようになってるけど、やってない。APCのデータキャッシュに入れてしまうと、メモリから送信されるから、より早くなるかも。

暗号化する場合のmysqlのテーブル

アカウント管理の続き。passwordの保存については前回書いた。emailの暗号化だが、mysqlのAES_ENCRYPT()関数とAES_DECRYPT関数を使えば良いらしい。MySQLのDocumentにも今のところ一番安全と書かれている。

MySQLに暗号化したデータを保存するには、BLOB型にしなければいけないと。そんなわけで、MySQLのテーブルは、以下のようになる。テーブル名はaccountにしよう。

  • id int primary key auto_increment
  • account varchar(128)
  • p_hash varchar(256)
  • email TINYBLOB

p_hashはpasswordの保存のところで作った、salt付きパスワードのハッシュを入れる。暗号化したemailはTINYBLOB型のemailに入れる。sha1のhash256バイトもないだろ。とか色々ツッコミどころはあるが、その辺は適当。TINYBLOBは256バイト。e-mailアドレスでそんなに長い奴いないし。後で逆算してe-mailアドレスの文字数制限でリミットかけないといけない。その上のBLOB型になると、65536バイトなのででかすぎ。

<?php
class Account{
  const SALT='awSEdrFTgyHUjiKOlp';
  const CRYPT_KEY="AHAHAHA";
  function registration($account,$password,$email)
    // password hashを作る
    $pwd_hash = sha1(self::SALT.$password);

    // sql queryを作る
    $sql = "INSERT INTO account (account,phash,email)".
       " VALUES('$account','$pwd_hash',AES_ENCRYPT('$email','".self::CRYPT_KEY."'))";
    $db = mysql_connect(DB_HOST,DB_USER,DB_PASSWORD);
    mysql_query($sql,$db);
    mysql_close($db);
  }
}
?>

んで、暗号化するまでは良いとして、じゃぁ、引っ張ってくるときどうするの?。って話だ。

  $sql="SELECT AEC_DECRYPT(email,'".self::CRYPT_KEY."') as email ".
          "FROM account WHERE account='$account'";

こんな感じで、復号化してあげる。

では、次にWHERE節で探す場合は?。

 $sql="SELECT * FROM account ".
     "WHERE email=AES_ENCRYPT('$email','".self::CRYPT_KEY."')";

暗号化したものを探すので、AES_ENCRYPT()で暗号化して、比較してあげれば良いのね。

じゃ、次に、Likeとかはどうすんだ?。

  $sql="SELECT * FROM account ".
    "WHERE AES_DECRYPT(email,'".self::CRYPT_KEY."')".
    " LIKE '%$keyword%'";

こう。今度は、復号化してからキーワードと比較しれあげれば良い。後は一緒でしょ。

mysql_fetch_xxとかは適当に。

ここで疑問、MySQLからデータが奪われてもe-mailは暗号化されているし、passwordも塩付きhashになってるから、まぁ大丈夫。でも・・。CRYPT_KEYがphpファイルに平文で書いてあるってどうなの?。phpのソースファイルは見られないって前提なのか?。と。そういわれりゃSSLの秘密鍵だってどっかのディレクトリの中にあるし、サーバーのフロントエンドがやられたら終わり。ということは、このCRYPT_KEYはSSLの秘密鍵のようにApacheの公開ディレクトリ以外のところに置いて、人から見られないようにしておくのがベターだよね。

passwordの保存

ちょっと、釣りのサイトをリアルタイム化したいので、アカウント管理を作りたい。条件は、以下の通り、

  1. アカウント名
  2. e-mail
  3. password

と、まぁ、普通のアカウント管理だわな。当然ながら、ユーザー別にidを作らなきゃいけないし、passwordもそのまま平文って分けにはいかない。e-mailも暗号化して保存したいよね、当然。そんなわけで、mysql,phpで作る。

んで、どうするのかと、アカウント名は面倒なのでアルファベットと_くらいでOK。ユニークじゃなきゃならないけど、その辺は一回チェック入れれば良いだけだから問題ない。そのアカウント名と関連付けるidはMySqlの自動インクリメントに任す。

パスワードの処理だが、普通に入力されたパスワードをhashにして保存しておけば良いのかと思っていた。

<?php
//登録
function registration($account , $password){
  $pwd_hash = sha1($password);
  save($account,$pwd_hash);
}
//login
function login($account,$password){
  $pwd_hash = load($account);
  if($pwd_hash == sha1($password)){
    return true; // OK
  }else{
    return false;//NG
  }
}
?>

まぁこんな感じ。ところが、このhashだとダメなんだと。レインボーテーブルってのが、ネットのどこかに有って、作られそうなhashを根こそぎテーブルにしてあるんだそうな。。なんてこったい。

で、解決策はsaltってのを使って塩加減を調整するそうな。ユーザーからの入力は単純な物が多いかもしれないけど、何文字かゴミを付けてからhash化すれば、レインボーテーブルって奴にも引っかかり難いと、そう言う事らしい。んで、さっきのを改造すると、

<?php
const SALT='awSEdrFTgyHUjiKOlp';
//登録
function registration($account,$password){
  $pwd_hash_s = sha1(SALT.$password);
  save($account,$pwd_hash_s);
}
//Login
function login($account,$password){
  $pwd_hash_s = load($account);
  if($pwd_hash_s == sha1(SALT.$password)){
    return true;  //OK
  }else{
    return false;  //NG
  }
}
?>

ま、こんな感じ。違いはSALTをくっつけてhashを作っているところ。linuxのパスワードもこんな感じでやっているらしく、今のところ問題は起きてなさそう。