갈루아의 반서재

아래 4개의 파일과 디렉토리에 주안점을 두고 본 튜토리얼을 진행한다. 현재 구성은 다음 이미지와 같다.

  • config/routes
  • Handler/
  • templates/
  • config/models

config/routes 는 URL → Code 로 매핑하는 설정을 하는 파일이다. 

config/models 은 데이터베이스 테이블 같은 지속성 객체에 대한 설정을 다룬다. 

templates/ HTML, js, 그리고 CSS 템플릿 파일을 포함한다.

Handler/ 디렉토리는 URL 을 통해 접근되는 코드를 포함한 파일이 들어있다.

Yesod framework의 보안성을 평가하기 위해 아래와 같이 간단한 echo 어플리케이션을 만든다. 

/echo/[some text]로 접속했을 때 h1 블럭 안에 있는 "some text"를 반환하는 웹페이지를 만들어보자. 

먼저 config/routes 파일에 /echo/#String EchoR GET 을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- By default this file is used by `parseRoutesFile` in Foundation.hs
-- Syntax for this file here: https://www.yesodweb.com/book/routing-and-handlers
 
/static StaticR Static appStatic
/auth   AuthR   Auth   getAuth
 
/favicon.ico FaviconR GET
/robots.txt RobotsR GET
 
/ HomeR GET POST
 
/comments CommentR POST
 
/profile ProfileR GET
 
/echo/#String EchoR GET
cs

추가된 /echo/#String EchoR GET 는 다음의 3가지 요소 - URL pattern, handler name, HTTP method - 를 포함한다. 

  • Handler/Echo.hs 파일 생성
  • 메인 Application.hs 파일에서 Handler.Echo 를 가져온다.
  • 어플리케이션 빌딩을 위해 cabla 파일에 Handler.Echo 를 선언한다. 

이제 localhost:3000/echo/foo 로 이동해보자. getEchoR 이 아직 구현되지 않았다는 메시지를 보게 될 것이다.

그러면 다음과 같은 src/Handler/Echo.hs 파일을 만들자.

1
2
3
4
5
6
module Handler.Echo where
 
import Import
 
getEchoR :: String -> Handler Html
getEchoR = error "Not yet implemented: getEchoR"
cs

이 경우 아래와 같이 페이지가 호출된다. 

너무 직관적이다. 다음과 같이 변경해보자. 너무 복잡할 것 없다. 간단히 말해서 String 타입의 theText 라는 하나의 인수를 가진 getEchoR 이라는 함수를 선언하는 것이다. 해당 함수가 호출되었을 때, Handler Html 을 반환한다. 

1
2
3
4
5
6
module Handler.Echo where
 
import Import
 
getEchoR :: String -> Handler Html
getEchoR theText = defaultLayout [whamlet|<h1>#{theText}|]
cs

src/Application.hs import Handler.Echo 를 추가한다.

1
2
3
4
5
6
7
-- Import all relevant handler modules here.
-- Don't forget to add new modules to your cabal file!
import Handler.Common
import Handler.Home
import Handler.Comment
import Handler.Profile
import Handler.Echo
cs

cabal 파일 blackbriar.cabal Handler.Echo 모듈을 추가한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  exposed-modules:
      Application
      Foundation
      Handler.Comment
      Handler.Common
      Handler.Home
      Handler.Profile
      Handler.Echo
      Import
      Import.NoFoundation
      Model
      Settings
      Settings.StaticFiles
  default-language: Haskell2010
cs

src/Foundation.hs  localhost/echo 루트에 모든 사람이 접속할 수 있도록 isAuthorized EchoR _= return Authorized 를 추가한다. 

1
2
3
4
5
6
7
8
9
10
    -- Routes not requiring authentication.
    isAuthorized (AuthR _) _ = return Authorized
    isAuthorized CommentR _ = return Authorized
    isAuthorized HomeR _ = return Authorized
    isAuthorized FaviconR _ = return Authorized
    isAuthorized RobotsR _ = return Authorized
    isAuthorized (StaticR _) _ = return Authorized
 
    isAuthorized ProfileR _ = isAuthenticated
    isAuthorized EchoR _ = return Authorized
cs

스페이스가 아닌 탭이 사용된 경우 다음의 에러가 발생할 수 있으니 유의한다. 

1
2
yesod: blackbriar.cabal: 73: user error (Do not use tabs for indentation (use spaces instead)
  Tabs were used at (line,column): [(73,0)])
cs

rebuild 를 해보면 다음과 같은 오류가 발생한다. 

1
2
3
4
5
6
7
5 of 12] Compiling Foundation       ( src/Foundation.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Foundation.o ) [config/routes changed]
/root/blackbriar/blackbriar/src/Foundation.hs:158:18: error:
    • The constructor ‘EchoR’ should have 1 argument, but has been given none
    • In the pattern: EchoR
      In an equation for ‘isAuthorized’:
          isAuthorized EchoR _ = return Authorized
      In the instance declaration for ‘Yesod App’
cs

앞서 src/Foundation.hs 에 추가했던 isAuthorized EchoR _ = return Authorized 를 isAuthorized (EchoR _) _ = return Authorized 로 고쳐서 실행해보자.

1
2
3
4
5
6
7
8
9
10
    -- Routes not requiring authentication.
    isAuthorized (AuthR _) _ = return Authorized
    isAuthorized CommentR _ = return Authorized
    isAuthorized HomeR _ = return Authorized
    isAuthorized FaviconR _ = return Authorized
    isAuthorized RobotsR _ = return Authorized
    isAuthorized (StaticR _) _ = return Authorized
    isAuthorized (EchoR _) _ = return Authorized
 
    isAuthorized ProfileR _ = isAuthenticated
cs

또 탭이 사용된 모양이다. 스페이스로 고친 후 다시 실행한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
blackbriar-0.0.0: build (lib)
Preprocessing library blackbriar-0.0.0...
[ 5 of 12] Compiling Foundation       ( src/Foundation.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Foundation.o )
 
/root/blackbriar/blackbriar/src/Foundation.hs:159:1: warning: [-Wtabs]
    Tab character found here.
    Please use spaces instead.
[ 6 of 12] Compiling Import           ( src/Import.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Import.o ) [Foundation changed]
[ 7 of 12] Compiling Handler.Comment  ( src/Handler/Comment.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Comment.o ) [Foundation changed]
[ 8 of 12] Compiling Handler.Common   ( src/Handler/Common.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Common.o ) [Foundation changed]
[ 9 of 12] Compiling Handler.Echo     ( src/Handler/Echo.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Echo.o )
 
/root/blackbriar/blackbriar/src/Handler/Echo.hs:6:48: error: parse error on input ‘{’
 
--  While building package blackbriar-0.0.0 using:
      /root/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.24.2.0 build lib:blackbriar --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1
Type help for available commands. Press enter to force a rebuild.
cs

이번에는 Illegal view pattern 이라는 에러가 뜬다. 이 경우 다음과 같이 처리한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
blackbriar-0.0.0: build (lib)
Preprocessing library blackbriar-0.0.0...
[11 of 12] Compiling Handler.Echo     ( src/Handler/Echo.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Echo.o )
[12 of 12] Compiling Application      ( src/Application.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Application.o )
 
/src/Application.hs:50:1: error:
    Illegal view pattern:  fromPathPiece -> Just dyn_anHx
    Use ViewPatterns to enable view patterns
 
--  While building package blackbriar-0.0.0 using:
      /root/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.24.2.0 build lib:blackbriar --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1
Type help for available commands. Press enter to force a rebuild.
^Cuser interrupt
yesod: Received ExitFailure 1 when running
Raw command: stack build --fast --file-watch blackbriar:lib --exec "/root/.stack/snapshots/x86_64-linux/lts-9.14/8.0.2/bin/yesod devel-signal" --flag blackbriar:dev --flag blackbriar:library-only
cs

스택오버플로우에 따르면, 먼저 yesod 버전이 오래되었으면 업데이트를 하고, view pattern 을 추가해야한다. 먼저 버전을 확인해보자. 1.4.17로 업데이트가 필요없다. 

1
2
(blackbriar) root@gcloudx:~/blackbriar/blackbriar# yesod version
yesod-bin version: 1.4.17
cs

그러면 package.yaml 파일을 열어 다음과 같이 dependencies 섹션 다음에 default-extensions: ViewPatterns 를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
name:    blackbriar
version: "0.0.0"
 
dependencies:
 
# Due to a bug in GHC 8.0.1, we block its usage
# See: https://ghc.haskell.org/trac/ghc/ticket/12130
- base >=4.8.2.0 && <4.9 || >=4.9.1.0 && <5
 
# version 1.0 had a bug in reexporting Handler, causing trouble
- classy-prelude-yesod >=0.10.2 && <1.0 || >=1.1
 
- yesod >=1.4.3 && <1.5
- yesod-core >=1.4.30 && <1.5
.
.
.
- case-insensitive
- wai
 
default-extensions: ViewPatternsr Scripter
cs

실행하면 다음과 같은 파싱에러가 발생한다. 이는 quasiquote 구문인  [whamlet|<h1>#{theText}|] 를 추가해놓고 QuasiQuotes GHC extension 이 사용가능하도록 설정하지 않았기 때문이다. 

1
2
3
4
5
6
7
[11 of 12] Compiling Handler.Echo     ( src/Handler/Echo.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Echo.o )
 
/src/Handler/Echo.hs:6:48: error: parse error on input ‘{’
 
--  While building package blackbriar-0.0.0 using:
      /root/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.24.2.0 build lib:blackbriar --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1
cs

아래와 같이 Echo.hs 최상단에 {-# LANGUAGE QuasiQuotes #-} 를 추가한다.

1
2
3
4
5
6
7
8
{-# LANGUAGE QuasiQuotes #-}
 
module Handler.Echo where
 
import Import
 
getEchoR :: String -> Handler Html
getEchoR theText = defaultLayout [whamlet|<h1>#{theText}|]
cs

여기까지 했으면 이제 실행해보자. 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# stack exec -- yesod devel
Yesod devel server. Enter 'quit' or hit Ctrl-C to quit.
Application can be accessed at:
 
http://localhost:3000
https://localhost:3443
If you wish to test https capabilities, you should set the following variable:
  export APPROOT=https://localhost:3443
 
blackbriar-0.0.0: configure (lib)
Configuring blackbriar-0.0.0...
blackbriar-0.0.0: build (lib)
Preprocessing library blackbriar-0.0.0...
1 of 12] Compiling Settings         ( src/Settings.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Settings.o ) [flags changed]
2 of 12] Compiling Settings.StaticFiles ( src/Settings/StaticFiles.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Settings/StaticFiles.o ) [flags changed]
3 of 12] Compiling Model            ( src/Model.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Model.o ) [flags changed]
4 of 12] Compiling Import.NoFoundation ( src/Import/NoFoundation.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Import/NoFoundation.o ) [flags changed]
6 of 12] Compiling Import           ( src/Import.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Import.o ) [flags changed]
7 of 12] Compiling Handler.Comment  ( src/Handler/Comment.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Comment.o ) [flags changed]
8 of 12] Compiling Handler.Common   ( src/Handler/Common.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Common.o ) [flags changed]
9 of 12] Compiling Handler.Echo     ( src/Handler/Echo.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Echo.o ) [flags changed]
[10 of 12] Compiling Handler.Home     ( src/Handler/Home.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Home.o ) [flags changed]
[11 of 12] Compiling Handler.Profile  ( src/Handler/Profile.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Handler/Profile.o ) [flags changed]
[12 of 12] Compiling Application      ( src/Application.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/Application.o )
blackbriar-0.0.0: copy/register
Installing library in
/.stack-work/install/x86_64-linux/lts-9.14/8.0.2/lib/x86_64-linux-ghc-8.0.2/blackbriar-0.0.0-18HzyIwvGfYDpXww1S7q4t
Registering blackbriar-0.0.0...
ExitSuccess
Type help for available commands. Press enter to force a rebuild.
Starting devel application
Devel application launched: http://localhost:3000
19/Jan/2018:11:41:47 +0900 [Debug#SQL] SELECT "id", "message", "user_id" FROM "comment" ORDER BY "id"; []
GET /
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  Status: 200 OK 0.089492s
cs