💾

Web APIを1から実装するために調べた備忘録

2024/09/12に公開

掲示板を作成するためにバックエンドの開発も必要なわけだが、
API通信を単語でしかわかっておらず、1から作るのにだいぶ苦労したのでその備忘録として残す。
他の記事見ても多少知ってる前提のが多すぎてわからなかったので本当に基礎からまとめる。

APIとは何か?

https://wa3.i-3-i.info/word12428.html
わからない単語を調べるときにお世話にならない人は少ないであろうここでまずAPIが何かを読む。
なるほど。窓口。窓口を作る処理を書けばいいんだな?

残念ながら今回はわかった気になるだけじゃ通用しないのが現実。。

APIの動作を確認する簡単なコードを作る(フロントエンド)

環境はVite + React
慣れるほど凄さがわかってくるReact。

コードはGPT先生に丸投げして作ってもらう。

ApiTest.jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';

function ApiTest() {
    const [message, setMessage] = useState('');

    useEffect(() => {
        // Perl APIへのGETリクエストを送信
        axios.get('https://your-api-url.com/api/test')
            .then(response => {
                setMessage(response.data.message);  // APIレスポンスを取得
                console.table(response.data)
            })
            .catch(error => {
                console.error("API request failed:", error);
            });
    }, []);

    return (
        <div>
            <h1>API Test</h1>
            <p>{message}</p>
        </div>
    );
}

export default ApiTest;

ふむふむ。特定のURLにアクセスして返してもらえばいいんだな。
axiosが動かなかったので追加でインストールしたくらい。

APIの動作を確認する簡単なコードを作る(バックエンド)

環境はPerl
……Perl!?と思うだろうが、現在の箱庭諸島に外付けする必要があるため、これは絶対条件である。致し方なし。
技術選定にもGPT先生に聞き、最初Dancer2を利用する予定だったが、使用しているレンタルサーバーに入っていなかったため古き良きCGI形式で書くことにした。致し方なし(2回目

もちろんコードはGPT先生に丸投げして作ってもらう(2回目

api.cgi
#!/usr/bin/perl
use strict;
use warnings;
use CGI qw(:standard);
use JSON;

# HTTPヘッダーを出力
print header('application/json');

# APIの処理
my $response = {
    message => 'Hello, World',
    status  => 'success',
};

# JSONレスポンスを出力
print encode_json($response);

こんなもんでいいのか。あとはこのapi.cgiを直接叩けばいいと。ほ~ん……

とはいかなかった。ここからが地獄の始まりである。

レスポンスが返ってくるまでの長い道のり

全部GPT先生に聞いた。AIなのでもしかしたらずれているかもしれないが、1つの手法としてまとめておく。人から聞いてもその人がたまたま特殊な環境だったら同じことになる。

  1. perlのサーバーって別に立てなくていいのか?

立てる必要はやっぱりあった。
手順を聞いて以下のコマンドでサーバーを起動しろとのこと
perl -MHTTP::Server::Simple::CGI -e 'HTTP::Server::Simple::CGI->new(8080)->run'
ちなみにこれのせいでさらに時間を溶かすことになる――

  1. というかperlのファイルってどこに置くんだ?

一般的には

project_root/
├── src/
│   ├── redux/
│   │   ├── ...
│   ├── ...
├── perl/
│   ├── api.cgi          // Perlスクリプトをここに配置
└── ...

srcと同じ位置に置くらしい。srcがフロントエンド、perlがバックエンド、と構造がわかりやすくなるからとのこと。
とりあえず参考程度に頭に入れた。

  1. 起動したサーバーのルートディレクトリってどこ?

初心者はこれすらわからないんだ。
言われてみればその通りなのだが、サーバーを起動した場所がルートディレクトリとなるらしい。
一応書いておくと、ルートディレクトリは基準となる場所。
~/perlに移動してからサーバーを起動すれば./api.cgiで十分ということになる。

  1. サーバーから他のサーバーへ繋がらないらしい

つまりこのエラーが出た。

Access to XMLHttpRequest at 'http://localhost:8080/server/api.cgi' from origin 'http://localhost:XXXX' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

恐らく1回はぶつかるだろう壁、CORSというものが存在する。

違うサイトから違うスクリプトを安易に動かせないようにするためのセキュリティみたいなのが入っているらしい。
スキーム(プロトコル)、ホスト、ポートの3つ組み合わせたものをorigin(オリジン)と言い、それが同じ場合のみリクエストを送れるというもの。はあ(上の空
要はもし全然知らないサイトからリクエストを大量に送られたり、書き換えられたりしたら……ということ。これは仕方ないので無効はできない。

ではどうするかというと、ヘッダーに送られてもいいよという設定をサーバー側でする必要がある。

ここで1.が逆に弊害になっていることに気がつく。設定しようがないから。
なのでサーバーを起動するためのファイルを新規作成した(作ってもらった)

  1. おまけ 動いたけど記法がダサいので書き直してもらった。でも動かなくなる

perl特有の古さとAIの中途半端さで2時間くらい溶けたので一応記載。
作ってもらったコードはこちら

myserver.pl
#!/usr/bin/perl
use strict;
use warnings;
use HTTP::Server::Simple::CGI;

# サーバーの実装
{
    package MyWebServer;
    use base qw(HTTP::Server::Simple::CGI);

    sub handle_request {
        my ($self, $cgi) = @_;

        my $path = $cgi->path_info();

        # CORSヘッダーを追加
        # print "HTTP/1.0 200 OK\r\n";
        # print "Access-Control-Allow-Origin: *\r\n";  # CORSを許可
        # print "Content-Type: application/json\r\n\r\n";

        # if ($path eq '/api/test') {
        #     print '{"message": "Hello from Perl API"}';
        # } else {
        #     print "HTTP/1.0 404 Not Found\r\n";
        #     print "Content-Type: text/plain\r\n\r\n";
        #     print "Not Found";
        # }
        my $headers = [
            'Access-Control-Allow-Origin' => "*",  # CORSを許可
            'Content-Type' => "application/json",
        ];

        if ($path eq '/api/test') {
            return [200, $headers, [ '{"message": "Hello from Perl API"}' ]];
        } else {
            return [404, ['Content-Type' => 'text/plain'], ['Not Found']];
        }
    }
}

# サーバーの起動
my $server = MyWebServer->new(8080);
$server->run();

printのベタ書きとか今どきイケてないのでいい感じにできる?と聞いた結果が下のreturnの形式。しかし動かない。何度聞いても解決しない。
わかることはCORSのエラーは出なくなったことだけ。

ずっとエディタの方でGPT-4o-miniに聞いていたのだが、仕切り直してブラウザの方で改めてGPT-4oで聞いてみた。
新しい方法で動かない理由として考えられるのは、HTTP::Server::Simple::CGIモジュール自体が、この形式(return [ステータスコード, ヘッダー, ボディ])をサポートしていない可能性があります。このモジュールは、基本的にprintを使ってレスポンスを直接出力する設計になっているので、手動でprintしない限りクライアントに適切にレスポンスが送信されないことが多いです。
そ、そういうことか…
素人はリファレンスを読まない…いやちょっとは読んだけど、全然欲しい情報を拾えないんだ…
じゃあなんでCORSのエラーは出ないんだ?ヘッダーは読み込めてない?という気はするのだが、解決できる気がしないのでここでは諦め。
そもそも将来perlを使わなくなる可能性が高いので、今回は一時しのぎで問題ないと判断。

そんなこんなでちょこちょこ調べ続けて2日間かかってついにHello from Perl APIの文字がReact側で表示される。うおおおおおおおおおおおお

エンドポイントの決定

これで終わりかと思いきや素人なのでさらにまた躓く。エンドポイントってなんだ…?
http://localhost:8080/api/testがそうなのだが、cgiしかいじってこなかった人間には理解しづらかった。
myserver.plがないのに繋がるの…?/api/testっていうルートが事前に必要なの…?そういえばGET POSTってsubmitボタンないのにどこに書くんだ…?と。

http://localhost:8080恐らくこの形式のせいで混乱をしてしまったんだろうなと思う。
/api/testなんてディレクトリは必要なく、直接その文字列をセットして叩けばサーバーが感知してmy_server.plが起動するという仕組みらしい。

API自体は自由に書ける。例えば/api/user/1でuserの1番の情報、/api/user/1/day/1でuserの1番で1日の情報、とか。
/apiはわかりやすく1枚噛んでるだけなので、レスポンス確認だけなら/testResponseとだけで問題ない。
またGET,POST,PUT,DELETEはaxiosのメソッドで使うので一緒に送ってくれるようになっている。
これとURLを組み合わせて、そのエンドポイントだけで何をしようとしているのかわかるように設計すべきだ、ということらしい。なるほどなあ……

ちなみにapi/user/1/day/1はサーバーで自動で分けてくれる処理でもあるのか?と思ったらsplitとか使って手動で分けてね、らしい。
そこは融通効かないんかいと思ったがまあ自由に決められるからこそ勝手に分けようもないのかなと勝手に納得した。perlは正規表現でも楽に取れるし。

終わりに

APIはよく聞くがいざ自分で組もうとすると、思ってたより難しいんだなということを思い知った作業だった。
ちなみにこれで残りのAPIを実装…とはやはりならず、Reduxにaxiosをただ組み合わせるだけじゃ実装できない罠にちゃんと嵌ったのでまた色々調べて勉強中。
複雑な処理を簡単に書けるようにしてはいると言っても根底は複雑なままだから覚える量が多すぎる…

Discussion