2015年10月31日土曜日

Go で JSON を Unmarshal する

JSON を構造体に変換する方法をメモ。
package main

import (
    "encoding/json"
    "fmt"
)

type Doc map[string]string

type DataStruct struct {
    Docs []Doc
}

type Response struct {
    Data DataStruct
}

func main() {
    byt := []byte(`
        {
          "data": {
            "docs": [
              {
                "key00": "val00",
                "key01": "val01"
              },
              {
                "key10": "val10",
                "key11": "val11"
              }
            ]
          }
        }
    `)

    res := &Response{}
    if err := json.Unmarshal(byt, res); err != nil {
        panic(err)
    }
    fmt.Println(res.Data.Docs[0]["key00"])
    fmt.Println(res.Data.Docs[1]["key10"])
}
$ go run main.go
val00
val10

package main

import (
    "encoding/json"
    "fmt"
)

type Track struct {
    Artist string `json:"artist"`
    Title  string `json:"title"`
}

type Picture struct {
    Id  int    `json:"id"`
    Url string `json:"url"`
}

type Attachment struct {
    Type    string `json:"type"`
    Track   `json:"track"`
    Picture `json:"picture"`
}

type Response struct {
    Id          int `json:"id"`
    FromId      int `json:"from_id"`
    Attachments []Attachment
}

func main() {
    byt := []byte(`
        {
          "id": 1,
          "from_id": 1111,
          "attachments": [
            {
              "type": "track",
              "track": {
                "artist": "Johann",
                "title": "Air on G String"
              }
            },
            {
              "type": "picture",
              "picture": {
                "id": 2222,
                "url": "http://example.com/public/picture.jpg"
              }
            }
          ]
        }
    `)
    response := &Response{}
    if err := json.Unmarshal(byt, response); err != nil {
        panic(err)
    }
    fmt.Println(response.Attachments[0].Track.Title)
    fmt.Println(response.Attachments[1].Picture.Url)
}
$ go run main.go
Air on G String
http://example.com/public/picture.jpg

2015年9月27日日曜日

Golang の receiver の種類について

Go にはクラスがありませんが、struct type に method を設定することで同じようなことができます。method とは receiver を持つ function のことです。

receiver には2つの種類があり、1つは value, そして pointer です。
package main

import (
    "fmt"
)

type document struct {
    title  string
    author string
}

func (d document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func (d *document) setTitle(title string) {
    d.title = title
}

func main() {
    d1 := &document{"A project report", "John"}
    d1.display()

    d2 := document{"A sales report", "Paul"}
    d2.setTitle("A business report")
    d2.display()
}
上記の場合、display() は value receiver, setTitle() は pointer receiver を持ちます。

ここで document の instance が value / pointer に関わらず、2つの method が使えているように見えるのは、Go が method の receiver に合うようにしてくれるからです。

例えば d1.display() は (*d1).display() のように呼び出してくれます。
また、d2.setTitle() は (&d2).setTitle() といった具合です。

しかし、interface を使用する場合には注意が必要です。
package main

import (
    "fmt"
)

type displayer interface {
    display()
}

type document struct {
    title  string
    author string
}

func (d *document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func displayDocument(d displayer) {
    d.display()
}

func main() {
    d := document{"A project report", "John"}
    displayDocument(d)
}
$ go build main.go
./main.go:29: cannot use d (type document) as type displayer in argument to displayDocument:
        document does not implement displayer (display method has pointer receiver)
この場合、コンパイルがエラーになってしまいます。

d := document{"A project report", "John"}
display() が pointer receiver なのに、d が value instance だからです。


Go のドキュメントには以下のようにあります。
The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T

どの type T の method set も receiver type T で宣言された method で構成されます。 pointer type *T に対応する method set は *T もしくは T で宣言された method set です。
分かりづらいですが、value の method set は value receiver のみという制限があるんですね。pointer の method set には value receiver, pointer receiver どちらも使用できます。

d := &document{"A project report", "John"}
このように修正すると、コンパイルが通るようになります。

なぜ、このような制限があるのかというと、アドレスが常に取得できるわけではないからです。
package main

import (
        "fmt"
)

type price int

func (p *price) appendYen() string {
        return fmt.Sprintf("\u00A5%d", *p)
}

func main() {
        fmt.Println(price(1000).appendYen())
}

$ go build main.go
./main.go:14: cannot call pointer method on price(1000)
./main.go:14: cannot take the address of price(1000)

2014年1月26日日曜日

Puppet の manifests で長い行を改行する

exec resource の command などは、行が長くなってしまうことがあります。
exec { 'build-mosh':
  command => 'tar xvzf mosh-X.X.X.tar.gz && cd mosh-X.X.X && ./configure --prefix=/usr/local/mosh-X.X.X && make && make install',
  ...
}

manifests を見やすくするために、できれば1行を 80 文字程度にしたいですよね。
その場合、下記のページにあるように '\' の後に改行が続くようにすると、(少し違和感はありますが)次の行に続けることができます。


あとは、コマンドを変数に入れておいて exec から呼び出すようにすると、さらにすっきりしますね。
$build_mosh = 'tar xvzf mosh-X.X.X.tar.gz && cd mosh-X.X.X && \
  ./configure --prefix=/usr/local/mosh-X.X.X && make && make install'

exec { 'build-mosh':   command => $build_mosh,   ... }

2013年8月25日日曜日

ESXi の ローカルストレージ上のVMパフォーマンス

ホスト内部のディスクは、ESXi のドキュメントではローカルストレージと呼ばれています。


このローカルストレージ上に仮想マシンを格納した場合に、仮想マシンのパフォーマンスが低く困っていました。

具体的には、1つの仮想マシンが多くのディスク書込を実行した場合、同じホスト上にある他の仮想マシンが一時停止したような状態になってしまいます。

調べてみると、以下の Knowledge Base を見つけました。


「パフォーマンスが低くなる場合に、ホストで write-back cache が設定されているか確認してください」という内容です。

私の環境でも write-back cache が設定されていませんでした。

write-back とは、RAID の write policy の1つで、RAID コントローラに搭載されているキャッシュを使用して書込性能を向上させる仕組みです。これを使用する場合、予期しない電源断などの異常時にデータ損失を防ぐため、RAIDコントローラにバッテリバックアップが搭載されていることを確認しておく必要があります。

以下のページでは、write-back/write-through の比較、ESXi で write-back が重要になる理由が説明されており、とても参考になりました。

To compensate for write-through scenarios, a server will often use it’s own RAM to assist in the caching process.

write-through の状況を補うために、多くの場合、サーバは自身の RAM をキャッシング・プロセスを補助するために使用するでしょう。

The ESX hypervisor does not steal memory to perform this caching, and thus has to wait on disk directly when write-through is used. Remember, the hypervisor is designed to be as lightweight and non invasive as possible, and caching large writes into memory would require that the hypervisor either 1) have larger amounts of memory assigned to it, or 2) steal from the resources normally reserved for VM workloads.

ESX ハイパーバイザは、このキャッシングを実行するためにメモリを steal しません。そのため write-through が使用されている時、直接ディスクを待たなければなりません。ハイパーバイザは、可能な限り軽量・非侵襲であるようにデザインされていることを忘れないで下さい。多数の書込をメモリにキャッシュすることは、ハイパーバイザが1)それにアサインする多くのメモリを持つか、2)本来 VM のワークロードのために予約されたリソースから steal することを必要とするでしょう。

2013年7月20日土曜日

Cyberduck から S3 へアップロード時のパーミッション

Cyberduck から S3 へファイルをアップロードした際に、自動的に Everyone へ閲覧許可が設定されてしまい、Bucket Policies でアクセス制限をコントロールしたい場合に、不便に感じていました。

例えば Bucket Policies で、特定のIPアドレスからのみアクセスを許可していても、ファイルに Everyone の閲覧許可がされていると、そちらが優先されてしまいます。

アップロード時のパーミッション設定が、Cyberduck の環境設定には見つからなかったのですが、ドキュメントに説明がありました。
$ defaults write ch.sudo.cyberduck s3.bucket.acl.default private

上記の設定をすることで、Everyone への閲覧許可が自動的に設定されなくなります。

2013年1月12日土曜日

EC2 のインスタンスで chef-client が UndefinedConversionError

EC2 のインスタンスに chef-client をインストールして使っていたのですが、user-data を設定したインスタンスだけ、chef-client がエラーになってしまいます。

エラーメッセージは以下のようなもの。
FATAL: Encoding::UndefinedConversionError: "\x8B" from ASCII-8BIT to UTF-8

確認してみたところ、json への変換部分でエラーになっている模様。
lib/chef/node.rb

    # Serialize this object as a hash
    def to_json(*a)
      result = {
        "name" => name,
        "chef_environment" => chef_environment,
        'json_class' => self.class.name,
        "automatic" => automatic_attrs,
        "normal" => normal_attrs,
        "chef_type" => "node",
        "default" => default_attrs,
        "override" => override_attrs,
        #Render correctly for run_list items so malformed json does not result
        "run_list" => run_list.run_list.map { |item| item.to_s }
      }
      result["_rev"] = couchdb_rev if couchdb_rev
      result.to_json(*a)
    end
result の中身で "\x8B" が入っているところを探すと、userdata の部分にありました。
{ ... "userdata"=>"\x1F\x8B ... }

どうやら、user-data を作成する際に圧縮をしていたため、userdata にバイナリのデータが入っていたことが原因のようです。

CloudInit のページを参考に、user-data を圧縮していたのですが、16KB のサイズ制限を超えているわけでもないので、圧縮しないことで回避することにしました。

user-data の内容は、Node の Attribute に含まれていて、chef-server で Attribute を表示するためなどの目的で、json へ変換しているのですね。

2012年12月1日土曜日

Amazon Linux で起動時にホスト名を設定する

Amazon Linux で起動時にホスト名を設定したいと思い、調べてみたところ CloudInit というものがあることが分かりました。

CloudInit とは、cloud-platform(EC2 や Openstack など)の Ubuntu でインスタンスの初期化を扱うためのものです。Amazon Linux では、CloudInit が使用可能になっているんですね。

サンプルを参考に、以下の内容をインスタンス起動時に渡してみましたが、うまくいきません。

cloudinit: /doc/examples/cloud-config.txt
$ cat cloud-config.txt
--------------------------------------
#cloud-config
hostname: myhostname
--------------------------------------
$ ec2-run-instances ami-4e6cd34f -g [GROUP] -k [KEYPAIR] -t [INSTANCE_TYPE] -f cloud-config.txt

Ubuntu の AMI を使用すると、うまくホスト名が設定できるので もう少し調べてみたところ、このフォーラムにたどり着きました。

Amazon AMI w/ #cloud-config - struggling with the basics
For your question as to why setting the hostname isn't working, it is because the version of cloud-init found in the Amazon Linux AMI does not currently support those options.

今の Amazon Linux では、hostname のオプションは使えないそう。フォーラムには、別の方法での設定例があって、以下の方法でも実現できるんですね。
#cloud-config
runcmd:
 - [ echo, 'Setting custom hostname' ]
 - [ sed, -i, 's/^HOSTNAME=[a-zA-Z0-9\.\-]*$/HOSTNAME=MyName/g', /etc/sysconfig/network ]
 - [ hostname, 'MyName' ]

2012年9月9日日曜日

Amazon S3 上の Web サイトへのアクセス制限

Amazon S3 上のバケットは、Web サイトとして設定することができます。

Hosting Websites on Amazon S3

また、Bucket Policies を使用することで、アクセスの制限をすることも可能です。例えば、特定のIPアドレスからのみアクセスを許可・拒否したい場合は、以下のように設定します。

Example Cases for Amazon S3 Bucket Policies
{
    "Version": "2008-10-17",
    "Id": "S3PolicyId1",
    "Statement": [
        {
            "Sid": "IPAllow",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::bucket/*",
            "Condition" : {
                "IpAddress" : {
                    "aws:SourceIp": "192.168.143.0/24"
                },
                "NotIpAddress" : {
                    "aws:SourceIp": "192.168.143.188/32"
                }
            }
        }
    ]
}
この例では、192.168.143.* からのアクセスを許可しますが、例外として 192.168.143.188 からの アクセスは拒否されます。

各要素の説明は、Element Descriptions に記載されています。

2012年9月1日土曜日

Auto Scaling から起動するインスタンスに Security Group を設定する

Auto Scaling を使ってインスタンスを起動する場合、Security Group を指定しないと default が設定されます。

Security Group を指定したい場合は、Launch Configuration を作成する時に group を設定します。
$ as-create-launch-config --help
…
   --group VALUE1,VALUE2,VALUE3...
       Security groups with which to associate the Amazon EC2 instances. Note
       that Amazon VPC security groups and Amazon EC2 security groups are
       mutually exclusive and can't be used together. Either all group names or
       all group ids are acceptable, but not both.

Auto Scaling Developer Guide には詳しく書いてなくて、色々調べた後に、コマンドのヘルプに書いてあることを知った。最初にココを見るべきだった。

command line tool のオプションは API と対応しているから、API Reference を参照して、まずオプションの有無を調べてみるのもいいかもしれない。

Auto Scaling API Reference - CreateLaunchConfiguration
SecurityGroups.member.N

The names of the security groups with which to associate Amazon EC2 or Amazon VPC instances. Specify Amazon EC2 security groups using security group names, such as websrv. Specify Amazon VPC security groups using security group IDs, such as sg-12345678. 

2012年8月26日日曜日

Amazon ELB からのみアクセスを許可する Security Group の設定

ELB の配下に設置しているインスタンスは、セキュリティを考慮すると ELB からのみアクセスを許可するようにしたいですね(管理用のアクセスは別として)。

ELB に用意されている特別な Security Group を使って、これを実現できます。

まず最初に ELB の API Tools を使用して、Source Security Group の名前を確認します。
$ elb-describe-lbs [LoadBalancerName] --show-long --headers
... ,"{owner-alias=example-elb,group-name=example-elb-sg}", ...
次に ELB 配下のインスタンスが所属する Security Group に、確認した名前を設定します。
$ ec2-authorize [group_name] -u example-elb -o example-elb-sg
もし、より制限の低いルールが設定されていた場合は、そのルールを削除します。
(以下の例は、tcp 80番へのアクセスを全て許可するルールが設定されていた場合)
$ ec2-revoke [group_name] -P tcp -p 80 -s 0.0.0.0/0

2012年8月5日日曜日

boto を使って Amazon S3 へのアップロード

boto を使って S3 へアップロードする時、東京リージョンなど、US Standard 以外のリージョンのバケットへのアップロードが失敗するという事象に遭遇しました。エラー内容は Broken pipe 。
>>> from boto.s3.connection import S3Connection
>>> conn = S3Connection()
>>> bucket = conn.get_bucket('mybucket')
>>> k = Key(bucket)
>>> k.set_contents_from_filename('myfile')
---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
...
    857             raise BotoServerError(response.status, response.reason, body)
    858         elif e:
--> 859             raise e
    860         else:
    861             msg = 'Please report this exception as a Boto Issue!'

error: [Errno 32] Broken pipe
しかも、50KBぐらいのファイルだと問題ないのに、500KBくらいになるとダメ。東京リージョンのサーバから実行してるから、US の方が距離的に遠いし、時間がかかってタイムアウトとは考えにくい。しかも、500KBってそんなに大きいサイズではないし。 

同じ事象で困っている人がいて、以下のページが参考になりました。

 broken pipe error, non US-Standard region

解決方法としては、Connection を作成する時に、host を指定すること。
>>> from boto.s3.connection import S3Connection
>>> conn = S3Connection(host='s3-ap-northeast-1.amazonaws.com')

指定するエンドポイントは、ここから参照できます。

Amazon Web Services Glossary - Regions and Endpoints

2012年6月23日土曜日

Amazon EC2 の AMI の種類

Amazon EC2 の AMI には、EBS-backed と instance store-backed の2種類あります。それぞれの違いについてあらためて整理してみる。

・root device のサイズ制限
EBS-backed は 1TBまで。instance store-backed は 10GBまで。 Windows などは サイズが大きいので、多くは EBS-backed になる。

・停止時の動作の違い
EBS-backed だと停止時の動作に stop を選択できる。stopped のインスタンスは EBS上に保持され、restart することが可能。EBS は永続性のあるストレージのため、restart 後もデータは保持される。

instance store-backed は、terminate のみ。terminate したインスタンスはデータが失われる。また、意図的でない停止(インスタンスの障害など)でも同様にデータが失われる。

すなわち EBS は、EC2のインスタンスとは独立してデータを保持するが、instance store は EC2のインスタンス起動時のみデータが保持される。

・起動にかかる時間
EBS-backed AMI は instance store-backed AMI よりも起動時間が短い。目安としては、EBS-backed で1分以内、instance store-backed で 5分以内。

・AMI の作成方法
instance store-backed の Linux/Unix OS でAMIを作成する場合、インスタンス自身の上でイメージを作成する必要があり、API が用意されていない。EBS-backed であれば ec2-create-image コマンドで作成が可能。

・課金方法
instance store-backed は、AMI ストレージである S3 の料金 と インスタンス使用量 に対して料金がかかる。EBS-backed は AMI ストレージである EBS snapshot の料金(= S3 の料金) と インスタンス使用量に加えて、EBS の 割当量と入出力量 に対して料金がかかる。

また、instance store-backed は、AMI をカスタマイズして新しく作成した際、全体が S3 に保存されるのに対し、EBS-backed では変更部分のみ保存される。そのため、次に保存する AMI のサイズは小さくなり AMI ストレージ の料金は instance store-backed に比べて低くなる。


こうやって見ていると EBS-backed の方が管理しやすいように思えますね。

2012年5月19日土曜日

iptables による ポートのリダイレクト

あるポートから違うポートへリダイレクトさせたい時があります。

例えば、下記ページの例のように「8080 ポートで待ち受けをしている Tomcat に対して、http://anyhost.com:8080 ではなく http://anyhost.com でアクセスさせたい」などです。

Firewalls-local-port-redirection

色々な方法がありますが、その1つとして iptables の REDIRECT ターゲット を使用する方法があります。この場合、リダイレクトする対象として、以下のパターンが考えられます。

1. 外から入ってきたパケットをリダイレクトする
2. ローカルで生成したパケットをリダイレクトする

サーバーに設定するのであれば、1 のパターンがメインになりますね。2 のパターンはテストをする時などでしょうか。

1 のパターンの場合の設定は以下になります。
(-dst の値は環境にあわせて変更)
# iptables -t nat -I PREROUTING --src 0/0 --dst 127.0.0.1 \
-p tcp --dport 80 -j REDIRECT --to-ports 8080
設定の意味が理解しやすいルールですね。ただ、2のパターンのルールを見たとき、ピンときませんでした。
# iptables -t nat -I OUTPUT --src 0/0 --dst 127.0.0.1 \
-p tcp --dport 80 -j REDIRECT --to-ports 8080

理由は、OUTPUT チェインが外に出て行く時に適用されると勘違いしていたから。あらためて man の TABLES の部分を見てみると「ローカルで生成したパケットをルーティング前に変更する」とハッキリ書いてますね!
nat: This table is consulted when a packet that creates a 
new connection is encountered. 〜 OUTPUT (for altering 
locally-generated packets before routing)
下記のページの図も参考になります。
NAT(Network Address Translation) の概要

設定を削除する場合は以下。(テーブルを指定しない場合のデフォルトは "filter" なので、"nat" テーブルを削除する場合には、明示的に指定する必要がある)
# iptables -t nat -F
また、個別にルールを削除したい場合は、以下のようにします。
# iptables -t nat -n -L --line-numbers
-------------------------------------------------
Chain PREROUTING (policy ACCEPT)
num  target     prot opt source               destination      
1    REDIRECT   tcp  --  0.0.0.0/0            127.0.0.1 ...

Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination      


Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination      
1    REDIRECT   tcp  --  0.0.0.0/0            127.0.0.1 ...
-------------------------------------------------

削除したいルールのチェインと行番号を指定する
# iptables -t nat -D PREROUTING 1

2012年5月4日金曜日

Amazon Linux の yum リポジトリ

Amazon Linux AMI のインスタンスで Python をソースからコンパイルしたかったのですが、いつもコンパイル時にインストールしていた tk-devel パッケージが見つからない。

Amazon Linux では、amzn-main という独自のリポジトリが設定されている模様。
$ ls /etc/yum.repos.d/
---------------------------------
amzn-nosrc.repo    amzn-updates.repo  epel.repo
amzn-main.repo     amzn-preview.repo  epel-testing.repo
---------------------------------

Amazon Linux に含まれている EPEL リポジトリのバージョンが 6 に設定されていたので、AWSのフォーラムを参考に、CentOS 6 のリポジトリを追加してインストールを試みた。
$ cd /etc/yum.repos.d
$ sudo vim CentOS-Base.repo
--------------------------------------------
[base]
name=CentOS-6 - Base
mirrorlist=http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os
enabled=0
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6
--------------------------------------------
$ sudo yum install --enablerepo=base tk-devel

$ rpm -qa | grep tk-devel
tk-devel-8.5.7-5.el6.x86_64

うまくインストールされ、Python のコンパイルも成功。

2011年12月4日日曜日

システムデフォルト以外の Python での pyflakes

特定バージョンの Python を使用するために、Prefix を指定して手動でコンパイルした環境で vim から pyflakes を使用しようとしたのですが、以下のようなエラーが出てしまいます。
AssertionError: Vim must be compiled with Python 2.5 or higher; you have 2.4.3
手動コンパイルした Python は 2.7 で、virtualenv の環境から実行しているため、そちらを見に行くのかと思いましたが、どうもシステムデフォルト(/usr/bin/python)を見ている様子。
$ which python
~/.virtualenvs/foobar/bin/python
$ python -V
Python 2.7.2
ここを見ると、環境ごとに vim のビルドが必要(!)と書かれている。とりあえず、試してみる。
$ LD_LIBRARY_PATH=$HOME/.virtualenvs/foobar/lib
    PATH=$HOME/.virtualenvs/foobar/bin:$PATH \
    ./configure --enable-pythoninterp \
    --with-python-config-dir=$HOME/.virtualenvs/foobar/lib/python2.7/config \
    --prefix=$HOME/vim27

$ make install
ビルドした vim から pyflakes を使用してみるとエラーは出なくなっている。バージョンを確認しても手動でコンパイルした Python を見ているようだ。
:python import sys; print(sys.version)
2.7.2
vim から呼び出す Python は、コンパイル時に指定されているんですね。目的は果たせたけど、、他に方法はないものか。

2011年11月12日土曜日

hg rollback の dry-run オプション

最近、Mercurialを使い始めました。一度コミットをしたものはプロジェクトの歴史として残す、というのが基本ポリシーみたいなのですが、MqExtensionなどを使用すると色々と修正ができるようです。少しずつ馴染んできたのですが、作業を重ねた後にrollbackした際、どの時点に戻るのか分からなくなる時があります。ふとマニュアルを見ていると、こんなオプションが!
hg rollback

Options:
-n, --dry-run do not perform actions, just print output

これで、安心してrollbackを実行できそうです。

2011年10月23日日曜日

エディタ

普段は、Komodo Edit というエディタを使っていて、Eclipse ほど重厚でなく、ほどよいバランスが気に入っています。 

Emacs は難しそうというイメージがあって敬遠してたけれど、emacs-for-pythonというPython用のエクステンションを集めて、簡単に使えるようにしてくれるものがあると知り、試してみました。 

導入はとても簡単で、ダウンロードしたファイルを適当なパスに保存して、必要なパッケージを入れ、設定ファイルに一行書くだけ。例えばUbuntu だと、こんな感じ。
$ sudo aptitude install pyflakes pymacs
$ vi ~/.emacs
---------------------
(load-file "~/.emacs.d/emacs-for-python-0.2.1/epy-init.el")
---------------------

IDEのように補完ができるようになります。

少しだけ悩んだのが、最初に一度だけ .ropeproject を作成しないとAuto Completeのウインドウが出てこないところ。 

補完のために Meta + / すると、保存する場所を聞かれるので指定してEnter。

確認が入るので、y を指定すれば、完了。

簡単に導入できて、便利に使えそうです。 

2011年9月17日土曜日

Apache 2.2.21 リリース

Apacheの脆弱性(CVE-2011-3192)に対応した 2.2.20 からまもなく 2.2.21 がリリースされています。 2.2.20 で行った修正に対する改善や、新たにMaxRangesディレクティブの追加などが含まれている模様。 

そもそも問題となっている Range ヘッダは、ブラウザでPDFの閲覧をしている時などにも使用されています。
GET /net/apache//httpd/docs/httpd-docs-2.2.14.en.pdf HTTP/1.1
Accept: */*
Range: bytes=3653554-3854257, 1077245-3603942, 3608039-3653553
...

このヘッダを使用して、なぜプロセスの肥大化が引き起こされるのかについては下記のページで丁寧に説明されていて、Range で細かい区間を要求されることにより、管理データを追加していくループ処理の増加が原因とのこと。

  CVE-2011-3192 Range header DoS vulnerability Apache 1.3/2.x の更に続き

追加された MaxRanges ディレクティブ では、制限値(デフォルト: 200)を超えた場合、要求されたコンテンツをそのまま返すようになっています。区間要求が無視されることにより、プロセスの肥大化を防ぐ仕組みですね。
# vi httpd.conf
----------------------
# MaxRanges: Maximum number of Ranges in a request before
# returning the entire resource, or 0 for unlimited
# Default setting is to accept 200 Ranges
MaxRanges 5
----------------------

// 制限内の数を指定した場合
GET / HTTP/1.1
Host: localhost
Range: bytes=0-1,1-2,2-3,3-4,4-5 <= 一度に複数の範囲を指定

HTTP/1.1 206 Partial Content <= 206 レスポンスが返る
...
Accept-Ranges: bytes
Content-Length: 412
Content-Type: multipart/byteranges; boundary=4ad1ca44eeaa22

// 制限を超過した場合
GET / HTTP/1.1
Host: localhost
Range: bytes=0-1,1-2,2-3,3-4,4-5,5-6

HTTP/1.1 200 OK <= 要求されたコンテンツをそのまま返している
...
Accept-Ranges: bytes
Content-Length: 44
Content-Type: text/html

2011年9月1日木曜日

PyPy を試してみる

ときどき Project Euler の問題をしているのですが、そのまま書いてしまったため、結果の出力にすごく長い時間のかかるコードをふと PyPy で実行してみると、あまりの速度にびっくり。
#!/usr/bin/env python

result = 0
i = 20
while True:
    for num in range(1, 21):
        if not i % num == 0:
            break
    else:
        result = i
    if result:
        break
    i += 1
print result

$ time pypy problem5.py 
real    0m35.395s
user    0m34.910s
sys     0m0.090s

$ time python problem5.py 
real    5m19.202s
user    5m15.520s
sys     0m0.070s
テストではないけれど、PyPy のすごさを垣間見た気分。

2011年8月19日金曜日

ビット演算って

どういった時に使用するのか、よく分からなかったビット演算。MACアドレスが正しいフォーマットか調べる必要があって、ここのコードを参考にしていた時に、こういう使い方があるのかと思ったのでメモ。
def _mac2int(addr, sep=_MACsep):
     # convert a MAC str to an int
    h = addr.split(sep)
    if len(h) > 4:
        i = 0
        for b in h:
            b = int(b, 16)
            if 0 <= b < (1<<8):
                i = (i << 8) | b
            else:
                break
        else:
            if 0 < i < (1<<48):
                return i
    raise ValueError('invalid MAC address: %r' % (addr,))
MACアドレスの文字列を int に変換している関数で、失敗すると ValueError になるようになっています。 その中でビット演算が使用されているのですが、分かりやすくするために'FF:FF'を変換するとした場合、まず左側の'FF'を int に変換します。
>>> int('FF', 16)
255
>>> bin(255)
'0b11111111'  <= 2進数にするとこうなる
次に、最初に変換した部分を 8bit 左にずらして、同様に次の'FF'を int に変換し、ビットOR をとる。
>>> 255 << 8
65280
>>> bin(65280)
'0b1111111100000000'
>>> bin(255)
'0b11111111'
>>> 65280 | 255
65535
>>> bin(65535)
'0b1111111111111111'
>>> bin(0xFFFF)
'0b1111111111111111'
結果、0xFFFF を int に変換したのと同じになります。MACアドレスは 6オクテットなので、これを6回繰り返すとMACアドレスを int に変換できるんですね。勉強になりました。