태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

티스토리 툴바


"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding

1. 소개

오늘날의 자바 웹 개발은 실제 필요보다 극적으로 더욱 복잡하다. 자바 영역의 대부분의 최신 웹 프레임워크는 필요 이상으로 복잡해졌고 DRY(Don't Repeat Yourself) 원칙을 받아들이지 않는다.

Rails, Django 그리고 TurboGears와 같은 동적 프레임워크들은 웹 애플리케이션에 대한 좀 더 현대적인 사고 방식으로 가득차게끔 도왔다. Grails는 이러한 개념에 기초하고 자바 플랫폼에서 웹 애플리케이션을 작성하는 복잡성을 극적으로 줄어준다. 그러나 이것이 다른 것과 차이가 나는 점은 grails는 Spring이나 Hibernate과 같이 이미 확립된 자바기술을 기초로 한다는 것이다.

Grails는 완전한 구성(full stack)의 프레임워크이고 핵심기술과 관련된 plug-in들로 웹 개발의 난제들을 풀수 있도록 한다. Grails에는 다음 것들이 포함되어 있다.

  • Hibernate를 기반으로 한 Object Relation Mapping(ORM) 계층의 용이한 이용
  • Groovy Server Page(GSP)라는 풍부한 표현을 가진 view 테크닉
  • Spring MVC 모델에 기반한 컨트롤러 계층
  • 막강 Groovy로 동작하는 Gant를 기반으로 한 명령줄(command line) 스크립팅 환경
  • 바로 구성 정보가 reloading되는 Jetty 컨테이너 내장
  • 내장된 Spring 컨테이너를 이용한 Dependency Injection
  • Spring의 핵심 MessageSource 개념을 기반으로 한 국제화(i18n) 지원
  • Spring의 트랜잭션 추상을 기반으로 한 트랜잭션 서비스 계층

이런 모든 것들로 인해 Groovy 언어와 도메인 기반 언어(Domain Specific Language - DSL)의 막강한 힘을 쉽게 이용할 수 있다.

이 문서는 Grails를 시작하고 Grails 프레임워크를 이용하여 웹 애플리케이션을 작성할 수 있도록 할 것이다.

---
원문: 1. Introduction

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
사용자 삽입 이미지
Grails 좀 공부해볼까해서 User Guide보고 나름 정리했습니다.
처음 시작하고 금방 정리할 줄 알았는데 생각보다 양이 많아 시간이 좀 걸렸습니다.

정리하면서 다시 한 번 느끼지만 진짜 막강 Grails입니다. 최신의 좋다는 기술을 잘 통합하였고 생산력 무지 좋습니다. RoR을 쓰고 싶지만 주로 쓰는 언어가 Java라 군침만 흘리고 있었는데 RoR/Ruby 스크립팅 언어의 유연성과 자바의 엔터프라이즈급 서비스가 만나 환상의 조합을 이루고 있습니다.

1. 소개
Grails의 일반적인 내용을 소개합니다.

2. 시작하기
 
Grails 설치 및 Hello World 수준의 Getting Start가 있습니다.

3. Configuration
여러가지 Grails 설정파일을 다룹니다.

4. 명령어
Grails에서 제공하는 유용한 명령어 사용법을 설명합니다.

5. Object Relational Mapping (GORM) - 1
도메인 클래스를 만들기 위해 객체-테이블 매핑(ORM) 기술을 다룹니다. 매우 중요한 내용들입니다.
  • 빠른 시작을 위한 가이드
  • GORM에서 도메인 모델 만들기
  • 영속성(Persistence) 기본
5. Object Relational Mapping (GORM) - 2
GORM에서 다양한 방법으로 SQL 질의하는 방법을 설명합니다.
5. Object Relational Mapping (GORM) - 3
GROM의 기타 고급 기능들에 대해 설명합니다.
  • 고급 GROM 기능들
  • 프로그램 방식의 트랜잭션
  • GORM과 제약조건
6. 웹 계층 - 1
웹 계층에서 요청을 처리하는 컨트롤러에 대해 집중 분석합니다.
6. 웹 계층 - 2
Grails에서 뷰를 표현하는 GSP에 대해 설명합니다.
6. 웹 계층 - 3
Grails의 강력한 태그 라이브러리 사용법과 사용자 정의 태그 라이브러리 작성방법을 설명합니다.
6. 웹 계층 - 4
다양한 방법으로 URL을 컨트롤러의 액션으로 매핑하는 방법을 이야기합니다.
6. 웹 계층 - 5
Spring의 웹 플로우를 Grails에서 사용하는 방법을 다룹니다.
6. 웹 계층 - 6
Grails의 강력한 필터 기능을 설명합니다.
6. 웹 계층 - 7
그 밖의 웹에서 Ajax을 사용하는 방법과 Grails가 자동으로 컨텐츠의 종류를 결정하는 방법에 설명합니다.
  • Ajax
  • 컨텐트 종류 결정(Content Negotiation)
7. 유효성검사
제약조건을 이용하여 데이터 바인딩 같은 곳에서 사용할 수 있는 편리한 입력 데이터 유효성 검사 방법을 다룹니다.

8. 서비스 계층
핵심 비즈니스 로직을 재사용이 쉽도록 서비스 계층으로 분리하고 이것을 이용하는 방법을 설명합니다.
물론 자바로 작성된 서비스를 이용하는 방법도 설명합니다.

9. 테스팅
Grails가 지원하는 자동화된 여러 가지 테스트 방법을 다룹니다.

10. 국제화
애플리케이션이 여러 언어로 서비스가 가능하도록 국제화(Internationalization)기능을 사용하는 방법을 설명합니다.

11. 보안
악의적인 공격자로 부터 애플리케이션을 보호하기 위해 Grails가 제공하는 방법과 그 외의 것들을 다룹니다.

12. 플러그인
Grails의 플러그인을 작성하는 방법을 설명합니다.

13. 웹서비스
SOAP이나 REST 스타일의 웹서비스를 Grails에서 얼마나 쉽게 작성할 수 있는지 보여줍니다.

14. Grails와 Spring
Grails와 Spring이 어떤 식으로 연동하는지 고급 사용자를 위해 설명합니다.

15. Grails 와 Hibernate
GORM 기능으로 부족할 때 Hibernate의 모든 기능을 완전히 사용할 수 있는 방법을 이야기합니다.
덧붙여 Hibernate로 구현된 자바 도메인 클래스를 이용하는 방법도 설명합니다.

16. Scaffolding
명령어 한 줄로 CRUD가 가능한 애플리케이션을 한방에 자동생성해 주는 강력한 Scaffolding에 대해 이야기 합니다.진짜 생산력 짱~입니다.
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


16. Scaffolding

Scaffolding으로 주어진 도메인 클래스를 위한 다음을 포함한 전체 애플리케이션을 자동 생성할 수 있다.

  • 필요한 뷰들
  • create/read/update/delete (CRUD) 연산을 위한 컨트롤러 액션들


Scaffolding 사용하기
Scaffolding을 사용하는 가장 간단한 방법은 scaffold 속성을 통해 scaffolding을 활성화하는 것이다. Book 도메인 클래스를 예로 들면 컨트롤러의 scaffold 속성을 true로 설정해야 한다.

class BookController {
    def scaffold = true
}

위 예제가 동작하는 것은 BookController가 Book 도메인 클래스와 동일한 명명규칙(naming convention)을 따르기 때문이다. 만일 다른 도메인 클래스를 scaffold하고 싶을 경우 scaffold 속성에 특정 도메인 클래스를 지정할 수 있다.

def scaffold = Author

그런 다음 Grails 애플리케이션을 실행하면 실행시간에 필요한 액션들과 뷰들을 자동으로 생성한다. 다음 액션들은 실행시간 scaffolding 메커니즘에 의해 동적으로 구현되는 것들이다.

  • list
  • show
  • edit
  • delete
  • create
  • save
  • update

이것과 함꼐 CRUD 인터페이스도 생성될 것이다. 간단히 http://localhost:8080/app/book로 가면 위 예제의 인터페이스를 접근할 수 있다.

만일 도메인 모델을 자바와 Hibernate 매핑으로 작성하는 것을 선호한다면 간단히 필요한 클래스를 임포트하고 scaffold 속성을 해당 자바 클래스로 설정하여 scaffolding을 사용할 수 있다.

동적 Scaffolding
scaffold 속성을 사용할 때 Grails는 이것을 위해 코드 템플릿이나 코드 생성 방법을 사용하지 않는다. 따라서 scaffold 컨트롤러에 scaffold 액션과 함께 당신의 고유 액션들을 추가할 수 있다. 예를 들면 아래 예제에서, changeAuthor는 실제 물리적으로 존재하지 않는 show 액션으로 리다이렉트한다.

class BookController {
    def scaffold = Book
    def changeAuthor = {
        def b = Book.get( params["id"] )
        b.author = Author.get( params["author.id"] )
        b.save()        // redirect to a scaffolded action
        redirect(action:show)
    }
}

필요하다면 scaffold된 액션을 당신의 고유한 액션으로 재정의(override)할 수 있다.

class BookController {
    def scaffold = Book
    // 저자와 책 둘 다 반환하도록 액션을 재정의
    def list = {
        [ "books" : Book.list(), "authors": Author.list() ]
    }
}

이 모든 것이 CRUD 인터페이스를 실행시간에 동적으로 생성하는 "동적 scaffolding"으로 알려진 것이다. Grails는 또한 다음 장에서 논의할 "정적" scaffolding도 지원한다.

생성된 뷰를 수정하기
Grails는 유효성 검사와 제약조건을 적합시킨 지능적인 형태의 뷰를 생성한다. 예를 들면, 간단히 빌더의 constraints를 재정렬하여 뷰에 나타나는 필드 순서를 변경할 수 있다.

def constraints = {
    title()
    releaseDate()
}

또는 inList 제약조건을 사용하여 텍스트 입력 대신 list를 생성할 수 있다.

def constraints = {
    title()
    category(inList:["Fiction", "Non-fiction", "Biography"])
    releaseDate()
}

또는 숫자에 대해 range 제약조건을 사용한다.

def constraints = {
     age(range:18..65)
}

제약조건을 통해 size를 제한하면 생성된 뷰에 얼마나 많은 문자를 입력할 수 있는지 제한하는 효과를 낼 수도 있다.

def constraints = {
    name(size:0..30)
}


컨트롤러와 뷰 생성하기
위의 scaffolding 특징들이 유용하기는 하지만 실세계 상황에서는 로직과 뷰를 수정하기를 원할 것이다. Grails에서 명령행을 통해 컨트롤러와 뷰들을 생성할 수 있다. 컨트롤러를 생성하기 위해서는 다음과 같이 입력한다.

grails generate-controller Book

또는 뷰를 생성하기 위해서는 다음과 같이 입력한다.

grails generate-views Book

또는 모든 것을 생성하기 위해서는 다음과 같이 입력한다.

grails generate-all Book

도메인 클래스가 패키지에 있거나 Hibernate로 맵핑된 클래스로 생성하기 위해서는 완전한 패키지 이름을 포함하는 것을 기억하라.

grails generate-all com.bookstore.Book

---
원문: 16. Scaffolding
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


15. Grails 와 Hibernate

만일 GORM(Grails Object Relation Mapping)이 원하는만큼 유연하지 않다면 대안으로 Hibernate를 이용하여 도메인 클래스를 매핑할 수 있다. 이것을 하려면 프로젝트의 grails-app/conf/hibernate 디렉토리의 hibernate.cfg.xml을 생성하고 당신의 도메인 클래스에 대응하는 HBM 매핑 xml 파일을 만든다.

사용자 삽입 이미지
매핑에 대한 더 자세한 정보는 Hibernate 웹사이트의 documentation on mapping을 참조한다.

이것으로 Grails 도메인 클래스를 더 다양한 범위의 레가시 시스템에 매핑할 수 있고 보다 유연하게 데이터베이스 스키마 생성을 할 수 있다.

Grails에서는 자바로 도메인 모델을 작성할 수 있거나 Hibernate로 매핑된 이미 존재하는 도메인 모델을 재사용할 수도 있다. 이것을 위해 해야 할 일은 필요한 hibernate.cgf.xml 파일과 관련 매핑 파일을 grails-app/conf/hibernate 디렉토리에 넣는 것이다.

게다가 좋은 소식은 여전히 GORM의 동적 저장과 질의 메소드를 호출할 수 있다는 것이다!


15.1 Hibernate Annotation으로 매핑하기
Grails에서 Hibernate의 자바 5.0 Annotation 지원을 이용하여 도메인 클래스를 매핑할 수 있다. Annotation을 사용 하기 위해서는 DataSourceconfigClass를 다음과 같이 설정하여 Grails에게 annotation 설정을 사용한다고 알려줘야 한다.

import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration
dataSource {
    configClass = GrailsAnnotationConfiguration.class
    … // remaining properties
}

설정은 이게 끝이다! Annotation을 사용해야 하기 때문에 자바 5.0을 설치하였는지 확인한다. 이제 annotation 클래스를 생성하기 위해 간단히 src/java 디렉토리에 새로운 자바 클래스를 생성하고 EJB 3.0 스팩의 일부분으로 정의된 annotation들을 사용한다. (더 자세한 정보는 Hibernate Annotations Docs를 보라)

package com.books;
@Entity
public class Book {
    private Long id;
    private String title;
    private String description;
    private Date date;
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

그런 후 이 클래스를 Hibernate sessionFactory에 등록해야 하기 위해 grails-app/conf/hibernate/hibernate.cfg.xml 파일에 다음과 같은 엔트리를 추가한다.

<!DOCTYPE hibernate-configuration SYSTEM
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <mapping package="com.books" />
        <mapping class="com.books.Book" />
    </session-factory>
</hibernate-configuration>

Grails가 로드될 때 이 클래스에 필요한 동적 메소드를 등록한다. Hibernate 도메인 클래스로 무엇을 할 수 있는지는 Scaffolding 장을 보라.


15.2 더 읽을거리
Grails 커미터인 Jason Rudolph는 Grails와 사용자 정의 Hibernate 매핑에 관한 많은 유용한 기사를 썼다.


---
원문: 15. Grails and Hibernate

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


13. 웹서비스

웹 서비스는 웹 애플리케이션에 웹 API를 제공하는 것을 이야기한다. 그리고 웹 서비스는 전형적으로 SOAP이나 REST로 구현한다.

13.1 REST
REST는 실제로는 그 자체로 기술은 아니고 아키텍쳐 패턴에 가깝다. RETS는 대단히 단순하며 통신 매개로 평이한 XML이나 JSON을 사용한다. 또한 기반 시스템의 표현과 GET, PUT, POST, DELETE와 같은 HTTP 메소드를 사용하는 URL 패턴과 결합한다.

HTTP 메소드는 액션으로 매핑된다. 예를 들면 데이터 조회에는 GET, 데이터 생성에는 PUT, 데이터 업데이트 등에는 POST를 사용한다. 이런 면에서 REST는 CRUD에 잘 들어맞는다.

URL 패턴
Grails로 REST를 구현하기 위한 첫번째 단계는 RESTfull URL 매핑을 제공하는 것이다.

static mappings = {
    "/product/$id?"(controller:"product"){
        action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
    }
}

여기서 우리는 컨트롤러에서 RESTful API를 제공하기 위해 HTTP 메소드로 매핑하는 URL 매핑 기능을 사용했다. GET, PUT, POST 그리고 DELETE와 같은 각 HTTP 메소드는 컨트롤러의 고유 액션으로 매핑된다.

XML 마셀링(marshaling) - 조회
컨트롤러 구현체는 GET 메소드를 구현하기 위해 Grails의 XML 마셀링을 사용할 수 있다.

import grails.converters.*
class ProductController {
    def show = {
        if(params.id && Product.exists(params.id)) {
            def p = Product.findByName(params.id)
            render p as XML
        }
        else {
            def all = Product.list()
            render all as XML
        }
    }
    ..
}

여기서 하는 일은 만일 id가 있으면 이름으로 Product을 찾아 반환하고 아니면 모든 Proudct을 반환한다. 이 방법으로 /produts로 간다면 모든 product들을 얻을 수 있으며 아니면 /product/MacBook으로 가면 단지 MacBook만 얻을 수 있다.

XML 마셀링 - 업데이트
PUT이나 POST로 업데이트 하기 위해서 들어오는 XML 패킷을 읽는 능력을 가진 params 객체를 사용할 수 있다. 유입되는 XML 패킷을 보자.

<?xml version="1.0" encoding="ISO-8859-1"?>
<product>
    <name>MacBook</name>
    <vendor id="12">
        <name>Apple</name>
    </vender>
</product>

params 객체를 통해 데이터 바인딩에서 설명한 동일한 기술을 사용하여 이 XML 패킷을 읽을 수 있다.

def save = {
    def p = new Product(params['product'])
    if(p.save()) {
        render p as XML
    }
    else {
        def errors = p.errors.allErrors.collect { g.message(error:it) }
        render(contentType:"text/xml") {
            error {
                for(err in errors) {
                    message(error:err)
                }
            }
        }
    }
}

이 예제에서 'product'키를 이용하여 params 객체의 인덱스를 찾아 Product 클래스의 생성자를 이용항 자동으로 XML을 바인드하였다. 다음 라인이 흥미롭다.

def p = new Product(params['product'])

이 라인은 폼 데이터 submit에서 XML 요청을 다루기 위해 코드 변경이 필요없다. 정확히 동일한 기술을 JSON 요청에서도 사용한다.

사용자 삽입 이미지
만일 서로 다른 클라이언트에 대해 서로 다른 응답(REST, HTML 등)이 필요하면 content negotation을 사용할 수 있다.

그런 후 Product 객체는 저장되고 XML로 렌더링된다. 그렇지 않으면 Grails 유효성 검사 기능을 이용하여 에러 메시지를 생성한다.

<error>
    <message>The property 'title' of class 'Person' must be specified</message>
</error>


13.2 SOAP
Grails는 SOAP 통합을 위해 인기있는 XFire SOAP 스택을 이용한 XFire 플러그인을 통해 SOAP을 지원한다. XFire 플러그인은 특별한 expose 속성을 이용하여 Grails 서비스를 SOAP 서비스로 노출한다.

class BookService {
    static expose=['xfire']
    Book[] getBooks(){
        Book.list() as Book[]
    }
}

WSDL은 다음과 같은 위치에서 접근할 수 있다.
http://127.0.0.1:8080/your_grails_app/services/book?wsdl

XFire 플러그인에 대한 더 자세한 정보는 wiki의 문서를 참조한다.


13.3. RSS와 Atom
Grails는 직접 FSS나 Atom을 지원하지는 않는다. render 메소드의 XML 기능으로 RSS나 ATOM 피드를 구축할 수 있다. 여하튼 인기있는 ROME 라이브러리를 이용한 RSS와 Atom 빌더를 Grails에서 지원하기 위해 Feeds 플러그인이 있다. 이것의 사용 예제는 아래에서 볼 수 있다.

def feed = {
    render(feedType:"rss", feedVersion:"2.0") {
        title = "My test feed"
        link = "http://your.test.server/yourController/feed"
        Article.list().each() {
            entry(it.title) {
                link = "http://your.test.server/article/${it.id}"
                it.content // return the content
            }
        }
    }
}

---
원문: 13. Web Services
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


11. 보안

Grails는 자바 서블릿보다 더 안전하지도 덜 안전하지도 않다. 어쨌거나 자바 서블릿(그리고 Grails)는 매우 안전하고 일반적인 버퍼 공격(overrun)과 악의적인 URL 공격에 큰 면역력을 가지고 있는데 이는 자바 가상 머신 기반 코드의 본질에 기인한다.

웹 보안 문제는 보통 개발자의 무경험이나 실수에 기인한다. 그리고 Grails에는 이런 일반적인 실수를 피할수 있게하고 안전한 애플리케이션을 쉽게 작성할 수 있는 방법이 몇 가지 있다.

Grails가 자동으로 해주는 것
기본으로 Grails는 내장된 안전 메커니즘이 몇 가지 있다.

  1.  GORM 도메인 클래스를 통한 모든 표준 데이터베이스 접근은 SQL 주입 공격(injection attacks)으로부터 보호 받으로 수 있도록 자동으로 SQL escpaed한다.
  2. 기본 scaffolding 템플릿은 출력할 때 모든 데이터 필드를 HTML escape한다.
  3. 코드 주입(injection)을 보호하기 위해 link, form, createLink, createLinkTo 등과 같은 Grails link는 알맞은 escaping 메커니즘을 사용한다.
  4. HTML, 자바스크립트 그리고 URL을 사용할 때 여기서 주입(injection) 공격을 보호하기 위해 간단히 데이터를 escape할 수 있는 codec을 제공한다.
역주)
escape 공격
: 악의적인 사용자가 의도하지 않은 실행을 가능하도록 입력값으로 코드를 입력하는 것을 말한다. 안전하지 않은 서버일 경우 입력값으로 사용자가 입력한 코드(SQL이나 기타 심각한 파라메터 변경)가 그대로 실행되어 심각한 보안문제를 읽으킬 수 있다. 이것을 처리하는 서버는 사용자가 입력한 값이 코드가 아닌 데이터로 안전하게 처리되도록 사용자 입력을 중 명령어로 인식될 수 있는 부분을 적절히 변환해 준다. 이렇게 명령어로 실행될 수 있는 문자를 escape(탈출) 문자로 변경하는 것을 escape한다고 한다. 적절한 우리말을 찾기가 어렵다.ㅠㅠ 예를 들어 SQL에서 입력값으로 '1 or 1 =1'로 들어오면 escape하지 않고 SQL의 문자중 조건으로 단순히 치환하면 모든 데이터를 접근할 수 있다. 이런 경우는 PreparedStatement를 사용하여 해결할 수 있다.


11.1 공격에 대해 안전하게 보호하기

SQL 주입(injection)
GORM 도메인클래스의 근간 기술인 Hibernate는 데이터베이스에 전하는 데이터를 자동으로 escape하므로 이것은 이슈가 되지 않는다. 어쨌거나 검사하지 않은 요청 파라메터를 사용하는 좋지 않은 동적 HQL 코드를 작성할 가능성은 여전히 존재한다. 예를 들어 다음은 HQL 주입 공격에 취약하다.

def vulnerable = {
    def books = Book.find("from Book as b where b.title ='" + params.title + "'")
}

절대 이렇게 하지 말라. 파라메터 전달이 필요하다면 대산 이름에 의한(named) 또는 순서에 의한(positional) 파라메터를 사용하라.

def safe = {
    def books = Book.find("from Book as b where b.title =?", [params.title])
}


피싱(Phishing)
이것은 당신의 브랜드를 훔치는 사회 이슈로 당신의 고객과 공적인 의사소통이 필요하다. 고객은 진짜 이메일 수신을 어떻게 식별하는지 알 필요가 있다.

XSS - cross-site scripting injection
들어오는 요청이 당신의 애플리케이션으로 부터 기인한 것인지 아니면 다른 사이트에서 온 것인지 검증하는 것은 매우 중요하다. 티켓팅과 플로우 시스템이 이것을 도와줄 수 있다. 그리고 Grails는 기본으로 보안을 포함한 Spring 웹 플로우 지원한다.

또한 모든 뷰로 표현되는 데이터 값이 확실히 알맞게 escaped되는 것도 매우 중요하다. 예를 들어 악의를 가진 사람들이 자바스크립트를나 다른 HTML를 데이터나 다른 사람에게 보여주는 태그에 끼워넣지(inject) 못하도록 HTML이나 XHTML로 렌더링할 때 모든 객체에 대해 encodeAsHTML을 호출해야 한다. Grails는 이 목적을 위해 몇 가지 동적 인코딩 메소드들을 제공하는데 출력에 escape된 포맷을 지원하지 않을 경우 고유의 코덱(codec)을 쉽게 작성할 수 있다.

또한 리다이렉트하기 위한 다음 URL을 결정하기 위해 request 파라메터나 데이터 필드를 사용하는 것을 피해야 한다. 예를 들어 로그인 성공 후 리다이렉트를 결정하기 위해 suuccessURL 파라메터를 사용한다고 할 때 공격자는 당신의 사이트를 이용하여 로그인 절차를 흉내낼 수 있어 로그인 한 사용자는 공격자의 사이트로 돌아갈 수 있다. 암시적으로 그 사이트의 로그인한 계정을 자바스크립트 코드로 이용할 수 있게 된다.

HTML/URL 주입
나중에 페이지 안에 link를 생성하기 위해 데이터에 link와 같은 나쁜 데이터를 넣으면, link를 클릭하는 경우 예상치 못한 작동을 유발하거나 다른 사이트로 리다이렉트 할 수도 있으며 request 파라메터를 바꿀 수도 있다.

Grails가 제공하는 코덱을 이용하여 HTML/URL 주입을 쉽게 처리할 수 있다. 그리고 Grails가 제공하는 모든 태그 라이브러리는 알맞은 곳에서 encodeAsURL을 호출한다. 만일 URL을 생성하는 당신 고유의 태그를 작성한다면 이것을 염두해 둘 필요가 있다.

서비스 거부
이 경우 로드 밸런서나 다른 장치가 보다 유용하다. 그러나 또한 터무니 없는 질의와 관련한 이슈가 있는데 예를 들어 공격자가 만든 link가 결과 세트의 개수를 최대로 해서 서버의 메모리 한계를 넘어 시스템이 느려지거나 다운되는 경우가 있다. 여기에서의 해결책은 항상 request 파라메터 동적 finder나 다른 ORM 질의 메소드에 전달하기 전 검사하여 나쁜 파라메터 값을 제거하는 것이다.

def safeMax = Math.max(params.max?.toInteger(), 100) // 100개 이상이 반환되지 않도록 조치
return Book.list(max:safeMax)

추측이 가능한 ID
많은 애플리케이션들이 URL의 마지막 부분으로 GORM이나 다른 곳에서 객체를 조회할 수 있는 ID를 사용한다. 특히 GORM의 경우 보통 ID가 순차적인 integer이므로 쉽게 추측이 가능하다.

따라서 사용자에게 응답을 보내기 전 사용자가 요청한 ID로 그 객체를 보는 것이 허가되었는지 반드시 확인해야 한다. 이렇게 하지 않는 것을 기본 비밀번호로 "letmein" 등을 쓰는 것 같은 필연적으로 깨지는 "애매한 보안(security through obscurity)"이라 한다.

반드시 모든 보호되지 않은 URL은 어떤 방법으로도 공개적으로 접근할 수 있다고 가정해야 한다.


11.2 문자열 인코딩과 디코딩
Grails는 동적 인코드/디코드 메소드 개념을 지원한다. Grails는 표준 코덱 집합을 포함하고 있다. Grails는 또한 실시간에 인식하는 고유한 코덱을 개발자가 공헌할 수 있도록 간단한 메커니즘을 지원한다.

코덱 클래스
Grails 코덱 클래스는 인코드 클로우저(closure)와 디코드 클로우저 둘 다 포함할 수 있다. Grails 애플리케이션이 시작될 때 Grails 프레임워크는 grails-app/utils 디렉토리에서 코덱들을 동적으로 로드한다.

프레임워크는 약속(convention)에 따라 grails-app/utils 아래에서 이름이 Codec으로 끝나는 클래스를 찾는다. 예를 들면 Grails가 제공하는 표준 코덱 중 하나는 HTMLCodec이다.

코덱이 코드 블럭을 지정한 encode 속성을 가지면 Grails는 동적인 encode 메소드를 생성하고 코덱을 표현하는 이름으로 String  클래스에 그 메소드를 추가한다. 예를 들면 HTMLCodec 클래스는 encode 블럭을 정의하므로 Grails는 그 클로우저를 encodeAsHTML이란 이름으로 String 클래스에 붙일 것이다.

HTMLCodec과 URLCodec 클래스는 또한 decode 블럭을 정의하므로 Grails는 이것들을 decodeHTMLdecodeURL 이름으로 붙일 것이다. Grails 애플리케이션 어디에서든지 동적 코덱 메소드를 호출할 수 있다. 예를 들면, 'description'이라는 속성을 가진 보고서가 있는데 설명은 HTML 문서로 표현되기 위해서 escape이 필요한 특수 문자를 가진 경우를 보자. 이것을 처리하기 위한 한 방법은 GSP에서 동적 인코드 메소드를 아래와 같이 호출하는 것이다.

${report.description.encodeAsHTML()}

디코딩은 value.decodeHTML() 문법으로 실행한다.


표준 코덱들

HTMLCodec
이 코덱은 HTML escaping과 unescaping을 수행한다. 따라서 어떠한 HTML 태그를 생성하지 않고 페이지 레이아웃에 해를 끼치지 않고 HTML 페이지에 값을 안전하게 렌더링할 수 있다. 예를 들면,  "Don't you know that 2 > 1?" 값은 > 문자가 태그를 닫기 때문에 HTML 페이지에 안전하게 보여줄 수 없다. 이 값은 특히 다음과 같은 input 필드의 값 속성으로 렌더링할 때 아주 나쁘다.
사용 예제

<input name="comment.message" value="${comment.message.encodeAsHTML()}"/>

사용자 삽입 이미지
HTML 인코딩은 apostrohe(소유격부호)와 단따옴표는 다시 인코드하지 않는다는 것에 주의하라. 따라서 apostrohpe(')가 페이지를 망가뜨리지 않도록 애트리뷰트 값에 반드시 쌍따옴표(")를 사용한다.


URLCodec
Link나 폼 액션 또는 URL 생성이 필요한 어느 시점에서든지 URL을 만들기 위해 URL 인코딩이 필요하다. 이것은 URL의 의미를 바꾸는 불법적인 문자를 막아준다. 예를 들면 "Apple & Blackberry"는 GET 요청에서 엠퍼센드(&)가 파라메터 파싱에서 둘을 분리하므로 파라메터로 잘 작동하지 않을 것이다.
사용예제

<a href="/mycontroller/find?searchKey=${lastSearch.encodeAsURL()}">Repeat last search</a>


Base64Codec
Base64 인코드/디코드 기능을 수행한다. 사용 예제

Your registration code is: ${user.registrationCode.encodeAsBase64()}


JavaScriptCodec
유효한 자바스크립트 문자열을 사용할 수 있도록 문자열 escape를 한다. 사용 예제:

Element.update('${elementId}', '${render(template: "/common/message").encodeAsJavaScript()}')


사용자 정의 코덱들
애플리케이션은 그들 고유의 코덱들을 정의할 수 있다. 그리고 Grails는 표준 코덱들과 함께 그것들을 로드할 것이다. 사용자 정의 코덱 클래스는 grails-app/utils/ 디렉토리에 정의하고 이름이 Codec로 끝나야 한다. 코덱은 static encode 블럭과 static decode 블럭  두 개 모두 가질 수 있다. 블럭은 동적 메소드를 호출하는 대상이 되는 객체를 나타내는 하나의 아규먼트를 가진다. 예를 들면

class PigLatinCodec {
    static encode = { str ->
        // convert the string to piglatin and return the result
    }
}

위 코덱을 애플리케이션에서 다음과 같이 사용할 수 있다.

${lastName.encodeAsPigLatin()}


11.3 인증(Authentication)
현재 인증을 위한 기본 메커니즘은 없지만 수천가지 다른 방법으로 인증을 구현할 수 있다. 어쨌거나 인터셉터필터를 이용한 간단한 인증 메커니즘을 구현하는 것은 진짜 간단한다.

필터로 모든 컨트롤러나 URI 공간을 가로질러 인증을 적용할 수 있다. 예를 들면 grails-app/conf/SecurityFilters.groovy에 새로운 필터 집합을 생성할 수 있다.

class SecurityFilters {
    def filters = {
        loginCheck(controller:'*', action:'*') {
            before = {
                if(!session.user && actionName != "login") {
                    redirect(controller:"user",action:"login")
                    return false    
                }
            }
        }
    }
}

여기의 loginCheck 필터는 액션이 실행되기전 실행을 가로챈다. 그리고 만일 세션에 사용자가 없고 login 액션이 아니라면 login 액션으로 리다이렉트한다.

login 액션도 다음과 같이 간단하다.

def login = {
    if(request.get) render(view:"login")
    else {
        def u = User.findByLogin(params.login)
        if(u) {
            if(u.password == params.password) {
                session.user = u
                redirect(action:"home")
            }
            else {
                render(view:"login", model:[message:"Password incorrect"])
            }
        }
        else {
            render(view:"login", model:[message:"User not found"])
        }
    }
}


11.4 보안 플러그인
만일 간단한 인증을 넘어서 인가, 역할 등의 진보된 기능이 필요하다면 유효한 보안 플러그인 중 하나를 고려해 볼 수 있다.

11.4.1 Acegi
Acegi 플러그인은 Spring Acegi 프로젝트를 기반으로 하는데 이것은 모든 인증, 인가 종류의 스키마를 구축하기 위한 유연하고 확장성 있는 프레임워크를 제공한다.

Acegi 플러그인을 사용하면 URI와 역할을 맵핑해야 하며 Acegi 플러그인은 사용자, 인증정보 그리고 request 맵 모델을 위한 기본 도메인 모델을 제공한다. 더 자세한 정보는 wiki의 문서여기를 보라.

11.4.2 JSecurity
JSecurity는 자바 POJO 지향의 또 다른 보안 프레임워크로 역시 realms, 사용자, 역할 그리고 권한 모델을 위한 기본 도메인 모델을 제공한다. JSecurity를 사용하려면 보안을 원하는 각 컨트롤러가 JsecAuthBase라는 컨트롤러를 상속해야 한다. 그런 후 역할을 설정하는 accessControl 블럭을 제공해야 한다.
다음은 그 예제이다.

class ExampleController extends JsecAuthBase {
    static accessControl = {
        // All actions require the 'Observer' role.
        role(name: 'Observer')
        // The 'edit' action requires the 'Administrator' role.
        role(name: 'Administrator', action: 'edit')
        // Alternatively, several actions can be specified.
        role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ])
    }    …
}


JSecurity 플러그인에 대한 더 자세한 정보는 JSecurity Quick Start를 참조하라.

---
원문: 11. Security

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


10. 국제화

Grails는 Spring MVC가 지원하는 국제화를 기반으로 뛰어난 국제화(i18n)을 지원한다. Grails로 어떤 뷰에 나타나는 텍스트든 사용자의 지역을 기반으로 커스터마이즈할 수 있다. 자바의 Local 클래스에 관한 javadoc을 인용한다.

Locale 객체는 특정 지리적, 정치적 또는 문화적 지역을 표현한다. 작업을 수행하기 위해 Locale이 필요한 연산자를 지역에 민감하다(locale sensitive)고 부르고 사용자를 위한 정보를 조정(tailor)하기 위해 Locale을 사용한다. 예를 들어 숫자를 보여주는 것은 지역에 민감한 연산자이다. 숫자는 사용자 원래 나라, 지역 또는 문화의 관습/관례에 따라 양식화(formatted)되어야 한다.

Locale은 언어 코드나라 코드로 이루어진다. 예를 들여 "en_US"는 US 영어이고 "en_GB"는 영국 영어이다.

10.1 메시지 번들의 이해
이제까지 locale에 대해 알아보았다. Grails에서 Locale의 잇점을 얻기 위해서는 메시지 번들을 생성해야 하는데 이것은 표현하고 싶은 서로 다른 언어를 가지고 있는 한다. Grails에서 메시지 번들은 grails-app/i18n 디렉토리 안에 있으며 단순한 자바 프로퍼티 파일이다.

각 번들은 약속에 의해 messages라는 이름으로 시작하고 locale로 끝난다. Grails는 grails-app/i18n 디렉토리 안에 있는 모든 범위의 언어를 위한 메시지 번들을 내장된 묶음들로 취급한다. 예를 들면

messages.properties
messages_de.properties
messages_es.properties
기타.

사용자가 특정 로케일을 지정하지 않는한 기본으로 메시지를 messages.properties에서 찾을 것이다. 추가하고자 하는 locale로 끝나는 새로운 프로퍼티 파일을 생성함으로써 간단히 고유한 메시지 번들을 생성할 수 있다. 예를 들어 messages_en_GB.properties는 영국 영어를 위한 것이다. (messages_ko_KR 한국.properties)

10.2 로케일 변경
기본으로 사용자 로케일은 들어오는 요청의 Accept-Language 헤더에서 찾는다. 어쨌거나 request 파라메터로 Grails에게 lang이라는 파라메터를 전달함으로써 간단히 로케일을 변경할 수 있다.

/book/list?lang=es

Grails는 자동으로 사용자 로케일을 바꾸고 이것을 쿠키(cookie)에 저장한다. 따라서 다음 request는 새로운 헤더를 갖게 된다.


10.3 메시지 읽기

뷰에서 메시지 읽기
메시지가 필요한 가장 일반적인 장소는 뷰 내부이다. 뷰에서 메시지를 읽기 위해서는 단지 message 태그를 사용한다.

<g:message code="my.localized.content" />

messages.properties(알맞은 로케일 접미어와 함께)안에 다음과 같은 키를 가지고 있으면 Grails는 메시지를 찾을 것이다.

my.localized.content=Hola, Me llamo John. Hoy es domingo.

어떤 경우는 메시지에 아규먼트를 전달할 필요가 있을지도 모른다. 이것도 message 태그로 가능하다.

<g:message code="my.localized.content" args="${ ['Juan', 'lunes'] }" />

그런 후 순서로 구분되는 파라메터(positional parameter)를 메시지 안에서 사용한다.

my.localized.content=Hola, Me llamo {0}. Hoy es {1}


컨트롤러와 태그 라이브러리에서 메시지 읽기
컨트롤러에서 태그를 메소드로 호출할 수 있기에 컨트롤러에서 메시지를 읽는 것은 간단하다.

def show = {
    def msg = message(code:"my.localized.content", args:['Juan', 'lunes'])
}

태그 라이브러리에서도 동일한 기법을 사용할 수 있다. 그러나 태그 라이브러리가 다른 네임스페이스를 가지고 있다면 g. 접두어를 사용할 필요가 있다.

def myTag = { attrs, body ->
    def msg = g.message(code:"my.localized.content", args:['Juan', 'lunes'])
}

---
원문: 10. Internationalization
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding

9. 테스팅

자동화된 테스트는 Grails의 핵심 부분으로 Groovy Tests를 이용하여 구현하였다. 따라서 Grails는 낮은 수준의 단위 테스트부터 높은 수준의 기능 테스트까지 쉽게 테스트를 만드는 여러 방법을 제공한다. 이 장은 Grails가 테스팅 관점에서 제공하는 차별화된 능력을 설명한다.

알아둬야 할 첫번째는 모든 create-* 명령어는 실제로 통합 테스트를 자동으로 생성하며 끝난다는 것이다. 예를 들어 아래와 같이 create-controller 명령어를 실행한다고 하자.

grails create-controller simple

Grails는 grails-app/controllers/SimpleController.groovy에 컨트롤러를 생성할 뿐만 아니라 test/integration/SimpleControllerTests.groovy에 통합 테스트도 생성한다. 어쨌거나 테스트 안에 로직을 만드는 것은 Grails가 할 수 없는 일이다! 이것은 개발자에게 남겨둔다.

이렇게 했다면 test-app 명령어로 모든 테스트를 실행할 수 있다.

grails test-app

위 명령어는 다음과 같은 결과를 만들어낸다.

-------------------------------------------------------
Running Unit Tests…
Running test FooTests...FAILURE
Unit Tests Completed in 464ms …
-------------------------------------------------------
Tests failed: 0 errors, 1 failures

test/reports 디렉토리에 위 결과 보고서가 만들어진다. 또한 테스트를 따로 돌리수 있는데 그러기 위해서는 Tests 접미어를 뺀 테스트 이름을 지정하면 된다.

grails test-app SimpleController

게다가 스페이스로 이름을 분리하여 지정하면 여러 개의 테스트들을 실행할 수도 있다.

grails test-app SimpleController BookController


9.1 단위 테스트
단위 테스트는 "구성단위" 수준의 테스트이다. 바꿔 말하면 주위 인프라를 고려하지 않고 개개의 메소드나 코드 블럭을 테스트하는 것을 말한다. Grails에서는 단위테스트와 통합테스트의 차이를 알아야 하는데 왜냐하면 Grails는 단위 테스트에서 통합테스트와 실행시간에 이루어지는 어떤 동적 메소드의 주입(inject)로 하지 않기 때문이다.

이 이유로 메소드를 흉내(mock)내기 위해 Groovy Mock이나 ExpandoMetaClass와 같은 것을 사용하는 것은 개발자의 몫이다.

예를 들어 BookController에 다음과 같은 action이 있다고 하자.

def show = {
    [ book : Book.get( params.id ) ]
}

params 객체와 get 메소드 둘 다 실행시간에 Grails가 제공하는 것이므로 단위테스트에서는 사용할 수 없다. 그러나 그것들을 흉내낼 수는 있다. 그럴려면 다음과 같이 ExpandoMetaClas를 사용한다.

void testShow() {
    // static get method를 흉내
    Book.metaClass.static.get = { Long id ->
        assert id == 10
        new Book(id:id, title:"The Stand")
    }
    // params 객체를 흉내
    BookController.metaClass.getParams = {-> [id:10]}
    def controller = new BookController()
    def model = controller.show()
    assert model
    assert model.book
    assertEquals 10, model.book.id
    assertEquals "The Stand", model.book.title
}

가짜 인스턴스를 반환하는 get 메소드를 어떻게 구현하여 제공하는지 그리고 어떻게 이 구현으로 테스트 단언(assertion)을 사용했는지 주목하라. 또한 어떻게 맵을 사용하여 params 객체의 가짜 인스턴스를 제공했는지 주의하라.

9.2 통합 테스트
통합테스트는 Grails의 모든 환경을 접근할 수 있다
는 점에서 단위테스트와 다르다. Grails는 통합테스트를 위해 메모리 HSQLDB 데이터베이스를 사용하고 각 테스트마다 데이터베이스에서 모든 데이터를 제거한다.

컨트롤러 테스트하기
컨트롤러를 테스트하기 위해 먼저 Spring Mock 라이브러리를 이해해야 한다.
본질적으로 Grails는 MockHttpServletRequest, MockHttpServletResponse 그리고 MockHttpSession으로 각 테스트를 자동으로 설정한다. 이것으로 테스트를 수행할 수 있다. 예를 들어 다음 컨트롤러를 보자.

class FooController {
    def text = {
        render "bar"
    }
    def someRedirect = {
        redirect(action:"bar")
    }
}

이것을 테스트하려면

class FooControllerTests extends GroovyTestCase {
    void testText() {
        def fc = new FooController()
        fc.text()
        assertEquals "bar", fc.response.contentAsStrng
    }
    void testSomeRedirect() {
        def fc = new FooController()
        fc.someRedirect()
        assertEquals "/foo/bar", fc.response.redirectedUrl
    }
}

위의 경우응답은 MockHttpServletResponse의 인스턴스이고 이것으로 응답으로 출력한 내용을 획득하기 위해 contentAsString를 사용하거나 예제의 리다이렉트 URL을 획득하기 위해 redirectedUrl을 사용할 수 있다. 실제 버전과 다르게 Servlet API의 이 가짜(mocked)  버전들은 변경할 수(mutable) 있기에 contextPath 등과 같은 request의 속성을 설정할 수 있다.

통합 테스트동안 액션을 호출할 때 Grails는 인터셉터들을 자동으로 호출하지 않는다. 필요하다면 기능(functional) 테스트를 통해 인터셉터는 따로 테스트해야 한다.

서비스와 함께 컨트롤러 테스트하기
컨트롤러가 서비스를 참조한다면 테스트에서 서비스를 명시적으로 초기화해야 한다.

아래 컨트롤러는 서비스를 사용한다.

class FilmStarsController {
    def populariryService
    def update = {
        // popularitySerivce로 뭔가를 한다.
    }
}

이것을 테스트하려면

class FilmStarsTests extends GroovyTestCase {
    def popularityService
    public void testInjectedServiceInController {
        def fsc = new FilmStartsController()
        fsc.popularityService = popularityService
        fsc.update()
    }
}


컨트롤러 명령객체(Command Object) 테스트하기
명령객체 테스트를 위해 request에 파라메터를 제공하고 액션을 파라메터 없이 호출하면 명령객체는 자동으로 작동할 것이다.

명령객체를 사용하는 컨트롤러를 보자

class AuthenticationController {
    def signup = { Signupform form -> ... }
}

다음과 같이 테스트할 수 있다.

def controller = new AuthenticationController()
controller.params.login = "marcpalmer"
controller.params.password = "secret"
controller.params.passwordConfirm = "secret"
controller.signup()

Grails는 마술과 같이 signup() 호출을 액션 호출로 여기고 가짜 request 파라메터로 명령객체를 채워준다(populate). 컨트롤러를 테스트하는 동안은 Grails가 제공하는 가짜 request와 함께 params도 변경할 수 있다.

컨트롤러와 render 메소드 테스트하기
액션의 몸체 안의 어느 지점에서도 render 메소드로 뷰를 렌더링할 수 있다. 예를 들어 다음 예제를 보자

def save = {
    def book = Book(params)
    if(book.save()) {
        // handle
    }
    else {
        render(view:"create", model:[book:book])
    }
}

이 예제에서 액션의 결과 모델을 반환 값으로 사용할 수 없다. 그러나 대신 컨트롤러의 modelAndView 속성에 저장할 수 있다. modelAndView 속성은 Spring MVC의 ModelAndView 클래스의 인스턴스이고 액션의 결과를 테스트하기 위해 이것을 사용할 수 있다.

def bookController = new BookController()
bookController.save()
def model = bookController.modelAndView.model.book


Request 데이터 흉내내기(Simulating)
REST 웹 서비스와 같은 request 데이터가 필요한 액션을 테스트할 경우 Spring MockHttpServletRequest 객체를 사용할 수 있다. 예를 들어 들어오는 request로 데이터바인딩을 수행하는 액션을 보자.

def create = {
    [book: new Book(params['book'])]
}

만일 'book' 파라메터를 XML request로 흉내내려면 다음과 같이 할 수 있다.

void testCreateWithXML() {
    def controller = new BookController()
    controller.request.contentType = 'text/xml'
    controller.request.contents = '''<?xml version="1.0" encoding="ISO-8859-1"?>
    <book>
        <title>The Stand</title>
       
    </book>
    '''.getBytes() // note we need the bytes
    def model = controller.create()
    assert model.book
    assertEquals "The Stand", model.book.title
}

JSON request도 동일하다.

void testCreateWithJSON() {
     def controller = new BookController()
     controller.request.contentType = "text/json"
     controller.request.content = '{"id":1,"class":"Book","title":"The Stand"}'.getBytes()
   
def model = controller.create()
     assert model.book
     assertEquals "The Stand", model.book.title
}

사용자 삽입 이미지
JSON을 사용할 때 바인드하기 위한 타입 이름을 지정하는 class 속성을 잊지 않도록 한다. XML에서는 <book> 노드의 이름을 암시적으로 사용하지만 JSON에서는 JSON 패킷의 일부로 이 속성이 필요하다.

REST 웹서비스 주제에 대한 자세한 정보는 REST 장을 보라.

웹플로우 테스트하기
웹플로우를 테스트하기 위해서는 Spring 웹플로우의 AbstractFlowExecutionTests클래스의 하위 클래스인 grails.test.WebFlowTestCase라는 특별한 장치가 필요하다.

사용자 삽입 이미지
WebFlowTestCase의 하위 클래스는 반드시 통합테스트이어야 한다.

예를 들어 다음의 간단한 흐름을 보자.

class ExampleController {
    def exampleFlow = {
        start {
            on("go") {
                flow.hello = "world"
            }.to "next"
        }
        next {
            on("back").to "start"
            on("go").to "end"
        }
        end()
    }
}

"흐름 정의"로 무엇을 사용할 것인지 테스트 장치에 알려줄 필요가 있다. 이것은 abstract getFlow 메소드를 재정의(override)하여 할 수 있다.

class ExampleFlowTests extends grails.test.WebFlowTestCase {
    def getFlow() { new ExampleController().exampleFlow }
    …
}

흐름 아이디를 지정할 필요가 있다면 getFlowId 메소드를 재정의할 수 있다. 그렇지 않을 경우 기본은 test이다.

class ExampleFlowTests extends grails.test.WebFlowTestCase {
    String getFlowId() { "example" }
    …
}

테스트에서 이것이 다 되었다면 startFlow 메소드로 흐름을 시작할 필요가 있다. startFlowViewSelection 객체를 반환한다.

void testExampleFlow() {
    def viewSelection = startFlow()
    assertEquals "start", viewSelection.viewName
    …
}

위에서 보듯 ViewSelection 객체의 viewName 속성으로 올바른 상태에 있는지 검사할 수 있다. 이벤트를 발생하려면 signalEvent 메소드를 사용한다.

void testExampleFlow() {
    …
    viewSelection = signalEvent("go")
    assertEquals "next", viewSelection.viewName
    assertEquals "world", viewSelection.model.hello
}

여기에서 우리는 "go" 이벤트를 실행하도록 흐름에 신호를 보냈다. 이것은 "next" 상태로 이동하게 한다. 예제에서 변이 액션은 플로우 범위에 hello 변수를 저장한다. 위에서 보듯 ViewSelection의 model 속성을 검사하여 이 변수의 값을 테스트할 수 있다.

태그 라이브러리 테스트하기
태그 라이브러리 테스트는 실제로 아주 단순하다. 왜냐하면 태그를 메소드로 호출하면 그 결과로 문자열로 반환하기 때문이다. 따라서 다음과 같은 태그 라이브러리가 있다고 하면

class FooTagLib {
    def bar =  { attrs, body ->
        out << "<p>Hello World!</p>"
    }
    def bodyTag =  { attrs, body ->
        out << "<${attrs.name}>"
        out << body()
        out << "</${attrs.name}>"    
    }
}

테스트는 다음과 같을 것이다.

class FooTagLibTests extends GroovyTestCase {
    void testBarTag() {
        assertEquals "<p>Hello World!</p>", new FooTagLib().bar(null,null)
    }
    void testBodyTag() {
        assertEquals "<p>Hello World!</p>", new FooTagLib().bodyTag(name:"p") {
            "Hello World!"
        }
    }
}

두번째 예제 testBodyTag에서 태그의 몸체를 반환하는 블럭을 전달하는 것에 주목하라. 이것은 body를 문자열로 표현하기 위해 사용하기 좋다.

GroovyPagesTestCase에서 태그 라이브러리 테스트하기
위와 같이 간단하게 태그 라이브러리를 테스트하는 것에 더해 태그 라이브러리를 테스트 하기 위해 grails.test.GroovyPagesTestCase를 사용할 수도 있다.
GroovyPagesTestCase 클래스는 일반 GroovyTestCase 클래스의 하위 클래스이고 GSP 렌더링의 결과를 테스트하기 위한 유틸리티 메소드들을 제공한다.

사용자 삽입 이미지
통합테스트에서만 GroovyPagesTestCase를 사용할 수 있다.

다음과 같은 날짜 포맷팅 태그 라이브러리를 보자.

class FormatTagLib {
    def dateFormat = { attrs, body ->
        out << new java.text.SimpleDateFormat(attrs.format) << attrs.date
    }
}

다음과 같이 간단히 테스트할 수 있다.

class FormatTagLibTests extends GroovyPagesTestCase {
    void testDateFormat() {
        def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />'
        def testDate = … // create the date
        assertOutputEquals( '01-01-2008', template, [myDate:testDate] )
    }
}

또한 GroovyPagesTestCase 클래스의 applyTemplate 메소드를 이용하여 GSP 결과를 획득할 수도 있다.

class FormatTagLibTests extends GroovyPagesTestCase {
    void testDateFormat() {
        def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />'
        def testDate = … // create the date
        def result = applyTemplate( template, [myDate:testDate] )
        assertEquals '01-01-2008', result
    }
}


도메인 클래스 테스트
도메인 클래스 테스트는 보통 GORM API를 사용하는 간단한 문제이다. 하지만 몇 가지 알아둬야 할 것들이 있다. 첫번째로 질의를 테스트하기 위해서는 현재 상태를 데이터베이스에 저장하기 위해 종종 "flush"를 할 필요가 있다. 다음 예를 들어보자.

void testQuery() {
    def books = [ new Book(title:"The Stand"), new Book(title:"The Shining")]
    books*.save()
    assertEquals 2, Book.list().size()
}

이 테스트는 실제로는 실패한다. 왜냐하면 save 호출은 호출될 때 실제로 Book 인스턴스를 저장하지 않는다. save 호출은 단지 미래 어느 시점에 이 인스턴스들을 저장해야 한다고 Hibernate에게 알려주는 것이다. 변화를 바로 적용하려면 "flush"를 해야 한다.

void testQuery() {
    def books = [ new Book(title:"The Stand"), new Book(title:"The Shining")]
    books*.save(flush:true)
    assertEquals 2, Book.list().size()
}

이 경우 flush 아규먼트를 true로 전달하기에 바로 업데이트되고 바로 후에 질의가 가능한다.

9.3 기능(functional) 테스트
기능테스트는 실제 구동하는 애플리케이션을 테스트하는 것이고 자동화하기가 더 어렵다. Grails는 특별히 기능테스트에 대한 지원은 없다. 그러나 Canno WebTest를 플러그인으로 지원한다.

Web Test를 설치하려면 다음 명령어를 입력한다.

grails install-plugin webtest

그런 후 Web Test와 Grails를 사용하려면 어떻게 해야 하는지 설명한 위키의 레퍼런스를 참조하라.

---
원문: 9. Testing

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


8. 서비스 계층

웹 계층과 함께 Grails는 서비스 계층 개념을 정의한다. Grails 팀은 핵심 애플리케이션 로직이 컨트롤러 안에 포함되지 않는 것을 권고한다. 핵심 로직이 컨트롤러에 있을 경우 재사용과 관심의 분리(separation of concerns)를 증진시킬 수 없다.

Grails의 서비스를 애플리케이션의 대부분의 로직을 넣는 장소로 여기고 컨트롤러에는 리다렉트 등의 요청을 다루는 것만 남겨둔다.

서비스 만들기
명령 프롬프트 안의 프로젝트 루트에서 create-service 명령어를 실행하여 Grails 서비스를 만들 수 있다.

grails create-service simple

이 명령어는 grails-app/services/SimpleService.groovy 위치에 서비스를 생성한다. 서비스의 이름은 약속에 의해 Service로 끝나며 서비스는 평범한 Groovy 클래스이다.

class SimpleService {
}


8.1 선언적 트랜잭션
서비스는 보통 도메인 클래스간의 상호작용하는 로직을 가진다. 따라서 종종 큰 오퍼레이션으로 나눠진 저장 기능들을 수행한다. 이러한 서비스의 본질 때문에 트랜잭션이 자주 필요하다. 물론 withTransaction 메소드를 이용하여 프로그램상에서 트랜잭션을 사용할 수 있지만 이것은 반복적이고 Spring이 내재한 트랜잭션 추상화의 힘을 충분히 활용하지 못하는 경우다.

서비스는 트랜잭션 경계를 사용할 수 있으며 이것은 근본적으로 서비스의 모든 메소드가 트랜잭션에 참여할 수 있게(transactional) 만드는 선언적인 방법이다. 기본으로 모든 서비스에 트랜잭션 경계를 만들수 있다. 이것을 쓰지 않으려면 단순히 transactional 속성을 false로 한다.

class CountryService {
    static transactional = false
}

물론 미래에 기본설정이 바뀔 경우 이 속성을 true로 바꿀 수 있다. 또는 서비스가 의도적으로 transactional하다고 명확히 하기 위해 사용할 수 있다.

사용자 삽입 이미지
주의: 선언적인 트랜잭션이 작동하기 위한 유일한 방법은 의존성 주입(dependency injection)이다. new BookService()와 같은 new 연산자를 사용하면 트랜잭션 서비스를 사용할 수 없을 것이다.

그 결과 모든 메소드를 트랜잭션으로 감싸고 메소드중 하나라도 예외를 발생하면 자동으로 롤백한다. 트랜잭션의 전파 수준(propagation level)은 기본으로 PROPAGATION_REQUIRED로 설정된다.

8.2 서비스 범위
기본으로, 서비스 메소드 접근은 동기화되어 있지 않다. 따라서 이런 메소드를 동시에 실행하는 것을 막지를 못한다. 사실, 서비스는 singleton이고 동시에 사용하기 때문에 상태를 서비스에 저장하는 것은 매우 주의를 요하는 것이다. 아니면 결코 상태를 서비스에 저장하지 않는 쉬운(보다 나은) 방법을 취한다.

서비스를 특정 범위에 넣음으로써 이러한 동작을 바꿀 수 있다. 지원하는 범위는 다음과 같다.

  • prototype: 매번 새로운 서비스를 생성하고 이것은 다른 클래스에 매번 넣어진다(injected).
  • request: Request마다 새 서비스를 생성한다.
  • flash: 오직 현재와 다음 request에만을 위해 새 서비스를 생성한다.
  • flow: 웹 플로우에서 플로우 범위마다 새 서비스를 생성한다.
  • conversation: 웹 플로우에서 대화 범위마다 서비스가 존재한다. 즉 루트 플로우와 그 하위 플로우에 대해서만..
  • session: 사용자 세션마다 서비스를 생성한다.
  • singleton (default): 오직 하나의 서비스 인스턴스만 존재한다.
사용자 삽입 이미지
서비스가 flash, flow 또는 conversation 범위에 있을 경우 이 서비스는 java.io.Serializable을 구현해야 한다. 그리고 오직 웹 플로우 상황에서만 사용할 수 있다.

범위 중 하나를 사용하려면 클래스에 static scope 속성을 위 범위 중 하나의 값으로 설정한다.

static scope = "flow"


8.3 의존성 주입(Dependency Injection)과 서비스

의존성 주입 기본
Grails 서비스의 핵심 중 하나는 Spring 프렘임워크의 의존성 주입 능력의 잇점을 취했다는 것이다. Grails는 약속에 의한 의존성 주입을 지원한다. 바꿔 말하면 서비스의 클래스 이름으로 속성이름을 사용하면 자동으로 서비스를 컨트롤러나 태그 라이브러리 등에 넣어(주입)준다.

예로 BookService라는 서비스가 있다고 치자. 다음과 같이 컨트롤러안에 bookService라는 속성을 정의하면

class BookController {
    def bookService
    ...
}

이 경우 Spring 컨테이너는 BookService의 인스턴스를 설정된 범위에서 자동으로 BookController에 넣어준다. 모든 의존성 주입은 이름에 의해 이루어진다. Grails는 타입에 의한 의존성 주입은 지원하지 않는다. 또한 다음과 같이 타입도 지정할 수는 있다.

class AuthorService {
    BookService bookService
}

하지만 이것은 개발 모드에서 BookService가 변경되었을 경우 리로딩 시 에러를 발생하는 불리한 점이 있긴하다.

의존성 주입과 서비스
동일한 기법으로 다른 서비스에도 서비스를 주입할 수 있다. BookService가 필요한 AuthorService가 있다고 하자. 다음과 같이 AuthorService를 선언하여 서비스를 사용할 수 있다.

class AuthorService {
    def bookService
}


의존성 주입과 도메인 클래스
도메인 클래스에도 서비스를 주입
할 수 있다. 이것은 풍부한(rich) 도메인 모델 개발을 도와준다.

class Book {
    ...
    def bookService
    def buyBook() {
        bookService.buyBook(this)
    }
}


8.4 자바에서 서비스 사용하기
서비스의 강력한 점 중 하나는 이것이 재사용 가능한 로직으로 포장되었기에 자바 클래스는 물론 다른 클래스에서 그것들을 사용할 수 있다는 것이다. 몇가지 방법으로 자바에서 서비스를 재사용할 수 있다. 가장 간단한 방법은 서비스를 grails-app/services 디렉토리안의 패키지로 옮기는 것이다. 기본 패키지(package 선언이 없는 경우)인 경우 그 심각성 때문에 자바에서 클래스로 임포트(import)할 수 없다. 따라서 아래의 BookService는 자바에서 그대로 사용할 수 없다.

class BookService {
    void buyBook(Book book) {
        // 로직
    }
}

어쨌거나 이 클래스를 grails-app/services/bookstore와 같이 하위 디렉토리로 옮기고 package 선언을 수정하여 패키지에 넣음으로써 바로잡을 수 있다.

package bookstore
class BookService {
    void buyBook(Book book) {
        // 로직
    }
}

패키징을 위한 다른 방법은 서비스가 구현할  자바 인터페이스를 특정 패키지에 작성하고

package bookstore;
interface BookSerice {
    void buyBook(Book book);
}

Grails 서비스는 이것을 구현한다.

class BookService implements bookstore.BookStore {
    void buyBook(Book b) {
        // 로직
    }
}

자바 쪽에는 오직 인터페이스의 레퍼런스만 가지고 구현 클래스는 없기에 어쩌면 뒤의 기법이 더 깨끗하다. 두 방법 모두 자바에서 사용하는 목적을 위해 컴파일 시에 정적으로 클래스(또는 인터페이스)를 결정한다. 이제 src/java 패키지안에 자바 클래스를 생성하고 Spring에서 사용하는 타입과 bean의 이름을 이용하여 setter를 제공하면 된다.

package bookstore;
// note: this is Java class
public class BookConsumer {
    private BookStore store;
    public void setBookStore(BookStore storeInstance) {
        this.store = storeInstance;
    }
    …
}

이것이 되었다면 자바 클래스를 grails-app/conf/spring/resources.xml에 Spring의 빈으로 설정할 수 있다. (더 자세한 정보는 Grails와 Spring의 관련 장을 보라)

<bean id="bookConsumer" class="bookstore.BookConsumer">
    <property name="bookStore" ref="bookService" />
</bean>

---
원문: 8. The Service Layer

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠
"올바른 성장과 따뜻한 나눔"이 있는 넥스트리
전체목록
1. 소개
2. 시작하기
3. Configuration
4. 명령어
5. GORM-1: 기본
5. GORM-2: 질의
5. GORM-3: 고급
6. 웹계층-1: 컨트롤러
6. 웹계층-2: GSP
6. 웹계층-3: 태그 lib
6. 웹계층-4: URL Mapping
6. 웹계층-5: Web Flow
6. 웹계층-6: 필터
6. 웹계층-7: Ajax등
7. 유효성검사
8. 서비스계층
9. 테스팅
10. 국제화
11. 보안
12. 플러그인
13. 웹서비스
14. Grails와 Spring
15. Grails 와 Hibernate
16. Scaffolding


7. 유효성 검사

Grails 유효성 검사 능력은 Spring의 Validator API와 데이터 바인딩에 기반을 둔다. 여하튼 Grails는 여기에 더하여 제약조건 메커니즘을 이용하여 일관성 있는 유효성 검사를 위한 "제약조건" 정의 방법을 제공한다.

Grails의 제약조건은 유효성 검사 규칙을 선언적으로 지정하는 방법이다. 대부분 일반적으로 도메인 클래스에 적용하나 URL Mapping 그리고 명령객체(Command Object)에서도 제약조건을 지원한다.

7.1 제약조건 선언
도메인 클래스에서는 코드블럭이 할당된 constraints 속성에 제약조건을 정의한다.

class User {
    String login
    String password
    String email
    Integer age
    static constraints = {
        …
    }
}

그런 다음 속성 이름에 대응하는 메소드를 호출하는데 여기에 named 파라메터로 제약조건을 지정한다.

class User {
    ...
    static constraints = {
        login(size:5..15, blank:false, unique:true)
        password(size:5..15, blank:false)
        email(email:true, blank:false)
        age(min:18, nullable:false)
    }
}

이 예제에서 우리는 login 속성은 문자 길이가 5와 15사이이어야 하고 빈칸이 없어야 하고 유일해야 한다고 선언했다. 또한 password, email 그리고 age 속성에 제약조건을 적용했다.

사용자 삽입 이미지
사용 가능한 제약조건의 완전한 참조는 레퍼런스 가이드에서 찾을 수 있다.


7.2 제약조건 검사

유효성검사 기본
도메인 클래스의 유효성검사를 위해서 모든 인스턴스가 가지고 있는 validate 메소드를 호출할 수 있다.

def user = new User(params)
if (user.validate()) {
    // user로 뭔가를 한다.
}
eles {
    user.errors.allErrors.each {
        println it
    }
}

도메인 클래스의 errors 속성은 Spring Errors 인터페이스의 인스턴스이다. Errors 인터페이스는 유효성검사 에러를 살펴볼(navigate) 수 있는 메소드와 원래 값을 조회할 수 있는 메소드를 제공한다.

검사 단계
Grails에는 근본적으로 2단계의 검사단계가 있다. 첫번째 단계는 데이터 바인딩으로 요청 파라메터를 다음과 같은 인스턴스에 바인드할 때 발생한다.

def user = new User(params)

이 지점에서 String을 Date로 변환하는 것 같은 타입변환에 의해 이미 errors 속성을 가질 수도 있다. 이것들을 검사하고 Errors API를 이용하여 원래의 입력값을 볼 수도 있다.

if(user.hasErrors()) {
    if(user.hasFieldError("login")) {
        println user.getFieldError("login").rejectedValue
    }
}

유효성 검사의 두번째 단계는 validatesave를 호출할 때 발생한다. 이것은 개발자가 정의한 constraints를 통해 Grails가 한계값을 검사할 때이다. 예를 들어 저장하는 save 메소드를 실행하기전 기본으로 validate를 호출한다. 따라서 다음과 같이 코드를 작성할 수 있다.

if (user.save()) {
    return user
}
else {
    user.errors.allErrors.each {
        println it
    }
}


7.3 클라이어트에서의 유효성검사

에러 보여주기
전형적으로 유효성검사 에러가 발생하면 렌더링을 하기 위해 뷰(페이지)가 뒤로 돌아가길 원할 것이다. 또한 에러를 표현하는 방법이 필요할 것이다. Grails는 에러를 다루는 풍부한 태그들을 지원하고 있다. 단순히 목록으로 에러를 표현하고 싶다면 renderErrors를 사용할 수 있다.

<g:renderErrors bean="${user}" />

좀 더 제어가 필요하다면 hasErroreachError를 사용할 수 있다.

<g:hasError bean="${user}">
    <ul>
        <g:eachError var="err" bean="${user}">
            <li>${err}</li>
        </g:eachError>
    </ul?
</g:hasError>


에러 강조하기(highlighting)
필드에 틀린 입력값이 있을 경우 빨간 박스나 어떤 표시를 사용하여 강조하는 것은 종종 유용하다. hasErrors를 메소드로 호출하여 이것을 할 수 있다. 예를 들어

<div class="value ${hasErrors(bean:user,field:'login','errors')}">
    <input type="text" name="login" value="${fieldValue(bean:user,field:'login')}" />
</div>

이 코드가 하는 일은 user의 login 필드가 에러가 없는지 검사하여 에러가 있으면 div를 강조하기 위한 errors CSS 클래스를 div에 추가한다.

입력값 복구
각 에러는 실제로 Spring에 있는 FieldError 클래스의 인스턴스인데 이것은 본래 입력값을 유지하고 있다. 이것은 fieldValue 태그를 이용하여 사용자에게 입력받은 값을 복구하기 위해 에러 객체를 사용할 수 있다는 점에서 유용하다.

<input type="text" name="login" value="${fieldValue(bean:user, field:'login')}" />

이 코드는 User bean에 FieldError가 있고 login필드의 본래 입력값을 획득할 수 있는지 살펴볼 것이다.

7.4 유효성검사와 국제화
Grails의 에러에 대해 주목할 또 다른 중요한 점은 에러를 보여주는 메시지가 어디에도 하드 코드되지 않는다는 것이다. Spring의 FieldError 클래스는 근본적으로 Grails의 i18n을 이용하여 메시지 번들로 메시지를 결정한다.

제약조건과 메시지 코드
규칙(convention)에 의해 메시지 코드를 결정한다. 예를 들어 앞에서 본 제약조건을 살펴보자

class User {
    ...
    static constraints = {
        login(size:5..15, blank:false, unique:true)
        password(size:5..15, blank:false)
        email(email:true, blank:false)
        age(min:18, nullable:false)
    }
}

만일 blank 제약조건을 위반했다면 Grails는 약속에 의해 다음 형식으로 메시지 코드를 찾는다.

[클래스이름].[속성이름].[제약코드]

이 blank 제약조건의 경우는 user.login.blank가 될 것이다. 따라서 grails-app/i18n/messages.properties 파일안에 다음과 같은 메시지가 필요할 것이다.

user.login.blank=Your login name must be specified!

각 제약조건이 어떤 코드인지를 보기 위해서는 레퍼런스 가이드에서 각 제약조건을 참조한다.

메시지 보여주기
renderErrors 태그는 message 태그를 사용하여 자동으로 메시지를 찾아준다. 어쨌거나 메시지 표현을 더 제어하기를 원한다면 개발자 스스로 할 필요도 있다.

<g:hasErrors bean="${user}">
    <ul>
        <g:eachError var="err" bean="${user}">
            <li><g:message error="${err}" /></li>
        </g:eachError>
    </ul>
</g:hasErrors>

이 예제에서 주어진 에러의 메시지를 읽기 위해 eachError 태그의 몸체안에 message 태그와 error 아규먼트를 사용했다.

---
본문: 7. Validation

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 행복한아빠