投稿日:

既存CGIを一切触らずに、前段にアクセス制御やアクセスロガーをつけたいとかの用途でラッパーCGIを使うと上手くいくので、その方法を書く。

なお前段が動くことしか試していないが、備考に後段で処理をさせる方法についても軽く触れている。

動作機序

CGIはコマンドライン引数、環境変数、標準入力を受け取り、何かを処理した結果を標準出力するプログラムである。

つまりラッパーCGIはコマンドライン引数、環境変数、標準入力を受け取り、それをラッピングするCGIにそのまま受け渡し、このCGIの標準出力をリダイレクトできればよい。

やり方

以下のようなコードを書き、exec()前に前段の処理を書けばよい。

#!/usr/bin/perl
use strict;
use warnings;

#
# ここに実行前に挟みたい処理
#

my $original_cgi = './hoge.cgi';
exec($original_cgi) or die "Cannot exec $original_cgi: $!";

今回実際に作ったサンプル

adiaryにはアクセス制限をする機能がなく、本体を弄るのが嫌だったので前段に処理を入れることで実現した。

#!/usr/bin/perl
use strict;
use warnings;

# 以下のコマンドでIP::Geolocation::MMDBをインストールしていることが前提
# cpanm -l extlib IP::Geolocation::MMDB
use lib './extlib/lib/perl5';
use IP::Geolocation::MMDB;
# https://download.db-ip.com/free/dbip-city-lite-YYYY-MM.mmdb.gz
# ex. https://download.db-ip.com/free/dbip-city-lite-2026-01.mmdb.gz
my $db = IP::Geolocation::MMDB->new(file => './DBIP-City.mmdb');
my $country_code = $db->getcc($ENV{REMOTE_ADDR});

# 日台韓は許可する方針(怪しい挙動を見たことがないため)
my @allow_country_codes = ('JP', 'TW', 'KR');

my $user_agent = $ENV{HTTP_USER_AGENT};

# 許可する国コードかチェック
my $is_allowed_country = grep { $_ eq $country_code } @allow_country_codes;

# 許可するBOTのUAパターン
my @allowed_bot_patterns = (
    qr/bot/i,
    qr/curl/i,
    qr/wget/i,
    qr/google/i,
    qr/bing/i,
    qr/mastodon/i,
    qr/misskey/i,
    qr/pleroma/i,
    qr/akkoma/i,
    qr/lemmy/i,
    qr/activitypub/i,
    qr/hatena/i,
    qr/github/i,
    qr/tumblr/i,
    qr/meta/i
);

# 許可するBOTかチェック
my $is_allowed_bot = 0;
for my $pattern (@allowed_bot_patterns) {
    if ($user_agent =~ $pattern) {
        $is_allowed_bot = 1;
        last;
    }
}

# 許可国でもなく、許可BOTでもなければエラーにする
if (!$is_allowed_country && !$is_allowed_bot) {
    # 未知のSNS BOTを将来的に許可するために、BOTくさいUAのログを集めておく
    if ($user_agent !~ /Windows|Mac OS|Linux|Android|iOS|iPhone|iPad/i) {
        # OGP取得BOTに間違いなく含まれない文字列が入ってるものはログに入れない
        my $deny_ua_log_file = './deny_ua.log';
        if (open my $fh, '>>', $deny_ua_log_file) {
           my $time = localtime();
           my $remote = $ENV{REMOTE_ADDR} // 'unknown';
           my $uri = $ENV{REQUEST_URI} // 'unknown';
           print $fh "[$time]\t\"$user_agent\"\t$country_code\t$remote\t$uri\n";
           close $fh;
        }
    }

    print "Status: 403 Forbidden\n";
    print "Content-Type: text/plain; charset=UTF-8\n\n";
    print "Access denied.\n";
    exit;
}

# adiary呼び出し
my $original_cgi = './adiary.cgi';
exec($original_cgi) or die "Cannot exec $original_cgi: $!";

備考

perldocを読んだ感じ、互換性に問題が出る可能性も少なからずあるようだ。

perldocのexec関数の説明を見る感じ、ENDブロックや、オブジェクトのDESTROYメソッドを起動しないとあるので、実装方法次第では正しく動かない可能性もあるのかもしれない。

また「戻って欲しい場合には、execではなく system関数を使ってください」とあるため、もし後処理をしたい場合はexec関数でなくsystem関数を使うとよいと思う。

あとがき

レンタルサーバーではWAFが自由に使えないため、なんちゃってWAFの様なものを作りたいとか、レンタルサーバーを新規に始めたく、CGIにバナー広告を差し込みたいといったケースがある場合に、今回のような手法は便利だろう。

今時、往年のレンタルサーバーを新規に始め、それもバナー広告を出したいと考える人物がいるかどうかは謎だが、共通的に何かを差し込みたいなど、何かしら活用方法はあるかもしれない。

ググって出てきた記事が軒並み古くて役に立たなかったので、令和八年最新版として書いておく。

やり方

  1. CPANMのインストール
    curl -L https://cpanmin.us | perl - App::cpanminus
    
  2. CPANMのパスを.cshrcに書く
    私はZSHを使っているため.zshrcに書いているが、デフォルト環境はcshのはずなので.cshrcに書けば成り立つと思う。
    echo PATH=${HOME}/perl5/bin:${PATH}
    
  3. local::libをインストールする
    local::libはroot以外にあるCPANモジュールを使うためのものらしい。これを自分のホームディレクトリ配下に入れるようにコマンドを流す。
    cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
    

使い方

以下のようなコマンドを流すとパスを指定してCPANモジュールを取得・展開できる。

# 書式
cpanm -l <パス> <モジュール名>

例えば以下を流すとpwd配下にextlibディレクトリが生成され、IP::Geolocation::MMDBがダウンロードされて展開される。勿論、依存関係も勝手に解決してくれる。

cpanm -l extlib IP::Geolocation::MMDB

モジュールを利用するときはライブラリの配置されているルートを指定し、次にモジュール名を指定するとうまくいくようだ。

use lib './extlib/lib/perl5';
use IP::Geolocation::MMDB;

あとがき

https://cpanmin.us/を見に行くと以下の記述があり、辿ってみると日本の人が作っていてちょっと驚いた。

# This is a pre-compiled source code for the cpanm (cpanminus) program.
# For more details about how to install cpanm, go to the following URL:
#
# https://github.com/miyagawa/cpanminus

最近思っているNext.jsを使った画面設計に関する考えを箇条書きで雑に殴り書きしていく。この記事は考えの垂れ流しなので深い説明はしない。AppRouterではなく、PageRouterの考え。

  • TypeScriptで実装し、型が騙せるような実装は極力避け、コードによる戻り値の型指定は不具合の原因になることがあるため、可能な限り型推論に任せる
  • SOLIDな設計を意識することで疎結合でテストしやすい設計になる
  • Clean Archtectureを意識することでSOLIDのSを意識しやすくなる
    • 画面として考える場合、実装レイヤーとしてはAPIを呼ぶ以外何もしないAdapter、ビジネスロジックやイベントハンドリングの実処理などを行うController、画面要素を配置しただけのView、画面状態を保持するState、それらをつなぎ合わせるUsecaseが、Usecaseを置くだけのPage(Next.jsのpageコンポーネントに埋め込むコンポーネント)、SSGやSSRをする場合のServer Side Controllerがあるとよいと考えている。大まかには下図のような感じで考えていて、過去の実務でもこれに相当するものを作ったことがある。
    • ただこれはModelに相当するものがなく、ビジネスロジックの共通化に課題が出てくるのと、ControllerがFatになりすぎると考えており、そこが課題になると考えている。
  • テストが容易なコードは必然的に疎結合になる
  • 疎結合にする場合、命名を抽象的にしておくと処理の入れ替えが容易になる(命名が具象、つまり実装の詳細に依存しないため)
  • 疎結合にするとパーツが増えるので認知負荷が上がる
  • 疎結合でかつ、命名が抽象化されている場合、仕様を知らない人にとっては実際の処理内容を推測しづらくなる
    • つまりこれは属人性が増えると考える
  • 例外についてはErrorクラスを継承し、カスタム例外を作成して、用途に応じたハンドリングができるようにする
    • 原則として処理を止める場合にのみ用いるべきで、続行する場合には使わない
    • 例外は原則としてスローして、カスタムエラーは特定の階層でフィルタしてハンドリング、全てすり抜けてきたものはルート処理でキャッチしてハンドリングすることで、取りこぼしをゼロにする。例外の握り潰しは原則行わない
      • 基本的にすべてロギングする
    • 処理を継続するものについては例外とせず、ワーニング用の処理フローを作成し、それに則って行う(例えば入力バリデーションはワーニング)
投稿日:

スマホでWebを見てるときにキーボードがUIにかぶって操作しづらくなることがあるので、いくつかのサイトでどうなっているか調べてみた。

去年の9月にAndroid Edgeで調べた内容なので、今とは事情が異なるケースもある。Android Chromeでは起きなかったケースもあったので、Edge特有の挙動と思われる。

Twitter

ログイン画面

キーボードが入力欄やログインボタンにかぶる。

ミュート設定

入力欄が上にあるためかぶらない。

投稿画面

入力欄が上にあるためかぶらない。

YouTube

コメント画面

キーボードがボタンにかぶる。

通報モーダル

キーボードがボタンにかぶる。

GitHub

Issueのコメント画面

入力欄が丸ごとキーボードにかぶる。

一休

検索モーダル

宿泊予算の入力が丸っとかぶる。

じゃらん

検索画面

キーボードと被らないようにするためか入力UIをモーダルにして画面トップに出すように工夫されている。これなら大抵の端末やブラウザで対応できそうなので、よくできていると思う。

Amazon.co.jp

レビュー画面

入力状態になると若干のラグの後に画面下に余白ができ、入力状態が外れると同様のラグの後、画面下の余白が消えるという挙動をする。

割と凝ったJSで何かしらの計算を行い、かなり頑張って調整しているようだった。タイムラグがあるのはイベント発火もあるだろうが、キーボードの検出や画面サイズに応じた余白計算に時間がかかっているのもあるのだろう。

ここまで凝った実装をしているのは他のサイトでは見られず、Amazon.co.jp特有に見えた。なお、Amazon.comのほうは見ていない。

あとがき

この調査時点では、じゃらんとAmazon.co.jpを除き入力欄を画面の上部に配置するなどレイアウトで調整しているサイトが比較的多く、どうしてもボタンなどが隠れる傾向があるように思った。

じゃらんは強制的に画面上部に入力欄を出すようにし、Amazon.co.jpは気合でキーボードが隠れないように調整していて、腐心の跡が見られた。

なお、今回動画を作成するにあたり一部をぼかす必要があったため、やり方を調べ実践したのでAviUtlで動画の一部にモザイクをかけ、動かす方法という記事を作成し、その過程を残している。

本動画の作成過程では上手くモザイクをかけられなかったが、上記の記事を作っているときに上手く行くようになったので、本記事の動画はモザイクではなく、ぼかしとなっている。

更新日:
投稿日:

意外とわかりやすいサイトが少なかったので収穫はあまりないがこんな感じ。消したらログアウトされるCookieとして調べた。

サービス名 保持期間 セッションキー
GitHub 15日 user_session
Cookpad 30日 access_token_global_v2
pixiv 30日 PHPSESSID
fantia 31日 _session_id
ドリパス 2ヶ月 remember_user_token
last.fm 1年 sessionid

Amazon.co.jpやヨドバシもしばらくアクセスしていないとログアウト食らった記憶があるので、何かしらのリミットはついてそうだが、さっと調べる程度の範疇ではわからなかった。

しかしGitHubはやたらログアウト食らうとと思ったらたった15日とは…。

ドリパスはDPSSIDがなければremember_user_tokenからセッションを生成し、DPSSIDremember_user_tokenの両方がなければログアウト状態になるようだった。DPSSIDはExpiresがSessionとなっていたため、remember_user_tokenを削除してもブラウザを落とすまではログインを継続できる。