# リクエスト

Source: https://docs.sisk-framework.org/ja/docs/fundamentals/requests.html

Requests は HTTP リクエストメッセージを表す構造体です。 [HttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.md) オブジェクトは、アプリケーション全体で HTTP メッセージを処理するための便利な機能を提供します。

HTTP リクエストは、メソッド、パス、バージョン、ヘッダー、ボディで構成されます。

このドキュメントでは、これらの要素を取得する方法を解説します。

## リクエストメソッドの取得

受信したリクエストのメソッドを取得するには、Method プロパティを使用します:

```cs
static HttpResponse Index(HttpRequest request)
{
    HttpMethod requestMethod = request.Method;
    ...
}
```

このプロパティは、リクエストのメソッドを表す [HttpMethod](https://learn.microsoft.com/pt-br/dotnet/api/system.net.http.httpmethod) オブジェクトを返します。

> [!NOTE]
> ルートメソッドとは異なり、このプロパティは [RouteMethod.Any](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md) アイテムを提供しません。その代わりに、実際のリクエストメソッドを返します。

## リクエスト URL コンポーネントの取得

リクエストの特定のプロパティを使用して、URL からさまざまなコンポーネントを取得できます。例として、次の URL を考えてみましょう。

```
http://localhost:5000/user/login?email=foo@bar.com
```

| コンポーネント名 | 説明 | コンポーネント値 |
| --- | --- | --- |
| [Path](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Path.md) | リクエストのパスを取得します。 | `/user/login` |
| [FullPath](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullPath.md) | リクエストのパスとクエリ文字列を取得します。 | `/user/login?email=foo@bar.com` |
| [FullUrl](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.FullUrl.md) | リクエスト全体の URL 文字列を取得します。 | `http://localhost:5000/user/login?email=foo@bar.com` |
| [Host](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Host.md) | リクエストのホストを取得します。 | `localhost` |
| [Authority](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Authority.md) | リクエストのホストとポートを取得します。 | `localhost:5000` |
| [QueryString](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.QueryString.md) | リクエストのクエリ文字列を取得します。 | `?email=foo@bar.com` |
| [Query](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Query.md) | 名前付き値コレクションとしてリクエストのクエリを取得します。 | `{StringValueCollection object}` |
| [IsSecure](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsSecure.md) | リクエストが SSL を使用しているかどうかを判定します（true = 使用、false = 未使用）。 | `false` |

また、[HttpRequest.Uri](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Uri.md) プロパティを使用すれば、上記すべてを 1 つのオブジェクトで取得できます。

## リクエストメタデータとキャンセル

Sisk は各リクエストに運用メタデータも付与します。これらのプロパティは、ログ、トレース、ローカリゼーション、診断、長時間実行される操作に役立ちます。

| プロパティまたはメソッド | 用途 |
| --- | --- |
| [RequestId](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestId.md) | リクエストの一意な識別子です。[IncludeRequestIdHeader](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.IncludeRequestIdHeader.md) を有効にすると `X-Request-Id` ヘッダーとして返されます。 |
| [RequestedAt](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestedAt.md) | Sisk がリクエストオブジェクトを作成した時点です。 |
| [RemoteAddress](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RemoteAddress.md) | 接続から解決されたクライアントアドレス、または [ForwardingResolver](https://docs.sisk-framework.org/ja/docs/advanced/forwarding-resolvers.md) から取得されたアドレスです。 |
| [Culture](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Culture.md) | `Accept-Language` から解決された最適なカルチャで、取得できない場合は現在のカルチャが使用されます。 |
| [DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) | クライアントが切断されたときに発行されるキャンセルトークンです（設定された HTTP エンジンがサポートしている場合）。 |
| [Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) | リクエストハンドラやルートアクション間で共有される型付きキー/バリュー ストアです。 |
| [GetRawHttpRequest](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRawHttpRequest.md) | 診断用のリクエストのテキスト表現です。 |

## リクエストボディの取得

リクエストには、フォームやファイル、API 取引などのボディが含まれることがあります。ボディは以下のプロパティから取得できます。

```cs
// リクエストのエンコーディングを使用して、リクエストボディを文字列として取得します
string body = request.Body;

// またはバイト配列として取得します
byte[] bodyBytes = request.RawBody;

// あるいはストリームとして取得できます。
Stream requestStream = request.GetRequestStream();

// 非同期でボディを読み取ることもできます
Memory<byte> bodyMemory = await request.GetBodyContentsAsync();
```

リクエストにボディが存在し、ロードされているかどうかは、[HasContents](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.HasContents.md) プロパティ（リクエストにコンテンツがあるかを判定）および [IsContentAvailable](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.IsContentAvailable.md) プロパティ（リモート側からコンテンツが完全に受信されたかを示す）で確認できます。

`GetRequestStream` でリクエストコンテンツを複数回読み取ることはできません。このメソッドで読み取った場合、`RawBody` と `Body` の値も利用できなくなります。リクエストストリームは、作成された HTTP セッションの終了時に自動的に破棄されるため、明示的に破棄する必要はありません。また、[HttpRequest.RequestEncoding](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RequestEncoding.md) プロパティを使用すれば、リクエストを手動でデコードする際の最適なエンコーディングを取得できます。

サーバーはリクエストコンテンツの読み取りに制限を設けており、これは [HttpRequest.Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) と [HttpRequest.RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md) の両方に適用されます。これらのプロパティは、入力ストリーム全体を [HttpRequest.ContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.ContentLength.md) と同サイズのローカルバッファにコピーします。

リクエストコンテンツは [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md) によって制限されます。この設定は Sisk 1.7 以降デフォルトで 100 MB です（以前のバージョンでは `0`（無制限）でした）。制限は以下のように適用されます：

- `Content-Length` が上限を超えているリクエストは、ルートが呼び出される前に `413 Content Too Large` 応答が返されます。
- 長さが宣言されていないチャンク化またはストリーミングされたボディは、読み取り中にチェックされます。上限を超えて [RawBody](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.RawBody.md)、[Body](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Body.md)、または [GetRequestStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetRequestStream.md) が返すストリームを読み込むと、[InsufficientMemoryException](https://learn.microsoft.com/en-us/dotnet/api/system.insufficientmemoryexception) がスローされ、コードまたは [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) が処理しない限り `500 Internal Server Error` が返されます。
- 同じ上限が受信した [WebSocket](https://docs.sisk-framework.org/ja/docs/features/websockets.md) メッセージの最大サイズにも適用されます。

プロパティを `0` に設定すると、制限が解除されます:

```cs
using var app = HttpServer.CreateBuilder()
    .UseConfiguration(config => config.MaximumContentLength = 0)
    .Build();
```

設定された制限がなくても、`Body` と `RawBody` は [Array.MaxLength](https://learn.microsoft.com/en-us/dotnet/api/system.array.maxlength)（約 2 GB）を超えるコンテンツをバッファできず、アクセスしようとすると例外がスローされます。ストリーミングを使用すれば、より大きなコンテンツを扱うことが可能です。

> [!NOTE]
> Sisk が許可している場合でも、HTTP のセマンティクスに従ってアプリケーションを構築し、許可されていないメソッドでコンテンツを取得または提供しないことが常に推奨されます。[RFC 9110 "HTTP Semantics"](https://httpwg.org/spec/rfc9110.html) を参照してください。

## JSON リクエストの読み取り

JSON API では、`Body` を読み取って手動でデシリアライズする代わりに、組み込みの JSON ヘルパーを使用してください。これらは [System.Text.Json](https://learn.microsoft.com/en-us/dotnet/api/system.text.json) を使用し、デフォルトで [HttpRequest.DefaultJsonSerializerOptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DefaultJsonSerializerOptions.md) が適用されます。

```cs
public record CreateUserRequest(string Name, string Email);

router.MapPost("/users", (HttpRequest request) =>
{
    CreateUserRequest? body = request.GetJsonContent<CreateUserRequest>();
    if (body is null)
        return new HttpResponse(System.Net.HttpStatusCode.BadRequest);

    return new HttpResponse(System.Net.HttpStatusCode.Created);
});
```

非同期ルート内で使用するか、リクエストのキャンセルでデシリアライズを中止したい場合は、非同期オーバーロードを使用してください。

```cs
router.MapPost("/users", async (HttpRequest request) =>
{
    CreateUserRequest? body =
        await request.GetJsonContentAsync<CreateUserRequest>(request.DisconnectToken);

    if (body is null)
        return new HttpResponse(System.Net.HttpStatusCode.BadRequest);

    return new HttpResponse(System.Net.HttpStatusCode.Created);
});
```

特定のエンドポイント向けにカスタムの [JsonSerializerOptions](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions) を渡すこともできます。

```cs
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    PropertyNameCaseInsensitive = true
};

UserDto? user = request.GetJsonContent<UserDto>(options);
```

Native AOT やトリミングに敏感なアプリケーションでは、`JsonSerializerContext` が生成する `JsonTypeInfo<T>` オーバーロードを使用してください。

```cs
[JsonSerializable(typeof(CreateUserRequest))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{
}

CreateUserRequest? body =
    await request.GetJsonContentAsync(
        AppJsonSerializerContext.Default.CreateUserRequest,
        request.DisconnectToken);
```

JSON ヘルパーにも同じ一度だけ読み取るルールが適用されます。Sisk が `GetJsonContent`、`GetJsonContentAsync`、`Body`、または `RawBody` を通じてリクエストストリームを読み取った後は、`GetRequestStream()` で同じボディを再度取得することはできません。

## リクエストコンテキストの取得

HTTP Context は、HTTP サーバー、ルート、ルーター、リクエストハンドラ情報を格納する Sisk 固有のオブジェクトです。これらのオブジェクトが整理しにくい環境で、情報を一元管理するために使用できます。

現在実行中の [HttpContext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.md) は、静的メソッド `HttpContext.GetCurrentContext()` で取得できます。このメソッドは、現在のスレッドで処理中のリクエストのコンテキストを返します。

```cs
HttpContext context = HttpContext.GetCurrentContext();
```

### ログモード

[HttpContext.LogMode](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.LogMode.md) プロパティを使用すると、現在のリクエストのロギング動作を制御できます。特定のリクエストに対してロギングを有効または無効にし、サーバーのデフォルト設定を上書きできます。

```cs
// Disable logging for this request
context.LogMode = LogOutputMode.None;
```

### リクエストバッグ

[RequestBag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.RequestBag.md) オブジェクトは、リクエストハンドラから別のポイントへ渡され、最終的な宛先で利用できる情報を格納します。このオブジェクトは、ルートコールバックの後に実行されるリクエストハンドラでも使用できます。

> [!TIP]
> このプロパティは [HttpRequest.Bag](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.Bag.md) プロパティからもアクセス可能です。

```cs {title="Middleware/AuthenticateUserRequestHandler.cs"}
public class AuthenticateUserRequestHandler : IRequestHandler
{
    public string Identifier { get; init; } = Guid.NewGuid().ToString();
    public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse;
    
    public HttpResponse? Execute(HttpRequest request, HttpContext context)
    {
        if (request.Headers.Authorization != null)
        {
            context.RequestBag.Add("AuthenticatedUser", new User("Bob"));
            return null;
        }
        else
        {
            return new HttpResponse(System.Net.HttpStatusCode.Unauthorized);
        }
    }
}
```

上記のリクエストハンドラは `AuthenticatedUser` をリクエストバッグに設定し、最終コールバックで後から取得できます。

```cs {title="Controller/MyController.cs"}
public class MyController
{
    [RouteGet("/")]
    [RequestHandler<AuthenticateUserRequestHandler>]
    static HttpResponse Index(HttpRequest request)
    {
        User authUser = request.Context.RequestBag["AuthenticatedUser"];
        
        return new HttpResponse() {
            Content = new StringContent($"Hello, {authUser.Name}!")
        };
    }
}
```

`Bag.Set()` と `Bag.Get()` ヘルパーメソッドを使用して、型シングルトンでオブジェクトを取得または設定できます。

`TypedValueDictionary` クラスは、より細かい制御のために `GetValue` と `SetValue` メソッドも提供します。

```cs {title="Middleware/Authenticate.cs"}
public class Authenticate : RequestHandler
{
    public override HttpResponse? Execute(HttpRequest request, HttpContext context)
    {
        request.Bag.Set<User>(authUser);
    }
}
```

```csharp {title="Controller/MyController.cs"}
[RouteGet("/")]
[RequestHandler<Authenticate>]
public static HttpResponse GetUser(HttpRequest request)
{
    var user = request.Bag.Get<User>();
    ...
}
```

### 原子性コンテキスト値

リクエストバッグは同期されていません。同じリクエスト内で、ルートが開始した並列タスクなど、同時に数値が変更される可能性がある場合は、[HttpContext.Interlocked](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.Interlocked.md) を使用してください。これにより、リクエストバッグ内の名前付き値に対して原子的な操作を行う [HttpContextInterlocked](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpContext.HttpContextInterlocked.md) オブジェクトが取得できます。

| メソッド | 説明 |
| --- | --- |
| `Add(name, value)` | `value` を既存の値に加算します。`name` が未設定の場合は `value` を保存します。 |
| `Exchange(name, value)` | 保存されている値を置き換え、以前の値を返します。`name` が未設定の場合は `value` を返します。 |
| `CompareExchange(name, value, comparand, notFound = -1)` | 保存された値が `comparand` と等しい場合にのみ `value` に置き換え、元の値を返します。`name` が未設定の場合は `notFound` を返します。 |
| `Inspect(name)` | 保存された値を返します。`name` が未設定の場合は `null` を返します。 |

リクエストバッグでは値は `double` として保存されます。`int` や `long` を受け取るオーバーロードは、内部で `double` に変換します。

```cs
router.MapPost("/import", async (HttpRequest request) =>
{
    string[] lines = request.Body.Split('\n');

    await Parallel.ForEachAsync(lines, async (line, cancellation) =>
    {
        if (await TryImportAsync(line, cancellation))
            request.Context.Interlocked.Add("imported", 1);
    });

    double imported = request.Context.Interlocked.Inspect("imported") ?? 0;
    return new HttpResponse($"{imported} line(s) imported");
});
```

> [!NOTE]
> これらの操作は他の `Interlocked` 呼び出しに対してのみ原子的です。`RequestBag` を直接介して同じキーを読み書きする場合、これらの操作とは同期されません。

## フォームデータの取得

[StringKeyStoreCollection](https://docs.sisk-framework.org/api/Sisk.Core.Entity.StringKeyStoreCollection.md) を使用して、以下の例のようにフォームデータの値を取得できます。

```cs {title="Controller/Auth.cs"}
[RoutePost("/auth")]
public HttpResponse Index(HttpRequest request)
{
    var form = request.GetFormContent();

    string? username = form["username"];
    string? password = form["password"];

    if (AttempLogin(username, password))
    {
        ...
    }
}
```

リクエストボディが大きい場合やキャンセルサポートが必要な場合は、非同期バージョンが便利です。

```cs
var form = await request.GetFormContentAsync(request.DisconnectToken);
```

## マルチパートフォームデータの取得

Sisk の HTTP リクエストでは、ファイルやフォームフィールド、任意のバイナリコンテンツなど、アップロードされたマルチパートコンテンツを取得できます。

```cs
// 以下のメソッドは、リクエストの入力全体を
// MultipartObject の配列に読み込みます
var multipartFormDataObjects = request.GetMultipartFormContent();

foreach (MultipartObject uploadedObject in multipartFormDataObjects)
{
    // マルチパートフォームデータで提供されたファイル名
    // オブジェクトがファイルでない場合は null が返されます。
    Console.WriteLine("File name       : " + uploadedObject.Filename);

    // マルチパートフォームデータオブジェクトのフィールド名
    Console.WriteLine("Field name      : " + uploadedObject.Name);

    // マルチパートフォームデータのコンテンツ長
    Console.WriteLine("Content length  : " + uploadedObject.ContentLength);

    // 各ファイルヘッダーに基づいて画像形式を判定します
    // 既知のコンテンツタイプです。認識できない一般的なファイル形式の場合、
    // 以下のメソッドは MultipartObjectCommonFormat.Unknown を返します
    Console.WriteLine("Common format   : " + uploadedObject.GetCommonFileFormat());
}
```

ルートが非同期の場合は、[GetMultipartFormContentAsync](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetMultipartFormContentAsync.md) を使用してください。

```cs
var multipartFormDataObjects =
    await request.GetMultipartFormContentAsync(request.DisconnectToken);
```

Sisk の [Multipart form objects](https://docs.sisk-framework.org/api/Sisk.Core.Entity.MultipartObject.md) とそのメソッド、プロパティ、機能の詳細は、こちらをご覧ください。

## クライアント切断の検出

Sisk v1.15 以降、フレームワークは [HttpRequest.DisconnectToken](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.DisconnectToken.md) を通じてキャンセルトークンを提供します。設定された HTTP エンジンが切断検出をサポートしている場合、クライアント接続がレスポンス完了前に閉じられるとこのトークンがキャンセルされます。これにより、クライアントが結果を待っていない長時間実行される操作を停止できます。

```csharp
router.MapGet("/connect", async (HttpRequest req) =>
{
    // gets the disconnection token from the request
    var dc = req.DisconnectToken;

    await LongOperationAsync(dc);

    return new HttpResponse();
});
```

このトークンはすべての HTTP エンジンで互換性があるわけではなく、各エンジンで実装が必要です。

デフォルトの Sisk エンジン（`System.Net.HttpListener` ベース）はクライアント切断検出をサポートしていません。デフォルトエンジンを使用している場合、`DisconnectToken` は `CancellationToken.None` となり、実質的にキャンセルできないトークンとして扱われます。

[Cadente engine](https://docs.sisk-framework.org/ja/docs/cadente.md) は `DisconnectToken` をサポートしています。ルートが切断検出付きのキャンセルに依存する場合は、Cadente もしくはこの機能を明示的に実装した他のエンジンを使用してください。サポートされたエンジンでも、キャンセルは協調的に行われます。トークンを非同期 API に渡し、長時間実行する処理内でトークンをチェックしてください。

## サーバー送信イベント (Server-sent events) のサポート

Sisk は [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) をサポートしており、チャンクをストリームとして送信し、サーバーとクライアント間の接続を維持できます。

[HttpRequest.GetEventSource](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequest.GetEventSource.md) メソッドを呼び出すと、HttpRequest がリスナ状態になります。この状態では、サーバーサイドイベントで送信されるパケットと重複するため、HTTP リクエストのコンテキストは HttpResponse を期待しません。

すべてのパケットを送信した後、コールバックは [Close](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpRequestEventSource.Close.md) メソッドを返す必要があります。これにより、サーバーに最終レスポンスが送信され、ストリーミングが終了したことが示されます。

送信されるすべてのパケットの総長さを予測できないため、`Content-Length` ヘッダーで接続の終了を判定することはできません。

ほとんどのブラウザのデフォルトでは、サーバーサイドイベントは GET 以外の HTTP ヘッダーやメソッドの送信をサポートしていません。そのため、特定のヘッダーが必要な event-source リクエストを扱うハンドラを使用する際は注意が必要です。

また、ほとんどのブラウザは、すべてのパケット受信後にクライアント側で [EventSource.close](https://developer.mozilla.org/en-US/docs/Web/API/EventSource/close) が呼び出されない場合、ストリームを再開します。これによりサーバー側で無限に処理が続く可能性があります。この問題を回避するため、イベントソースがすべてのパケット送信を完了したことを示す最終パケットを送るのが一般的です。

以下の例は、ブラウザが Server-sent events をサポートするサーバーと通信する方法を示しています。

```html {title="sse-example.html"}
<html>
    <body>
        <b>フルーツ：</b>
        <ul></ul>
    </body>
    <script>
        const evtSource = new EventSource('http://localhost:5555/event-source');
        const eventList = document.querySelector('ul');
        
        evtSource.onmessage = (e) => {
            const newElement = document.createElement("li");

            newElement.textContent = `message: ${e.data}`;
            eventList.appendChild(newElement);

            if (e.data == "Tomato") {
                evtSource.close();
            }
        }
    </script>
</html>
```

そして、クライアントへ順次メッセージを送信します。

```cs {title="Controller/MyController.cs"}
public class MyController
{
    [RouteGet("/event-source")]
    public async Task<HttpResponse> ServerEventsResponse(HttpRequest request)
    {
        var serverEvents = await request.GetEventSourceAsync ();
        
        string[] fruits = new[] { "Apple", "Banana", "Watermelon", "Tomato" };
        
        foreach (string fruit in fruits)
        {
            await serverEvents.SendAsync(fruit);
            await Task.Delay(1500);
        }

        return await serverEvents.CloseAsync();
    }
}
```

このコードを実行すると、以下のような結果が得られるはずです。

<img src="/assets/img/server side events demo.gif" />

## プロキシされた IP とホストの解決

Sisk はプロキシと併用できるため、クライアントからプロキシへの取引において IP アドレスがプロキシエンドポイントに置き換えられることがあります。

Sisk では、[forwarding resolvers](https://docs.sisk-framework.org/ja/docs/advanced/forwarding-resolvers.md) を使用して独自のリゾルバを定義できます。

## ヘッダーエンコーディング

ヘッダーのエンコーディングは、一部の実装で問題になることがあります。Windows では UTF-8 ヘッダーがサポートされていないため、ASCII が使用されます。Sisk には組み込みのエンコーディングコンバータがあり、誤ったエンコーディングのヘッダーをデコードするのに役立ちます。

この操作はコストが高く、デフォルトでは無効化されていますが、[HttpServerConfiguration.NormalizeHeadersEncodings](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.NormalizeHeadersEncodings.md) を有効にすることで使用可能になります。
