POSTにすべきところをGETにしてハマった話

POSTにすべきところをGETにしてハマった話

投稿日: 2024年11月02日

学習振り返り
要約
  • POSTメソッドを使用すべきところでGETメソッドを使ったため、キャッシュによる問題が発生し、LINE通知が届かなかった。
  • GETメソッドはデータ取得に適しているが、サーバーの状態を変更する場合はPOSTメソッドを使用するべきである。
  • 最終的にPOSTメソッドに変更することで問題が解決し、定期的にLINE通知を送信する機能が正常に動作するようになった。

どうも、しくじり先生の吉本です。

盛大にしくじったので私みたいにならないでという気持ちを込めて起こったこと(自分で起こしたことですがw)を共有します。

テーマ

POSTメソッドであるべきところでGETメソッドを使っていたため、キャッシュされたレスポンスが返ってきて内部の処理が意図した通りに行われなかった

処理内容

今回用意していたエンドポイントはブラウザ操作などでフロント側から呼び出さずに定期的に実行する専用のもので、ザっと内容は

  1. 通知を受けたい日時が入ったカラム(Date)が現在の時間の一定範囲内(YYYY-MM-DD HH:00:00~YYYY-MM-DD HH:59:59)に登録されいるデータを探す
  2. 1件もなければ処理終了
  3. LINEメッセージ送信
  4. LINEから返ってきたメッセージをレスポンスに含めて返す

タスク管理のアプリで、予定のある日のN日前(number)、0-23時(select box)の設定が何個でも出来るようにしています。

例えば、

  • 0日前/6時→予定日の当日朝LINE通知が届く
  • 1日前/20時→前日の夜20時頃LINE通知が届く

複数登録もOKという感じです。

メソッド

GETを使っていました。これが間違いだったところです・・

私もパッと考えるのはPOSTだったんですが、別にリクエストボディで受け取りたいものがない。

クエリパラメータに入れたいものも特にないけど。

リクエストボディがいらないならGETでいいのかなと思ってGETにしちゃったんです。

叩いてさえもらえたらあとは中で処理するだけだからいいと思ったんです。

POSTMANでたたいた時は問題なかったです。キャッシュとかないからでしょうね・・

多分今回上手くいかなかったのはvercelの仕様とかの問題もあるのかもと思います。

でも素直にPOSTにしておけばよかった・・

GETって言ってんだから。データの取得でしょうよ、普通。。

普通はPOSTなのわかってるのに謎の好奇心でGETにしたのがおバカでした。

気になりますよね、、、

GETとPOSTの違い

AI先生に聞いてきました。

GETメソッド

1. 目的: GETメソッドは、サーバーからデータを取得するために使用されます。例えば、Webページを開くときに、そのページのデータを取得するためにGETリクエストが使われます。

2. データの送信方法: データはURLの一部として送信されます。例えば、`http://example.com/page?name=John&age=30`のように、クエリパラメータとしてデータがURLに含まれます。

3. データの可視性: URLにデータが含まれるため、送信されたデータは誰でも見ることができます。したがって、GETメソッドは機密情報の送信には適していません。

4. キャッシュ: GETリクエストはキャッシュされやすく、ブックマークも可能です。これは、同じリクエストを何度も行う場合に便利です。

5. データ量の制限: URLの長さに制限があるため、送信できるデータ量にも制限があります。通常、URLは2048文字以内に収める必要があります。

POSTメソッド

1. 目的: POSTメソッドは、サーバーにデータを送信して何らかの処理を行うために使用されます。例えば、フォームに入力した情報をサーバーに送信する場合などです。

2. データの送信方法: データはHTTPリクエストのボディ部分に含まれて送信されます。URLには表示されないため、GETよりも多くのデータを送信できます。

3. データの可視性: URLにデータが含まれないため、GETに比べて送信データが見えにくく、機密情報の送信に適しています。ただし、暗号化されていない場合は、依然として安全とは言えません。

4. キャッシュ: POSTリクエストはキャッシュされないのが一般的で、ブックマークもできません。通常、データの送信やデータベースの更新など、状態を変える操作に使用されます。

5. データ量の制限: GETに比べて大きなデータを送信可能です。具体的な制限はサーバーの設定によりますが、通常はGETよりも大容量のデータを扱えます。

サーバー上のデータや状態を変更しない場合はGETでいいけど、サーバー上のデータや状態を変更したいならPOSTです。

起こったこと

Github Actionsで定期実行(1時間に1回)して、ログ上もちゃんと実行してレスポンスが返ってきているけどLINE通知が届かなかったです。

vercelのログにはリクエストがあったログが出ていませんでした。

なのにgithubのログでは成功していることになっている・・なんで・・・

失敗したなら失敗したっていってください!!(号泣)」でした。

いや、失敗してないんですけどね。プログラムした通りに処理されていますよね。

レスポンスかえって来てるから。データの取得が目的のmethodでこの挙動は適切ですよね。

私はそれが欲しいわけじゃなかったけど。

おかしいなと思ってたのが、通知した時に発行されるIDが一意のもののはずなのに同じだったんです。

通知きてないから発行されてないのも納得なんですけど・・

気付いてみるとそういうことか・・だったのですがなかなか気付かず。

・・で2日経過しました。

色んな事を試しましたが何やっても上手くいかなかったです。

キャッシュがどうのってのも見つけたのでvercel.jsonでキャッシュの無効化も設定したんですが、効果なしでした。

そんな中ふと思い浮かんだ「キャッシュ・・GET・・?まさかPOST・・?」みたいな。

試したら一発解決!!無事に完成しました。

コード

話のテーマに関係ないですが、参考になるかわからないですけどそのエンドポイントのコードは一応貼っていきますね。

コードレビュー受けていないコードなので綺麗かどうかはわかりません。へーくらいにしてください。

import { NextResponse } from "next/server";
import { buildPrisma } from "@/app/_utils/prisma";
import { dayjs } from "@/app/_utils/dayjs";
import { messagePush } from "./_utils/messagePush";

export const POST = async () => {
  const prisma = await buildPrisma();
  const now = dayjs().tz("Asia/Tokyo");

  const startOfHour = now.startOf("hour").toDate();
  const endOfHour = now.endOf("hour").toDate();

  try {
    const schedules = await prisma.schedule.findMany({
      where: {
        datetime: {
          gte: startOfHour,
          lte: endOfHour,
        },
      },
      include: {
        notification: {
          include: {
            task: {
              include: {
                roomTasks: {
                  include: {
                    room: true,
                    task: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    if (!schedules || schedules.length === 0) {
      return NextResponse.json({ message: "data is null" }, { status: 200 });
    }
    const groupedRoomsTasks = schedules
      .map(schedule =>
        schedule.notification.task.roomTasks.map(roomTask => ({
          lineId: roomTask.room.lineId,
          taskId: roomTask.task.id,
          task: roomTask.task.task,
          date: dayjs(roomTask.task.date).format("YYYY/MM/DD"),
        }))
      )
      .flat()
      .reduce((acc, { lineId, taskId, task, date }) => {
        if (!acc[lineId]) {
          acc[lineId] = { lineId, data: {} };
        }
        if (!acc[lineId].data[taskId]) {
          acc[lineId].data[taskId] = { task, date };
        }
        return acc;
      }, {} as Record<string, { lineId: string; data: Record<string, { task: string; date: string }> }>);

    const result = await messagePush(
      Object.values(groupedRoomsTasks).map(room => ({
        lineId: room.lineId,
        data: Object.values(room.data),
      }))
    );

    return NextResponse.json({ message: "success", result }, { status: 200 });
  } catch (e) {
    console.log(e);
    if (e instanceof Error) {
      return NextResponse.json({ error: e.message }, { status: 400 });
    }
  }
};
import { fetcher } from "../../_utils/fetcher";
import { RoomsTasks } from "../_types/RoomsTasks";
import { PushResponse, sentMessage } from "../_types/PushResponse";
import { PushRequest } from "../_types/PushRequest";
export const messagePush = async (roomsTasks: RoomsTasks[]) => {
  const resposeMessages: sentMessage[] = [];
  const endpoint = "https://api.line.me/v2/bot/message/push";
  try {
    for (const roomTask of roomsTasks) {
      const text = roomTask.data
        .map(task => `日付:${task.date}\n予定:${task.task}\n`)
        .join("\n");
      const options = {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.CHANNEL_ACCESS_TOKEN}`,
        },
        body: {
          to: roomTask.lineId,
          messages: [
            {
              type: "text",
              text,
            },
          ],
        },
      };
      //1つ失敗しても次のline通知したいのでここでもtry-catchいれとく
      try {
        const resp: PushResponse = await fetcher<PushResponse, PushRequest>(
          endpoint,
          options
        );
        if (resp.sentMessages.length > 0) {
          const { id, quoteToken } = resp.sentMessages[0];
          resposeMessages.push({ id, quoteToken });
        }
      } catch (e) {
        console.error(e);
      }
    }
  } catch (e) {
    console.error(e);
  }
  return resposeMessages;
};

この処理でGETはないわ・・

つまらないことで時間を溶かした1週間でした。

無事に1時間に1回実行して、予定を登録した日時にリマインドしてくれるLINEのbot(with Poké API)みたいなのが出来ました!!

おわりに

ぜひ反面教師にしてください。

謎の好奇心は封印してこれからは普通にしようと思います。

シェア!

Threads
user
吉本茜
山口在住/二児の母/育休中
Loading...
記事一覧に戻る
Threads
0