Expires

ここから、HTTPキャッシュの話。

HTTP キャッシュ - HTTP | MDN
Expires - HTTP | MDN

ブラウザの挙動として、キャッシュをさせるかさせないかというところに関して試してみると、キャッシュをさせないという命令については簡単なんだけど、逆にキャッシュをさせたい場合は難しかった・・・。
まあ、基本的にはキャッシュをさせたくないという要望が一般的になっている模様なので。
Expiresヘッダで、未来の時刻とか設定すると、キャッシュからファイルを取ってくるパターンが発生する。
http_server.cで、

時刻を表示するようにした。
そしてヘッダに"Expires: Sat, 31 Dec 2022 12:00:00 GMT\r\n"を追加。
キャッシュ期限が未来なら、キャッシュも起きやすくなる。

起動して1発目で、Chromeのデベロッパーツールを開いてアクセスすると、時刻が出てちゃんとアクセスしている模様。
起動して1発目で、Chromeデベロッパーツールを開いてアクセスすると、時刻が出てちゃんとアクセスしている模様。

起動して1発目で、Chromeデベロッパーツールを開いてアクセスすると、時刻が出てちゃんとアクセスしている模様。

サーバ側にもリクエストは来てる。まあ下はfavicon.icoだけど。
サーバ側にもリクエストは来てる。まあ下はfavicon.icoだけど。

サーバ側にもリクエストは来てる。まあ下はfavicon.icoだけど。

基本的に現代のブラウザは基本ノーキャッシュでアクセスしようとするのでそのままではキャッシュを見てくれない
基本的に現代のブラウザは基本ノーキャッシュでアクセスしようとするのでそのままではキャッシュを見てくれない

基本的に現代のブラウザは基本ノーキャッシュでアクセスしようとするのでそのままではキャッシュを見てくれない。

またリクエスト来てる。faviconはno-cacheと付けてるし。
またリクエスト来てる。faviconはno-cacheと付けてるし。

またリクエスト来てる。faviconはno-cacheと付けてるし。
ただこうやってアクセスしてるのは意味があって、Expiresで未来の期限を付けてることで、このサイトはキャッシュを見てもいいんだなと、ブラウザに思い込ませてるのよね。
さて、ここからが本番。一番起きやすいのは起動して一発目のアクセス。

まず起動する準備をしておく
まず起動する準備をしておく

まず起動する準備をしておく。

そしてURLもセットしておく。サジェストが出てない状態の方がいいかな。URLのテキストだけ出てる状態。
そしてURLもセットしておく。サジェストが出てない状態の方がいいかな。URLのテキストだけ出てる状態。

そしてURLもセットしておく(アクセスはしない)。サジェストが出てない状態の方がいいかな。URLのテキストだけ出てる状態。

Chromeのデベロッパーツールを起動
Chromeデベロッパーツールを起動

Chromeデベロッパーツールを起動。

デフォルトのmsnを表示してる状態でサーバ起動
デフォルトのmsnを表示してる状態でサーバ起動

デフォルトのmsnのトップページを表示してる状態でサーバ起動。
で、URLバーの入力のところでリターンキーでアクセスする。

惜しい。本体がprefetch cacheになってる。faviconはdisk cacheだが。
惜しい。本体がprefetch cacheになってる。faviconはdisk cacheだが。

惜しい。本体がprefetch cacheになってる。faviconはdisk cacheだが。

acceptは来てるのにリクエストは来てない
acceptは来てるのにリクエストは来てない

acceptは来てるのにリクエストは来てない。
で、サーバをいったん止めて、ブラウザも1回閉じて、同じようにブラウザを起動し、デベロッパーツールを開き、URLバーからリターンでアクセス。

今度は両方disc cacheになってる
今度は両方disc cacheになってる

今度は両方disc cacheになってる。

またacceptは来るのにリクエストはきてない
またacceptは来るのにリクエストはきてない

またacceptは来るのにリクエストはきてない・・・。
そしておわかりいただけだろうか・・・。
時刻が07:29:04 GMTから変わっていないことを・・・。
ちなみに・・・。

赤い枠のリロードボタンで更新した場合は、キャッシュではなく本体を見に行きます。
赤い枠のリロードボタンで更新した場合は、キャッシュではなく本体を見に行きます。

赤い枠のリロードボタンで更新した場合は、キャッシュではなく本体を見に行きます。

リクエストも来てる
リクエストも来てる

リクエストも来てる。
またChromeでは、初回のリターンキーアクセスではキャッシュを見に行くものの、2回目のリターンキーアクセス以降は本体を見に行く模様。
Edgeもそんな感じっぽい(ちゃんと試してないけど。)
ちなみに、

更新ボタンリロードだと、ブラウザもCache-Control: max-age=0とノーキャッシュ要求してくる。
更新ボタンリロードだと、ブラウザもCache-Control: max-age=0とノーキャッシュ要求してくる。

更新ボタンリロードだと、ブラウザもCache-Control: max-age=0とノーキャッシュ要求してくる。
さて、

IEの場合は開発者ツール
IEの場合は開発者ツール

IEの場合は開発者ツール。

こちらも、あらかじめ何度かアクセスさせてExpiresを解釈してもらってから、再起動初回1発目アクセスするとキャッシュを見に行く。
こちらも、あらかじめ何度かアクセスさせてExpiresを解釈してもらってから、再起動初回1発目アクセスするとキャッシュを見に行く。

こちらも、あらかじめ何度かアクセスさせてExpiresを解釈してもらってから、再起動初回1発目アクセスするとキャッシュを見に行く。
IEの場合は、リターンキーアクセスであれば、何度アクセスしてもキャッシュを見に行くところがChromeと違う。

ただし、リロードボタンはやはり本体を見に行く。
ただし、リロードボタンはやはり本体を見に行く。

ただし、リロードボタンはやはり本体を見に行く。

Sample/http/Expires/Expires/src/Expires at master · bg1bgst333/Sample · GitHub

CGI::param('POSTDATA')

POSTで送られたデータのContent-typeが、'application/x-www-form-urlencoded'や'multipart/form-data'でない時に限り、CGI::paramに'POSTDATA'という文字列を指定することで、リクエストボディ丸ごと取得できる。

CGI - Common Gateway Interface のリクエストとレスポンスを扱う - perldoc.jp

これもついでにWindows環境でやる。
前回のHttpClientをベースに、

受け側のCGIファイル名を"CGI.cgi"に変えたので、それだけ修正。
CGI.cgiは、

$q->param('POSTDATA')で、$bufをもらって、それをprintでOUT("image1.bmp")に書き出し。

送信
送信

送信。

ファイル出来てたし、開けた。
ファイル出来てたし、開けた。

ファイル出来てたし、開けた。

Sample/perl/CGI/param_POSTDATA/src/cgi at master · bg1bgst333/Sample · GitHub

binmode

binmodeは、C言語で扱ったsetmodeと同じで、指定のファイルハンドルをバイナリモードにする。

Perlの組み込み関数 binmode の翻訳 - perldoc.jp

また、Windows上のApacheで試すのだが、今回はPerlCGIなので、Perlを入れないといけない。

Windows版ApacheでCGI(Perl)を使用する方法 ActivePerl - [Windows/サーバー] ぺんたん info

ここを参考に、

Download & Install Perl | ActiveState

ActivePerlをダウンロード。

最近はmsiダウンロードじゃなくてPowerShellで入れさせようとするの・・・。
最近はmsiダウンロードじゃなくてPowerShellで入れさせようとするの・・・。

最近はmsiダウンロードじゃなくてPowerShellで入れさせようとするの・・・。

さっきのコマンドをコピペしてPowerShellで打つとこうなる
さっきのコマンドをコピペしてPowerShellで打つとこうなる

さっきのコマンドをコピペしてPowerShellで打つとこうなる。
Yを入力。

で、こうなって
で、こうなって

で、こうなって、

入ったかな
入ったかな

入ったかな。

コマンドプロンプトからもperlは呼べるのでパスは通ってるみたい
コマンドプロンプトからもperlは呼べるのでパスは通ってるみたい

コマンドプロンプトからもperlは呼べるのでパスは通ってるみたい。
ただ、PerlCGIスクリプトの先頭に書くperlコマンドのパスがわからんのよね。
いろいろ探し回った結果、

ここっぽいのかなとおもったんだけど
ここっぽいのかなとおもったんだけど

ここっぽいのかなとおもったんだけど、

さっきのperl.batの中身を見たら
さっきのperl.batの中身を見たら

さっきのperl.batの中身を見たら、

ここっぽいのよね
ここっぽいのよね

ここっぽいのよね。

#!C:\Users\bg1\AppData\Local\activestate\d0653c90\bin\perl

print "Content-type: text/plain\n";
print "\n";

print "perl cgi hello";

こういう試しのCGI置いて試してたら、

こういうのも作られてよくわからないのだけど
こういうのも作られてよくわからないのだけど

C:\Apache24\cgi-binの下にこういうのも作られてよくわからないのだけど、

上記の試しCGIが動いたので、これでようやくできそう。
上記の試しCGIが動いたので、これでようやくできそう。

上記の試しCGIが動いたので、これでようやくできそう。

#!C:\Users\bg1\AppData\Local\activestate\d0653c90\bin\perl

use strict; # 厳密な文法チェック.
use CGI; # CGI

# CGIオブジェクトの作成.
my $q = CGI->new; # CGI::newでCGIオブジェクトを生成し, $qに格納.
  
# パラメータの有無.
if ($ENV{'REQUEST_METHOD'} eq 'POST'){ # 'POST'の時.

  # CGIヘッダの出力.
  print "Content-type: image/bmp\n"; # "Content-type: image/bmp\n"を出力.
  print "\n"; # "\n"を出力.

  # リクエストの読み込み.
  my $file = $q->upload('uploadedfile'); # アップロード対象のファイルが返る.
  #print '$file = '."$file\n"; # printだとファイル名.
  my $buf; # バッファ.
  my $numbytes = 1024; # 1回で読み込むバイト数を1024とする.
  my $bytesread; # 実際に読み込んだバイト数.
  #print "-----file begin-----\n"; # 開始.
  while ($bytesread = read($file, $buf, $numbytes)){ # 全部読み込む.
    print $buf; # バッファ出力.
  }
  #print "-----file end-----\n"; # 終了.
  #print 'ref($file) = '.ref($file)."\n"; # ref($file)を出力.

}
else{ # 'POST'以外.('GET'など.)

  # HTTPヘッダの出力.
  print $q->header;

  # HTMLヘッダの出力.
  print $q->start_html("binmode"); # <html>タグなどの出力.

  # HTMLフォームの出力.
  print $q->start_form; # <form>タグの出力.
  print "\n"; # 改行の出力.
  print $q->filefield(-name => 'uploadedfile'); # ファイル選択.
  print $q->submit; # submit
  print $q->end_form; # </form>タグの出力.

  # HTMLフッタの出力.
  print $q->end_html; # </html>タグなどの出力.

}

前回までのCGIをContent-typeをimage/bmpにして、余計なprintをコメントアウトして、試す。

そうするとエラー
そうするとエラー

そうするとエラー。
C:\Apache24\logs\error.logを見ると、

[Xxx Xxx xx xx:xx:xx.xxxxxx 2021] [cgi:error] [pid 6788:tid 536] [client ::1:60733] AH01215: Can't locate CGI.pm in @INC (you may need to install the CGI module) (@INC contains: C:/Users/bg1/AppData/Local/activestate/d0653c90/site/lib C:/Users/bg1/AppData/Local/activestate/d0653c90/lib) at C:/Apache24/cgi-bin/binmode.cgi line 4.\r: C:/Apache24/cgi-bin/binmode.cgi

やっぱCGI.pmか。
モジュールの入れ方がわからん。
cpan使えるのかわからんし、ppmというものがあったらしいけど、stateに置き換えになったらしい。
(ActivePerlの情報、最近のが全然無い・・・。)

いろいろ試してなんとかsearchに成功
いろいろ試してなんとかsearchに成功

いろいろ試してなんとかsearchに成功。

searchできるのになんでinstall失敗するの
searchできるのになんでinstall失敗するの

searchできるのになんでinstall失敗するの・・・。
この後、管理者権限でコマンドプロンプトを起動し直して、もう一度installコマンドしたら、今度は成功した。
でもその後、一般ユーザでinstallコマンドしても、成功した。

意味わからんけど入ったからいいか
意味わからんけど入ったからいいか

意味わからんけど入ったからいいか。

#!C:\Users\bg1\AppData\Local\activestate\d0653c90\bin\perl

use strict; # 厳密な文法チェック.
use CGI; # CGI

# CGIオブジェクトの作成.
my $q = CGI->new; # CGI::newでCGIオブジェクトを生成し, $qに格納.
  
# パラメータの有無.
if ($ENV{'REQUEST_METHOD'} eq 'POST'){ # 'POST'の時.

  # CGIヘッダの出力.
  print "Content-type: text/plain\n"; # "Content-type: text/plain\n"を出力.
  print "\n"; # "\n"を出力.

  # リクエストの読み込み.
  my $file = $q->upload('uploadedfile'); # アップロード対象のファイルが返る.
  print '$file = '."$file\n"; # printだとファイル名.
  my $buf; # バッファ.
  my $numbytes = 1024; # 1回で読み込むバイト数を1024とする.
  my $bytesread; # 実際に読み込んだバイト数.
  print "-----file begin-----\n"; # 開始.
  while ($bytesread = read($file, $buf, $numbytes)){ # 全部読み込む.
    print $buf; # バッファ出力.
  }
  print "-----file end-----\n"; # 終了.
  print 'ref($file) = '.ref($file)."\n"; # ref($file)を出力.

}
else{ # 'POST'以外.('GET'など.)

  # HTTPヘッダの出力.
  print $q->header;

  # HTMLヘッダの出力.
  print $q->start_html("CGI::upload(uploadedfile)"); # <html>タグなどの出力.

  # HTMLフォームの出力.
  print $q->start_form; # <form>タグの出力.
  print "\n"; # 改行の出力.
  print $q->filefield(-name => 'uploadedfile'); # ファイル選択.
  print $q->submit; # submit
  print $q->end_form; # </form>タグの出力.

  # HTMLフッタの出力.
  print $q->end_html; # </html>タグなどの出力.

}

さっきのimage/bmp版じゃなくて以前のtext/plain版(余計なprintもあり)を試す。

こっからtest1.txtを選択して送信
こっからtest1.txtを選択して送信

こっからtest1.txtを選択して送信すると、

成功
成功

成功。
問題は画像の場合。

#!C:\Users\bg1\AppData\Local\activestate\d0653c90\bin\perl

use strict; # 厳密な文法チェック.
use CGI; # CGI

# CGIオブジェクトの作成.
my $q = CGI->new; # CGI::newでCGIオブジェクトを生成し, $qに格納.
  
# パラメータの有無.
if ($ENV{'REQUEST_METHOD'} eq 'POST'){ # 'POST'の時.

  # CGIヘッダの出力.
  print "Content-type: image/jpg\n"; # "Content-type: image/jpg\n"を出力.
  print "\n"; # "\n"を出力.

  # リクエストの読み込み.
  my $file = $q->upload('uploadedfile'); # アップロード対象のファイルが返る.
  #print '$file = '."$file\n"; # printだとファイル名.
  my $buf; # バッファ.
  my $numbytes = 1024; # 1回で読み込むバイト数を1024とする.
  my $bytesread; # 実際に読み込んだバイト数.
  #print "-----file begin-----\n"; # 開始.
  while ($bytesread = read($file, $buf, $numbytes)){ # 全部読み込む.
    print $buf; # バッファ出力.
  }
  #print "-----file end-----\n"; # 終了.
  #print 'ref($file) = '.ref($file)."\n"; # ref($file)を出力.

}
else{ # 'POST'以外.('GET'など.)

  # HTTPヘッダの出力.
  print $q->header;

  # HTMLヘッダの出力.
  print $q->start_html("binmode"); # <html>タグなどの出力.

  # HTMLフォームの出力.
  print $q->start_form; # <form>タグの出力.
  print "\n"; # 改行の出力.
  print $q->filefield(-name => 'uploadedfile'); # ファイル選択.
  print $q->submit; # submit
  print $q->end_form; # </form>タグの出力.

  # HTMLフッタの出力.
  print $q->end_html; # </html>タグなどの出力.

}

image/bmp版でもimage/jpg版でも、

こっから送信で
こっから送信で

こっから送信で、

binmodeを付けなくても成功してしまう
binmodeを付けなくても成功してしまう

binmodeを付けなくても成功してしまう。
もしかして、CGI.pmは中でbinmodeセットしてるんかな。
ということで方針を変えて、HttpClientプログラムを作成し、マルチパートではなく画像バイナリをCGI.pmを使わないbinmode.cgiに直接送信し、binmode.cgiでそれを読み込み、ファイルに書き出した時に問題が起きるかどうかを試す。
HttpClient側は、

とする。
binmode.cgiは、

#!C:\Users\bg1\AppData\Local\activestate\d0653c90\bin\perl

use strict; # 厳密な文法チェック.

# パラメータの有無.
if ($ENV{'REQUEST_METHOD'} eq 'POST'){ # 'POST'の時.

  # CGIヘッダの出力.
  print "Content-type: image/bmp\n"; # "Content-type: image/bmp\n"を出力.
  print "\n"; # "\n"を出力.

  # リクエストの読み込み.
  my $buf; # バッファ.
  read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});

  # ファイルへの書き出し.
  my $ret = open(OUT, ">", "image1.bmp"); # test.txtを開く.
  if ($ret){ # trueの時.
    print OUT $buf; # OUTに$bufを書き込む.
  }

}

最初こうする。

送信
送信

送信。

image1.bmpが出来てる
image1.bmpが出来てる

image1.bmpが出来てる。

しかし開けない
しかし開けない

しかし開けない。

送信元のimage1.bmp
送信元のimage1.bmp

送信元のimage1.bmp

送信先のimage1.bmpのサイズを見ると違うのがわかる。
送信先のimage1.bmpのサイズを見ると違うのがわかる。

送信先のimage1.bmpのサイズを見ると違うのがわかる。

そこでbinmodeでSTDINとOUTをバイナリモードにする。

今度はimage1.bmpが開けた。
今度はimage1.bmpが開けた。

今度はimage1.bmpが開けたし、サイズも同じになった。

Sample/perl/binmode/binmode/src at master · bg1bgst333/Sample · GitHub

CGI::upload(uploadedfile)

下の記事にもある通り、CGI::paramでファイル(名?ハンドル?)を取得する方法は古い方法で、安全性に問題があったみたい。

CGI - Common Gateway Interface のリクエストとレスポンスを扱う - perldoc.jp
https://pointoht.ti-da.net/e6805798.html

そんなわけでCGI::uploadが出来た模様。

paramをuploadに置き換えただけ。
使い方としては変わらない。

送信すると
送信すると

送信すると、

挙動も全く変わらない
挙動も全く変わらない

挙動も全く変わらない。
見た目ではわからないけど、多少安全になったんかなあ・・・。

Sample/perl/CGI/upload_uploadedfile/src/CGI at master · bg1bgst333/Sample · GitHub

CGI::param(uploadedfile)

CGIのアップロード周りに戻ってきた。

CGI - Common Gateway Interface のリクエストとレスポンスを扱う - perldoc.jp
https://pointoht.ti-da.net/e6805798.html

マルチパートのファイルアップロードの場合、標準入力STDINからreadで全部読み込むと、マルチパートなのでパースが大変。
CGI.pmだと、CGI::paramにinputタグのnameで指定した名前を渡すだけで、パースされた"ファイル"を取得できる。
(ここでいう"ファイル"はファイルネームなのかファイルハンドルなのか謎の存在である。)

inputタグのnameを'uploadedfile'という名前に変えた。
paramにそれを指定すると$fileが返る。
これをprintすると、ファイル名。
しかし、readにファイルハンドルとして$fileを渡すと、ちゃんと機能して、ファイルの内容を読み込める。
そして、ファイル内容を出力した後、最後にrefで$fileが何かを調べる。

test1.txtを選択
test1.txtを選択

test1.txtを選択。
送信すると、

ファイル名を指してるのに、ちゃんと読み込めてる。結局refの$fileはFhと出ててファイルハンドルっぽい。
ファイル名を指してるのに、ちゃんと読み込めてる。結局refの$fileはFhと出ててファイルハンドルっぽい。

ファイル名を指してるのに、ちゃんと読み込めてる。結局refの$fileはFhと出ててファイルハンドルっぽい。

Sample/perl/CGI/param_uploadedfile/src/CGI at master · bg1bgst333/Sample · GitHub

open(my $fh,mode,expr)

ファイルハンドル、現代的な方法。
Perl 5.6からは、openの第1引数にundefな変数が渡されると無名型グロブへのリファレンスを返す。

https://pointoht.ti-da.net/e5689400.html
ファイルハンドルの概念を理解する - Perlゼミ - Perl元気塾のPerl講座

open.plで、

これで、

$ vi open.pl
$ perl open.pl
$ cat test.txt
ABCDE
$

ファイルが書き込める。

Sample/perl/open/open_myfh_mode_expr/src/open at master · bg1bgst333/Sample · GitHub

IO::File

ファイルハンドル、5つ目の方法。
IO::Fileは、IO::Handleを継承してるので、ファイルハンドルを生成できる。

https://pointoht.ti-da.net/e8904484.html
ファイルハンドルの概念を理解する - Perlゼミ - Perl元気塾のPerl講座

IO__File.plで、

と書く。

$ vi IO__File.pl
$ perl IO__File.pl
$ cat test.txt
ABCDE
$

IO::Fileで書き込めた。

Sample/perl/IO__File/IO__File/src/IO__File at master · bg1bgst333/Sample · GitHub