[バッドノウハウ] いろんなWebサーバとつきあうために

観測気球

収集物の記録書庫 a data archive of collection -- collectible toys

[要旨] 「ソーシャルブックマーク管理ツール」を作っているうちに身についたバッドノウハウの一端を紹介してみます。
[キーワード] バッドノウハウ,Webサーバ,https,proxyサーバ

« ここあっぷる(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 | 固定リンク | このエントリーをはてなブックマークに追加 | このエントリを del.icio.us に登録 このエントリの del.icio.us での登録状況 | このエントリを Buzzurl に追加このエントリの Buzzurl での登録状況 | このエントリをlivedoorクリップに登録 このエントリのlivedoorクリップでの登録状況 このエントリをlivedoorクリップに登録している人の数 | 酢鶏巡回中

楽天市場


プログラミング」カテゴリ内の最近の記事

品揃え豊富で安い!NTT-X Store


アマゾンわくわく探検隊

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/6737/13166203

この記事へのトラックバック一覧です: [バッドノウハウ] いろんなWebサーバとつきあうために:

コメント


コムの、方法っぽい経由しないです。
またきょうarimyはここにエラーするはずだったの。

投稿者: BlogPetのarimy (2006.12.23 午前 10:37)

コメントを書く




※イタズラ防止のため、メールアドレスを入力しないと投稿できません。

次からのコメント入力の手間を省くために、名前やメールアドレスをcookieに記憶しますか?


URL を入力すると、その URL にリンクがはられます。
なお、メールアドレスは公開されません。ご安心ください。


ワード

ニッセン

fujisan.co.jp

楽天市場