api gateway + lambda(python) + laravel (2) フォームから送信して受け取る編

とりあえず、いま laravel側が

Route::get('/test', function () {

みたいになっているので、これだと具合があんまよくないというか、getしたときはformを表示してpostしたときにapiの送信を行いたいので、ちょっと組替える

use Illuminate\Http\Request;

Route::get('/test', function () {
    // フォームの提示
    return Inertia::render('Test');
});
Route::post('/test', function (Request $request) {
    // apiの送信
    dd($request->all());
})->name('test.api');

で、今回はややこしい事にinertia + reactという構成でいくので、過去の記事読んでだめならこの先はキツいかもしれん


inertiaによるformの提示

とりあえずチェックボックス1つだけ付けておいて様子を見てみよう。

use Inertia\Inertia;
Route::get('/test', function () {
    // フォームの提示
    return Inertia::render('Test');
}

で resources/js/Pages/Test.jsx

import React, { useState, useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import { Head, useForm } from '@inertiajs/react';

export default function ApiTestForm() {
  return (
    <GuestLayout>
      <Head title="Api Gateway テストページ" />


    </GuestLayout>
  )
}

これが最低限の表示であり、formもまだない


GuestLayoutを使うと必ず認証レイアウトになってアレだが…

ここにcheckboxとsubmitを書いていく。各種フォームはそれぞれコンポーネントがあるからそれ使ったらよさそう(もちろん書いてもいいけど…)

まあこの辺りはいつものように resources/js/Pages/Auth/Login.jsx からコピってくるといいだろう。

resources/js/Pages/Test.jsx

import React, { useState, useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import PrimaryButton from '@/Components/PrimaryButton'
import { Head, useForm } from '@inertiajs/react';

import Checkbox from '@/Components/Checkbox'
import InputError from '@/Components/InputError'
import InputLabel from '@/Components/InputLabel'

export default function ApiTestForm() {
  const { data, setData, post, processing, errors, reset } = useForm({
    flag1: false,
  });

  const submit = (e) => {
    e.preventDefault();
    post(route('test.api'))
  }

  return (
    <GuestLayout>
      <Head title="Api Gateway テストページ" />

      <form onSubmit={submit}>
        <div className="block mt-4">
          <label className="flex items-center">
            <Checkbox
              name="flag1"
              checked={data.flag1}
              onChange={(e) => setData('flag1', e.target.checked)}
            />
            <span className="ml-2 text-sm text-gray-600">Flag1</span>
          </label>
        </div>
        <div className="flex items-center justify-end mt-4">
          <PrimaryButton className="ml-4" disabled={processing}>
            APIテスト
          </PrimaryButton>
        </div>
      </form>
    </GuestLayout>
  )
}
admin


受けとれているようだ。

DBに保存してみる

まあ、DBに保存なんてしなくてもいいのかもしれんが、とりあえずそれっぽいものを作るという意味で、やってみよう。モデルもそのものずばりTestでok

% ./vendor/bin/sail artisan make:model Test -m

   INFO  Model [app/Models/Test.php] created successfully.

   INFO  Migration [database/migrations/2023_09_04_030831_create_tests_table.php] created successfully.
        Schema::create('tests', function (Blueprint $table) {
            $table->id();
            $table->json('data');
            $table->timestamps();
        });

json型で作りまるっとはめこむ事にしよう。

% ./vendor/bin/sail artisan migrate:fresh 
class Test extends Model
{
    use HasFactory;

    protected $fillable = ['data'];
}

fillableを書いて準備はokだ

postをぶん投げる

Route::post('/test', function (Request $request) {
    // apiの送信
    dd($request->all());
})->name('test.api');

こういう謎コードなので、書き換えていく、ってまあ基本的に前ののコピペだけど…

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
Route::post('/test', function (Request $request) {
    // apiの送信
    $client = new Client();
    $data = $request->all();
    try {
        $response = $client->post('https://*.execute-api.ap-northeast-1.amazonaws.com/test', [
            'json' => $data  // リクエストボディとしてJSONを送信
        ]);

        $statusCode = $response->getStatusCode();  // HTTPステータスコード
        $headers = $response->getHeaders();  // レスポンスヘッダー
        $body = $response->getBody()->getContents();  // レスポンスボディ

        dd([
            'status_code' => $statusCode,
            'headers' => $headers,
            'body' => json_decode($body, true)  // JSONをPHPの配列に変換
        ]);

    } catch (GuzzleException $e) {
        // 例外処理
        dd([
            'error_message' => $e->getMessage()
        ]);
    }
})->name('test.api');

(いい加減apiのurlはconfig(.env)に追い出してもいいかもね)

lambdaの更新

import json

def lambda_handler(event, context):
    # eventの 'body' からデータを取得し、JSONとして解析
    body = json.loads(event.get('body', '{}'))
    
    # keyとvalueのペアを格納する辞書
    output_dict = {}
    
    # body内のすべてのkey-valueペアをイテレーション
    for key, value in body.items():
        output_dict[key] = value  # キーと値をoutput_dictに追加
    
    # レスポンスを生成
    response = {
        'statusCode': 200,
        'body': json.dumps(output_dict)  # output_dictをJSON形式にしてbodyに設定
    }
    
    return response

こんな感じでoutput_dictに詰めて、json_dumpして返す

まあいずれにせよこれで


これはflagを付けた状態

こんな感じで取得できるようになったはずだ。

DBへの書き込み

まあこれに関してはbody部だけでいい

今回はtestsテーブルにじゃんじゃん書いていく。id=1をひたすら更新したいという場合はupdateOrCreateでもいい、かも

Route::post('/test', function (Request $request): RedirectResponse
{
    // apiの送信
    $client = new Client();
    $data = $request->all();
    try {
        $response = $client->post('https://5bxynejh3h.execute-api.ap-northeast-1.amazonaws.com/test', [
            'json' => $data  // リクエストボディとしてJSONを送信
        ]);

        $statusCode = $response->getStatusCode();  // HTTPステータスコード
        $headers = $response->getHeaders();  // レスポンスヘッダー
        $body = $response->getBody()->getContents();  // レスポンスボディ
        // Test::updateOrCreate(['id' => 1], ['data' => $body]); // ID1をひたす>
ら更新
        Test::create(['data' => $body]);
        return redirect()->back();

    } catch (GuzzleException $e) {
        // 例外処理
        dd([
            'error_message' => $e->getMessage()
        ]);
    }
})->name('test.api');

取り出し

use App\Models\Test;
Route::get('/test', function () {
    // フォームの提示
    $testData = Test::latest()->get();
    return Inertia::render('Test', ['testData' => $testData]);
});

的な、なお

app/Models/Test.php

class Test extends Model
{
    use HasFactory;

    protected $fillable = ['data'];

    protected $casts = [
        'data' => 'array',
    ];
}

json型をphpで使う場合このようにarrayのcastを書いておく必要がある(必要があるというか、これがないと面倒くさい)

viewを書く

resources/js/Pages/Test.jsx

import React, { useState, useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import PrimaryButton from '@/Components/PrimaryButton'
import { Head, useForm } from '@inertiajs/react';

import Checkbox from '@/Components/Checkbox'
import InputError from '@/Components/InputError'
import InputLabel from '@/Components/InputLabel'

export default function ApiTestForm({ testData }) {
  const { data, setData, post, processing, errors, reset } = useForm({
    flag1: false,
  });

  const submit = (e) => {
    e.preventDefault();
    post(route('test.api'))
  }

  return (
    <GuestLayout>
      <Head title="Api Gateway テストページ" />

      <form onSubmit={submit}>
        <div className="block mt-4">
          <label className="flex items-center">
            <Checkbox
              name="flag1"
              checked={data.flag1}
              onChange={(e) => setData('flag1', e.target.checked)}
            />
            <span className="ml-2 text-sm text-gray-600">Flag1</span>
          </label>
        </div>
        <div className="flex items-center justify-end mt-4">
          <PrimaryButton className="ml-4" disabled={processing}>
            APIテスト
          </PrimaryButton>
        </div>
      </form>

      <div className="min-w-full bg-white shadow rounded-lg overflow-hidden">
        <table className="min-w-full bg-white">
          <thead>
            <tr className="w-full h-16 border-gray-300 border-b py-8">
              <th className="pl-14 text-gray-600 text-left text-sm leading-4 tracking-wider">ID</th>
              <th className="text-gray-600 text-left text-sm leading-4 tracking-wider">Flag1</th>
            </tr>
          </thead>
          <tbody>
            {testData.map((item, index) => (
              <tr key={item.id} className="h-20 border-gray-300 border-b">
                <td className="pl-14 text-sm leading-4 text-blue-500 tracking-wider">{item.id}</td>
                <td className="text-sm leading-4 tracking-wider">{item.data.flag1 ? 'True' : 'False'}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </GuestLayout>
  )
}


ま、デザインはともかく、正しく取得できてはいるようだ。

まとめ

とまあこのように値を送信し、api gatewayでそれを受信してそのまま投げ返す場合、あとはどんどん生やしていけばいいという感じである。ただ、値を送信したものをそのまま投げ返す事はほぼ無いだろうから、実際には整形済みデーター等を受け取る事になるだろう(終わり)

次回はflagのあるなしで読み込むファイルを変化させてその内容をloggingしたりしよう。

この記事が気に入ったらサポートをしてみませんか?