Dasar http4s

HttpRoutes, permintaan, respons, dan middleware pada server ember http4s.

Daftar isi

http4s adalah pustaka Scala untuk membangun layanan web berbasis HTTP di atas cats-effect. Topik ini membahas model permintaan dan respons pada http4s, pendefinisian rute dengan HttpRoutes dan DSL (domain-specific language), penyambungan rute ke server ember, penerapan middleware, serta pengujian layanan dengan curl. Setelah topik ini diikuti, sebuah layanan JSON kecil dapat dibangun, dijalankan pada port 18080, dan diuji dari terminal.

Model permintaan dan respons#

http4s memodelkan layanan HTTP sebagai fungsi dari permintaan (request) ke respons (response) yang dibungkus efek. Permintaan dinyatakan oleh nilai bertipe Request[IO], yaitu struktur yang memuat metode HTTP, jalur, header, dan isi, sedangkan respons dinyatakan oleh Response[IO] yang memuat status, header, dan isi. Tipe fungsinya ditulis Request[IO] => IO[Response[IO]], dan fungsi semacam itu dibungkus dalam Kleisli, pembungkus fungsi dari satu nilai ke nilai efek. Dengan bentuk ini, layanan adalah nilai biasa yang dapat dikomposisi seperti fungsi Scala lainnya.

http4s sebenarnya bersifat polimorfik terhadap tipe efek, sehingga kode dapat ditulis untuk F[_] mana pun yang memiliki instance type class yang sesuai. Contoh pada topik ini tetap menggunakan IO dari cats-effect agar mudah dibaca dan dijalankan. Dua tipe turunan yang sering muncul adalah HttpRoutes[IO] dan HttpApp[IO]. HttpRoutes[IO] menyatakan kumpulan rute yang mungkin tidak mencakup sebuah permintaan, sehingga hasil pencocokannya dibungkus OptionT. HttpApp[IO] menyatakan aplikasi lengkap yang selalu menghasilkan respons untuk permintaan mana pun, dan bentuk inilah yang diterima server.

Rute dengan HttpRoutes.of#

Rute didefinisikan dengan HttpRoutes.of, yaitu fungsi yang menerima sekumpulan pola permintaan beserta responsnya. Setiap pola ditulis dengan pencocokan pola Scala terhadap metode dan jalur. Pola GET -> Root / "catatan" mencocokkan permintaan GET pada jalur /catatan, sedangkan segmen tambahan dituliskan setelahnya dengan pemisah yang sama. Ekstraktor seperti IntVar(id) menangkap satu segmen jalur sebagai bilangan bulat agar nilainya dapat digunakan di dalam rute. Kombinator Ok, Created, dan NotFound membangun Response[IO] yang terbungkus IO, sehingga isi rute berupa nilai IO[Response[IO]] yang dapat dirangkai dengan map atau flatMap.

scala Contoh: bentuk umum pencocokan rute
HttpRoutes.of[IO] {
  case GET -> Root / "catatan"              => Ok(daftarCatatan.asJson)
  case GET -> Root / "catatan" / IntVar(id) => Ok(cariCatatan(id).asJson)
  case _                                    => NotFound()
}

Pola case _ menangkap permintaan apa pun yang tidak cocok dengan pola sebelumnya, dan NotFound() menghasilkan respons 404 untuknya. Apabila pola semacam ini tidak ditulis, permintaan yang tidak tercakup keluar dari kumpulan rute sebagai nilai kosong pada OptionT, dan kondisi itu diterjemahkan menjadi 404 saat rute disambungkan ke server. Selain itu, HttpRoutes.of menerima PartialFunction, sehingga beberapa kumpulan rute dapat digabung dengan operator <+> tanpa menyentuh definisi yang sudah ada.

Menyambungkan rute ke server ember#

Rute menjadi layanan yang benar-benar berjalan setelah disambungkan ke server. http4s menyediakan server ember melalui EmberServerBuilder, yaitu pembangun (builder) server bawaan yang tidak memerlukan kontainer servlet atau dependensi tambahan. Konfigurasi minimal mencakup port melalui withPort dan aplikasi melalui withHttpApp, kemudian build menghasilkan Resource[IO, Server] yang mengelola siklus hidup server. Dengan Resource dan use(_ => IO.never), server tetap berjalan sampai prosesnya dihentikan.

scala server-catatan.scala
//> using dep org.http4s::http4s-ember-server:0.23.38
//> using dep org.http4s::http4s-dsl:0.23.38
//> using dep org.http4s::http4s-circe:0.23.38
//> using dep io.circe::circe-generic:0.14.17
//> using dep org.slf4j:slf4j-nop:2.0.17

import cats.data.Kleisli
import cats.effect.IO
import cats.effect.IOApp
import com.comcast.ip4s.port
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s.HttpApp
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.io._
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits._

// data contoh yang dikirim sebagai JSON
case class Catatan(id: Int, isi: String)

// rute layanan: mencocokkan metode dan jalur, lalu membangun respons
val routes: HttpRoutes[IO] = HttpRoutes.of[IO] {
  case GET -> Root / "catatan" =>
    Ok(List(Catatan(1, "belajar http4s")).asJson)
}

// middleware: menerima sebuah aplikasi dan mengembalikan aplikasi baru
def denganHeader: HttpApp[IO] => HttpApp[IO] = aplikasi =>
  Kleisli { permintaan =>
    aplikasi.run(permintaan).map { respons =>
      respons.putHeaders("X-Layanan" -> "server-catatan")
    }
  }

object ServerCatatan extends IOApp.Simple:
  def run: IO[Unit] =
    EmberServerBuilder
      .default[IO]
      .withPort(port"18080")
      .withHttpApp(denganHeader(routes.orNotFound))
      .build
      .use(_ => IO.never)

Direktif //> using dep pada bagian atas berkas digunakan scala-cli untuk mengunduh dependensi, yaitu server ember, DSL, dukungan circe, serta slf4j-nop yang meredam log agar keluaran server bersih. case class Catatan berperan sebagai data respons. Impor io.circe.generic.auto._ menyediakan derivasi encoder untuk case class, sedangkan CirceEntityEncoder menghubungkan encoder tersebut ke EntityEncoder, sehingga Ok menerima List(Catatan(1, "belajar http4s")).asJson secara langsung. Metode orNotFound mengubah HttpRoutes menjadi HttpApp dengan menambahkan respons 404 untuk permintaan yang tidak tercakup rute mana pun. Terakhir, denganHeader adalah middleware yang diterapkan pada aplikasi sebelum server dibangun, dan penjelasannya ada pada bagian berikutnya.

Middleware#

Middleware adalah fungsi yang menerima sebuah aplikasi HTTP dan mengembalikan aplikasi baru dengan perilaku tambahan, sehingga kebutuhan lintas rute seperti pencatatan, autentikasi, atau penambahan header cukup ditulis satu kali. Middleware bertipe HttpApp[IO] => HttpApp[IO], sehingga dapat disusun bertingkat sebelum aplikasi diberikan ke server.

Contoh pada berkas server-catatan.scala menggunakan middleware kecil bernama denganHeader. Tipe HttpApp[IO] => HttpApp[IO] menegaskan bahwa middleware mengubah satu aplikasi menjadi aplikasi lain. Di dalam denganHeader, aplikasi.run(permintaan) menjalankan aplikasi asli untuk memperoleh IO[Response[IO]], lalu map menyisipkan putHeaders pada responsnya. Hasilnya dibungkus kembali dalam Kleisli agar bertipe HttpApp[IO]. Middleware diterapkan dengan memanggil denganHeader(routes.orNotFound) pada withHttpApp, sehingga setiap respons, termasuk 404, melewati middleware tersebut. Middleware lain dapat ditambahkan dengan cara yang sama, misalnya denganHeader(denganLog(routes.orNotFound)), dan urutan penyusunannya menentukan lapisan terluar.

Menguji layanan dengan curl#

Uji layanan dengan dua terminal. Terminal pertama menjalankan server melalui scala-cli run server-catatan.scala pada folder berkasnya, dan eksekusi pertama memerlukan waktu karena dependensi diunduh serta dikompilasi lebih dahulu. Setelah server berjalan, panggil endpoint dari terminal kedua dengan curl seperti berikut.

bash
# tidak-dijalankan
# panggil endpoint catatan pada server yang sedang berjalan
curl -s http://localhost:18080/catatan
text Keluaran dari curl
[{"id":1,"isi":"belajar http4s"}]

Respons berupa larik JSON berisi satu catatan dengan dua properti. Header Content-Type: application/json diberikan secara otomatis oleh encoder circe yang tersambung pada berkas program. Nilai id tetap bertipe bilangan bulat, sedangkan isi bertipe string, sesuai definisi Catatan. Apabila server belum berjalan, curl menampilkan pesan connection refused, dan pesan itu berarti port 18080 belum siap menerima permintaan.

Ringkasan#

  • http4s memodelkan layanan HTTP sebagai fungsi dari Request[IO] ke IO[Response[IO]], dibungkus Kleisli dan terbagi atas HttpRoutes untuk kumpulan rute serta HttpApp untuk aplikasi lengkap.
  • HttpRoutes.of mendefinisikan rute melalui pencocokan pola terhadap metode dan jalur, sedangkan kombinator seperti Ok dan NotFound membangun respons dalam efek IO.
  • EmberServerBuilder menyambungkan aplikasi ke server ember, orNotFound melengkapi rute dengan 404, dan use(_ => IO.never) menjaga server tetap berjalan.
  • Middleware mengubah HttpApp menjadi aplikasi baru untuk kebutuhan lintas rute, dan layanan diuji dengan curl pada port yang dikonfigurasi.

Referensi#

  • DSL — http4s — Dokumentasi resmi DSL http4s untuk pencocokan metode, jalur, dan kombinator respons.
  • Service — http4s — Dokumentasi HttpRoutes, HttpApp, dan penyambungan layanan ke server.
  • Middleware — http4s — Daftar middleware bawaan http4s beserta pola penulisannya.