ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [도전] Scala로 REST API 만들려면 어떻게 하나? #3
    기술 관련/Scala 2021. 2. 8. 17:31

    지난 번 글에서 AKKA HTTP의 Tutorial 예제 코드를 살펴 보았었다. 대략 HTTP 서비스를 위한 구동 및 어떻게 Route 를 구성하는지에 대한 내용이었는데, 여기에 추가적인 궁금한 것이 생겼다.

     

    1. Path Parameter 인식 방법

     

    이건 이미 예제에 있던 내용이지만 지난 번 글에서 얼렁뚱땅 넘어가 버렸던 것이다. UserRoutes.scala 코드를 다시 확인 해보자.

    //#users-get-delete
      val userRoutes: Route =
        pathPrefix("users") {
          concat(
    ...
            //#users-get-delete
            //#users-get-post
            path(Segment) { name =>
              concat(
                get {
                  //#retrieve-user-info
                  rejectEmptyResponse {
                    onSuccess(getUser(name)) { response =>
                      complete(response.maybeUser)
                    }
                  }
                  //#retrieve-user-info
                },
                delete {
                  //#users-delete-logic
                  onSuccess(deleteUser(name)) { performed =>
                    complete((StatusCodes.OK, performed))
                  }
                  //#users-delete-logic
                })
            })
          //#users-get-delete
        }

    지난번에 봤던 부분은 /users 라는 경로로 끝나는 경우(pathEnd)에 대한 내용이었고, 이번에는 그 아래 path(Segment)라는 부분을 보자. Segment는 경로의 정보를 인식하는 Path Matcher 가운데 하나로서 경로 슬래시와 슬래스 사이에 문자열 부분(Segment)을 인식한다. 이 부분은 /users/이름 을 입력했을 때 name이라는 parameter에 "이름" 이라는 값이 들어가게 된다.

     

    지난 번 입력했던 이름 "Gil-Dong Hong" 을 입력하면 다음과 같은 결과를 얻게 된다. 참고로, 이 예제코드는 서버가 재시작되면서 User Registry가 없어지므로, Gil-Dong Hong에 대한 POST API로 추가해야 사용자 정보를 찾을 수 있다.

     

    만약, 없는 이름을 입력한다면? 리소스가 없다고 출력된다. 

    AKKA HTTP에서는 Segment 이외에 정규식을 비롯한 다양한 형태의 Path Matcher 제공하므로 이를 이용할 수 있다.

     

    2. Query Parameter 인식 방법

     

    HTTP 요청에서는 요청할 때 다양한 방법으로 Parameter를 전달하는데, 대표적인 것이 Query Parameter이다. 요청 URL에 ? 문자 뒤에 key=value 형태로 전달되는 방식인데 이걸 AKKA HTTP에서는 Parameter Directive 를 이용하면 parameter를 받을 수 있다.

     

    기존 Hello World 예제를 마개조응용해서 다음과 같이 써 볼 수 있다. /users/get 경로 요청을 하게 되면 query parameter로 color 와 backgroundColor 두 가지를 입력 받을 수 있다.

            path("get") {
              concat {
                get {
                  parameters("color", "backgroundColor".optional) { (color, backgroundColor) =>
                    val backgroundStr = backgroundColor.getOrElse("<undefined>")
                    complete(s"The color is '$color' and the background is '$backgroundStr'")
                  }
                }
              }
            },

     

    http://127.0.0.1:8080/users/get

     

    http://127.0.0.1:8080/users/get?color=red

     

    http://127.0.0.1:8080/users/get?color=red&backgroundColor=blue

     

    코드를 다음과 같이 고쳐서 path 로 입력 받은 name을 query로 입력 받을 수 있는지 확인 해 보자.

            path("get") {
              concat {
                get {
                  parameters("name") { name =>
                    rejectEmptyResponse {
                      onSuccess(getUser(name)) { response =>
                        complete(response.maybeUser)
                      }
                    }
                  }
                }
              }
            },

     

    그냥 보면 별 문제는 없어 보였는데, sbt 명령으로 빌드하면 다음과 같은 오류 메시지를 봤었다. (근데 다음에는 이 메시지가 안나타나는데 왜 그런건지 잘 모르겠다)

    type mismatch 오류!

     

    혹이 위와 같은 오류가 발생한다면 명확하게 "name" parameter는 String으로 입력 받겠노라고 다음과 같이 타입을 추가해 주면 된다. 위의 문제를 해결 할 수 있다.

                get {
                  parameters("name".as[String]) { name =>
                    rejectEmptyResponse {
                      onSuccess(getUser(name)) { response =>
                        complete(response.maybeUser)
                      }
                    }
                  }
                }

     

    이를 브라우저에서 호출하면 사용자 정보가 나타난다.

     

     

    3. 요청 오류 처리

    예제에서 사용자 정보가 없는 경우 얻는 메시지를 보면 다음과 같다.

    이를 curl의 -w 옵션을 이용하면 응답 코드를 비롯한 응답 정보를 확인 해 볼 수 있다.

    curl -w '%{http_code}, %{content_type}' http://127.0.0.1:8080/users/Gil-Dong

    출력되는 메시지는 다음과 같다

    이렇게 응답 코드 404와 "The requested resource could not be found." 메시지는 AKKA HTTP의 rejectEmptyResponse에서 제공하는 것이다. 코드를 다시 보면 Misc. Directive 중 하나인 rejectEmptyResponse가 onSuccess 상황에서만 응답을 처리하도록 되어 있다.

                  rejectEmptyResponse {
                    onSuccess(getUser(name)) { response =>
                      complete(response.maybeUser)
                    }
                  }

     

    만약, 이를 직접 처리하고자 한다면 Reject Handler 를 이용할 수 있다. 우선 다음과 같이 필요한 클래스를 UserRouter.scala 파일에 추가 하고

    import akka.http.scaladsl.server.MethodRejection
    import akka.http.scaladsl.server.ValidationRejection
    import akka.http.scaladsl.server.AuthorizationFailedRejection
    import akka.http.scaladsl.server.MissingCookieRejection
    import akka.http.scaladsl.server.RejectionHandler
    import akka.http.scaladsl.model.StatusCodes.InternalServerError
    import akka.http.scaladsl.model.StatusCodes.NotFound
    import akka.http.scaladsl.model.StatusCodes.MethodNotAllowed
    import akka.http.scaladsl.model.StatusCodes.Forbidden
    import akka.http.scaladsl.model.StatusCodes.BadRequest
    import akka.http.scaladsl.model.HttpResponse

     

    Reject Handler 코드를 추가한다.

      // Rejection Handler
      def myRejectionHandler =
        RejectionHandler.newBuilder()
          .handleNotFound { complete((NotFound, "Not here!")) }
          .result()
    

     

    그리고, Router에 handleRejections 디렉티브를 Route 코드를 둘러 싼다.

      val userRoutes: Route = handleRejections(myRejectionHandler) {
        pathPrefix("users") {
        
        ...
        
        }
      }

     

    그리고, http://127.0.0.1:8080/users/get?name=Gil-Dong 에 접속을 해보면 다음과 같이 변경된 값이 반영된 것을 볼 수 있다.

    댓글

Designed by Tistory.