@kyanny's blog

My thoughts, my life. Views/opinions are my own.

curl(1) で POST する際の --data と --form の違いについて

調べてみた。動作確認用のサーバは plackup で立てている。 app.psgi の中身は一番最後に。

--data (-d, --data-ascii)

application/x-www-form-urlencoded 形式で POST する。 @/path/to/file のように value の先頭が @ ではじまっているとファイルを読み込んで改行文字を取り除く。パラメータや @ つきで指定したファイルの中身はすべて URL エンコードされていることが期待される。つまり curl(1) は URL エンコードしてくれない。 -d を複数回指定するとすべてのパラメータが & で連結される。 @ でファイルを指定する場合、 -d 'file=@sale.txt' のようにすると中身が展開されないので注意 (file=@sale.txt という文字列が渡される)

$ curl -d 'foo=bar' -d 'discount=50% off' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => 'foo=bar&discount=50% off', # <-- URL エンコードされない
          'Content-Type' => 'application/x-www-form-urlencoded'
        };
$ cat > sale.txt
all books
50% off
^D
$ curl -d '@sale.txt' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => 'all books50% off', # <-- 改行文字が取り除かれる
          'Content-Type' => 'application/x-www-form-urlencoded'
        };

--data-binary

--data とほぼ同じだが、改行文字を取り除かない。バイナリファイルのデータをそのまま (as is) 送りたい場合に使うと良いらしい。でもそういう用途なら --form を使っておけば良い気がする。用途が違うってことなのかな。

追記@2011/08/01 http://b.hatena.ne.jp/mattn/20110801#bookmark-40093294 form keyの無いXMLRPCみたいなので使うのではというコメントをもらった、もっともですね

$ curl --data-binary '@sale.txt' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => 'all books # <-- 改行文字が含まれたまま
50% off
',
          'Content-Type' => 'application/x-www-form-urlencoded'
        };

--data-urlencode

    • data とほぼ同じだが、 URL エンコードしてくれる。

--form (-F)

multipart/form-data 形式で POST する。 key=value 形式で指定する。 @ を value の先頭に置くとファイルのデータを「添付して」送る。フォームでファイルアップロードした場合と同じ。 boundary とかも面倒みてくれる。 < を value の先頭に置くとファイルのデータを読み込んで「テキストフィールドの値として」送る。この違いがちょっとよくわかってない。

$ curl -F 'file=@sale.txt' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => '------------------------------d60ae256bff6
Content-Disposition: form-data; name="file"; filename="sale.txt"
Content-Type: text/plain

all books
50% off

------------------------------d60ae256bff6--
',
          'Content-Type' => 'multipart/form-data; boundary=----------------------------d60ae256bff6'
        };

複数のパラメータを送りたい場合は -F を複数回書く。

$ curl -F 'file=@sale.txt' -F 'Filename=sale.txt' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => '------------------------------35c31540d7a9
Content-Disposition: form-data; name="file"; filename="sale.txt"
Content-Type: text/plain

all books
50% off

------------------------------35c31540d7a9
Content-Disposition: form-data; name="Filename"

sale.txt
------------------------------35c31540d7a9--
',
          'Content-Type' => 'multipart/form-data; boundary=----------------------------35c31540d7a9'
        };

@ じゃなくて < を使った場合。ちょっとマルチパートの中身が違う。 Content-Type がない。これもいまいち使いどころがわからない、というか @ との差がよくわからない。

$ curl -F 'file=<sale.txt' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => '------------------------------f0752eec7e81
Content-Disposition: form-data; name="file"

all books
50% off

------------------------------f0752eec7e81--
',
          'Content-Type' => 'multipart/form-data; boundary=----------------------------f0752eec7e81'
        };

--form-string

--form とほぼ一緒で、唯一 @ と < が特別な意味を持たない点が違う。 -F 'screen_name=@kyanny' みたいなデータを送りたいときに、意図せず kyanny というファイルの中身を読みにいってしまう、みたいなのを抑止するためにあるらしい。

$ curl -F 'screen_name=@kyanny' http://localhost:5000/
curl: (26) failed creating formpost data
$ curl --form-string 'screen_name=@kyanny' http://localhost:5000/
$VAR1 = {
          'POSTDATA' => '------------------------------686e87c00add
Content-Disposition: form-data; name="screen_name"

@kyanny
------------------------------686e87c00add--
',
          'Content-Type' => 'multipart/form-data; boundary=----------------------------686e87c00add'
        };

まとめ

  • form タグによるフォーム送信と同じことをしたい場合は (-d) 事前に URL エンコードが必要。
  • ファイルアップロードと同じことをしたい場合は -F を @ つきで使う。
  • どちらにせよ curl(1) は URL エンコードをしないので、必要ならば URI::Escape 等を使って自前で頑張る。

googlability 低そうなトピックだ・・・ (@ とか < とか) これが誰かの助けになりますように (特に n ヶ月後のおれとか)

app.psgi はこんな感じ。ワンライナーで書けそうだけどごちゃごちゃしたのでファイルに書いた。

$ cat app.psgi
#!/usr/bin/env perl
use strict;
use warnings;
use Plack;
use Data::Dumper;

sub {
  my $env = shift;
  my $input = $env->{"psgi.input"};
  my $postdata = do { local $/; <$input> };
  return [
    "200",
    ["Content-Type" => "text/plain"],
    [Dumper {
      "Content-Type" => $env->{"CONTENT_TYPE"},
      "POSTDATA" => $postdata,
    }]
  ];
};