# HTTP コンテクスト

`@lazarv/react-server` を使用すると、サーバーサイドレンダリングのコンテクストに関連するすべてにアクセスできます。このガイドでは、HTTPコンテクストにアクセスする方法について説明します。

これから紹介するものは、HTTPコンテクストにアクセスして操作するためのフックまたは関数です。

これらの関数はすべて、ミドルウェアやファイルシステムベースのルーターを使用したルートハンドラーでも利用可能です。これらはランタイム固有であり、Reactとは関係ありません。

`useHttpContext()` を使用すると、完全なHTTPコンテクストにアクセスできます。

```jsx
import { useHttpContext } from "@lazarv/react-server";

export default function MyComponent() {
  const context = useHttpContext();

  return (
    <div>
      <p>Method: {context.request.method}</p>
      <p>URL: {context.request.url.toString()}</p>
      <p>Request Headers: {JSON.stringify(context.request.headers)}</p>
      <p>Request Cookies: {JSON.stringify(context.request.cookie)}</p>
    </div>
  );
};
```

## リクエスト

`useRequest()` を使用すると、完全なHTTPリクエストにアクセスできます。

```jsx
import { useRequest } from "@lazarv/react-server";

export default function MyComponent() {
  const request = useRequest();

  return (
    <div>
      <p>Method: {request.method}</p>
      <p>URL: {request.url.toString()}</p>
      <p>Headers: {JSON.stringify(request.headers)}</p>
      <p>Cookies: {JSON.stringify(request.cookie)}</p>
    </div>
  );
};
```

## レスポンス

`useResponse()` を使用すると、完全なHTTPレスポンスにアクセスできます。これは、レスポンスがクライアントに送信された後、サスペンドされ、後でクライアントにストリーミングされたReactコンポーネント内でのみ利用可能です。

```jsx
import { useResponse } from "@lazarv/react-server";

export default async function MyComponent() {
  const response = await useResponse();

  return (
    <div>
      <p>Headers: {JSON.stringify(response.headers)}</p>
      <p>Cookies: {JSON.stringify(response.cookie)}</p>
    </div>
  );
};
```

## URL

`useUrl()` を使用すると、現在のリクエストのURLにアクセスできます。

```jsx
import { useUrl } from "@lazarv/react-server";

export default function MyComponent() {
  const url = useUrl();

  return <p>URL: {url.href}</p>;
};
```

## パス名

`usePathname()` を使用すると、現在のリクエストのパス名にアクセスできます。

```jsx
import { usePathname } from "@lazarv/react-server";

export default function MyComponent() {
  const pathname = usePathname();

  return <p>Pathname: {pathname}</p>;
};
```

## 検索パラメータ

`useSearchParams()` を使用すると、現在のリクエストの検索パラメータにアクセスできます。これは、検索パラメータのキーと値のペアを持つオブジェクトです。同じキーに複数の値がある場合、その値は配列になります。

```jsx
import { useSearchParams } from "@lazarv/react-server";

export default function MyComponent() {
  const searchParams = useSearchParams();

  return (
    <p>Search params: {JSON.stringify(searchParams)}</p>
  );
};
```

## ヘッダー

`headers()` を使用すると、現在のリクエストのヘッダーにアクセスできます。

```jsx
import { headers } from "@lazarv/react-server";

export default function MyComponent() {
  const requestHeaders = headers();

  return <p>Headers: {JSON.stringify(requestHeaders)}</p>;
};
```

キーと値のペアのオブジェクトを渡すことで、現在のレスポンスのヘッダーを変更することもできます。

```jsx
import { headers } from "@lazarv/react-server";

export default function MyComponent() {
  headers({
    "X-My-Header": "My value",
  });

  return <p>Headers: {JSON.stringify(headers())}</p>;
};
```

または、`Headers` オブジェクトを渡すことでも変更できます。

```jsx
import { headers } from "@lazarv/react-server";

export default function MyComponent() {
  headers(new Headers({
    "X-My-Header": "My value",
  }));

  return <p>Headers: {JSON.stringify(headers())}</p>;
};
```

または、キーと値のペアの配列を渡すことでも変更できます。

```jsx
import { headers } from "@lazarv/react-server";

export default function MyComponent() {
  headers([
    ["X-My-Header", "My value"],
  ]);

  return <p>Headers: {JSON.stringify(headers())}</p>;
};
```

`headers()` 関数を使用してヘッダーを変更すると、現在のレスポンスのヘッダーが上書きされます。レスポンスヘッダーを直接変更したい場合は、ヘッダーを設定、追加、削除するための3つの補助関数を使用できます。これらの関数は `setHeader()`、`appendHeader()`、および `deleteHeader()` です。

```jsx
import { setHeader, appendHeader, deleteHeader } from "@lazarv/react-server";

export default function MyComponent() {
  setHeader("X-My-Header", "My first value");
  appendHeader("X-My-Header", "My second value");
  deleteHeader("X-My-Header");

  return <p>Check the response headers!</p>;
}
```

> **注意:** HTTPヘッダーは大文字と小文字を区別しないことに注意してください！

## クッキー

`cookie()` を使用すると、現在のリクエストのクッキーにアクセスできます。

```jsx
import { cookie } from "@lazarv/react-server";

export default function MyComponent() {
  const requestCookies = cookie();

  return <p>Cookies: {JSON.stringify(requestCookies)}</p>;
};
```

また、現在のレスポンスのコンテクストでクッキーを設定または削除することもできます。

```jsx
import { setCookie, deleteCookie } from "@lazarv/react-server";

export default function MyComponent() {
  setCookie("my-cookie", "my-value");
  deleteCookie("other-cookie");

  return <p>Cookies: {JSON.stringify(cookie())}</p>;
};
```

## ステータス

`status()` を使用すると、現在のレスポンスのステータスコードとテキストを設定できます。

```jsx
import { status } from "@lazarv/react-server";

export default function MyComponent() {
  status(404, "Not found");

  return <p>Not Found</p>;
};
```

## フォームデータ

`useFormData()` を使用すると、現在のリクエストのフォームデータにアクセスできます。

```jsx
import { useFormData } from "@lazarv/react-server";

export default function MyComponent() {
  const formData = useFormData();

  return (
    <p>Form data: {JSON.stringify(Object.fromEntries(formData.entries()))}</p>
  );
};
```

## リダイレクト

`redirect()` を使用すると、現在のリクエストを別のURLにリダイレクトできます。

> **警告:** `redirect()` 関数はエラーをスローし、ランタイムがそれをキャッチしてリダイレクトを実行します。`try`/`catch` ブロック内で `redirect()` を使用する場合、リダイレクトエラーであれば再スローすることを確認してください。

```jsx
import { redirect } from "@lazarv/react-server";

export default function MyComponent() {
  redirect("https://example.com");
};
```

`redirect()` 関数は、クライアントでのリダイレクトの動作を制御するオプションの第3引数 `kind` を受け付けます。利用可能な種類は以下の通りです：

| 種類 | 説明 |
| --- | --- |
| `"navigate"` | **（デフォルト）** `replaceState` を使用したRSCナビゲーションを実行します。ブラウザのURLは変更されますが、履歴エントリは追加されません。 |
| `"push"` | `pushState` を使用したRSCナビゲーションを実行します。ブラウザのURLが変更され、新しい履歴エントリが追加されるため、ユーザーは戻るボタンで戻ることができます。 |
| `"location"` | `location.href` を使用した完全なブラウザナビゲーションを強制します。外部URLへのリダイレクトやページの完全なリロードが必要な場合に便利です。 |
| `"error"` | ナビゲーションの代わりにクライアントでリダイレクトエラーをスローします。サーバーアクション呼び出しで `try`/`catch` によるカスタム処理が可能になります。 |

```jsx
import { redirect } from "@lazarv/react-server";

// pushStateを使用したRSCナビゲーション（履歴エントリを追加）
redirect("/dashboard", 302, "push");

// 完全なブラウザナビゲーション
redirect("/oauth/authorize", 302, "location");

// カスタム処理のためにクライアントでスロー
redirect("/login", 302, "error");
```

サーバーアクションで `"error"` 種類を使用する場合、クライアントでリダイレクトエラーをキャッチして処理できます：

```jsx
"use client";

import { myServerAction } from "./actions";

export function MyComponent() {
  const handleClick = async () => {
    try {
      await myServerAction();
    } catch (e) {
      if (e?.digest?.startsWith("Location=")) {
        const url = e.digest.split("Location=")[1]?.split(";")[0];
        console.log(`リダイレクト先: ${url}`);
      }
    }
  };

  return <button onClick={handleClick}>送信</button>;
}
```

## リライト

`rewrite()` を使用すると、現在のリクエストを別のURLにリライトできます。これは、ミドルウェア関数内で現在のリクエストのURLパス名を変更するのに便利です。

```jsx
import { rewrite, useUrl } from "@lazarv/react-server";

export function init$() {
  return async () => {
    const { pathname } = useUrl();

    if (pathname === "/old-pathname") {
      rewrite("/new-pathname");
    }
  };
}

export default function MyComponent() {
  const { pathname } = useUrl();

  return <p>Current pathname: {pathname}</p>;
}
```

## アウトレット

`useOutlet()` を使用すると、現在のリクエストのアウトレットにアクセスできます。これは、現在のリクエストがレンダリングされているアウトレットの名前を取得するのに便利です。

```jsx
import { useOutlet } from "@lazarv/react-server";

export default function MyComponent() {
  const outlet = useOutlet();

  return <p>Outlet: {outlet}</p>;
}
```

## レンダーロック

`useRender()` を使用すると、現在のリクエストのレンダーロックにアクセスできます。これは、非同期関数が実行されている間、またはロックが解除されるまで、React Server Componentのレンダリングをロックしたい場合に便利です。なぜならReact Server Componentはデフォルトでストリーミングを使用してレンダリングされるからです。特にHTTPヘッダーやクッキーを非同期のReact Server Componentで処理する場合に役立ちます。レンダリングをロックしないと、非同期関数が終了する前にヘッダーやクッキーがクライアントに送信されてしまいます。レンダリングプロセスでロックが検出されると、レンダリングはロックが解除されるまで待機し、ヘッダーやクッキーの送信を開始してからReact Server Componentのストリーミングを開始します。

```jsx
import { headers, useRender } from "@lazarv/react-server";

export default function MyComponent() {
  const { lock } = useRender();

  await lock(async () => {
    // Do something async
    await new Promise((resolve) => setTimeout(resolve, 1000));
    headers({
      "x-lock": "works",
    });
  });

  return <p>Render lock</p>;
}
```

`lock()` 関数を使用して、後でロックを解除するための `unlock()` 関数を取得することもできます。

```jsx
import { headers, useRender } from "@lazarv/react-server";

export default function MyComponent() {
  const { lock } = useRender();

  const unlock = lock();
  // Do something async
  await new Promise((resolve) => setTimeout(resolve, 1000));
  headers({
    "x-lock": "works",
  });
  unlock();

  return <p>Render lock</p>;
}
```

## ロガー

`logger` を使用すると、ランタイムの組み込みロガーを使ってメッセージをログに記録できます。`logger` オブジェクトは `info`、`warn`、`error`、`debug` メソッドを提供し、ランタイムのロギングシステムと統合されて、一貫したフォーマットの出力を提供します。

```jsx
import { logger } from "@lazarv/react-server";

export default function MyComponent() {
  logger.info("Rendering MyComponent");

  return <p>Hello World</p>;
}
```

`logger` は開発モードではランタイムのVite統合ロガーを自動的に使用してきれいにフォーマットされた出力を提供し、プロダクションでは `console` にフォールバックします。コンテキストを認識するため、`after()` コールバック内で呼び出された場合、ログ出力に `(after)` ラベルが付加され、レスポンス後のログとレンダリングログを区別できます。

```jsx
import { after, logger } from "@lazarv/react-server";

export default function MyComponent() {
  logger.info("Rendering component");

  after(() => {
    logger.info("Response sent"); // 開発モードでは (after) ラベル付きでログ出力
  });

  return <p>Hello World</p>;
}
```

利用可能なメソッド：

| メソッド | 説明 |
|---|---|
| `logger.info(msg, ...args)` | 情報メッセージをログに記録 |
| `logger.warn(msg, ...args)` | 警告メッセージをログに記録 |
| `logger.error(msg, ...args)` | エラーメッセージまたは `Error` オブジェクトをログに記録 |
| `logger.debug(msg, ...args)` | デバッグメッセージをログに記録 |

> **Note:** `logger` はサーバー上のどこでも使用できます — コンポーネント、サーバー関数、ミドルウェア、ルートハンドラ、ワーカー、`after()` コールバック内で利用可能です。リクエストコンテキストは必須ではありませんが、利用可能な場合はコンテキスト固有のロガーインスタンスを使用します。

## After

`after()` を使用すると、**レスポンスがクライアントに送信された後**に実行されるコールバック関数を登録できます。これは、クリーンアップタスク、ロギング、アナリティクス、またはレスポンスを遅延させるべきではない副作用を実行するのに便利です。

```jsx
import { after, logger } from "@lazarv/react-server";

export default function MyComponent() {
  after(() => {
    logger.info("Response sent to client.");
  });

  return <p>Hello World</p>;
}
```

`after()` フックは複数回呼び出して複数のコールバックを登録できます。登録されたすべてのコールバックは、レスポンスストリームが完了した後に `Promise.allSettled` を介して並行して実行されるため、1つのコールバックが失敗しても他のコールバックの実行は妨げられません。リクエストがエラーで失敗した場合、エラーは最初の引数として各コールバックに渡されます：

```jsx
import { after, logger } from "@lazarv/react-server";

export default function MyComponent() {
  after((error) => {
    if (error) {
      logger.error("Request failed:", error.message);
    } else {
      logger.info("Request completed successfully");
    }
  });

  return <p>Hello World</p>;
}
```

```jsx
import { after } from "@lazarv/react-server";

export default function MyComponent() {
  after(async () => {
    await saveAnalytics({ page: "/home", timestamp: Date.now() });
  });

  after(async () => {
    await cleanupTempFiles();
  });

  return <p>Home</p>;
}
```

サーバー関数、ミドルウェア、ルートハンドラ、またはリクエストコンテキスト内で実行されるサーバーサイドコードでも `after()` を使用できます：

```jsx
import { after } from "@lazarv/react-server";

export async function submitForm(formData) {
  "use server";

  const data = Object.fromEntries(formData.entries());
  await saveToDatabase(data);

  after(async () => {
    await sendNotificationEmail(data.email);
  });
}
```

> **Note:** `after()` フックはリクエスト中にのみ呼び出すことができます。リクエストコンテキスト外（モジュールスコープやスタンドアロンスクリプトなど）で呼び出すとエラーがスローされます。