リクエストの処理
Ktorでは、ルートハンドラー内で入力リクエストを処理し、レスポンスを送信できます。
ルートハンドラーは、クライアントとサーバー間の単一のHTTPエクスチェンジを表すApplicationCall上で動作します。これはルートハンドラー内でcallプロパティを通じて利用でき、受信リクエスト(ApplicationRequest)と送信レスポンス(ApplicationResponse)の両方が含まれています。
ルートハンドラー内では、ApplicationCallを使用して以下のようなアクションを実行できます。
- ヘッダー、Cookie、接続の詳細などのリクエスト情報を取得する。
- パスパラメータの値を取得する。
- クエリパラメータを取得する。
- データオブジェクト、フォームパラメータ、ファイルなどのリクエストボディの内容を受信する。
一般的なリクエスト情報
call.requestプロパティを使用してリクエストデータにアクセスできます。これはApplicationRequestインスタンスを返し、低レベルのHTTPリクエスト情報へのアクセスを提供します。
たとえば、GETリクエストハンドラー内でcall.request.uriを使用してリクエストURIを取得できます。
routing {
get("/") {
val uri = call.request.uri
call.respondText("Request uri: $uri")
}
}call.respondText()関数は、クライアントにプレーンテキストのレスポンスを返送します。
ヘッダー
すべてのHTTPリクエストヘッダーにアクセスするには、ApplicationRequest.headersプロパティを使用します。
便宜上、Ktorはよく使用されるヘッダーにアクセスするための専用の拡張関数も提供しています。たとえば、.acceptEncoding()、.contentType()、.cacheControl()などがあります。
Cookie
リクエストと一緒に送信されたCookieにアクセスするには、ApplicationRequest.cookiesプロパティを使用します。
Cookieを使用したセッションの処理方法の詳細については、セッションセクションを参照してください。
接続の詳細
ホスト名、ポート、スキームなどの接続の詳細にアクセスするには、ApplicationRequest.localプロパティを使用します。
X-Forwarded- ヘッダー
HTTPプロキシまたはロードバランサーを経由したリクエストに関する情報を取得するには、Forwarded headersプラグインをインストールし、ApplicationRequest.originプロパティを使用します。
パスパラメータ
リクエストを処理する際、ApplicationCall.parametersプロパティを使用してパスパラメータの値を取得できます。
たとえば、以下のコードスニペットでは、パス/user/adminに対してcall.parameters["login"]は"admin"を返します。
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}クエリパラメータ
URLクエリ文字列のパラメータを取得するには、ApplicationRequest.queryParametersプロパティを使用できます。
以下の例では、/products?price=ascへのリクエストからpriceクエリパラメータにアクセスしています。
get("/products") {
if (call.request.queryParameters["price"] == "asc") {
// 低価格から高価格の順に商品を表示する
}
}また、ApplicationRequest.queryString()関数を使用してクエリ文字列全体を取得することもできます。
必須のリクエストパラメータ
リクエストを処理する際、パスパラメータ、クエリパラメータ、ヘッダー、またはCookieから値を取得し、リクエスト処理を続行する前にそれらが存在することを確認するのが一般的です。
すべてのルートハンドラーで欠落している値を手動でチェックする代わりに、Ktorは必須のリクエストデータへのアクセスを簡素化する以下のヘルパー関数を提供しています。
ApplicationCall.requireQueryParameter()— リクエストURLから必須のクエリパラメータを取得します。パラメータがない場合は例外をスローします。ApplicationCall.requireHeader()— 必須のHTTPヘッダー値を取得します。リクエストにヘッダーが存在しない場合は例外をスローします。ApplicationCall.requireCookie()— 必須のCookie値を取得します。オプションで指定されたエンコーディングを使用してデコードします。Cookieがない場合は例外をスローします。RoutingCall.requirePathParameter()— ルート定義から必須のパスパラメータを取得します。一致したルートにパラメータが存在しない場合は例外をスローします。
各関数は非null値を返すか、値が欠けている場合にMissingRequestParameterExceptionをスローします。
post("/checkout/{cartId}") {
val userId = call.requireCookie("userId")
val cartId = call.requirePathParameter("cartId")
val amount = call.requireQueryParameter("amount").toLong()
// ビジネスロジック
}ボディの内容
このセクションでは、POST、PUT、またはPATCHで送信されたボディの内容を受信する方法を説明します。
生のペイロード
生のボディペイロードにアクセスして手動で解析するには、受信するペイロードの型を受け取るApplicationCall.receive()関数を使用します。次のようなHTTPリクエストがあるとします。
POST http://localhost:8080/text
Content-Type: text/plain
Hello, world!このリクエストのボディは、以下のいずれかの方法で、指定した型のオブジェクトとして受信できます。
String
リクエストボディをString値として受信するには、
call.receive<String>()を使用します。.receiveText()を使用して同じ結果を得ることもできます。kotlinpost("/text") { val text = call.receiveText() call.respondText(text) }ByteArray
リクエストのボディをバイト配列として受信するには、
call.receive<ByteArray>()を呼び出します。kotlinpost("/bytes") { val bytes = call.receive<ByteArray>() call.respond(String(bytes)) }ByteReadChannel
call.receive<ByteReadChannel>()または.receiveChannel()を使用して、バイトシーケンスの非同期読み取りを可能にするByteReadChannelを受信できます。kotlinpost("/channel") { val readChannel = call.receiveChannel() val text = readChannel.readRemaining().readString() call.respondText(text) }以下のサンプルは、
ByteReadChannelを使用してファイルをアップロードする方法を示しています。kotlinpost("/upload") { val file = File("uploads/ktor_logo.png") call.receiveChannel().copyAndClose(file.writeChannel()) call.respondText("A file is uploaded") }
Ktorのチャンネルと、
RawSink、RawSource、OutputStreamなどの型との間の変換については、I/O相互運用性を参照してください。
完全な例については、post-raw-dataを参照してください。
オブジェクト
KtorはContentNegotiationプラグインを提供し、リクエストのメディアタイプをネゴシエートして、コンテンツを必要な型のオブジェクトにデシリアライズします。
リクエストのコンテンツを受信して変換するには、データクラスをパラメータとして受け取るApplicationCall.receive()関数を呼び出します。
post("/customer") {
val customer = call.receive<Customer>()
customerStorage.add(customer)
call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
}詳細については、Ktor Serverにおけるコンテンツネゴシエーションとシリアライズを参照してください。
フォームパラメータ
Ktorでは、receiveParameters関数を使用して、x-www-form-urlencodedとmultipart/form-dataの両方のタイプで送信されたフォームパラメータを受信できます。以下の例は、ボディにフォームパラメータを渡したHTTPクライアントのPOSTリクエストを示しています。
POST http://localhost:8080/signup
Content-Type: application/x-www-form-urlencoded
username=JetBrains&[email protected]&password=foobar&confirmation=foobarコード内でパラメータ値を取得するには、次のようにします。
post("/signup") {
val formParameters = call.receiveParameters()
val username = formParameters["username"].toString()
call.respondText("The '$username' account is created")
}完全な例については、post-form-parametersを参照してください。
マルチパートフォームデータ
マルチパートリクエストの一部として送信されたファイルを受信するには、.receiveMultipart()関数を呼び出し、必要に応じて各パートをループします。
マルチパートリクエストデータは順次処理されるため、特定のパートに直接アクセスすることはできません。また、これらのリクエストには、フォームフィールド、ファイル、バイナリデータなど、異なる種類のパートが含まれる場合があり、それぞれを異なる方法で処理する必要があります。
この例では、ファイルを受信してファイルシステムに保存する方法を示します。
import io.ktor.server.application.*
import io.ktor.http.content.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import java.io.File
fun Application.main() {
routing {
post("/upload") {
var fileDescription = ""
var fileName = ""
val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)
multipartData.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
fileDescription = part.value
}
is PartData.FileItem -> {
fileName = part.originalFileName as String
val file = File("uploads/$fileName")
part.provider().copyAndClose(file.writeChannel())
}
else -> {}
}
part.dispose()
}
call.respondText("$fileDescription is uploaded to 'uploads/$fileName'")
}
}
}デフォルトのファイルサイズ制限
デフォルトでは、受信可能なバイナリおよびファイル項目の許容サイズは50MiBに制限されています。受信したファイルまたはバイナリ項目が50MiBの制限を超えると、IOExceptionがスローされます。
デフォルトのフォームフィールド制限を上書きするには、.receiveMultipart()を呼び出すときにformFieldLimitパラメータを渡します。
val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)この例では、新しい制限が100MiBに設定されています。
フォームフィールド
PartData.FormItemはフォームフィールドを表し、その値はvalueプロパティを介してアクセスできます。
when (part) {
is PartData.FormItem -> {
fileDescription = part.value
}
}ファイルアップロード
PartData.FileItemはファイル項目を表します。ファイルアップロードをバイトストリームとして処理できます。
when (part) {
is PartData.FileItem -> {
fileName = part.originalFileName as String
val file = File("uploads/$fileName")
part.provider().copyAndClose(file.writeChannel())
}
}.provider()関数は、データをインクリメンタルに読み取ることができるByteReadChannelを返します。.copyAndClose()関数を使用すると、適切なリソースクリーンアップを保証しながら、ファイルの内容を指定された宛先に書き込みます。
アップロードされたファイルのサイズを特定するには、postハンドラー内でContent-Length ヘッダー値を取得できます。
post("/upload") {
val contentLength = call.request.header(HttpHeaders.ContentLength)
// ...
}リソースのクリーンアップ
フォームの処理が完了したら、リソースを解放するために.dispose()関数を使用して各パートを破棄します。
part.dispose()このサンプルの実行方法については、upload-fileを参照してください。
