« ここあっぷる(BlogPet) | トップページ | OFF:¥(BlogPet) »
2006.12.23
[バッドノウハウ] いろんなWebサーバとつきあうために
「ソーシャルブックマーク管理ツール」を『BlogPet 気になる記事』に対応させるための作業をしているところなのですが、いつの間にか、拙作の Webクライアントでは BlogPet の管理画面に login できなくなっているようです。たぶん、ここ1週間か2週間のうちに何かが変わったものと思われます。
ログイン画面の html を見ても特に大きくは変わってないし、Internet Explorer や Firefox では問題なく login できています。拙作の Webクライアントで login しようとすると、500 Internal Server Error が返ってきます。
結局、proxyサーバ経由かつ https で通信するときにだけ起きる問題であることが判明。以前に、ドリコムRSS の Clip! に対応するためにいじったところを元に戻せば、ちゃんと login できることもわかりました。ただ、元に戻してしまうと、今度は、ドリコムRSS(正確にはドリコムアカウント)への login が 503 Service Unavailable になってしまうんですよね。
どういうことかというと。proxy サーバ経由で https 通信をするときは、通常は
POST http://ログイン用のアドレス HTTP/1.1
のような文字列でリクエストヘッダ部分が始まる訳なんですが、なぜかドリコムアカウントだけは
POST https://ログイン用のアドレス HTTP/1.1
のように、https と書かないと 503 Service Unavailable が返ってくるのです。同じドリコムのサーバでもドリコムアカウント以外のサーバでは http のままでも問題なく通信できたりして、どういうわけか、サーバの挙動が統一されていません。
で、このドリコムアカウントの挙動に対応するためにはどうするか悩んだ結果、以下のような方法で切り抜けることに決めました。つまり、「たいていの Webサーバは http でも https でもどっちでも通ってしまうという経験則」を利用して、決めうちでヘッダ部分に書くアドレスは https ではじめるように いじってみたわけです。
この状態で、ここ1年くらいは何の問題もなく、運用できていました。うまくいった、よかった、と、すっかり安心し切っていた今日この頃。
そんなある日。今度は BlogPet の Web サーバが、https だと 500 Internal Server Error を返すようになってしまったのです。BlogPet の方は https だと駄目、http にしないと怒られます。今までは https でも通っていたので、最近になって、何かサーバの設定をいじったんでしょう。噂のはまちちゃんが入社したのと関係しているんではないかと踏んでるんですが、それは考えすぎかな (^^;
本来の http/https の仕様からいくと、proxyサーバ経由で https するときは、(たとえ https 通信であっても)ヘッダ部分に書くアドレスは http:// で始まるのが正しいので、そういう意味ではドリコムアカウントの挙動がイリーガルなわけです。でも、IE や Firefox は対応しちゃってるし、拙作の Web クライアントも対応した方がいいでしょう。ということで、以下のような実装にしてみました。
- まず、http なヘッダで通信を試みる
- 5xx系のエラーが返って来たら、https なヘッダで改めて通信し直す
この実装で、ドリコムもBlogPetも、それ以外のサーバでも問題なく通信ができることを確認。といっても、世の中には、まだまだ私の知らない挙動をするサーバがありそうなので、そういうサーバに出会ったら、また修正することになるのでしょう。そして、バッドノウハウが増えていくという訳です。
こんなの、自分で Webクライアント(しかも TCP/IP のパケットを自前でごりごり作成するようなの)を書いてないとなかなか気が付きません。低レベルな部分を自分で作るのはおもしろいんですが、この手の罠も多いのであまりおすすめできません。既存の高レベルなライブラリやモジュールを使った方が楽です。
今回のバッドノウハウの実装の一部(httpリクエストヘッダ部分の生成処理)を公開します。次の版の「ソーシャルブックマーク管理ツール」や「ここうさぎ」には、この実装を取り込んだ xmlRPC.dll を採用する予定です。
実装例
今回解説したもの以外に、今までの経験を元に培ったバッドノウハウの数々を取り込んでいます。 このソースを見ても何が何だかわからないかもしれませんが、雰囲気だけでも味わってください。
char *
makeRequestHeader(
const char *method,
const char *webServer, const char *webPage,
const char *mimeType,/* ← 必要なのは POST, PUT のみ */
const char *sndBody, /* ← 必要なのは POST, PUT のみ */
const char *userName,/* ← 必要なのは BASIC認証使用時のみ */
const char *password,/* ← 必要なのは BASIC認証使用時のみ */
const char *wsse,
const char *cookie,
size_t sndLength,
int flag )
{
if ( (size_t)(xmlrpc_p->sndHTTPBufferSize) <= sndLength ) {
char *newBuffer = (char *)malloc( sndLength * 2 );
if ( newBuffer == NULL ) {
fputs( "makeRequestHeader: memory exhausted.\n", stderr );
return ( xmlrpc_p->sndHTTP );
}
free( xmlrpc_p->sndHTTP );
xmlrpc_p->sndHTTP = newBuffer;
xmlrpc_p->sndHTTP[0] = NUL;
xmlrpc_p->sndHTTPBufferSize = sndLength * 2;
}
if ( xmlrpc_p->useProxy ) {
if ( (webPage[0] == NUL) || !strcmp( webPage, "/" ) ) {
if ( xmlrpc_p->useSSL == FALSE )
sprintf( xmlrpc_p->sndHTTP, "%s http://%s/",
method, webServer );
else
sprintf( xmlrpc_p->sndHTTP, "%s %s",
method, webServer );
}
else {
if ( xmlrpc_p->useSSL && xmlrpc_p->recoverySSL ) {
/* 解説: 本来、https 通信時も GET/POST するのは http:// で */
/* いいはずであるが、なぜか、ドリコムアカウントのサー */
/* バは https:// を指定しないとエラーを返すので */
/* https:// を使うようにした。ちなみに、他のサーバは、*/
/* 一般的に http:// でも https:// でもどちらでも通る */
/* ようなので、https:// で行くことにする */
/* (2006年12月22日 追記) */
/* http:// でないと 500 Internal Server Error を返 */
/* すサーバが存在することが判明 */
sprintf( xmlrpc_p->sndHTTP, "%s https://%s%s",
method,
webServer, webPage );
}
else
sprintf( xmlrpc_p->sndHTTP, "%s http://%s%s",
method,
webServer, webPage );
}
}
else if ( (flag == FALSE) && (xmlrpc_p->useSSL == FALSE) )
sprintf( xmlrpc_p->sndHTTP, "%s http://%s%s",
method, webServer, webPage ); /* (a) */
else
sprintf( xmlrpc_p->sndHTTP, "%s %s", method, webPage ); /* (b) */
/* (1) proxy を通す場合は http:// を含めてフルURLで指定する必要がある */
/* (2) proxy を通さない場合は、通常、フルURLから http://サーバ名 を取 */
/* り除いた文字列を指定する(b) が、サーバによっては 404 Not Found */
/* になってしまう。そのようなサーバでは、(a) のように http:// を */
/* 含めたフルURL を指定することで、正常にアクセスできる。たいてい */
/* のサーバでは (a) でも (b) でも正常にアクセスできるが、(a) でな */
/* いと正しくアクセスできないサーバ、 逆に (b) でないと正常にアク */
/* セスできないサーバがあるので注意 */
if ( xmlrpc_p->recoverySSL )
if ( xmlrpc_p->useProxy )
if ( xmlrpc_p->useSSL == FALSE )
xmlrpc_p->recoverySSL = FALSE;
if ( !strcmp( method, "GET" ) ) {
// url に # が含まれる場合、GET しようとすると、404 Not Found を返
// してくるサーバがあることが判明。対策として、# 以降を切り捨てるよ
// うにしてみた(= Internet Explorer 6.0 の挙動に合わせた)
char *p;
p = strrchr( xmlrpc_p->sndHTTP, '#' );
if ( p )
*p = NUL;
}
if ( wsse ) {
strcat( xmlrpc_p->sndHTTP, " HTTP/1.1\r\n" ); /* HTTP 1.1 */
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"User-Agent: %s\r\n",
xmlrpc_p->userAgent[0]
? xmlrpc_p->userAgent
: "httpWsse/1.0 (written by H.Tsujimura)" );
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"Host: %s\r\n", webServer );
if ( cookie && *cookie )
strcat( xmlrpc_p->sndHTTP, cookie );
strcat( xmlrpc_p->sndHTTP, "X-WSSE: " );
strcat( xmlrpc_p->sndHTTP, wsse );
strcat( xmlrpc_p->sndHTTP, "\r\n" );
}
else {
#ifdef SIMPLE_HEADER
strcat( xmlrpc_p->sndHTTP, " HTTP/1.0\r\n\r\n" );
#else
strcat( xmlrpc_p->sndHTTP, " HTTP/1.1\r\n" );
strcat( xmlrpc_p->sndHTTP, "Accept: */*\r\n" );
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"User-Agent: %s\r\n",
xmlrpc_p->userAgent[0]
? xmlrpc_p->userAgent
: "httpRead/1.0 (written by H.Tsujimura)" );
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"Host: %s\r\n", webServer );
if ( cookie && *cookie )
strcat( xmlrpc_p->sndHTTP, cookie );
#endif
}
if ( userName && *userName && password && *password ) {
char auth[64];
char *p;
sprintf( auth, "%s:%s", userName, password );
p = base64( (unsigned char *)auth, strlen( auth ) );
if ( p )
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"Authorization: Basic %s\r\n", p );
}
strcat( xmlrpc_p->sndHTTP, "Connection: close\r\n" );
// strcat( xmlrpc_p->sndHTTP, "Connection: Keep-Alive\r\n" );
strcat( xmlrpc_p->sndHTTP, "Cache-Control: no-cache\r\n" );
if ( sndBody ) {
if ( !mimeType || !(*mimeType) )
strcat( xmlrpc_p->sndHTTP, "Content-Type: text/xml\r\n" );
else {
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"Content-Type: %s\r\n",
mimeType );
}
sprintf( &xmlrpc_p->sndHTTP[strlen(xmlrpc_p->sndHTTP)],
"Content-Length: %d\r\n\r\n",
sndLength );
strcat( xmlrpc_p->sndHTTP, sndBody );
}
else
strcat( xmlrpc_p->sndHTTP, "\r\n" );
if ( xmlrpc_p->verbose )
fprintf( stderr, "\t%s\n", xmlrpc_p->sndHTTP );
return ( xmlrpc_p->sndHTTP );
}
投稿者: tsupo 2006.12.23 午前 03:02
| 固定リンク
|
|
| ![]()
|
|
アマゾンわくわく探検隊
トラックバック
この記事のトラックバックURL:
この記事へのトラックバック一覧です: [バッドノウハウ] いろんなWebサーバとつきあうために:
コメント
コムの、方法っぽい経由しないです。
またきょうarimyはここにエラーするはずだったの。
投稿者: BlogPetのarimy (2006.12.23 午前 10:37)



