Akka-http.
akka-http 설치
akka-http 는 akka module에서 분리 되어 독립적인 배포 사이클을 가진다. 현제 10.1.1 version 에 akka 2.5.13, scala 2.12.6 version, 으로 진행 한다.
akka-http 는 akka-http-core와 akka-http 로 되어 있있는데 akka-http가 akka-http-core 를 dependency하게 되어 있으므로 akka-http 만 선언해도 된다.
이는 g8 template project 가 있어 쉽게 설치 할 수 있다.
sbt - Dsbt . version = 1.0 . 4 new https :// github . com / akka / akka - http - scala - seed . g8
Akka-Http route
Akka-http route API 는 DSL를 제공하며, 이는 1개 이상의 Directive 로 구성되어 요청에 대한 route 처리를 기술하게 한다.
아래에 예제는 akka-io 에 나와 있는 예제 이다.
object WebServer {
def main ( args : Array [ String ]) : Unit = {
implicit val system = ActorSystem ( "myFirstHttpServer" )
implicit val mat = ActorMaterializer ()
implicit val ec = system . dispatcher
val route = path ( "hellow" ) {
get {
complete ( HttpEntity ( ContentTypes . `text/html(UTF-8)` , "<h1>hellow world akka-http</h>" ))
}
}
val bindFuture : Future [ Http.ServerBinding ] = Http (). bindAndHandle ( route , "localhost" , 8080 )
StdIn . readLine ()
bindFuture . flatMap ( serverBinding => serverBinding . unbind ()). onComplete ( _ => system . terminate ())
}
}
그리고 다음과 같은 명령으로 server를 띄운다.
Macintosh : akka-http sslee$ sbt " runMain com.sslee.http.intro.WebServer "
그리고 command에서 다음과 같은 명령을 하면 결과는 아래와 같이 나온다. (난 HTTPie 를 설치한 상황이므로)
Macintosh : akka-http sslee$ http GET localhost: 8080 /hellow
HTTP / 1.1 200 OK
Content - Length : 30
Content-Type: text/html ; charset = UTF - 8
Date : Sat , 30 Jun 2018 04 : 56 : 29 GMT
Server : akka-http/ 10 . 1 . 1
<h1>hellow world akka-http</h>
akka-http spray-json
akka-http 는 client, server 간의 form data => scala type(Unmarshalling), 혹은 scala type => json string(Marshalling)으로 spray-json module를 사용한다.
아래 예는 Item(scala type) => Json String 으로의 marshalling과 , form json data => Order(scala type)으로의 unmarshalling을 보여 준다.
object SimpleMarshallWebServer {
case class Item ( name : String , id : Long )
case class Order ( items : List [ Item ])
var mockDatabase = List . empty [ Item ]
//route 실행 필수
implicit val system = ActorSystem ( "SimpleMarshallWebServer" )
implicit val mat = ActorMaterializer ()
//Future에 필요
implicit val ec = system . dispatcher
//fake select database
def getItem ( id : Long ) : Future [ Option [ Item ]] = Future {
mockDatabase . find ( item => item . id == id )
}
//fake insert database
def saveOrder ( order : Order ) : Future [ Done ] = {
order match {
case Order ( items ) =>
mockDatabase = items ::: mockDatabase
case _ => mockDatabase
}
Future { Done }
}
def main ( args : Array [ String ]) : Unit = {
//spray-json 을 이용한 marshall, unmarshall시 필요한 암시자
implicit val itemFormatter = jsonFormat2 ( Item )
implicit val orderFormatter = jsonFormat1 ( Order )
val route : Route = get {
pathPrefix ( "item" / LongNumber ) { id =>
val item = getItem ( id )
onSuccess ( item ) {
//marshalling
case Some ( item ) => complete ( item )
case None => complete ( StatusCodes . NotFound )
}
}
} ~ post {
path ( "createOrder" ) {
//unmarshalling
entity ( as [ Order ]) { order =>
val saved : Future [ Done ] = saveOrder ( order )
onComplete ( saved ) { done =>
complete ( "order created" )
}
}
}
}
val bindFuture : Future [ Http.ServerBinding ] = Http (). bindAndHandle ( route , "localhost" , 8080 )
StdIn . readLine ()
bindFuture . flatMap ( serverBinding => serverBinding . unbind ()). onComplete ( _ => system . terminate ())
}
}
서버 기동
sbt "runMain com.sslee.http.intro.SimpleMarshallWebServer"
그리고 Order(items: List[Item]) 으로 Unmarshalling될 요청 값을 json.txt file로 만들어 HTTPie를 이용하여 실행을 해보자.
//json.txt file
{ "items" : [ { " name " : " akka-book " , " id " : 45 } ] }
//요청실행 Order 등록
Macintosh : akka-http sslee$ http POST localhost: 8080 /createOrder < ./json.txt
HTTP / 1.1 200 OK
Content - Length : 13
Content-Type: text/plain ; charset = UTF - 8
Date : Sat , 30 Jun 2018 06 : 37 : 38 GMT
Server : akka-http/ 10 . 1 . 1
order created
등록값 조회
Macintosh : akka-http sslee$ http GET localhost: 8080 /item/ 45
HTTP/ 1 . 1 200 OK
Content - Length : 28
Content-Type: application/json
Date : Sat , 30 Jun 2018 06 : 38 : 13 GMT
Server : akka-http/ 10 . 1 . 1
{
" id " : 45 ,
" name " : " akka-book "
}
akka http 와 akka-stream
akka-http에서 akka-stream을 쉽게 이용할 수 있으며, 이를 통해 첨부파일이나 memory size를 넘는 대량의 데이터를 처리하더라도 OOM이 발생하지 않는다. 이는 reactive stream 구조의 akka-stream 덕분이다. 아래의 예제를 통해 client browser가 Sink, server에서 무한 Random 생성기가 Source로 이는 무한이지만, 절대 OOM 이 발생되지 않음을 볼 수 있으며, 이는 client의 즉 Sink쪽에서 Source쪽으로의 backpressure 를 통해 (water mark) async 로 수위를 조절한다. 이는 akka-stream에서 이야기 한 부분이다.
소스는 akkak-io site 에 있다.
object ExampleStreamWebServer {
def main ( args : Array [ String ]) {
//route 실행에 필수, Stream
implicit val system = ActorSystem ( "ExampleStreamWebSystem" )
implicit val mat = ActorMaterializer ()
//Future 에 필요
implicit val ec = system . dispatcher
val randomSource = Source . fromIterator (() => Iterator . continually ( Random . nextInt ))
val route = path ( "random" ) {
get {
complete {
HttpEntity (
ContentTypes . `text/plain(UTF-8)` , randomSource . map ( n => ByteString ( s "$n\n" )))
}
}
}
val bindFuture : Future [ Http.ServerBinding ] = Http (). bindAndHandle ( route , "localhost" , 8080 )
println ( s "Server online at http://localhost:8080/\nPress RETURN to stop..." )
StdIn . readLine ()
bindFuture . flatMap ( serverBinding => serverBinding . unbind ()). onComplete ( _ => system . terminate ())
}
}
실행시 curl로 요청 rate 를 이용하여 요청수위를 늦춘다.
Macintosh : akka-http sslee$ curl --limit-rate 50 b 127 . 0 . 0 . 1 : 8080 /random
- 1090963985
- 472554423
193702927
1496244304
.... 중략
akka-http 와 Actor
akka-http 는 Actor 하고 쉽게 상화 작용할 수 있다. 이때 client에서 요청은 route 를 거처 Acotor에게 호출하고 있는 즉 Actor의 응답을 기다리지 않고 바로 client에게 응답결과를 보낸다(아래 예제의 put). 또한 Actor의 ask 요청을 하고 Future을 받으며 이 Future의 결과가 완료시 client 에게 응답이 보내진다.(아래 예제 get)
아래 예제는 akka.io 에 있는 예제를 약간 수정만 했다.
GET요청시 actor의 receive method에서 고의로 Thread.sleep 를 주어 client화면에 이의 영향을 받지 않고 바고 응답결과를 받는지 확인 할 수 있다.
또한 PUT의 요청을 처리하는 부분도 Thread.sleep를 주었지만, 비동기로 요청의 응답을 Future로 받기 때문에 ###### 문자열이 바로 console 에 출력됨을 알 수 있다.
object ExampleViaActorWebServer {
case class Event ( eventId : String , ticket : Int )
case object GetEvents
case class Events ( xs : List [ Event ])
class EventActor extends Actor with ActorLogging {
var events = List . empty [ Event ]
def receive = {
case ev @ Event ( eventId , ticket ) =>
Thread . sleep ( 3000L )
events = ev :: events
case GetEvents =>
Thread . sleep ( 3000L )
sender () ! Events ( events )
}
}
def main ( args : Array [ String ]) = {
implicit val system = ActorSystem ( "ViaActorSystem" )
implicit val mat = ActorMaterializer ()
implicit val ec = system . dispatcher
val actor = system . actorOf ( Props [ EventActor ], "eventActor" )
implicit val eventFormater = jsonFormat2 ( Event )
implicit val eventsFormater = jsonFormat1 ( Events )
val route = path ( "event" ) {
put {
parameter ( "eventId" , "ticket" . as [ Int ]) { ( eventId , ticket ) =>
actor ! Event ( eventId , ticket )
complete (( StatusCodes . Accepted , "create event success" ))
}
} ~
get {
implicit val timeout : Timeout = 5 seconds
val events : Future [ Events ] = ( actor ? GetEvents ). mapTo [ Events ]
println ( "########################" )
complete ( events )
}
}
val serverBind : Future [ Http.ServerBinding ] = Http (). bindAndHandle ( route , "localhost" , 8080 )
println ( s "Server started host localhost port 8080, Do you want shutdown server? and then press RETURN" )
StdIn . readLine ()
serverBind . flatMap ( serverB => serverB . unbind ()). onComplete ( _ => system . terminate ())
}
}
호출
Macintosh : akka-http sslee$ http PUT localhost: 8080 /event eventId = = Metallica ticket == 10
HTTP / 1.1 202 Accepted
Content - Length : 20
Content-Type: text/plain ; charset = UTF - 8
Date : Thu , 19 Jul 2018 14 : 42 : 28 GMT
Server : akka-http/ 10 . 1 . 1
create event success
Macintosh : akka-http sslee$ http GET localhost: 8080 /event
HTTP / 1.1 200 OK
Content - Length : 44
Content-Type: application/json
Date : Thu , 19 Jul 2018 14 : 42 : 40 GMT
Server : akka-http/ 10 . 1 . 1
{
" xs " : [
{
" eventId " : " Metallica " ,
" ticket " : 10
}
]
}
web server console
[ success ] Total time : 78 s , completed Jul 19 , 2018 11 : 41 : 58 PM
Macintosh : akka-http sslee$ sbt " runMain com.sslee.http.intro.ExampleViaActorWebServer "
...중략
ace - http / akka - http / target / scala - 2.12 / akka - http_2 . 12 - 0.1 - SNAPSHOT . jar ...
[ info ] Done packaging .
[ info ] Running com . sslee . http . intro . ExampleViaActorWebServer
Server started host localhost port 8080 , Do you want shutdown server ? and then press RETURN
####################### #