SHOJI's Code
仕事や趣味で書いた各種言語のプログラミングコード(エクセルVBA,PHP,C/C++/C#,JavaScript等)、その他雑記。
2009.10<<123456789101112131415161718192021222324252627282930>>2009.12
リダイレクト (PHP)
最近作ったコード。他のサイトへリダイレクトするPHPスクリプト。
http://(サイト)/gateway.php/path/to/access にアクセスすると、 http://xxx.xxx.xxx.xxx/path/to/access からの応答を返すものである。

gateway.php
<?php
//////////////////////////////////////////////////////////////////
// 準備
ini_set("max_execution_time", 0); // スクリプトの実行時間を無制限にする
set_time_limit(0);
ignore_user_abort(true); // クライアントから切断されても継続する

//////////////////////////////////////////////////////////////////
// 接続先情報取得
$host = "xxx.xxx.xxx.xxx";
$port = 80;
$path = $_SERVER["PATH_INFO"]; // アクセスするパス
if( $path=="" ) $path="/"; // パスが空の場合は "/"(ルート)に
if( ($q=$_SERVER["QUERY_STRING"])!="" ) $path.="?$q"; // クエリ文字列がある場合にはパスにクエリ文字列を付加

//////////////////////////////////////////////////////////////////
// リクエストの作成
$method = $_SERVER["REQUEST_METHOD"]; // メソッド (GET POST HEADなど)
$protocol = $_SERVER["SERVER_PROTOCOL"]; // プロトコル (HTTP/1.0 または HTTP/1.1)
$request = "$method $path $protocol\r\n"; // リクエスト行

// リクエストヘッダをコピーする
foreach(getallheaders() as $name=>$value)
{
// 一部のヘッダは書き換える
switch( $name )
{
case "Host": // Hostヘッダ
$request.="$name: $host\r\n"; // 接続先ホストに変更する
break;

case "Connection": // Connectionヘッダ
$request.="$name: close\r\n"; // 最終的にソケットはクローズするため、"close"と指定する
break;

default: // その他
$request.="$name: $value\r\n"; // そのままコピー
break;
}
}
$request .= "\r\n"; // ヘッダの終端

//////////////////////////////////////////////////////////////////
// ホストに接続し、リクエストを送信する
$fp = fsockopen($host, $port); // 接続
fputs($fp, $request); // 送信

//////////////////////////////////////////////////////////////////
// ヘッダのクリア
$header = array(); // ヘッダー(生)
$info = array(); // ヘッダ情報

//////////////////////////////////////////////////////////////////
// レスポンス受信
$response = $header[] = fgets($fp); // レスポンス行の受信
list($p, $status) = split(" ", $response); // プロトコルとステータスコードに分ける

//////////////////////////////////////////////////////////////////
// ヘッダを受信する
while( ($h = fgets($fp))!==false )
{
if( eregi("^\r?\n$", $h) ) break;

$header[] = $h;

list($name, $value) = split(": ?", $h);
$info[$name] = eregi_replace("\r?\n$", "", $value);
}

//////////////////////////////////////////////////////////////////
// クライアントにヘッダを送信
foreach($header as $h) header($h);

//////////////////////////////////////////////////////////////////
// 本体を受信し、クライアントへ送信する (メソッドがHEAD)
if( strtoupper($method) == "HEAD" )
;

//////////////////////////////////////////////////////////////////
// 本体を受信し、クライアントへ送信する (ステータスが100番台もしくは204,304)
else if( ereg("1[0-9][0-9])|204|304", $status) )
;

//////////////////////////////////////////////////////////////////
// 本体を受信し、クライアントへ送信する (チャンクエンコーディングの場合)
else if( $info["Transfer-Encoding"]=="chunked" )
{
// 本体受信
while( ($c = fgets($fp))!==false )
{
// クライアントとの接続が切れていれば、処理を終了する
if( connection_status()!=CONNECTION_NORMAL ) goto close_socket;

// クライアントへデータ長を送信
echo $c;

// 終了の確認
list($s, $ext) = split("; ?|\r\n", $c); // データ長を抽出
if( $s=="0" ) break; // "0"なら抜ける

// 本体部分の受信
$data = "";
while( !feof($fp) && strlen($data)<$size )
{
if( connection_status()!=CONNECTION_NORMAL ) goto close_socket;
$data.=fread($fp, $size - strlen($data));
}
$data .= fread($fp,2); // エンドマークのCR/LFを受信

// クライアントへデータ本体を送信
echo $data;
}

// 追加ヘッダ受信
while( ($h = fgets($fp))!==false )
{
// クライアントとの接続が切れていれば、処理を終了する
if( connection_status()!=CONNECTION_NORMAL ) goto close_socket;

// クライアントへ追加ヘッダを送信
echo $h;

// 終了の確認
if( eregi("^\r?\n$", $h) ) break; // 空行なら抜ける
}
}

//////////////////////////////////////////////////////////////////
// 本体を受信し、クライアントへ送信する (Content-lengthヘッダによる長さ指定)
else if( isset($info["Content-Length"]) )
{
// 受信バイト数
$bytes = $info["Content-Length"] - 0;

// 受信
$data = "";
while( !feof($fp) && strlen($data)<$bytes )
{
if( connection_status()!=CONNECTION_NORMAL ) goto close_socket;
$data.=fread($fp, $size - strlen($data));
}

// クライアントへ出力
echo $data;
}

//////////////////////////////////////////////////////////////////
// 本体を受信し、クライアントへ送信する (EOFまで受信)
else
{
while( !feof($fp) )
{
// クライアントとの接続が切れていれば、処理を終了する
if( connection_status()!=CONNECTION_NORMAL ) goto close_socket;

echo fread($fp, 8192);
}
}

// 接続を閉じる
close_socket:
fclose($fp);
?>


そんな面倒くさいことしなくても、header("Location: http://xxx.xxx.xxx.xxx"); で終わりじゃん。と思うのだろうが、スクリプトを通すことによって、間に処理を入れることが可能になる。実際のJOBではリダイレクト先に別のフォームで入力したユーザー名/パスワードを使ってAuthorizationヘッダを挿入したりした。
Apacheの設定を以下のようにしてやれば、単純にプロキシされているのと見た目区別がつかない。
ProxyPass /dir /gateway.php

テーマ:PHP - ジャンル:コンピュータ
INIファイルからデータを取得 (BAT)
複数の実行モジュールで構成されるシステムがあり、その起動用バッチファイルを作成していて、INIファイルに記述されている設定をバッチファイルで取得したくて、作ってみた。

実際のJOBでは、読み出したデータを環境変数にセットしたりしたが、下の例は読み出したデータを出力するもの。出力する部分を変えれば、環境変数にセットするなどは簡単にできると思う。

@echo off

:GET-INI
REM *******************************************************
REM INIファイルから項目を読み取り返す
REM %1: セクション名
REM %2: キー名
REM %3: デフォルト値
REM %4: INIファイル名
REM *******************************************************
setlocal enabledelayedexpansion

REM -------------------------------------------------------
REM 指定されたファイルが存在しないなら
REM 項目が見つからなかったものとして処理する
REM -------------------------------------------------------
if "%4"=="" goto __GET-INI__NOT_FOUND
if not exist %4 goto __GET-INI__NOT_FOUND

REM -------------------------------------------------------
REM ファイルを1行ずつ読み出して、検索
REM -------------------------------------------------------
set SN=
for /f "usebackq eol=; delims== tokens=1,2" %%a in (%4) do (
set V=%%a&set P=!V:~0,1!!V:~-1,1!&set S=!V:~1,-1!
if "!P!"=="[]" set SN=!S!
if "!SN!"=="%~1" if "!V!"=="%~2" (
echo %%b
goto __GET-INI__EXIT
)
)

REM -------------------------------------------------------
REM 項目が見つからない場合はデフォルト値を表示
REM -------------------------------------------------------
:__GET-INI__NOT_FOUND
if "%~3"=="" (echo/) else (echo %~3)

REM -------------------------------------------------------
REM 終了
REM -------------------------------------------------------
:__GET-INI__EXIT
endlocal
exit /B 0

テーマ:Windows 全般 - ジャンル:コンピュータ
はまった・・・ (PHP他)
かなり久々の投稿である(汗)

とあるJOBで、ネットワークカメラと組合せてWEBで画像などを見せる機器用のソフト(WEB)を作成していた。
カメラは直接グローバルなネットワークには接続されておらず、機器と直結。機器がカメラへのゲートウェイ的な役割をしている。
動画の形式としては、MPEG-4とMotion JPEGがあり、そのどちらも(IEで)表示させるためのActiveXが用意されていた。

最初は単純に apache の hpptd.conf に以下のように書いて、カメラへ接続させていた。
<VirtualHost 0.0.0.0:8080>
ProxyPass / http://(カメラのアドレス)/
ProxyPassReverse / http://(カメラのアドレス)/
</VirtualHost 0.0.0.0:8080>


とりあえず、これで、MPEG-4については RTP over HTTP でならうまく表示されたのだが、Motion JPEG については画像が乱れて最後にはIEが(おそらくはMotionJPEG用のActiveXが)アプリケーションエラーを起こしてしまう。

設計を進めていくうち、カメラに対しての認証をトラップする必要が出てきて、PHPでゲートウェイ用スクリプト redir.php を作り、 httpd.conf も以下のように変えた。
<VirtualHost 0.0.0.0:8080>
RewriteEngine On
RewriteRule ^/(.*)$ http://(自分のアドレス)/redir.php/$1 [L]
</VirtualHost 0.0.0.0:8080>


ところが、今度はMPEG-4がうまく繋がらなくなり、Motion JPEGは表示されるようになった。

ちなみに httpd.conf を以下のように変えると
<VirtualHost 0.0.0.0:8080>
ProxyPass / http://(自分のアドレス)/redir.php/
ProxyPassReverse / http://(自分のアドレス)/redir.php/$1
</VirtualHost 0.0.0.0:8080>


最初のとき同様、MPEG-4は表示され、Motion JPEGは表示されない。

で、それぞれ、キャプチャーして、うまくいくときとうまくいかないときを比べてみると、問題はActiveXからのHTTPのリクエストにあった。

MPEG-4の場合、ActiveXがRTP over HTTPでRTPを取得するときに、どうやらカメラから決まったヘッダしか付かないことを前提にプログラムが組んであるようで、Apacheが勝手につけるDateヘッダやServerヘッダが付いているとうまく動かない。これらのヘッダは Header unset Server などとしてもうまく削除できないのだが、ProxyPass でリダイレクトさせたときはリダイレクト先の応答をそのまま返すからなのか、それらのヘッダは付かずうまく動く。

Motion JPEGの場合、ActiveXからのリクエストに従ってカメラにアクセスするのだが、カメラからの応答にはContent-Lengthがなく、Content-Typeがmultipart/x-mixed-replaceであるため、接続が切れるまで、応答は終わらないのだが、それをクライアントに返すときにApacheが勝手にCHUNKにしてしまっていた。CHUNKでの応答でなく生だと思っているActiveXはそのままJPEGとして表示し、画像が乱れるということらしい。
でも、ActiveXからのリクエストにはHTTP/1.1となっているからCHUNKにも対応しなきゃいけないはずなんだけど、いまいち作りが中途半端。

結局最終的な httpd.conf は以下のようになった。
ServerTokens Prod
<VirtualHost 0.0.0.0:8080>
BrowserMatch ^$ downgrade-1.0
ProxyPass / http://127.0.0.1/fme100p/root/redir.php/
ProxyPassReverse / http://127.0.0.1/fme100p/root/redir.php/
Header unset Server
Header unset Date
</VirtualHost>


これでとりあえず両方表示されるようになったけど、こんなんでいいのか?
テーマ:PHP - ジャンル:コンピュータ
オブジェクトのコピー (JavaScript)
なんとなく書いたプログラミングコード。

JavaScriptでオブジェクト a と b があったとして、
a = b

とすると、オブジェクト b が オブジェクト a に代入されるが、それは参照を代入しているだけだ。
だから、
a.name = "AAA";
b.name = "BBB";


としても、 a.name も b.name も どちらも "BBB" である。

そこで、新しいインスタンスを作成し、内容をコピーする copyObject なる関数を作ってみた。
function copyObject(src)
{
var dest;

if (typeof src == 'object') {
if (src instanceof Array) {
dest = new Array();
for (var i=0;i<src.length;i++) {
dest[i] = copyObject(src[i]);
}
}
else
{
dest = new Object();
for (prop in src) {
dest[prop] = copyObject(src[prop]);
}
}
}
else {
dest = src;
}
return dest;
}


これを使って、以下のようにすると
b.name = "BBB";

a = copyObject(b);
alert(a.name+"/"+b.name);

a.name = "AAA";
alert(a.name+"/"+b.name);


最初の表示では "BBB/BBB" だが、次の表示は "AAA/BBB" である。
オブジェクトをJSON形式に (JavaScript)
この間、知り合いのホームページを作るときにちょっと作ってみた。
function getJSON(obj)
{
switch (typeof obj) {
case 'number':
return obj;

case 'string':
obj = obj.replace('"', '\\"');
obj = obj.replace('\r', '\\r');
obj = obj.replace('\n', '\\n');
return '"'+obj+'"';

case 'object':
var arr = [];
if (obj instanceof Array) {
for (var i=0;i<obj.length;i++) {
arr.push( getJSON(obj[i]) );
}
return '['+arr.join(',')+']';
}
else {
for (prop in obj) {
arr.push( '"'+prop+'":'+getJSON(obj[prop]) );
}
return '{'+arr.join(',')+'}';
}
}
return "";
}

パスの取得 (C#)
最近書いたちょっとしたコード。
public string GetConfigurationPath(){
/// エントリアセンブリのロケーション(パス)を区切り文字で分割
string[] dirs = Assembly.GetEntryAssembly().Location.Split('\\');

/// 最後の要素(=EXEファイル名)を空にする
dirs[dirs.Length - 1] = "";

/// 結合したものを返す
return string.Join("\\", dirs);
}


テーマ:プログラミング - ジャンル:コンピュータ
このところ忙しい・・・。
このところちょっと忙しい。更新も滞っているが、最近ではC#を書いたりしている。
一応このブログのカテゴリにもC#を追加しておこう。

今回は、とりあえずこれだけ・・・。
copyright © 2004-2006 SHOJI, Powered By FC2ブログ all rights reserved.
FC2ブログ