GSP(Groovy Server Page)
GSP(Groovy Server Page)
GSP는 Grails의 뷰 기술이다.
이 것은 ASP와 JSP 사용자에게 친숙하도록 설계됐다. 하지만 좀 더 유연하고 직관적으로 설계하였다.
Grails의 GSP는 grails-app/views 디렉토리에 있다.
관례(Convension)에 따라서 자동적으로 렌더링되도록 할 수 있고 render 메소드를 사용할 수도있다.(Convension 으로 자동으로 view 페이지를 찾아도 되지만 Controller의 Action에서 명시적으로 지정할 수 도 있다는 의미)
Convention이란 view는 grails-app/views/ 컨트롤러 이름/액션.[gsp | jsp] 규칙으로 자동으로 view 페이지를 찾아준다.
GSP Basics
Groovy 로직(<% %> 스클립틀릿 구문)을 GSP 안에 넣을 수도 있지만 권장되는 방법도 아니고,
JSP 에서 사용되는 스크립틀릿 구문과 페이지 지시어 등 사용법도 같고,
실제로 사용되지도 않기때문에 설명은 생략함
Expressions
GSP 표현식은 JSP의 EL 표현식, Groovy의 GString과 유사할 뿐만 아니라 ${표현식}의 형태로 사용한다
JSP와 다르게 ${..}블럭 안에 Groovy 표현식을 사용할 수 있다
view.jsp를 아래와 같이 변경한다.
확인주소 : http://localhost:8080/mygrails/book/view?name=kshmeme
hello, ${params.name}!
BookController 의 내용을 아래와 같이 변경한다.
package net.grails.my class BookController { def view ={ } }
변경후 실행하면
Controller에서 model 데이터를 넘겨주지 않아도 GSP 표현식 에서 Groovy 표현식을 사용할 수 있음을 알 수 있다.
(JSP 표현식과 가장 큰 차이점이라고 생각됨)
GSP에서는 ${..}블럭에 있는 변수의 값이 그대로 표현된다.(JSP도 마찬가지 이지만..)
XSS(Cross-Site-Scripting)공격의 위험을 줄이기 위해 자동으로 HTML을 제거하도록 설정할 수 있다.
먼저 스크립트가 실행되는 예제를 살펴보면..
BookController 의 내용을 아래와 같이 변경한다.
확인주소 : http://localhost:8080/mygrails/book/view?name=kshmeme
package net.grails.my class BookController { def view ={ def hello = "<script>alert('hello, "+params.name+"');</script>" [hello : hello] } }
view.gsp의 내용을 아래와 같이 변경한다
${hello}
아래와같이 alert()메시지로 인삿말이 나오는 것을 확인할 수 있다.
HTML 제거하는 방법은
grails-app/conf/Config.groovy 파일에서 코덱을 설정한다.
grails.views.default.codec 값이 기본으로 설정되어 있다면 html로 변경하고
없다면 추가한다.
코덱 변경 후 서버재구동을 한다.
grails.views.default.codec='html'
설정변경후 서버 재구동 후 확인을 해보면
경고창으로 인삿말이 나오던 페이지가
아래 이미지처럼 텍스트로 나오게 된다.
GSP Tags
GSP의 빌트인 태그들에 대해서 설명한다
Tag라이브러리를 만드는 방법은 추후 설명예정 (Tag Libraries)
빌트인GSP태그는 g: 로 시작한다.
변수를 정의하는 set 태그를 이용하여 예제를 실행해본다.
view.gsp를 아래와 같이 수정한다.
확인주소: http://localhost:8080/mygrails/book/view
<%@ page contentType="text/html;charset=UTF-8" %> <!-- 방법 1 --> <g:set var="now" value="${new Date()}"> </g:set> <!-- 방법 2 --> <g:set var="now2" > ${new Date()} </g:set> 방법1: ${now} <br /> 방법2: ${now2}
gsp태그를 사용해야 하므로 page 지시어를 삽입했다
첫번째 방법처럼 attribute로 전부 지정을 해도되고 value 속성은
두번째 방법처럼 태그바디에 지정을 해도된다.
Variables and Scopes
변수의 scope의 종류는 아래와 같다.
page - 현재 페이지에서만(기본).
request - 현재 요청에서만
flash - flash 스콥에 저장되므로 다음 요청까지 살아남는다.
session - 사용자의 세션에서
application - 어플리케이션 어디에서나 살아남는다.
gsp 에서의 flash만 새로운 개념이다.
나머지는 jsp 에서 존재하는 개념이다.
Logic and Iteration
조건문과 반복문에 대해서 설명한다.
조건문(if, elseif, else) 에 대한예제를 살펴보면
view.gsp를 아래와 같이 변경한다.
확인주소: http://localhost:8080/mygrails/book/view?role=admin
파라미터를 user, 또는 다른 값으로도 변경하여 확인해본다.
<%@ page contentType="text/html;charset=UTF-8" %> <g:if test="${params.role=='admin'}" > 관리자 입니다. </g:if> <g:elseif test="${params.role=='user'}" > 일반사용자입니다. </g:elseif> <g:else> 로그인하지 않았습니다. </g:else>
파라미터가 role 이 admin이면 관리자, user이면 일반사용자 아니면 로그인하지 않은 사용자를 나타낸다.
아래와같이 관리자, 일반사용자 등 메시지가 표시된다
반복문(each, while)에 대한 예제
view.gsp의 소스를 아래와 같이 변경한다.
<%@ page contentType="text/html;charset=UTF-8" %> <g:each in="${1..3}" var="num"> <p>each : ${num}</p> </g:each> <g:set var="num" value="${1}"></g:set> <g:while test="${num<4 }"> <p>while : ${num++}</p> </g:while>
each문 : in : 데이터(배열, Collection 등) var: 반복문 안에서 사용되는 지역변수
while문 : num이 4보다 작으면 반복문(while)실행
아래와같이 브라우저에 출력되는 걸 확인할 수 있다.
Search and Filtering
객체의 컬랙션을 정렬하고 필터링이 필요할 때 findAll과 grep을 사용할 수 있다.
Book 도메인 클래스를 아래와 같이 변경한다
확인주소 : http://localhost:8080/mygrails/book/view
package net.grails.my class Book { static constraints = { } String name; String userid; String author; }
BookController 클래스를 아래와 같이 변경한다
package net.grails.my class BookController { def view ={ //데이터 생성 def lists = new ArrayList<Book>() def nameList = ["Groovy in Action", "토비의 스프링", "HeadFirst JSP"] def authorList =["디에크쾨니히", "tobi", "케이시시에라"] (0..2).each { seq-> Book temp = new Book() temp.name = nameList[seq] temp.author = authorList[seq] lists.add(temp) } //생선된 데이터를 view 에 전달 render(view:"/book/view",model:[list:lists]) } }
view.gsp를 아래와 같이 변경한다
<%@ page contentType="text/html;charset=UTF-8" %> <!-- in : 데이터(배열, Collection 등) var: 반복문 안에서 사용되는 지역변수 --> <g:findAll in="${list}" expr="it.author=='tobi'"> <p>book name : ${it.name}, author : ${it.author }</p> </g:findAll> <g:grep in="${list.name }" filter="~/.*?Groovy.*?/"> <p>book name : ${it} </g:grep>
in : 반복에 들어갈 객체
expr : GPath 표현식
filter : 정규식 또는 정규식을 구현한 필터 클래스
아래에서 결과를 확인할 수 있다.
첫번째 bookname 은 findAll을 이용하여 저자가 'tobi' 결과를 보여주었고,
두번째 bookname은 grep을 이용하여 정규식을 이용하여 책 이름에 Groovy가 들어간 결과를 보여주었다
Links and Resources
컨트롤러와 액션을 쉽게 연결 할 수 있는 태그도 지원한다( 태그를 자동으로 만들어준다.)
이 것은 URL 매핑(URL Mappings)에 의존하여 자동으로 행해진다
확인주소 : http://localhost:8080/mygrails/book/view
view.gsp를 아래와 같이 변경한다.
<%@ page contentType="text/html;charset=UTF-8" %> <!-- 현재 컨트롤러에 show action --> <g:link action="show" id="1">Book 1</g:link><br /> <!-- book controller에 default action --> <g:link controller="book">Book Home</g:link><br /> <!-- book controller에 list action --> <g:link controller="book" action="list">Book List</g:link><br /> <!-- book controller list action 또 다른 표현방식--> <g:link url="[action:'list',controller:'book']">Book List</g:link><br /> <!-- list action에 쿼리스트링지정, 기타 속성들--> <g:link action="list" target="_blank" params="[sort:'title',order:'asc']" userattr="userattaVal"> Book List </g:link><br />
브라우저에서 확인을 해보면
태그가 생성된 걸 확인할 수 있다
실제 렌더링 된 소스는
link tag에서 알지 못하는 속성은 그대로 html로 렌더링 해 준다
Forms and Fields
GSP에서 HTML 폼과 필드를 다루는 여러 태그 중 하나이다.
HTML의 form 태그에 컨트롤러/액션을 이해할 수 한 것이다.
view.gsp 소스를 아래와 같이 변경한다.
확인주소 : http://localhost:8080/mygrails/book/view
<%@ page contentType="text/html;charset=UTF-8" %> <g:form name="myForm" url="[controller:'book', action:'list']"> book/list로 submit 되는 form tag </g:form>
화면을 띄우고 소스보기를 해보면
url에서 지정한 controller와 action에 맞게 from 태그의 action을 지정한 것을 볼 수 있다.
그 외에도 여러가지 태그들을 GSP에서 만들 수 있다.
자세한 다른 태그들은
http://grails.org/doc/latest/ref/Tags/grep.html
에서 더 확인할 수 있다.
Views and Templates
Template Basics(템플릿의 기초)
Grails는 템플릿을 식별하기 위해서 뷰의 이름 앞에 '_'를 붙이는 관례를 사용한다
Book을 렌더링 하는 템플릿은 grails-app/views/book/_bookTemplate.gsp에 위치한다
아래와 같이 bookTemplate이라는 이름의 템플릿을 만든다
bookTemplate 이라는 이름의 템플릿은 _bookTemplate.gsp 라는 이름의 gsp 파일을 만든다(under bar)
확인주소: http://localhost:8080/mygrails/book/view
_bookTemplate.gsp 의 파일 내용을 아래와 같이 변경한다
<div class="book" id="${book?.id}"> <div>Title: ${book?.title}</div> <div>Author: ${book?.author}</div> </div>
BookController 의 내용을 아래와 같이 변경한다
package net.grails.my class BookController { def index() { } def view = { Book book = new Book() book.title = "Groovy in Action" book.author = "쾨니히" book.id = 1 [myBook:book] } }
view.gsp의 내용을 아래와 같이 변경한다
<%@ page contentType="text/html;charset=UTF-8" %> <g:render template="bookTemplate" model="[book:myBook]" />
아래와 같이 _bookTemplate.gsp 의 내용을 view.gsp 에서 가져다 사용한 것을 알 수 있다.
view에서 템플릿을 렌더링 하려면 render 태그를 사용한다.
model=[book:myBook]
에서 book은 템플릿으로 넘기는 이름이고 myBook은 Controller에서 넘어온 모델이다
템플릿에 컬렉션 형태로도 넘길 수 있다.
BookController 의 내용을 아래와 같이 변경한다.
확인주소 : http://localhost:8080/mygrails/book/view
package net.grails.my class BookController { def index() { } def view = { List<Book> lists = new ArrayList<Book>(); (1..3).each{ seq -> Book temp = new Book() temp.title = "title" + seq temp.author = "author" + seq temp.id = seq lists.add( temp) } [lists : lists] } }
view.gsp의 내용을 아래와 같이 변경한다.
<%@ page contentType="text/html;charset=UTF-8" %> <g:render template="bookTemplate" collection="${lists}" var="lists" />
_bookTemplate.gsp의 내용을 아래와 같이 변경한다.
<%@ page contentType="text/html;charset=UTF-8" %> <div class="list"> <g:each in="${lists }" var="book"> <div class="book" id="${book.id}"> <div> Title: ${book.title} </div> <div> Author: ${book.author} </div> </div> <br /> </g:each> </div>
아래와 같이 _bookTemplate.gsp 에서 렌더링 한 내용을 확인할 수 있다
Shared Templates(템플릿 공유하기)
grails-app/views/book/_bookTemplate.gsp 템플릿은 book 컨트롤러에서 사용한 템플릿이지만
어플리케이션 전체에서 사용가능하다.
보통 공유되는 템플릿은 grails-app/views/shared 폴더에 저장한다.
공유폴더에 템플릿을 하나 만들어보면..
먼저 view/하위에 shared 폴더를 생성한다.
shared 폴더 하위에 _mySharedTemplate.gsp 라는 템플릿을 하나 생성한다
_mySharedTemplate.gsp의 내용을 아래와 같이 변경한다(bookTemplate.gsp 와동일)
<%@ page contentType="text/html;charset=UTF-8" %> <div class="list"> <g:each in="${lists }" var="book"> <div class="book" id="${book.id}"> <div> Title: ${book.title} </div> <div> Author: ${book.author} </div> </div> <br /> </g:each> </div>
view.gsp의 내용을 아래와 같이 mySharedTemplate.gsp 템플릿을 사용하는 형태로 변경한다.
grails-app/views/shared/_mySharedTemplate.gsp 이라는 템플릿을 사용하기위해서는
grails-app/views 하위 경로부터 작성한다 /shared/mySharedTemplate(under bar 는 삭제한것에 유의)
확인주소 : http://localhost:8080/mygrails/book/view
<%@ page contentType="text/html;charset=UTF-8" %> <g:render template="/shared/mySharedTemplate" collection="${lists}" var="lists" />
아래와같이 결과를 확인할 수 있다
Layouts with Sitemesh
Creating Layouts(레이아웃 만들기)
sitemesh에 기반한 레이아웃 지원한다.
레이아웃은 grails-app/views/layouts 디렉터리에 위치한다
레이아웃 폴더에 list.gsp 라는 레이아웃을 하나 생성한다
grails-app/views/layouts /list.gsp 의 내용을 아래처럼 변경한다.
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title><g:layoutTitle default="default Title"/></title> <g:layoutHead/> </head> <body> <div>header~!!!</div> <div id="mainContents"> <g:layoutBody/> </div> <div> footer~! </div> </body> </html>
layoutTitle - 대상 페이지의 제목을 출력
layoutHead - 대상 페이지의 head 태그의 내용을 출력
layoutBody - 대상 페이지의 body 태그의 내용을 출력
Triggering Layouts(레이아웃 사용하기)
여러가지 방법 중 가장 단순한 방법은 meta 태그를 이용하는 방법이다
확인주소 : http://localhost:8080/mygrails/book/view2
book/view2.gsp를 만든다.(만드는 과정 생략)
view2.gsp의 내용을 아래와 같이 변경한다.
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR"/> <meta name="layout" content="list"/> <title>title 입니다~@_@</title> <script type="text/javascript"> function msg(t){ alert(t); } </script> </head> <body> <div class="body"> view2 페이지 입니다!! </div> </body> </html>
view2.gsp의 내용은 아주 단순한 html페이지 이다.
BookController의 내용을 아래와 같이 변경한다.(view2 액션을 생성하였다)
package net.grails.my class BookController { def index() { } def view = { List<Book> lists = new ArrayList<Book>(); (1..3).each{ seq -> Book temp = new Book() temp.title = "title" + seq temp.author = "author" + seq temp.id = seq lists.add( temp) } [lists : lists] } def view2 = { } }
실행후 확인한다.
아래는 실제 렌더링된 html의 소스보기 이미지이다.
view2의 title이 list.gsp의
view2의 header 영역이 list.gsp의
view2의 body 영역이 list.gsp의
이게 가능한 이유는 view2.gsp에서 5번째 line에서
<meta name="layout" content="list"/>
라고 지정을 하였기 때문에 list 레이아웃이 사용되어진 것이다.
레이아웃을 사용하는 두 번째 방법은 관례(Convention)을 따르는 것이다.
예를들면 BookController에 list 액션에만 적용되는 레이아웃을 만들고 싶다면
/layout/book/list.gsp 파일을 생성하면된다.
확인주소 : http://localhost:8080/mygrails/book/list
/layout/book/list.gsp 내용을 아래와 같이 변경한다
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title><g:layoutTitle default="default Title"/></title> <g:layoutHead/> </head> <body> <div>book/list/header~!!!</div> <div id="mainContents"> <g:layoutBody/> </div> <div> book/list/footer~! </div> </body> </html>
BookController의 내용을 아래와 같이 변경한다.(list 액션 추가하였다)
package net.grails.my class BookController { def index() { } def view = { List<Book> lists = new ArrayList<Book>(); (1..3).each{ seq -> Book temp = new Book() temp.title = "title" + seq temp.author = "author" + seq temp.id = seq lists.add( temp) } [lists : lists] } def view2 = { } def list = { } }
view/book/list.gsp 추가한다
list.gsp의 내용을 아래와 같이 변경한다
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR"/> <title>Insert title here</title> </head> <body> <div class="body"> book/list 입니다. </div> </body> </html>
실행후 확인하면 아래와 같이 /layout/book/list.gsp 레이아웃이 적용된 걸 확인할 수 있다.
Tag Libraries
사용자 정의 태그라이브러리를 지원한다.
만드는 방법은
1. Taglib로 끝나는 클래스를 만든다.
2. grails-app/taglib 디렉토리에 넣는다.
아래처럼 SimpleTaglib를 만들어보자.
SimpleTaglib 의 내용을 아래와 같이 변경한다
package mygrails class SimpleTagLib { def dateFormat = { attrs, body -> out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date) } }
dafeformat 이라는 메서드는 attrs, body 두개의 인자를 받는다.
attrs : 태그의 속성
body : 태그의 본문내용
out : Stream(output Writer에 대한 참조)
dateFormat 은 format 속성(날짜 포맷)과 date(날짜 인스턴스)를 전달받아 문자열로 출력 하는 예제이다.
view.gsp를 아래와 같이 변경한다
<%@ page contentType="text/html;charset=UTF-8" %> <g:dateFormat format="yyyy-MM-dd" date="${new Date() }" ></g:dateFormat>
SimpleTagLib의 dateFormat 에서 사용되는 attrs를 정의한 것을 확인할 수 있다.
실행하면 아래 그림처럼 현재 날짜(date 속성)가 지정한 포맷(format 속성)으로 브라우저에출력되는 것을 확인할 수 있다.
Logical Tags
특정 조건이 만족돼야만 결과를 출력하는 논리 태그를 만들 수도 있다
예를들면 관리자일 경우에만 관리자 버튼이 화면에 나온다던지 하는..
IsAdminTagLib를 만들어보자.
SimpleTagLib 와 같은 위치에 IsAdminTagLib Groovy 클래스를 생성한다.
IsAdminTagLib 의 내용을 아래와 같이 변경한다
package mygrails class IsAdminTagLib { def isAdmin = {attrs, body -> def user = attrs["user"] if(user != null && user == "kshmeme"){ out << body() } } }
BookController의 내용을 아래와 같이 변경한다
package net.grails.my class BookController { def view = { def userid = "" if(params.containsKey("userid")){ userid = params.userid } ["userid" : userid] } }
view.gsp의 내용을 아래와 같이 변경한다.
<a href="javascript:;">마이페이지</a> <g:isAdmin user="${userid}"> <a href="javascript:;">관리자</a> </g:isAdmin>
BookController 의 view Action 에서 전달받은 userid 데이터를 IsAdminTagLib 의 isAdmin 메서드로 전달한다.
userid가 kshmeme 일 경우에만 관리자 버튼이 나타난다.
확인주소 : http://localhost:8080/mygrails/book/view?userid=kshmeme
아래와 같이 확인할 수 있다.
Iterative Tags
body를 여러번 실행 가능하게 할 수 있다.
IsAdminTagLib 의 소스를 아래와 같이 변경한다.
package mygrails class IsAdminTagLib { def repeat = { attrs, body -> attrs.times?.toInteger().times { num -> out << body(num) } } }
view.gsp의 내용을 아래와 같이 변경한다
<div> <g:repeat times="3"> <p>Repeat this 3 times! Current repeat = ${it}</p> </g:repeat> </div>
times? : times가 null이면 코드실행 중단하고 null 리턴
확인주소 : http://localhost:8080/mygrails/book/view
IsAdminTagLib 에서는 times attribute가 존재하면 int로 변환 후 times 숫자만큼 loop를 돌며body를 출력한다.
이 때 현재 index(num)을 파라미터로 넘겨준다.
gsp에서는 IsAdminTagLib(repeat) 에서 전달받은 num을 it 이라는 이름으로 접근하여 출력 하였다.
문제점 : it 이라는 변수 이름은 태그를 중첩하여 사용할 경우 충돌을 일으킬 수 있기때문에
body 에서 사용할 변수이름을 지정해야한다.
IsAdminTagLib의 소스를 아래와 같이 변경한다.
package mygrails class IsAdminTagLib { def repeat = { attrs, body -> def var = attrs.var ? attrs.var : "num" attrs.times?.toInteger().times { num -> out << body((var):num) } } }
reapeat 태그에 var 속성이 존재하면 그대로 사용하고 존재하지 않을경우에는 num 이라는 이름으로 접근 할 수 있다.
view.gsp의 코드를 아래와 같이 변경한다
------------------------------------------------<br /> var 속성이 존재할 경우<br /> ------------------------------------------------<br /> <div> <g:repeat times="3" var="repeatNum"> <p>Repeat this 3 times! Current repeat = ${repeatNum}</p> </g:repeat> </div> <br /> ------------------------------------------------<br /> var 속성이 존재하지 않을 경우<br /> ------------------------------------------------<br /> <div> <g:repeat times="3" > <p>Repeat this 3 times! Current repeat = ${num}</p> </g:repeat> </div>
아래와 같이 결과를 확인할 수 있다
두 개의 결과는 같지만 body 안의 변수의 이름이 다른 걸 확인할 수 있다
Tag Namespaces
태그는 자동으로 Grails의 기본네임스페이스에 추가되고 GSP 페이지에서 "g:" 접두어로 태그를 사용한다.
태그 하지만 이를 변경 할 수 있다.
IsAdminTagLib 클래스에 아래와 같이 namespace 멤버를 추가한다.
static namespace = "my"
서버 재구동 후 확인해보면 아래와 같이 에러가 발생한다.
g namespace에 repeat 태그가 존재하지 않는다 라는 에러 메시지를 확인할 수 있다.
view.gsp의 내용을 아래와 같이 g: 로 시작된 부분을 my로 변경한다.
------------------------------------------------<br /> var 속성이 존재할 경우<br /> ------------------------------------------------<br /> <div> <my:repeat times="3" var="repeatNum"> <p>Repeat this 3 times! Current repeat = ${repeatNum}</p> </my:repeat> </div> <br /> ------------------------------------------------<br /> var 속성이 존재하지 않을 경우<br /> ------------------------------------------------<br /> <div> <my:repeat times="3" > <p>Repeat this 3 times! Current repeat = ${num}</p> </my:repeat> </div>
그러면 다시 정상적으로 작동하는 걸 확인할 수 있다