Open3

Hono 参考資料

masterakmasterak
import { Handlers } from "$fresh/server.ts";
import { setCookie } from "std/http/cookie.ts";

export const handler: Handlers = {
  async POST(req) {
    const url = new URL(req.url);
    const form = await req.formData();
    if (form.get("username") === "deno" && form.get("password") === "land") {
      const headers = new Headers();
      setCookie(headers, {
        name: "auth",
        value: "bar", // this should be a unique value for each session
        maxAge: 120,
        sameSite: "Lax", // this is important to prevent CSRF attacks
        domain: url.hostname,
        path: "/",
        secure: true,
      });

      headers.set("location", "/");
      return new Response(null, {
        status: 303, // "See Other"
        headers,
      });
    } else {
      return new Response(null, {
        status: 403,
      });
    }
  },
};

このコードは、HTTP レスポンスを作成し、ユーザーにリダイレクトする際にクッキーを設定する例です。以下に各部分の詳細な説明をします。

1. Headers オブジェクトの作成

const headers = new Headers();
  • 空の Headers オブジェクトを作成します。これに対して後で HTTP ヘッダーを追加します。
  • Headers オブジェクトは、HTTP リクエストやレスポンスに関連するヘッダーを操作するためのインターフェイスです。

2. クッキーの設定

setCookie(headers, {
  name: "auth",
  value: "bar", // this should be a unique value for each session
  maxAge: 120,
  sameSite: "Lax", // this is important to prevent CSRF attacks
  domain: url.hostname,
  path: "/",
  secure: true,
});
  • setCookie 関数を使って、クッキーを設定します。クッキーの詳細は以下の通りです:
    • name: "auth": クッキーの名前は "auth" です。
    • value: "bar": クッキーの値は "bar" です(セッションごとに一意であるべき)。
    • maxAge: 120: クッキーの有効期間は 120 秒です。
    • sameSite: "Lax": クッキーは SameSite 属性を "Lax" に設定しています。これにより、CSRF 攻撃を防ぎます。
    • domain: url.hostname: クッキーのドメインを url.hostname に設定します。
    • path: "/": クッキーのパスをルートに設定します。
    • secure: true: クッキーは HTTPS 接続でのみ送信されます。

3. リダイレクトヘッダーの設定

headers.set("location", "/");
  • location ヘッダーを "/" に設定します。これにより、クライアントはルートパスにリダイレクトされます。
  • リダイレクト先を変更する場合はここを編集します。

4. HTTP レスポンスの作成と返却

return new Response(null, {
  status: 303, // "See Other"
  headers,
});
  • Response オブジェクトを作成します。ボディは null です。
  • HTTP ステータスコードは 303 ("See Other") に設定されます。これは、クライアントに他の URI を取得するよう指示するリダイレクトコードです。
  • 先ほど設定した headers をレスポンスに追加します。

全体の流れ

  1. 新しいヘッダーオブジェクトを作成します。
  2. クッキーを設定し、その設定をヘッダーに追加します。
  3. location ヘッダーを設定し、リダイレクト先を指定します。
  4. HTTP レスポンスを作成し、ステータスコード 303 でリダイレクトを指示し、設定したヘッダーを含めてレスポンスを返します。

このコードは、ユーザーが特定の URL にアクセスしたときに、クッキーを設定してから別のページにリダイレクトするシナリオを実現するためのものです。クッキーの設定には、セキュリティとセッション管理のための重要な属性が含まれています。

pretyJsonについて

Pretty JSON middleware enables "JSON pretty print" for JSON response body. Adding ?pretty to url query param, the JSON strings are prettified.

つまりPretty JSON Middleware を使用すると、URLのクエリパラメータに ?pretty を追加することで、JSONレスポンスを整形された(見やすい)形式で返すことができます。

通常、APIからのJSONレスポンスはデータ転送の効率を上げるために、空白や改行を省略したコンパクトな形式で送信されます。Pretty JSON Middleware を使うと、デバッグや開発時にそのJSONレスポンスを整形して見やすくすることができます。

具体例

通常のJSONレスポンス

{"name":"John","age":30,"city":"New York"}

?prettyを使用した場合の整形されたJSONレスポンス

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

使用方法

URLに ?pretty を追加するだけです。

整形されたJSONレスポンスを取得するリクエスト

curl http://localhost:8787/api/posts?pretty
masterakmasterak

HonoでZennのRSSデータを取得して、パース。ほとんどAIが書いてくれた。
そのままだと多分使えないので、事前にキャッシュしておく方法を考える。
今回の教訓は、正規表現の使い方が未だによくわからないマンだけどやっぱり便利ということを再認識した。

import { Hono } from 'hono'

const app = new Hono();

app.get('/', async (c) => {
  
  const res = await fetch('https://zenn.dev/catnose99/feed');
  const rss = await res.text();
  try {
    const json = await parseRSStoJson(rss);
    return c.json(json);
  } catch (e) {
    console.log(e)
    return c.text("Not Found");
  };
})

export default app;

type RSSItem = {
  title: string;
  link: string;
  pubDate: string;
}

async function parseRSStoJson(text:string): Promise<RSSItem[]> {
  const items:RSSItem[] = [];
  // <items>の検索・正規表現
  const itemsRegex = /<item>([\s\S]*?)<\/item>/g; 
  let match;
  while((match = itemsRegex.exec(text)) !== null) {
    const itemContent = match[1];
    const titleMatch = itemContent.match(/<title>([\s\S]*?)<\/title>/);
    const linkMatch = itemContent.match(/<link>([\s\S]*?)<\/link>/);
    const pubDateMatch = itemContent.match(/<pubDate>([\s\S]*?)<\/pubDate>/);

    // CDATAセクションを削除
    let title = titleMatch ? titleMatch[1].trim() : '';
    title = title.replace(/<!\[CDATA\[(.*?)\]\]>/, '$1');

    // Date型に変換
    let pubDate = pubDateMatch ? pubDateMatch[1].trim() : '';
    const date = new Date(pubDate);
    pubDate = `${date.getFullYear()}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}`;

    items.push({
      title,
      link: linkMatch ? linkMatch[1].trim() : '',
      pubDate,
    });
  }
  return items;
};