2009/10/02


tthttpd(tinytinyhttpd)にiratqqさんがIPv6対応をコードを入れてくれました。通常起動でIPv4、IPv6どちらも扱える様になっていて、引数で # tthttpd -4
とすればIPv4だけで # tthttpd -6
とすればIPv6だけで起動する様になります。iratqq++
また、静的なファイルをサーブする場合にはこれまでreadしながらsendしていたのですが、sendfile(2)を使う様にしました。Windowsの場合は昨日Plackの高速化対応した時の様にTransmitFileを使うようにしました。
小さいサイズのファイルだと差は出ませんが、数メガくらいのファイルから格段に差が出て速くなりました。
mattn's tinytinyhttpd at master - GitHub

tiny tiny httpd

http://github.com/mattn/tinytinyhttpd

LINUXサ-バHACKS: プロが使うテクニック&ツ-ル100選 LINUXサ-バHACKS: プロが使うテクニック&ツ-ル100選
ロブ フリッケンガー, Flickenger,Rob, 晴広, 山口, イメージズアンドワーズ
オライリージャパン 単行本 / ¥88 (2003年11月01日)
 
発送可能時間:

Posted at by




最近熱いPSGI/Packですが、サーバのStandaloneではかなり高速なパフォーマンスが出ているらしく、試してみようと思った所、Windowsでは動かない箇所があったのでいろいろやった作業履歴。
まず、Standaloneサーバが速いと言われる理由としてsendfile(2)を使っているのですが、Windowsにはsendfile(2)がありません。しかしソケット前提であるならばTransmitFileというAPIがあります。
sendfile(2)と同じようにZeroCopyでファイルから指定ディスクリプタに流し込むAPIです。
Plackが使っているSys::Sendfileに対してこのAPIを使う様にWindowsポーティングしました。
Sys::Sendfileのauthorにもpull requestを送りました。次にPlackにてWindowsで使えないAPIを叩いている部分に対するpatchを書きました。
これで一応、高速化対応は完了。
Plackに付属の画像を返すpsgiアプリケーションを起動してベンチマークを取ってみました。
# plackup -s Standalone -a eg/dot-psgi/image.psgi
ベンチマーク結果は以下の通り。まず高速化対応前。
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        Plack-Server-Standalone/0.01
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        2397678 bytes

Concurrency Level:      1
Time taken for tests:   1.219 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      23978730 bytes
HTML transferred:       23976780 bytes
Requests per second:    8.21 [#/sec] (mean)
Time per request:       121.876 [ms] (mean)
Time per request:       121.876 [ms] (mean, across all concurrent requests)
Transfer rate:          19213.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   4.9      0      16
Processing:   109  119  10.9    125     141
Waiting:        0    5   7.5      0      16
Total:        109  120  12.9    125     141

Percentage of the requests served within a certain time (ms)
  50%    125
  66%    125
  75%    125
  80%    141
  90%    141
  95%    141
  98%    141
  99%    141
 100%    141 (longest request)
そして対応後 This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        Plack-Server-Standalone/0.01
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        2397701 bytes

Concurrency Level:      1
Time taken for tests:   0.578 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      23978960 bytes
HTML transferred:       23977010 bytes
Requests per second:    17.30 [#/sec] (mean)
Time per request:       57.813 [ms] (mean)
Time per request:       57.813 [ms] (mean, across all concurrent requests)
Transfer rate:          40504.51 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   6.6      0      16
Processing:    31   55  22.4     47      94
Waiting:        0    3   6.6      0      16
Total:         31   58  24.5     47     109

Percentage of the requests served within a certain time (ms)
  50%     47
  66%     47
  75%     63
  80%     94
  90%    109
  95%    109
  98%    109
  99%    109
 100%    109 (longest request)
だいたいRequests perl secondで2倍くらい速くなってます。すばらしい。

miyagawa++ kazuho++ Plack++
Posted at by



2009/09/30


ASINを指定すると、Amazonのアフィをヨロシク表示してくれるblosxomのプラグインでawsxomというのがあるのですが、以前それをItemSearchにも対応させ、以降何回か使ってました。ただ最近は記事を書く際に文章で頭が一杯になってしまい、毎回アフィを貼るのを忘れてしまうという難病にかかってしまったせいでawsxomをamazonの仕様変更に追従させるのを忘れてました。
で、案の定先ほどの記事をポストした際にASIN書いたら見事に記事が壊れて泣くハメに...

ええいと重い腰を上げて修正してみました。
修正方法はhail2uさんが書いた物をベースに修正しました。
あまりawxsomの原形を留めていないので修正後のファイルで...
#!/usr/bin/perl
# ---------------------------------------------------------------------
# awsxom: AWSからデータを取得して書影その他を作成(ECS v4対応版)
# Author: Fukazawa Tsuyoshi <tsuyoshi.fukazawa@gmail.com>
# Version: 2006-11-24
# http://fukaz55.main.jp/
# Modified: Yasuhiro Matsumoto <mattn.jp@gmail.com>
# ---------------------------------------------------------------------
package awsxom;

use strict;
use LWP::UserAgent;
use CGI qw/:standard/;
use FileHandle;
use URI::Escape;
use Digest::SHA::PurePerl qw(hmac_sha256_base64);

# --- Plug-in package variables --------
my $asoid = "XXXXXX-22";            # AmazonアソシエイトID
my $devkey = "XXXXXXXXXXXXXXXXXXXX";        # デベロッパートークン
my $secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";    # シークレットキー
my $cachedir = "$blosxom::plugin_state_dir/aws_cache";  # XMLのキャッシュ用ディレクトリ
my $EXPIRE = 24 * 7;                # データを再読込する間隔(単位:時間)
my $default_template = "awsxom";        # デフォルトのテンプレートファイル名

my $VERSION = '1.4';
my $ua_name = "awsxom $VERSION";
my $endpoint = "ecs.amazonaws.jp";
my $unsafe   = "^A-Za-z0-9\-_.~";
my $debug_mode = 0;

# ---------------------------------------------------------------------
sub start {
    # キャッシュ用ディレクトリの作成
    if (!-e $cachedir) {
        my $mkdir_r = mkdir($cachedir, 0755);
        warn $mkdir_r
        ? "blosxom : aws plugin > \$cachedir, $cachedir, created.\n"
        : "blosxom : aws plugin > mkdir missed:$!";
        $mkdir_r or return 0;
    }

    1;
}

# ---------------------------------------------------------------------
sub story {
    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
    $$body_ref = conv($$body_ref);
    1;
}

# ---------------------------------------------------------------------
sub foot {
    my($pkg, $currentdir, $foot_ref) = @_;
    $$foot_ref = conv($$foot_ref);
    1;
}

# ---------------------------------------------------------------------
sub conv {
    $_ = shift;

    # ASIN/ISBNが書かれていたら置き換える
    # テンプレート指定版
    s/(?:ASIN|ISBN):([A-Z0-9]{10}):(.*?):/to_html_asin($1,$2)/ge;

    # テンプレート無指定版
    s/(?:ASIN|ISBN):([A-Z0-9]{10})/to_html_asin($1,$default_template)/ge;

    # テンプレート無指定版
    s/(?:AWSWORD):([a-zA-Z0-9_]*?):/to_html_word($1,$default_template)/ge;

    return $_;
}

# ---------------------------------------------------------------------
# ASINからAmazonのアフィリエイト用HTMLを作成
sub to_html_asin {
    my ($asin, $template) = @_;    # ASINとテンプレ名称
    my $cache = "$cachedir/$asin.xml";
    my $outfile = "$cachedir/$asin.html";

    my $q = CGI->new;
    $q->param("AWSAccessKeyId", $devkey);
    $q->param("AssociateTag", $asoid);
    $q->param("Timestamp", sprintf("%04d-%02d-%02dT%02d:%02d:%02d.000Z", sub { ($_[5]+1900, $_[4]+1, $_[3],  $_[2], $_[1], $_[0] ) }->(gmtime(time))));
    $q->param("Service", "AWSECommerceService");
    $q->param("Operation", "ItemLookup");
    $q->param("ItemId", $asin);
    $q->param("ResponseGroup", "Medium,Offers");
    $q->param("Version", "2009-01-06");
    my @p = $q->param();
    foreach (@p) {
        $_ = escape($_) . "=" . escape($q->param($_));
    }
    my $qs = join("&", sort(@p));
    my $signature = hmac_sha256_base64("GET\n$endpoint\n/onca/xml\n$qs", $secret) . "=";
    my $url = "http://$endpoint/onca/xml?$qs&Signature=" . escape($signature);

    # 取り込み直す必要はあるか?
    if (!(-e $cache) || (-M $cache > ($EXPIRE / 24))) {
    # AWSから情報を取得してキャッシュファイルに保存
        # UserAgent初期化
        my $ua = new LWP::UserAgent;
        $ua->agent($ua_name);
        $ua->timeout(60);
        my $rtn = $ua->mirror($url, $cache);
    }

    # キャッシュからXMLを読み込んで解析
    my $content = getFile($cache);
    my %detail = parseXML($content, $asin);

    # テンプレートを展開。エラーの場合はエラー文字列を返す
    my $form;
    if (!defined($detail{"ErrorMsg"})) {
        #$form = &$blosxom::template($blosxom::path, $template, 'html');
        my $fh = new FileHandle;
        if ($fh->open("< $blosxom::datadir/$template.html")) {
            $form = join '', <$fh>;
            $form =~ s/\$(\w+)/$detail{$1}/ge;
            $fh->close();
        }
    }
    else {
        $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
    }

    return $form;
}

# ---------------------------------------------------------------------
# ASINからAmazonのアフィリエイト用HTMLを作成
sub to_html_word {
    my ($word, $template) = @_;    # ASINとテンプレ名称
    my $cache = "$cachedir/$word.xml";
    my $outfile = "$cachedir/$word.html";

    my $q = CGI->new;
    $q->param("AWSAccessKeyId", $devkey);
    $q->param("AssociateTag", $asoid);
    $q->param("Timestamp", sprintf("%04d-%02d-%02dT%02d:%02d:%02d.000Z", sub { ($_[5]+1900, $_[4]+1, $_[3],  $_[2], $_[1], $_[0] ) }->(gmtime(time))));
    $q->param("Service", "AWSECommerceService");
    $q->param("Operation", "ItemSearch");
    $q->param("Keywords", $word);
    $q->param("SearchIndex", "Books");
    $q->param("ResponseGroup", "Medium,Offers");
    $q->param("Version", "2009-01-06");
    my @p = $q->param();
    foreach (@p) {
        $_ = escape($_) . "=" . escape($q->param($_));
    }
    my $qs = join("&", sort(@p));
    my $signature = hmac_sha256_base64("GET\n$endpoint\n/onca/xml\n$qs", $secret) . "=";
    my $url = "http://$endpoint/onca/xml?$qs&Signature=" . escape($signature);

    # 取り込み直す必要はあるか?
    if (!(-e $cache) || (-M $cache > ($EXPIRE / 24))) {
    # AWSから情報を取得してキャッシュファイルに保存
        # UserAgent初期化
        my $ua = new LWP::UserAgent;
        $ua->agent($ua_name);
        $ua->timeout(60);
        my $rtn = $ua->mirror($url, $cache);
    }

    # キャッシュからXMLを読み込んで解析
    my $content = getFile($cache);
    $content =~ s!.*?(<Item>.*?</Item>).*!$1!is;
    my $asin = "";
    $asin = $1 if ($content =~ /<ASIN>([^<]*)<\/ASIN>/);
    return "" if !$asin;
    my %detail = parseXML($content, $asin);

    # テンプレートを展開。エラーの場合はエラー文字列を返す
    my $form;
    if (!defined($detail{"ErrorMsg"})) {
        #$form = &$blosxom::template($blosxom::path, $template, 'html');
        my $fh = new FileHandle;
        if ($fh->open("< $blosxom::datadir/$template.html")) {
            $form = join '', <$fh>;
            $form =~ s/\$(\w+)/$detail{$1}/ge;
            $fh->close();
        }
    }
    else {
        $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
    }

    return $form;
}

# ---------------------------------------------------------------------
# ファイルを読み込む
sub getFile {
    my $cache = shift;
    my $fh = new FileHandle;

    $fh->open($cache);
    my @data = <$fh>;
    $fh->close();
    my $content = join('', @data);
    return undef if (!$content);

    return $content;
}

# ---------------------------------------------------------------------
sub parseXML {
    my ($buf, $asin) = @_;
    my %detail;

    # Amazonへのリンク
    $detail{"Link"} = "http://www.amazon.co.jp/exec/obidos/ASIN/$asin/ref=nosim/$asoid";

    # 個々の要素の抽出
    $detail{"Asin"} = $1 if ($buf =~ /<ASIN>([^<]*)<\/ASIN>/);
    $detail{"ProductName"} = $1 if ($buf =~ /<Title>([^<]*)<\/Title>/);
    $detail{"Catalog"} = $1 if ($buf =~ /<Binding>([^<]*)<\/Binding>/);
    $detail{"ReleaseDate"} = $1 if ($buf =~ /<PublicationDate>([^<]*)<\/PublicationDate>/);
    $detail{"ReleaseDate"} = $1 if ($buf =~ /<ReleaseDate>([^<]*)<\/ReleaseDate>/);
    $detail{"Manufacturer"} = $1 if ($buf =~ /<Manufacturer>([^<]*)<\/Manufacturer>/);
    $detail{"ImageUrlSmall"} = $1 if ($buf =~ /<SmallImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"ImageUrlMedium"} = $1 if ($buf =~ /<MediumImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"ImageUrlLarge"} = $1 if ($buf =~ /<LargeImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"Availability"} = $1 if ($buf =~ /<Availability>([^<]*)<\/Availability>/);
    $detail{"ListPrice"} = $1 if ($buf =~ /<LowestNewPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"OurPrice"} = $1 if ($buf =~ /<ListPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"UsedPrice"} = $1 if ($buf =~ /<LowestUsedPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"Author"} = $1 if ($buf =~ /<Author>([^<]*)<\/Author>/);
    # エラー?
    if ($buf =~ /<Errors>.*?<Message>([^<]*)<\/Message>/) {
        $detail{"ErrorMsg"} = $1;
    }

    return %detail;
}

# ---------------------------------------------------------------------
# デバッグ用
sub print_debug {
    return if (!$debug_mode);

    my $fd = new FileHandle;
    $fd->open("/path/to/log/output/directory/logfile.log", "a");
    print $fd "$_[0]";
    $fd->close();
}

sub escape {
  my $s = shift;

  $s =~ s/([^\0-\x7F])/do {
    my $o = ord($1);
    sprintf("%c%c", 0xc0 | ($o >> 6), 0x80 | ($o & 0x3f));
  }/ge;

  return uri_escape($s, $unsafe);
}

1;
ちなみに、オリジナルに追加した機能は「AWSWORD:perl」と書くとItemSearch結果の1番目を表示する...というモノグサ機能です。

Amazon Hacks 世界最大のショッピングサイト完全活用テクニック100選 Amazon Hacks 世界最大のショッピングサイト完全活用テクニック100選
ポール・ボシュ, 篠原 稔和, ウェブ・ユーザビリティ研究会
オライリー・ジャパン 単行本 / ¥53 (2004年04月24日)
 
発送可能時間:


モダンPerl入門: 業務で使う実用的なPerlの“すべて” モダンPerl入門: 業務で使う実用的なPerlの“すべて”
牧 大輔
翔泳社 単行本 / ¥932 (2009年02月01日)
 
発送可能時間:

Posted at by