이전 섹션에서는 요청 매개변수를 직접 읽는 방법을 소개했습니다. 각 매개변수를 개별적으로 읽는 것이 번거로운 경우, iris 프레임워크는 매개변수 바인딩 메커니즘을 제공하며, 이를 사용하여 요청 매개변수를 구조체에 바인딩할 수 있으며, 또한 폼 매개변수 유효성 검사 메커니즘을 지원합니다.

모델 바인딩과 유효성 검사

요청 본문을 유형에 바인딩하려면 모델 바인딩을 사용합니다. 현재 우리는 JSON, JSONProtobuf, Protobuf, MsgPack, XML, YAML, 표준 폼 값 (foo=bar&boo=baz)과 같은 유형 바인딩을 지원합니다.

// 아래는 다양한 형식의 요청 매개변수를 구조체에 바인딩하는 함수 정의
ReadJSON(outPtr interface{}) error
ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error
ReadProtobuf(ptr proto.Message) error
ReadMsgPack(ptr interface{}) error
ReadXML(outPtr interface{}) error
ReadYAML(outPtr interface{}) error
ReadForm(formObject interface{}) error
ReadQuery(ptr interface{}) error

ReadBody를 사용할 때, Iris는 Content-Type 헤더를 기반으로 바인더를 추론합니다. 바인딩할 내용을 확신한다면, ReadXXX 메서드를 사용할 수 있습니다. 예를 들어, ReadJSON 또는 ReadProtobuf와 같은 메서드입니다.

ReadBody(ptr interface{}) error

Iris는 기본 내장된 데이터 유효성 검사를 제공합니다. 그러나, ReadJSON, ReadXML 등의 메서드에서 자동으로 호출될 유효성 검사기를 첨부할 수 있습니다. 이 예시에서는 go-playground/validator/v10을 사용하여 요청 본문을 유효성 검사하는 방법을 배우겠습니다.

유의해야 할 점은, 바인딩할 모든 필드에 해당 바인딩 태그를 설정해야 합니다. 예를 들어, JSON에서 바인딩할 때는 json:"fieldname"을 설정합니다.

특정 필드를 필수 필드로 지정할 수도 있습니다. 만약 필드에 binding:"required" 데코레이션이 있는데 바인딩 중에 값이 제공되지 않으면, 오류가 반환됩니다.

package main

import (
    "fmt"

    "github.com/kataras/iris/v12"
    "github.com/go-playground/validator/v10"
)

func main() {
    app := iris.New()
    app.Validator = validator.New()

    userRouter := app.Party("/사용자")
    {
        userRouter.Get("/유효성-오류", 오류해결문서화처리)
        userRouter.Post("/", 사용자등록)
    }
    app.Listen(":8080")
}

// 사용자는 사용자 정보를 포함합니다.
type 사용자 struct {
    FirstName      string     `json:"fname" validate:"required"` // 성, 필수
    LastName       string     `json:"lname" validate:"required"` // 이름, 필수
    Age            uint8      `json:"age" validate:"gte=0,lte=130"` // 나이, 0에서 130 사이의 범위
    Email          string     `json:"email" validate:"required,email"` // 이메일, 필수
    FavouriteColor string     `json:"favColor" validate:"hexcolor|rgb|rgba"` // 즐겨찾는 색, 유효한 16진수, RGB 또는 RGBA 색상 값이어야 합니다.
    Addresses      []*주소    `json:"addresses" validate:"required,dive,required"` // 주소 목록, 비어 있으면 안 되고 각 주소 항목은 필수입니다.
}

// 주소는 사용자 주소 정보를 저장합니다.
type 주소 struct {
    Street string `json:"street" validate:"required"` // 거리, 필수
    City   string `json:"city" validate:"required"` // 도시, 필수
    Planet string `json:"planet" validate:"required"` // 행성, 필수
    Phone  string `json:"phone" validate:"required"` // 전화번호, 필수
}

type 유효성오류 struct {
    ActualTag string `json:"tag"` // 실제 태그
    Namespace string `json:"namespace"` // 네임스페이스
    Kind      string `json:"kind"` // 종류
    Type      string `json:"type"` // 유형
    Value     string `json:"value"` // 값
    Param     string `json:"param"` // 매개변수
}

func 유효성오류감싸기(errs validator.ValidationErrors) []유효성오류 {
    validationErrors := make([]유효성오류, 0, len(errs))
    for _, validationErr := range errs {
        validationErrors = append(validationErrors, 유효성오류{
            ActualTag: validationErr.ActualTag(),
            Namespace: validationErr.Namespace(),
            Kind:      validationErr.Kind().String(),
            Type:      validationErr.Type().String(),
            Value:     fmt.Sprintf("%v", validationErr.Value()),
            Param:     validationErr.Param(),
        })
    }

    return validationErrors
}

func 사용자등록(ctx iris.Context) {
    var user 사용자
    err := ctx.ReadJSON(&user)
    if err != nil {
        // 오류 처리, 다음이 올바른 방법입니다...

        if errs, ok := err.(validator.ValidationErrors); ok {
            // 오류를 JSON 형식으로 감싸기, 기본 라이브러리는 인터페이스 형식의 오류를 반환합니다.
            validationErrors := 유효성오류감싸기(errs)

            // 응용 프로그램/json+problem 응답 반환 및 이후 핸들러의 실행 중지
            ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
                Title("유효성 오류").
                Detail("하나 이상의 필드가 유효성 검사를 통과하지 못했습니다").
                Type("/사용자/유효성-오류").
                Key("errors", validationErrors))

            return
        }

        // 내부 JSON 오류일 수 있으며 여기에는 추가 정보가 제공되지 않습니다.
        ctx.StopWithStatus(iris.StatusInternalServerError)
        return
    }

    ctx.JSON(iris.Map{"message": "OK"})
}

func 오류해결문서화처리(ctx iris.Context) {
    ctx.WriteString("이 페이지는 웹 개발자 또는 API 사용자에게 유효성 오류 해결 방법을 설명하는 데 사용됩니다")
}
{
    "제목": "유효성 검사 오류",
    "상세": "하나 이상의 필드가 유효성 검사에 실패했습니다",
    "유형": "http://localhost:8080/user/validation-errors",
    "상태": 400,
    "필드": [
        {
            "태그": "필수",
            "네임스페이스": "User.FirstName",
            "종류": "문자열",
            "타입": "문자열",
            "값": "",
            "매개변수": ""
        },
        {
            "태그": "필수",
            "네임스페이스": "User.LastName",
            "종류": "문자열",
            "타입": "문자열",
            "값": "",
            "매개변수": ""
        }
    ]
}

URL 쿼리 매개변수 바인딩

ReadQuery 메서드는 요청 본문 데이터가 아닌 쿼리 매개변수만을 바인딩합니다. 요청 본문 데이터를 바인딩하려면 ReadForm을 사용하세요.

package main

import "github.com/kataras/iris/v12"

type Person struct {
    Name    string `url:"name,required"`
    Address string `url:"address"`
}

func main() {
    app := iris.Default()
    app.Any("/", index)
    app.Listen(":8080")
}

func index(ctx iris.Context) {
    var person Person
    if err := ctx.ReadQuery(&person); err != nil {
        ctx.StopWithError(iris.StatusBadRequest, err)
        return
    }

    ctx.Application().Logger().Infof("Person: %#+v", person)
    ctx.WriteString("Success")
}

임의 데이터 바인딩

클라이언트가 보낸 데이터의 콘텐츠 유형에 기반하여 요청 본문을 "ptr"에 바인딩하세요. 이때 콘텐츠 유형은 JSON, XML, YAML, MessagePack, Protobuf, Form, URL 쿼리 등이 있습니다.

package main

import (
    "time"
    "github.com/kataras/iris/v12"
)

type Person struct {
    Name       string    `form:"name" json:"name" url:"name" msgpack:"name"`
    Address    string    `form:"address" json:"address" url:"address" msgpack:"address"`
    Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1" json:"birthday" url:"birthday" msgpack:"birthday"`
    CreateTime time.Time `form:"createTime" time_format:"unixNano" json:"create_time" url:"create_time" msgpack:"createTime"`
    UnixTime   time.Time `form:"unixTime" time_format:"unix" json:"unix_time" url:"unix_time" msgpack:"unixTime"`
}

func main() {
    app := iris.Default()
    app.Any("/", index)
    app.Listen(":8080")
}

func index(ctx iris.Context) {
    var person Person
    if err := ctx.ReadBody(&person); err != nil {
        ctx.StopWithError(iris.StatusBadRequest, err)
        return
    }

    ctx.Application().Logger().Infof("Person: %#+v", person)
    ctx.WriteString("Success")
}

다음 명령어로 테스트할 수 있습니다:

$ curl -X GET "localhost:8085/testing?name=kataras&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"

URL 경로 매개변수 바인딩

package main

import "github.com/kataras/iris/v12"

type myParams struct {
    Name string   `param:"name"`
    Age  int      `param:"age"`
    Tail []string `param:"tail"`
}

func main() {
    app := iris.Default()
    app.Get("/{name}/{age:int}/{tail:path}", func(ctx iris.Context) {
        var p myParams
        if err := ctx.ReadParams(&p); err != nil {
            ctx.StopWithError(iris.StatusInternalServerError, err)
            return
        }

        ctx.Writef("myParams: %#v", p)
    })
    app.Listen(":8088")
}

요청

$ curl -v http://localhost:8080/kataras/27/iris/web/framework

헤더 요청 매개변수 바인딩

package main

import "github.com/kataras/iris/v12"

type myHeaders struct {
    RequestID      string `header:"X-Request-Id,required"`
    Authentication string `header:"Authentication,required"`
}

func main() {
    app := iris.Default()
    r.GET("/", func(ctx iris.Context) {
        var hs myHeaders
        if err := ctx.ReadHeaders(&hs); err != nil {
            ctx.StopWithError(iris.StatusInternalServerError, err)
            return
        }

        ctx.JSON(hs)
    })
    
    app.Listen(":8080")
}

요청

curl -H "x-request-id:373713f0-6b4b-42ea-ab9f-e2e04bc38e73" -H "authentication: Bearer my-token" \
http://localhost:8080

응답

{
  "RequestID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73",
  "Authentication": "Bearer my-token"
}