Perlの最近のブログ記事

技術的には今更な話だが・・・(笑)

来年早々、もう15年くらい前に Perl で作ったシステムのサーバ移行があって、この機会に潜在バグというか、計算結果がおかしいんだけど、使わない項目なんで放置していた点とかあるので直そうかなっと。

20260924_perl1.jpg
今まで Perl に関しては CDE とか全然使わず、何千行もあるようなプログラムも「秀丸エディタで作って」「Linux のサーバにあげて実際に起動してテスト」って感じで開発してた。なので、今のメイン機の Windows にも Perl は入っていない。秀丸エディタには Perl のステートメントを色を変えて表示する機能があるので、それで十分だったんよね。

んが、今回は Windows 上の VSCode でソースコードを編集することにしたので、Windows にも Perl を入れる必要があるのよね。

というわけで Windows 用のインストールパッケージを探してみたら、今は Strawberry Perl ってのが一番メジャーなようなねえ。Gemini にもそれを勧められた(笑)

以前、遊び目的で Windows に入れた時は Active Perl ってのがメジャーだったけど。あ、今調べたら ActivePerl って商用利用を有料にしたり、ダウンロードにアカウント登録を必須にしたりして、一気に人気がなくなって、代わりに Strawberry Perl が台頭してきたんじゃね。Gemini にも「Strawberry Perl のほうがデファクトスタンダード(事実上の標準)です」・・・と教えられた(笑)。知らんかった・・・(^^;;;

独立して最初の 10年は Perl 案件ばかりしてて、Perl に関してはかなり詳しかったし、一時は「母国語は Perl です」なんて言ってた俺が、すっかり Perl 界の浦島太郎になってしまった・・・
サーバの自動監視をしているプログラムからの状況連絡のメールが Gmail に届かない。

いや、届かない理由はわかってる。RFC5321に違反した From メールアドレスは Gmail で弾かれる。

Gmail のサーバからの、

Remote_host_said:_553-5.1.7_The_sender_address_<#@[]>_is_not_a_valid_RFC_5321_address._For_more/553-5.1.7_information,_go_to/553-5.1.7__https://support.google.com/a/answer/3221692_and_review_RFC_5321/553_5.1.7_specifications._d2e1a72fcca58-839684a49e3si35399207b3a.131_-_gsmtp/

なんてメッセージがログに残っている。

ここで言う From アドレスはメールを見るときに表示されている From アドレス(いわゆる MAIL FROM)ではなく、メールソフトやメールサーバがメールデータの頭に自動でセットしている Envelope From アドレスである。

具体的には、メールの頭の

Return-Path: <hoge@exsample.com>

とかね。Envelope From を指定せずに sendmail とかでメールを送ると、ここが

Return-Path: <>

みたいに空になってたりして、Gmail から「RFC5321に違反してる」と判断され捨てられてしまうわけだね。

なので最近はちゃんとプログラム内で Envelope From を指定してるんだけど、古いプログラムはそのままだ(^^;;;。まあ、うちのメールサーバはそれでも受信するので今までは問題なかったんだけど、最近、メールを Gmail に転送して外出時なんかにスマホでチェックするようになったので困ったことになったんよね。
古い監視プログラムとかからのメールが届かなくなっちゃって。

というわけで修正。ま、-f オプションを付けるだけだけど(笑)

Perl ならこんな感じ。

if (!open(OUT,"| /usr/sbin/sendmail -t -f hoge\@exsample")) {
return(0);
}

print OUT <<EOM;
From: hoge hoge <hoge\@exsample>
To: atesaki\@exsample.co.jp
Subject: Test Mail
MIME-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP
X-Mailer: The original perl script - LOLI POPPER

Test Test Test Go Go Go
EOM

close(OUT);

これでちゃんと Gmail に転送しても送られるぞ。もちろん直接 Gmail のアドレスに送ってもグー。
この年末に、某システムのデータの圧縮方式を LZH から ZIP に変更した。
ユーザーが複数の CSV ファイルをひとつのアーカイブにまとめアップロードしたものを、サーバ(CentOS)上で自動で展開(解凍)し諸々の処理をするシステムだ。

初版公開が 2005年(俺が独立した翌年に作ったシステムだ(笑))なので、当時の Windows のファイル圧縮方式としては LHA(LZH方式)が主流だった。いや、もう、ZIP形式が LZH形式を駆逐してしまう未来は見えていた頃だが、このシステムを使うユーザーの職場ではまだ LZH形式が主流だったのだ。

しかし、あれから 18年。ついにユーザーより「もう、ZIP形式に変更したい~!」という声が出てきた。そのため、解凍コマンドを lha から unzip に変更した際の苦労話は「unzip の日本語ファイル名問題、なんとか解決」というエントリーにもちょっと書いているので、興味がある人は御一読下さいませ(笑)

件のエントリーでは「展開後の日本語ファイル名を EUC-JP で出力する」苦労話をまとめているが、その後、展開したファイルを正しく読み込めないという不具合が発生した。
ログなどを見て、どうもダブルバイト文字を正しく読めていないようだということはわかった。古い Perl のプログラムなので EUC-JP で書かれており、読み込むファイルの中身も EUC-JP である前提で処理をしているようだ。

ユーザーがアップロードするファイルは、Windows で作られているので文字コードは Shift_JIS である。しかし、プログラム内で Shift_JIS を EUC-JP に変換している箇所が無い。なのに、なんで今まではファイルの中身がちゃんと読めてたの???(どこで文字コードが EUC-JP に変換されてるの???) 

まあ、このエントリーのタイトルに思いっきり「落ち」を書いてるけど、lha コマンドでファイルを解凍するときに同時に EUC 変換していたのだ。lha コマンドにはそういうオプションがある。

lhaコマンドの引数には「オプション」と「コマンド」というのがあり、-e と書けば「圧縮ファイルを展開する」というコマンドを設定したことになる。
そのコマンドの前に e と書けば「テキストファイルの文字コードを EUC と相互変換する」というオプションが設定されたことになり、ファイル展開時に自動的にテキストファイルの文字コードが EUC に変換されるのだ。

具体的には、

/usr/local/bin/lha e -e -i -w=./temp hogehoge.lzh

のようなコマンドを投げていた。「e -e」という部分がそうだ。

いやあ、ハマったわ・・・。まさか lha コマンドが EUC 変換までしているとは思いもしなかった・・・(^^;;;
俺が書いたプログラムだけど 18年も前のものなので記憶が・・・

結局、unzip による ZIP ファイルの解凍のあとに、ファイルの中身を EUC 変換する処理を追加して解決した。

昔の UNIX 及び UNIX ライクな OS の主流の文字コードは Shift_JIS ではなく、まだ Unicode も正式に実装されておらず、EUC-JP の一人勝ち状態だった。だから lha にも EUC 変換オプションなんかが実装されているんだろうなあ(笑)
随分前に作った Linux(CentOS)上で動くプログラム。
LZH ファイルを解凍し、そのファイルをほげほげするのだが、さすがにもう Windows 上で LZH ファイルを作るのもきつくなってきた・・・ということで ZIP ファイル対応を依頼されたのだが(例えば、7-Zip なんかでも LZH 形式には対応してないからな(^^;)・・・ハマった(^^;

yum で入れた unzip は -O オプション(アーカイブ内のファイル名のエンコードが指定できる)が使える(以前のバージョンではパッチを当てないと駄目だった)ので、例えば unzip -Ocp932 -l exsample.zip で、exsample.zip の中の「かわいい中年男性一覧.csv」みたいなファイル名は取ってこれるんだけど、これをディスク上に解凍するとファイル名が化けまくる・・・

ちなみに、プログラムは EUC-JP で書かれている。なにせ、もう、18年前に初版公開したプログラムだからな(笑)。

これ、lha コマンドは EUC-JP でファイル名を出力するパッチが当たっていたので問題なかったんだけど、unzip は「サーバの locale のエンコードでファイル名が作られる」ため、実行サーバの locale である UTF-8 のファイル名となる。

他のプログラムにも影響あるから、なんとか EUC-JP でファイル名を設定してほしいなあ。

しかし、unzip にそのようなオプションはない。結局、unzip を実行する前に、locale を EUC-JP にする形でなんとかなった。

(例)
export LC_CTYPE=ja_JP.eucJP; /usr/bin/unzip -OCP932 -d /tmp exsample.zip

このプログラムは実行後シェルを閉じるので、LC_CTYPE の設定を投げっぱなしだが、同一シェル内で他のコマンドなど実行するのなら、

export LC_CTYPE=ja_JP.UTF-8

で locale を元の UTF-8 に戻すこと。

急に、CGI から送信していたメールが、Gmail に届かなくなった。

maillog を確認すると、

550-5.7.26 This mail is unauthenticated, which poses a security risk to the
550-5.7.26 sender and Gmail users, and has been blocked. The sender must
550-5.7.26 authenticate with at least one of SPF or DKIM. For this message,
550-5.7.26 DKIM checks did not pass and SPF check for [www.exsample.co.jp] did
550-5.7.26 not pass with ip: [210.XXX.XXX.XXX]. The sender should visit
550-5.7.26  https://support.google.com/mail/answer/81126#authentication for
550 5.7.26 instructions on setting up authentication.

ああ、定番の 550 エラー・・・

www.exsample.co.jp ドメインの SPF レコードに、送信元サーバの IP 210.XXX.XXX.XXX が登録されていないので、怪しいからメールは受け取らんよ・・・という Gmail のエラー。

最近、セキュリティ対策でサーバ構成が変わって、www.exsample.co.jp が別のサーバの CNAME になったので(お客さんのサーバなので、俺の権限ではどうしようもない)、www.exsample.co.jp の TXT レコードが書けんのよね。なので、その中に SPF レコードも書けず・・・

ちなみに、example.co.jp の SPF レコードは存在する。

# nslookup -q=TXT exsample.co.jp

と引いてみると、

exsample.co.jp   text = "v=spf1 ip4:202.XXX.XXX.XXX ip4:163.XXX.XXX.XXX ip4:210.XXX.XXX.XXX include:spf.securemx.jp ~all"

と返ってくる。
エンベロープ From を admin@www.exsample.co.jp から admin@exsample.co.jp に変えてやればいいというわけだ。

CGI の中では、

    $mail_data = <<EOM;
From: admin@exsample.co.jp
To: hogehoge@gmail.com
Subject: TEST MAIL
MIME-Version: 1.0
Content-type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit
X-Mailer: CGI-Script

test death.

EOM

    if (!open (MAIL,"| sendmail -t")) {
        print "Error : Mail Send\n";
    }

    print MAIL $mail_data;
    close(MAIL);

のように sendmail でメール送信をしている。

ここで、sendmail の引数に -f を追加してやれば、エンベロープ From を書き換えることができる。

具体的には、↓こう。

    if (!open (MAIL,"| sendmail -t -f admin@exsample.co.jp")) {
        print "Error : Mail Send\n";
    }

これで、エンベロープ From には admin@exsample.co.jp がセットされ、Gmail は example.co.jp の SPF レコードを参照し、万事解決である(笑)
以前 Perl で組んだシステム上からツイートをする必要が出てきたので、久しぶりにテスト用のボットを作ってみるか・・・と思って、Perl の Twitter API v2 対応のモジュールを探してみたけど・・・

無い・・・

@perl_api の過去ツイートで、2021/04/23 に「Twitter APIv2のサポートは進行中です。早期アクセスのために、GitHubのapi-v2ブランチから今すぐ使用できます。ディスカッションに参加して、Twitter::API(またはNet::Twitter/Lite)を使用するアプリのスムーズな移行を確実にしてください。」というのはあるが、試しに Twitter 関係の Perl モジュールをインストールして、Net/Twitter/API.pm の中を見てみたけど、v1.1に関する記述しかないみたい・・・

未だ GitHub に対応進行中ソースが存在するだけなのか?

Twitter 公式の「Developer Platform」ページを確認すると、「Community tools and libraries for v2」の一覧の中には、C#.NET、Go、Java、JavaScript (Node.JS) / TypeScript、Kotlin、PHP、PowerShell、Python、R、Ruby しかない・・・(v1.1にはちゃんと Perl もあるのに)

だいたい、Kotlin(コトリン)とか、何?(ああ、Android アプリ開発でメジャーな言語なのか(^^;)

仕方ない。Perl の既存アプリの中で編集だけして、その中から呼び出されるツイート投稿プログラムは Python で書こう。

まだ v1.1の API も使えるみたいだけど、いつ使えなくなるかわからんからなあ・・・
ちょっと仕事の関係で Perl の GD::SecurityImage モジュールをインストールしなくちゃいけなくなって、どうするんだっけ?とググったら、12年前の自分の記事がヒットした。


とか読むと、なんかインストールに七転八倒してるな(笑)

勝手のよくわからないレンタルサーバというのもあったんだろうけど、なんかソースから make しようとしてるし。

今回は、試しに全部 yum で入れてみたら一発でうまくいった。

# yum -y install gd
# yum -y install gd-devel
# yum -y install perl-GD
# yum -y install perl-GD-SecurityImage

perl-GD は入れずに、いきなり perl-GD-SecurityImage のインストールすればいいと思うけど、perl-GD 入れたら SecurityImage も入るのかと思ってたのよね。perl-GD-SecurityImage でインストールが必要であった。

ああ、便利な世の中や。

でも、国内インターネットサービス黎明期に、せっせとソースを make してはサーバを構築していた身からすると、なんか yum でポンっと入れちゃうのって罪悪感を覚えるというか、人間、こんなに怠慢でいいのか?とか思っちゃうのよね(笑)

そういう気持ちわかる人、けっこういると思うけど(笑)
今回の LINE API テストにつかった Perl ソース(PSGI 対応)の bot.psgi を載せておく。
API リファレンスに載っているソース例を元に作成した。
(前回のエントリーを書いてから、メッセージの出力を追加してるけど)

use strict;
use warnings;
use utf8;
use LINE::Bot::API;
use LINE::Bot::API::Builder::SendMessage;
use Plack::Request;

my $channel_secret = '84xxxxxxxxxxxxxxxxxxxxxxxxxxxxec';
my $channel_access_token = '8sXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcDnyilFU=';

my $bot = LINE::Bot::API->new(
channel_secret       => $channel_secret,
channel_access_token => $channel_access_token,
);

sub {
my $req = Plack::Request->new(shift);

unless ($bot->validate_signature($req->content,
$req->header('X-Line-Signature'))) {
return [200, [], ['bad request']];
}

my $events = $bot->parse_events_from_json(
$req->content);
foreach my $event (@{ $events }) {
if ($event->is_user_event && $event->is_message_event
&& $event->is_text_message) {

my $messages = LINE::Bot::API::Builder::SendMessage->new;
$messages->add_text( text => $event->text );
$messages->add_text( text => 'って言ったのね?');
my $res = $bot->reply_message($event->reply_token,
$messages->build);
}
}

return [200, [], ["OK"]];
};

これで、

20200512_line1.jpg

こんな感じに、ユーザが入力した文字を復唱したあとで、「って言ったのね?」と問いかけるウザい形に(笑)。

ポイントは、use utf8 と宣言して、プログラム内に記述される文字列リテラルがすべて「flagged utf8」(utf8 フラグ有効)になるようにすること。
LINE Messaging API に送る文字列は utf8 フラグ有効にしたものでないと駄目。use utf8 してないと、LINE 画面で文字化けする。

まあ、use Encode 宣言して、ひとつひとつ Encode::decode('utf8', 'って言ったのね?') という具合にしても(多分)一緒だけど。いちいちするのは面倒なので、use utf8 した方がいいだろう。

さて、LINE がらみの提案をしたいお客さんのサーバでは PSGI サーバが使えないので、CGI 版に直してみるかな。

<追記>
ちょっとソースを転記したとき、コピーミスがあったので修正した(2020/05/13 12:00)
いやあ、やっと LINE Messaging API を使った「オウム返し bot」がちゃんと動いた。

試しに Perl でテストプログラムを作ってみようと昨夜手をつけたんだけど、これがなかなかうまく行かなくて(^^;

公式 API リファレンスに載ってるスクリプト例が PSGI(Perl Web Server Gateway Interface)前提になってるんで、CGI 形式になおしてもよかったんだけど、まあ、せっかくなんで PSGI 使ってみようかなっと。その分よけいに時間がかかってしまった(^^;

やることは、

    1. CPAN から Perl モジュールを簡単にインストールするための cpanm のインストール
    2. Plack インストールの前準備(openssl-devel、expat-devel などを yum で入れておく)
    3. Plack(PSGI サーバソフトウェア)のインストール(cpanm で、Task::Plack、XMLRPC::Transport::HTTP::Plack をインストール)
    4. LINE API 関係のモジュールをインストール(cpanm で、LINE::Bot::API をインストール)
    5. Apache に PSGI サーバへのリバースプロキシ設定を追加(https://www.hoge.jp/line_api/ にアクセスがあったら、http://localhost:5000/ に転送・・・みたいな)
    6. API を使うチャンネルを LINE Developersコンソールで作成
    7. チャンネルの設定(基本設定、Webhook やチャネルアクセストークンなど Messaging API の設定)
    8. プログラム作成(GitHub 上の「LINE::Bot::API - SDK of the LINE Messaging API for Perl」にサンプルプログラムあり)
    9. Apache Web サーバ起動
    10. PSGI サーバ起動(例:plackup --max-workers 30 --port 5000 -a bot.psgi&)
    11. LINE でこのチャンネルと友達になる
    12. トーク画面で話しかけて、同じ言葉をチャンネル側が返してくるか確認

って感じ。けっこう色々あるっしょ?

20200511_line1.jpg
これがなかなか大変。Task::Plack のインストールはかなりの数のモジュールがインストールされるので時間がかかるし、「チャンネルの設定」って一行でさらっと書いているけど、アイコンとかチャンネル名とかの基本情報は LINE Official Account Manager で設定するし、Webhook やチャネルアクセストークンの発行などは LINE Developers で行う。どっちで何を設定できるのかよくわからんし、飛んだ先のページからの戻りリンクが無かったり、なんか手間取った。

結局、うまく行かなかったのも、チャネルアクセストークンの発行が正しく行われてなかったからのようだ。ずっとうまく行かなくて悩んでいたのだが、トークンを再発行してプログラムに設定したら、あっけなく上手く行った。

PSGI も初めてだったので、その仕組から調べたので時間がかかった。素直に PSGI のソースを CGI に書き直した方が早かっただろう(笑)
ググっても、ngrok を利用する記事が多くて、最初は PSGI のこともよくわからなかったので、何が何やら(^^;・・・

まあ、そのうち、ルータの内側の PSGI サーバへ外部から直接アクセスするために ngrok というトンネリングサービスを使っていることや、つまりは PSGI サーバは Java の Tomacat みたいなアプリケーションサーバ(のようなもの)なのか・・・とかそういうことがわかってきたので、「うちのテストサーバはグローバルIPアドレス空間にいるので ngrok なんか使わずに、Apache で Reverse Proxy 設定してやればいいだけじゃん」と。そこまで考えが至るにもけっこう時間がかかった(^^;

疲れた(^^;

具体的な設定やプログラムについては追々書いていこう(笑)
もう10年近く前に作ったシステムの移行をしているのだけど、その中に UDP パケットでデータを受け取っているプログラムがある。firewall の設定をしたり、ちょっとだけ新サーバに合わせてそのプログラムの受信部分を修正したので、疎通確認だけでもしときたいなっと。Perl で簡単なプログラムでも作ろうと思ったんだけど、念の為にググってみると、ちゃんとそういうプログラムが公開されているね。


もう、そこに載ってるソースそのまま(スコープがグローバルしかないので、変数宣言の my だけ外すという繊細さを見せてるが(笑)>俺)にコピー。

#!/usr/bin/perl

use Socket;

$ipstr = $ARGV[0];
$port = $ARGV[1];
$msg = $ARGV[2];

$ipaddr = inet_aton($ipstr);
$sockaddr = pack_sockaddr_in($port, $ipaddr);
socket(SOCKET, PF_INET, SOCK_DGRAM, 0);
send(SOCKET,$msg , 0, $sockaddr);

無事、テスト終了。

最近 Perl もあまり触ってないので、これだけのコード書くにも色々調べないと駄目だったと思うので、大変助かりました。ありがとうございます>ProjectLiliO

このアーカイブについて

このページには、過去に書かれたブログ記事のうちPerlカテゴリに属しているものが含まれています。

前のカテゴリはAIR/Flexです。

次のカテゴリはPHPです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら