Help us understand the problem. What is going on with this article?

Akka-HttpとSwaggerを連携させる

連携させるのにハマったので備忘録です。

使用するライブラリ

libraryDependencies ++= Seq(
  "com.typesafe.akka"            %% "akka-http"         % "10.1.7",
  "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.3",
  "javax.ws.rs"                  % "javax.ws.rs-api"    % "2.0.1",
  "ch.megard"                    %% "akka-http-cors"    % "0.4.1"
)

APIエンドポイントの定義

/itemsでレスポンスを返すAPIを作成します

ItemContoller
package json_server.http.controller

import akka.http.scaladsl.model.{ ContentType, HttpEntity, HttpResponse, MediaTypes, StatusCodes }
import akka.http.scaladsl.server.{ Directives, Route }
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.{ Content, Schema }
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import javax.ws.rs._

@Path("/items")
@Consumes(Array("application/json"))
@Produces(Array("application/json"))
class ItemController extends Directives {

  def route: Route = create

  @GET
  @Operation(
    summary = "Get Items",
    description = "",
    requestBody = new RequestBody(content = Array(new Content(schema = new Schema()))),
    responses = Array(
      new ApiResponse(responseCode = "200",
                      description = "Create response",
                      content = Array(new Content(schema = new Schema()))),
      new ApiResponse(responseCode = "500", description = "Internal server error")
    )
  )
  def create: Route = path("items") {
    get {
      complete(
        HttpResponse(entity = HttpEntity(ContentType(MediaTypes.`application/json`), """
          {"items":"pen"}
          """))
      )
    }
  }
}

SwaggerDocServiceの定義

SwaggerDocService.scala
package json_server.http

import com.github.swagger.akka.SwaggerHttpService
import com.typesafe.config.ConfigFactory

class SwaggerDocService(val apis: Set[Class[_]]) extends SwaggerHttpService {

  val conf = ConfigFactory.load

  val address = conf.getString(MY_HOST)    // 0.0.0.0
  val port    = conf.getInt(MY_PORT)       // 5000

  override val apiClasses          = apis
  override val host                = s"$address:$port"
  override val apiDocsPath         = "api-docs"         // このパスでyaml/jsonを生成する
  override val schemes             = List("https")      // httpsを使いたい場合はhttpsを指定
}

Routeの定義

Routes.scala
package json_server.http

import akka.actor.ActorSystem
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{ ExceptionHandler, Route }
import akka.util.Timeout
import ch.megard.akka.http.cors.scaladsl.CorsDirectives._ 

import json_server.http.controller.ItemControlle

import scala.util.control.NonFatal

class Routes()(implicit system: ActorSystem, timeout: Timeout) extends SprayJsonSupport {

  /***
    * Custom Error Handler
    */
  implicit def customExceptionHandler: ExceptionHandler = ExceptionHandler {
    case NonFatal(ex) =>
      extractLog { implicit log =>
        extractUri { uri =>
          val errorMessage = s"ERROR!! URI: $uri, REASON: ${ex.getMessage}"

          log.error(errorMessage)

          complete(StatusCodes.InternalServerError -> errorMessage)
        }
      }
  }

  /***
    * Routing
    */
  val routes: Route = (cors() | handleExceptions(customExceptionHandler)) { // Route作成時にcors()を呼ばないとSwaggerでExecuteした時にレスポンスの内容が画面に表示されない
    extractLog { implicit log =>
      extractUri { uri =>
        extractMethod { method =>
          log.info(s"call api. method: ${method.value}, uri: $uri")

          path("swagger") {
            getFromResource("swagger/index.html")
          } ~ 
          // swaggerディレクトリ下のファイルにアクセス可能にする
          getFromResourceDirectory("swagger") ~ 
          // APIエンドポイントとなるクラスをSwaggerHttpService.apiClassesに渡す
          new SwaggerDocService(Set(classOf[ItemController])).routes ~ 
          // エンドポイントのルーティングを追加
          new ItemController().route
        }
      }
    }
  }
}

swagger.yamlを生成する

ブラウザで以下のURLにアクセスする。

https://localhost:5000/api-docs/swagger.yaml

すると、以下のyaml形式のテキストがブラウザに表示される。

openapi: 3.0.1
info:
  title: ""
  description: ""
  termsOfService: ""
  version: ""
servers:
- url: https://0.0.0.0:5000
security: []
paths:
  /items:
    get:
      summary: Get Items
      operationId: create
      requestBody:
        content:
          application/json: {}
      responses:
        200:
          description: Create response
          content:
            application/json: {}
        500:
          description: Internal server error
components:
  schemas:
    Function1RequestContextFutureRouteResult:
      type: object

resources下にswagger.yamlを作成し、この内容をコピペする。

Swaggerの実行

ブラウザで以下のURLにアクセスすると、Swagger UIが起動する。

https://localhost:5000/swagger

ペットストアのAPIサービスらしい。
ワークスペース 1_003.png

画面上部のテキストボックスに、ペットストア用のSwaggerリソースhttps://petstore.swagger.io/v2/swagger.jsonが指定されているので、先程作成したswagger.yamlを指定する。
実行するとSwaggerでエンドポイントにアクセスすることができるようになる。
範囲を選択_001.png

毎回swagger.yamlを指定するのが面倒なので、以下のURLで画面を表示すればswagger.yamlを読み込んだ状態でスタートできる。

https://localhost:5000/swagger?https://localhost:5000/swagger.yaml

参考:
akka-httpにswagger2.xを組み込む方法

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away