Laravelバックエンドにfetch APIでDELETEリクエストを送ろうとしてハマった話

この記事は ATJ-TECH Advent Calendarの16日目です。

adventar.org

先日はレトロな駄文を失礼しました。今回はWebアプリケーション開発でハマったポイントのお話です。

実行環境

  • OS: Ubuntu 20.04 Server
  • Laravel: 8.26.1
  • PHP 8.0.12

ハマったポイント

  1. 削除ボタンがクリックされたら確認ダイアログを表示させ、OKであればDELETEメソッドでリクエストを送信する
  2. Laravel側のコントローラに定義してあるレコードの削除処理が走る
    以上のような処理を実現するため、1.の部分をfetch APIを利用して次のように実装しました。
<button onclick="confirmAndDelete('/api/hoge')">
    DELETE
</button>

function confirmAndDelete(target) {
    let conf = confirm("Delete this record?");
    if (conf) {
        fetch(target, {
            method: 'DELETE'
        });
    }
}

 これをブラウザに表示させ削除ボタンをクリックしてみましたが、登録されているレコードは削除されずに表示されたままになってしまいました。

原因解明

 Laravel側で受け取ったリクエストを全部吐き出すようなログを作成して確認してみたところ、DELETEメソッドでのリクエスト自体は受け取られていました。
 次に、ブラウザの開発者ツールからfetch APIの動作を確認してみたところ、419エラーではじかれていたことが判明しました。
 普段はform要素内に@csrfディレクティブを記述することでCSRFトークンを埋め込んでいたのですが、Javascriptを利用してDELETEリクエストを送信する場合でもトークンを持たせる必要があることまでは理解していなかったことが原因でした。

問題解決

 リクエストボディにフォームデータ_tokenとしてCSRFトークンを持たせてあげれば解決しそうではあったんですが、DELETEメソッドにはリクエストボディを持たせられない模様。
 CSRFトークンのチェックを無効化することも考えたのですが、さすがに安全性を犠牲にするのは気が向かなかったので公式ドキュメントを確認したところ、以下のような記述を見つけました。

POSTパラメータとしてCSRFトークンをチェックすることに加えて、App\Http\Middleware\VerifyCsrfTokenミドルウェアはX-CSRF-TOKENリクエストヘッダもチェックします。
参照:CSRF保護 8.x Laravel

 以上のことを参考にして、リクエストの送信前にX-CSRF-TOKENヘッダを追加するようにしたところ、無事登録されたレコードを削除することができました。
 以下変更後のコード。

<button onclick="confirmAndDelete('/api/hoge', '{{ csrf_token() }}')">
    DELETE
</button>

function confirmAndDelete(target, token) {
    let conf = confirm("Delete this record?");
    if (conf) {
+       let header = new Headers();
+       header.append("X-CSRF-TOKEN", token);
        fetch(target, {
            method: 'DELETE',
+           headers: header
        });
    }
}

2021年にもなってAMD K6を動かしてみる

この記事はATJ-TECH Advent Calendar 2021の第13日目です。
adventar.org

さて、今年もこの時期がやってまいりましたね。去年はTCUACのらぴーと君の記事に触発されて、特に何かのACに参加するわけでもなくWindows NT3.15の話をしたようなしてないような。
今年は身内でこういう企画をやる話が出たので、これ以外にも何本か記事をあげたいと思います。

さて、ここから本題。
今回の主役は家の押し入れから出てきためっちゃ古いパソコンくんです。1998年夏モデルらしい。ってことで、やっていきましょう。

スペック

  • Model: FMV DESKPOWER MVII 267
  • OS: Windows 98
  • GPU: ATI Rage 2C
  • RAM: 64MB
  • CPUソケット: Socket 7
    なんでCPUだけソケット表記なのかって?だってこいつ、押入れから引っ張り出したのは良いんですけどCPUがないんだもん。ということで、某所でSocket7対応のCPUを入手。

    はい、ここでタイトル回収です。こちら、AMD K6-2 450AFXとなっております。なんでPentiumじゃないかって?メインPCでRyzenRadeonを使ってるから元々このPCにはK6が積まれていたからです。
    参照: FMV-DESKPOWER - AzbyClub サポート : 富士通

内部を見てみる

早速カバーを開けて、中身を見ていこうと思います。

この時代のPC特有の太いケーブル(ぼくは愛着を込めて「きしめん」って呼んでます)が何より先に目に入ってきますね。
もちろんSATAなんてものは存在せず、ストレージインタフェースはIDEかフロッピーだけという仕様です。
また、拡張カードインタフェースも特徴的。PCI4発(白)、ISA3発(黒)がサポートされています。PCIe一択の今に比べ、バラエティ豊かで面白いですね。
なお、PCIスロットにはモデム(下)とグラフィックカード(上)が、ISAスロットにはサウンドカードが挿さっています。

CPU搭載・起動

では、空っぽのソケットにCPUを載せていきます。しっかりピンの位置を確認して、

こうして、

こう。
今の時代に比べるとCPUクーラが小ぶりで可愛いですね。
最大450MHz、プロセスルール0.3~0.2μmってこともあって発熱は控えめなんですかね?(教えてエロい人)
これで最低限起動に必要なものは揃ったので、フロッピードライブにWindows98の起動ディスクを挿し込んで電源を入れてみた。

富士通のロゴが出てきました。通電は問題なさそう。
しかし、次の瞬間。「Password violated. System halted!」の表示とともにフリーズしてしまいました。

BIOSパスワードの怪

この時点で疑ったのは、バックアップの電池切れかBIOSの破損。とりあえずボタン電池を交換してみましたが、症状が好転することはありませんでした。
とりあえずCMOSクリアしたいなぁってことで、ジャンパかボタンを探していたところ、マザーボードに興味深い表示が。
f:id:KLag:20211205140343j:plain

S4  | PASSWORD
------------------
ON  | CHECK
OFF | BYPASS

つまりスイッチS4をOFFにすればパスワードチェックを回避して起動できるようになるのでは...?ということでやってみます。
S1-S4はDIPスイッチで、CPUの近くにありました。さっきのファンを取り付けたときの画像の右下あたり、コネクタの近くにあります。

再挑戦

スイッチをいじったので、もう一回電源を投入。これでだめなら諦めます。
f:id:KLag:20211205175157j:plain
はい、無事BIOSまでブートできました。結構嬉しかったです。あとはここから例によってDOSブート、パテ割り、フォーマットを済ませてOSのインストールへ。
電源周りが壊れて使えなくなったHDDレコーダから摘出したIDE接続のHDDが無事動いてくれたことに多少感謝しつつ、作業を進めていきます。 f:id:KLag:20211205180710j:plain ブレッブレですが、親の顔より見たあの画面。
f:id:KLag:20211205181419j:plain 大体一時間弱で終わりました。やったね。
どうでもいいけどAMDってこの時代からCPUINFOの文字列が“AuthenticAMD”なんですね。たしかRyzenも同じように出てたような気がする。

CPU-Zで確認してみる

これだけだと「動かした」感が出ないので、みんな大好きCPU-Zで詳細を確認してみましょう。 もちろん通常版のCPU-Zがこんな化石OSで動くわけがないので、こちら↓を使っていきます。
CPU-Z Vintage Edition | News | CPUID
なんとこちら、Windows 95/98で動作する 2019年に発表された アプリケーションです。ダウンロードしてみると、解凍前サイズで1.30MBでした。ちょうど2HDフロッピーに収まりきるサイズで感動しています。
f:id:KLag:20211205183316j:plain
動かしてみるとこんな感じでした。参考程度にメインPCのCPU-Zスクリーンショットを貼り付けて、この記事を終わりにしたいと思います。
f:id:KLag:20211205183719p:plain

まとめ

案外適当に放置されてたPCでもしっかり動いてくれるんですね。電源回りもそこまで酷使されてはなさそうなので、まだまだ遊ぶ程度の用途には耐えてくれると思います。
ただ、今はキーボードしかつながっていない状態なので、とりあえずはPS/2マウスを手に入れたいと思います。

firstOrFailの例外処理でハマった話

Laravel 8においてDBにデータが存在するかどうかをチェックして、存在したらそのデータを、存在しなかったら定数を返却するような処理を書く際に一時間近くハマったので備忘録として残しておく。

環境

  • OS: Ubuntu 20.04 server
  • Laravel 8.26.1
  • PHP 8.0.12

やりたかったこと

  1. 特定の条件に当てはまるデータが存在した場合、firstOrFail()関数を用いてそのデータの特定カラム(foo)の値を返却する。
  2. 条件に当てはまらない場合、スローされた例外をキャッチして定数(-1)を返却する。

これを実現するため、以下のようなコードを作成。

try{
      $user= User::where([
           ['user_id', '=', $userId],
           他の条件…,
      ])
      ->firstOrFail();
      return $user->foo;
}catch(Exception $e){
      return -1;
}

実際にこの処理を行うAPIをブラウザから叩くと、Laravel標準の 「404 | Not Found」ページが表示された。

原因

findOrFailメソッドとfirstOrFailメソッドは、クエリの最初の結果を取得します。ただし、結果が見つからない場合は、Illuminate\Database\Eloquent\ModelNotFoundExceptionを投げます。
(中略)
ModelNotFoundExceptionをキャッチしない場合は、404 HTTPレスポンスをクライアントへ自動的に返送します。

引用: Eloquentの準備 8.x Laravel

どうやらModelNotFoundExceptionがキャッチされない限り、firstOrFailメソッドは例外発生時にApp::abort(404)と同様の動作をするらしい。

解決

きちんとModelNotFoundExceptionをキャッチするようにしたところ、想定していた通りの動作をしてくれた。

use Illuminate\Database\Eloquent\ModelNotFoundException;
...
try{
      $user= User::where([
           ['user_id', '=', $userId],
           他の条件…,
      ])
      ->firstOrFail();
      return $user->foo;
}catch(ModelNotFoundException $e){
      return -1;
}

EC2上のnginxにHTTPSで接続できるようにした話

大学の友人間でLaravelを使ったWebアプリケーションを開発することになり、開発環境としてEC2上にnginxサーバを構築しました。
この環境にHTTPSで接続できるようにしたのでメモ程度に手順を残しておきます。

Certbotのインストール

今回はLet's Encryptを利用してサーバ証明書を発行してもらうので、Certbotを使ってSSL設定を行っていきます。aptからインストールできるのは便利ですね。

$ sudo apt install certbot python3-certbot-nginx

nginxのconfファイル記述

Certbot/etc/nginx/sites-enabled下にあるファイルのserver_nameディレクティブをサーチし、一致したドメインの存在するファイルに自動で設定を適用してくれます。
なので、confファイル内にserver_nameの項目を登録していきます。

server{
    server_name exaple.com www.example.com;
    root /var/www/hogehoge/public;
    location / {
       ...
    }
}

なお、ここですでにlistenディレクティブが定義されている場合、Certbotを実行する際に「同じポートで既にlistenされてるよ」とのエラーが発生し、設定が適用されなくなってしまうので注意。
confファイルの作成が終わったら設定をリロードします。

$ sudo systemctl reload nginx

Certbotの実行

準備が完了したので、certbotを実行してSSL通信に必要な設定を反映していきます。-dオプションでconfファイルに記述したドメイン名を指定するのを忘れずに。

$ sudo certbot -d example.com -d www.example.com

あとはCertbotが渡されたドメインが実際に存在するかなどをチェックして、自動でconfファイルに追記・変更を加えてくれます。
全ての処理が完了したら、サーバとのHTTPS通信が可能になります。

TerraformでProxmox上にVMを作成してみた

インフラエンジニア志望なのにIaCツール触ったことないのはどうなのよ?ってことで、Terraformをさわさわしてみました。
AWSインスタンスをプロビジョニングしても良かったんですが、インスタンスを止め忘れてえげつない請求が飛んでくるのが怖かったので、とりあえず手元のProxmox環境にVMを立てることから始めてみました。

大まかな手順

  • Terraformから接続するためのユーザ・権限を作成する
  • VMのテンプレートを作成する
  • HCLを記述する
  • Terraform plan/applyでVMを作成する

Terraform用ユーザの作成

https://registry.terraform.io/providers/Telmate/proxmox/latest/docs
公式ドキュメントに沿ってユーザと権限を設定していきます。

pveum role add TerraformProv -privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.Audit VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit"
pveum user add terraform-prov@pve --password <password>
pveum aclmod / -user terraform-prov@pve -role TerraformProv

VMのテンプレート作成

テンプレート作成の手順に関しては、こちらの記事を参考にさせていただきました。
blog.nishi.network
ここで作成したテンプレートをもとにVMを作成していきます。

HCLの記述

先ほど作成したテンプレートをクローンして新たにVMを立てていくため、各種構成を記述したtfファイルを作成します。
はじめに、Proxmoxサーバに接続するための情報をmain.tfに定義していきます。

provider "proxmox"{
    pm_api_url = var.api_url
    pm_user = var.user
    pm_tls_insecure =true
}

さらに、公開したくない情報を別のファイルに定義しておくこともできるため、var.tfにいくつかの値を定義しておきました。

variable "api_url"{
    default = "ProxmoxサーバのURL"
}
variable "user"{
    default = "Proxmoxサーバにログインするユーザ"
}

また、今回はProxmoxプロバイダを使用するため、プラグイン関連の設定をversion.tfに記述しました。

terraform {
    required_providers {
        proxmox = {
            source = "Telmate/proxmox"
            version = "2.9.0"
        }
    }
}

最後に、作成するVMの構成をVMs.tfにまとめていきます。今回は3台のVMKubernetesクラスタを作成することを見込んだ設定としました。

resource "proxmox_vm_qemu" "UbuntuCP"{
    name = "ubuntucp"
    target_node = "atras"
    clone = "UbuntuServerTemplate"
    memory = 2048
    os_type = "cloud-init"

    disk{
        type = "scsi"
        storage = "VMStore"
        size = "20G"
    }
}

resource "proxmox_vm_qemu" "UbuntuWN1"{
    name = "ubuntuwn1"
    target_node = "atras"
    clone = "UbuntuServerTemplate"
    memory = 2048
    os_type = "cloud-init"

    disk{
        type = "scsi"
        storage = "VMStore"
        size = "20G"
    }
}

resource "proxmox_vm_qemu" "UbuntuWN2"{
    name = "ubuntuwn2"
    target_node = "atras"
    clone = "UbuntuServerTemplate"
    memory = 2048
    os_type = "cloud-init"

    disk{
        type = "scsi"
        storage = "VMStore"
        size = "20G"
    }
}

VMの作成

さて、必要なファイルをあらかた定義し終えたので、さっそくProxmoxサーバにVMを作成していきます。
$export PM_PASS="PASSWORD"でProxmoxユーザのパスワードを環境変数に登録し、 $terraform planで、エラーチェックと作成されるVMの構成の確認を行います。
ここで表示された構成で問題がなければ、$terraform applyで実際にVMを作成します。 Apply complete!と表示されれば無事作成が終了していますので、実際にProxmox上で確認してみると、
f:id:KLag:20211111145908p:plain
こんな風に作成できていることがわかります。

ProxmoxへのLVMパーティションの追加

サーバにHDDを増設したことに伴って、Proxmox環境に新しくLVMストレージを追加しようとしたんですが、Webコンソール上のストレージ設定から作成しようとしたところ、ディスク単位でしか新規作成できなさそうだったのでここにメモっておく。

ざっくりとした手順

パーティション作成

300GBの物理ディスク /dev/sdb内に、ISOイメージ保管用のパーティションを100GB、余った部分をVMの仮想ストレージ保管用のパーティションとして作成します。

# fdisk /dev/sdb 
Welcome to fdisk (util-linux 2.36.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): n
Partition Number: (デフォルトで確定)
First Sector: (デフォルトで確定)
Last sector, +/-sectors or +/-size{K,M,G,T,P}: +100G

Created a new partition 1 of type 'Linux filesystem' and of size 100 GiB.

Command (m for help): n
Partition Number: (デフォルトで確定)
First Sector: (デフォルトで確定)
Last sector, +/-sectors or +/-size{K,M,G,T,P}: (空欄)

Created a new partition 2 of type 'Linux filesystem' and of size xxx GiB.

これでパーティションを作成できたので、mkfsでフォーマットを行っていきます。続行しますか?的なことが聞かれるのでyで続行。

# mkfs -t ext4 /dev/sdb1
# mkfs -t ext4 /dev/sdb2

ここでディスクの下準備は完了したので、VGの作成に移っていきます。

VGの作成

今回はVMのストレージ領域のみをLVMとしてProxmoxに追加していきます。そのため、/dev/sdb2のみを含むVGを作成します。

# vgcreate VMStore /dev/sdb2
# vgscan

vgdisplayvgsで作成したVMStoreが表示されていればOK。

Proxmoxのストレージ設定変更

Proxmoxのストレージ設定ファイルである/etc/pve/storage.cfgを編集していきます。
さっき作成したLVMのVG名を次のように指定して追記。

lvm: VMStore(ここは任意の名前でOK)
        vgname VMStore(作成したVGの名前)

ここまで設定が終われば、以下のように反映されます。
f:id:KLag:20211107235706p:plain
本筋からは逸れますが、使用しなくなったストレージを削除する場合も同じファイルのエントリを削除することによって行います。

自宅のIPアドレスが変わったら自動的に通知してくれるDiscord Botを作った話

お久しぶりです。
前回紹介した通り、IX2105で自宅にVPN接続を可能にしているのですが、DDNSの登録などをしていないので、ルータの電源が落ちるなどしてIPアドレスが変わったときに外部から接続できなくなってしまう弱点がありまして、これはあまりにも不便だということで、

一時的なしのぎの措置として、こんな感じに通知してくれるようなBotを作成しました。(アイコンに関するツッコミは受け付けておりません。)

つくったもの

以下のリンクからコードを見ることができます。(可読性はゴミカス以下)

github.com

簡単な説明

動かすために必要なファイルは以下の通り。

  • IPNotify.py (メイン処理)
  • DiscordConfig.py (トークン・チャンネルIDの記入用)

2ファイルで作ってますが、これはGithubに公開するにあたってトークンの類を非公開にしたかったためです。そのため、.gitignoreにDiscordConfig.pyを登録しています。

IPNotify.pyでやってること

簡単に言ってしまえば、指定した時間ごとに自分のIPアドレスを確認し、前回確認時から変更があればDiscordのAPIを叩いてメッセージを送信しているだけです。
リクエスト間隔はtime.sleep内の値を変更することで調節できますが、あまりにも短いとただのDoS攻撃になってしまうので注意。

結論

早いとこDDNS登録しよ...
以上、海月でした。