일반적인 플레이 철학
이 글을 읽고 있는 독자는 이미 플레이 프레임 워크에 대해 어느 정도는 알고 있을 것으로 생각합니다만, 이 글에서는 반드시 알아야 할 인사이드적인 것들에 대해 썰을 풀 것 입니다:
플레이는 상태가 없습니다. 아주 중요한 이야기에요. 밑줄 쫙~!! 프레임워크에 관해서는 요청들 사이에 서버에 저장되는 것은 없습니다. 물론 일반적인 웹 응용 프로그램에는 일종의 퍼시스턴스 엔진 (SQL, NoSQL, 파일 등)이 있지만 그것이 프레임 워크의 일부는 아닙니다.
Play에는 세션이라고 불리는 항목이 있지만 실제로는 쿠키일 뿐입니다. 따라서 여기에다 문자열만 저장할 수 있는 이유에 대해 문서에 자세히 설명되어 있습니다.플레이는 리액티브입니다. 요즘 많이들 사용하고 있는데요. 단순히 말하자면 가능한 한 적은 수의 스레드를 사용하자는 것이고, 클라이언트간에 스레드가 "공유"됩니다. 이는 Server Sent Events 또는 웹 소켓과 같이 오래 지속되는 연결에 있어서 매우 중요합니다. 일반적인 JEE 애플리케이션은 각 HTTP 연결에 스레드를 제공하고 있습니다 (적어도 JSR 315 : Servlet 3.0까지). 그것은 프레임워크 디자인과 당신이 그것을 사용하는 방식 모두에 큰 영향을 미칩니다 (특히 당신이하지 말아야 할 것들).
플레이 버전 2는 완전한 재 작성되었으며 완전히 다른 작품입니다.
타입 세이프 (typesafe)라는 회사가 플레이를 뒷받침하는데, 이름에도 느껴지듯이 타입 안정성에 훨씬 더 중점을 두고 있습니다. (역주: Lightbend로 이름이 변경됨. 2016년2월, Akka 등도 제공 )
리액티브 플랫폼 구성 요소들
플레이의 내부는 함수형 프로그래밍과 객체지향의 장점을 혼합하여 설계되었습니다. 대부분은 스칼라로 작성되었고 프레임워크는 그 위에 Java 를 위한 변경 레이어를 가지고 있습니다. 일반적으로 코드는 매우 간단하며, 그것을 이해하기 위해 스칼라 전문가일 필요는 없습니다. 가변성 상태를 가능한 한 많이 피합니다. 스칼라에서 선호되듯이 말이죠.
플레이는 오픈 소스입니다! 커뮤니티는 당신이 참여하기를 기다리고 있습니다. 문서 또는 버그 수정에 대한 도움은 언제나 환영합니다.
본격적으로 탐구를 해 봅시다.!
플레이 어플리케이션 런칭!
당신은 play start 를 입력했습니다. 자! 무슨 일이 발생 한 걸까요?
사실, 당신은 방금 특정 매개변수가 있는 기본 스칼라 빌드 도구인 sbt를 호출한 거 였습니다. (예, 'S'는 심플이 아닙니다.) Sbt는 당신의 응용 프로그램 빌드 정의를 "읽고" 나서, play % sbt-plugin 라는 불리는 플러그인을 찾습니다. project/plugins.sbt 에서 볼 수 있을 것입니다.
이 플러그인에는 응용 프로그램의 진입점 (main)이 어디인지 sbt에게 알려주는 config 키가 있습니다. 여기서는 sbt를 다루지 않겠지만 (play 빌드만으로도 많은 얘기 꺼리가 있습니다) main이 play.core.server.NettyServer 여기에 있다는 것만 알려드립니다.
표준 JEE와는 반대로 애플리케이션을 호스팅 (톰캣같은) 하는 애플리케이션 서버는 없습니다. play new를 사용하여 만든 애플리케이션은 그 자체로 서버이며, Play는 그 중 하나의 종속성 일뿐입니다 (정확히 말하면, 플레이는 모듈로 분할되어 있기 때문에 여러 종속성이 있습니다).
위의 그림에도 있듯이 Play는 현재 매우 유명한 (그리고 자바 기반의)고성능 비동기 네트워크 프레임워크인 Netty를 기반으로 합니다. Sbt는 이 객체를 조사하여 main를 찾습니다. mainDev라는 메소드를 발견했을 것인데요 이것은 dev 모드에서 호출됩니다 ( play run 을 사용할 때). 이제 main 에 집중합니다.
역주: 프로그램을 프로덕션 모드(prod mode) 로 배포하려는 경우 play start 로 시작해야 합니다.
auto-reloading-class 및 기본적으로 개발에만 필요한 기능들을 꺼버리기 때문에 응답속도가 빠릅니다.
코드를 읽으면 알 수 있듯이 인수를 구문 분석하고 PID를 포함하는 파일을 만들고 (응용 프로그램을 중지하려는 경우를 대비하여) 다른 지루한 초기화 상용구를 작성합니다. 재미있는 곳으로 건너 뜁시다.
결국 새로운 NettyServer가 일련의 인수로 만들어집니다. 하지만 먼저 스칼라는 새로운 StaticApplication (applicationPath)을 평가합니다.
부트스트랩
먼저 Play 애플리케이션을 초기화 해야 합니다. StaticApplication을 살펴 보시죠. "정적어플리케이션 이라고 하는 이유는 무엇인가요?" 묻는다면, 단순히 hot redeploy 코드변경을 하지 않기 때문이라고 답해드립니다. (우리는 프로덕션 모드로 사용한다고 한걸 기억하고 있나요? 현명한 사람들은 프로덕션 환경에서 hot redeploy 를 하지 않습니다).
그래서 좀 더 그곳을 살펴보면. DeafaultappApplication을 만듭니다. 이 클래스는 app 폴더, configuration, classloader 등 (코드 here)와 같이 현재 앱에 대한 일반 정보를 포함하는 case 클래스이며 Play.start (application)를 호출합니다.
그것이 하는 일은 상당히 간단합니다.
각 플러그인에서 onStart를 하나씩 호출하여 올바른 클래스로더를 사용하고 있는지 확인합니다. 이러한 플러그인은 예를 들어 DB에 대한 커넥션 풀을 만듭니다.
이 시점에서 플러그인이 예외를 던지면 응용 프로그램이 중지됩니다.
우리는 애플리케이션 설정을 알고 있기 때문에 플러그인을 성공적으로 시작했습니다. 이제 HTTP 요청을 리스닝 할 준비가 되었습니다.
Http 요청에 대한 리스닝
이제 NettyServer를 만들 차례입니다. 앞에서 보았듯이 NettyServer 클래스의 인스턴스를 생성합니다 (NettyServer 객체와 혼동하지 마십시오).
이 클래스는 서버의 인스턴스를 만들고, 스레드 풀, 파이프 라인 인코더 및 디코더를 구성하고, 서버를 주소 및 포트에 바인딩하고, SSL을 구성하지만 더 중요한 것은
newPipeline.addLast ( "handler", defaultUpStreamHandler) 입니다.
따라서 HTTP 또는 HTTPS 요청이 수신 될 때마다 Netty는이 PlayDefaultUpstreamHandler 인스턴스의 messageReceived를 호출합니다.
이 메소드는 다음과 같은 작업을 수행합니다.
- Netty의 HttpRequest에서 play.api.mvc.RequestHeader를 만듭니다.기본적으로 헤더 값을 채우고 쿼리 문자열을 파싱하는 등의 작업을 수행하지만 요청 본문을 파싱하지는 않습니다.
- 플래시 쿠키를 관리하여 1 회의 요청에 대해서만 유효하게 만듭니다.
- 요청에 "태그"를 달아 라우팅에 대한 요청 객체에 메타 데이터를 추가 할 수 있습니다.
- 이 메소드는 응용 프로그램 전역 객체를 사용하여 이 요청으로 수행 할 작업을 찾습니다.
여기에서는 예외 및 매개 변수 구문 분석을 처리합니다. 무엇인가 실패하면 onBadRequest (rh, e.getMessage)가 응용 프로그램 Global 객체에서 호출됩니다 (전역이 제공되지 않으면 기본값 Global을 사용함).이 객체는 상태 500의 Error 페이지를 렌더링해야합니다. 그렇지 않으면 server.getHandlerFor rh)) [Result, (Handler, Application)] 중 하나를 반환합니다.
getHandlerFor는, 불려가는 Handler를 해결합니다. Handler의 두 가지 주요 유형은 다음과 같습니다.
- EssentialAction : 기본적으로 컨트롤러에서 Action {request => ...}을 작성하여 정의한 내용입니다.
- 웹 소켓
이 코드는 Global.onRouteRequest를 호출하여 호출 할 Handler를 찾거나 오류가 발생하면 적절한 Response를 반환합니다. 실패한 경우 Response (예 : 상태가 500 인 HTML 페이지)가 포함된 Left를 반환하고, 그렇지 않으면 앱에 정의 된 Handler가 들어있는 Right (예 : 컨트롤러에 정의 된 Action)를 반환합니다.
요청 본문 다루기
굿! 지금까지 우리는 호출되어야하는 코드를 어떻게든 발견했습니다. RequestHeader로만 작업한다는 사실을 인지했을 수도 있구요. RequestHeader는 본문이 없는 요청입니다. 패턴매칭을 사용하여 getHandlerFor가 반환 한 값을 어떻게 처리할지 결정합니다.
가장 일반적인 경우는 EssentialAction입니다.
근본적으로, EssentialAction은 단지 함수 (RequestHeader) => Iteratee[Array[Byte], Result] 일뿐입니다. 이 함수에 RequestHeader를 주면 요청 본문을 리액티브적으로 소비하는 Iteratee를 제공하고 결국 Result를 "반환"합니다.
다음 단계는 요청 본문을 사용할 항목이 있기 때문에 매우 명확합니다. 우리가 수행해야 할 것은 바이트를 피드로 가져와 결과를 얻는 것뿐입니다. 그것이 바로 handleAction이 하는 일입니다. 클라이언트로부터 바이트를 받아 필요할 경우 청킹을 처리하고 결과를 얻기 위해 Iteratee에게 피드를 보내지만 먼저 전역에 정의 된 필터를 호출합니다. (val filteredAction = app.map (_. global) .getOrElse (DefaultGlobal) .doFilter (action)).
Iteratee에 피드를 제공하려면 Enumerator [Array [Bytes]]를 만들어야합니다.
Play는 클라이언트로부터의 청크를 열거하는 Enumerator를 만들고 BodyParser로 구성합니다. 그런 다음 Future[Result] 를 얻기 위해 결과 Iteratee를 실행하고 이 결과를 클라이언트에 다시 보내면 됩니다.
이게 다에요.