Go 언어

본문 바로가기
사이트 내 전체검색


Go 언어
Go 언어

4. RESTful API 서버 만들기 (1)

페이지 정보

작성자 관리자 댓글 1건 조회 1,009회 작성일 21-07-18 09:05

본문

4. RESTful API 서버 만들기 (1)

GoLang에서는 net/http 라는 강력한 HTTP 패키지가 내장되어 있습니다. 

이 패키지를 사용하면 Request를 보내는 것부터 받는 것까지 모든 HTTP 통신을 처리할 수 있다.

여기에서도 net/http 패키지를 기본으로 하여 RESTful API 서버를 만듭니다. 

또한 웹서버의 기본이라고도 할 수 있는 라우팅은 httprouter 를 사용하였습니다.


일반적으로 RESTful API 서버는 웹기반의 URI를 사용해서 만들어진다.

여기에서도 웹기반의 RESTful API 서버를 만듭니다. 

때문에 먼저 HTTP 서버를 열고 각 메소드를 받는 것부터 시작합니다. 

먼저 웹서버의 라우팅을 처리할 httprouter 패키지를 다운로드 해야 합니다.


$ go get github.com/julienschmidt/httprouter 



간단한 api 서버를 만들어보록 하겠습니다.



[jklee@www golang]$ mkdir simple_api

[jklee@www golang]$ cd simple_api/

[jklee@www simple_api]$ vi go_simple_api.go


package main


import (

    "fmt"

    "github.com/julienschmidt/httprouter"

    "net/http"

    "log"

)


func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

    fmt.Fprint(w, "Welcome!\n")

}


func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))

}


func main() {

    router := httprouter.New()

    router.GET("/", Index)

    router.GET("/hello/:name", Hello)


    log.Fatal(http.ListenAndServe(":8080", router))

}



보시면 사용법이 아주 간단합니다.


httprouter.New()로 라우터를 생성하고, GET, POST, PUT, DELETE 등 대문자로 작성된 HTTP 메소드의 이름을 함수로 호출하면 해당 메소드의 라우팅이 등록됩니다.


만약 POST 메소드를 등록하려면 아래와 같이 하면 등록된다.


router.POST("/some/post", handleSomePost)



컴파일하고 실행해보겠습니다.


[jklee@www simple_api]$ go mod init simple_api

go: creating new go.mod: module simple_api

go: to add module requirements and sums:

        go mod tidy

[jklee@www simple_api]$ go mod tidy

go: finding module for package github.com/julienschmidt/httprouter

go: downloading github.com/julienschmidt/httprouter v1.3.0

go: found github.com/julienschmidt/httprouter in github.com/julienschmidt/httprouter v1.3.0

[jklee@www simple_api]$ go install

[jklee@www simple_api]$ simple_api



웹브라우저에서 실행해본다.


1.PNG


2.PNG



simple_api를 수정해보도록 하겠습니다.


HTTP 기반의 RESTful API 서버에서는 Get, Post, Put, Delete의 네 가지 메소드를 사용합니다.

이를 인터페이스로 만들어 보겠습니다.


type Response struct {

Code    int         `json:"code"`

Message string      `json:"message"`

Data    interface{} `json:"data"`

}


type Resource interface {

Uri() string

Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Post(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Put(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Delete(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

}


이 리소스의 URI를 반환할 Uri() 함수, 각 메소드에 대응되는 핸들러들이 정의되어 있는 인터페이스 입니다. 

하지만 리소스가 네 가지 메소드를 모두 지원하지 않을 수도 있죠. 그러니 각 메소드에 맞게 NotSupported를 만들어줍니다.


type (

GetNotSupported    struct{}

PostNotSupported   struct{}

PutNotSupported    struct{}

DeleteNotSupported struct{}

)


func (GetNotSupported) Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (PostNotSupported) Post(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (PutNotSupported) Put(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (DeleteNotSupported) Delete(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


만약 "/hello"라는 URI로 Get 메소드만 지원하는 API를 만들고 싶다면 아래와 같이 사용하면 될 것입니다.


type HelloResource struct {

    PostNotSupported

    PutNotSupported

    DeleteNotSupported

}


func (HelloResource) Uri() string {

    return "/hello"

}


func (HelloResource) Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{200, "message", map[string]interface{}{

        "key1": "value1",

    }}

}


이렇게 만들어진 API는 아래와 같이 등록할 수 있습니다.


router.GET(HelloResource.Uri(), HelloResource.Get)

router.POST(HelloResource.Uri(), HelloResource.Post)

router.PUT(HelloResource.Uri(), HelloResource.Put)

router.DELETE(HelloResource.Uri(), HelloResource.Delete)


그런데 보기엔 좋은 것 같지만 여전히 반복적인 코드가 생겨납니다. 

인터페이스도 만들었으니, 함수를 만들어보겠습니다.


func HttpResponse(rw http.ResponseWriter, req *http.Request, res Response) {

content, err := json.Marshal(res)


if err != nil {

abort(rw, 500)

}


rw.WriteHeader(res.Code)

rw.Write(content)

}


func AddResource(router *httprouter.Router, resource Resource) {

fmt.Println("\"" + resource.Uri() + "\" api is registerd")


router.GET(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Get(rw, r, ps)

HttpResponse(rw, r, res)

})

router.POST(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Post(rw, r, ps)

HttpResponse(rw, r, res)

})

router.PUT(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Put(rw, r, ps)

    HttpResponse(rw, r, res)

})

router.DELETE(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Delete(rw, r, ps)

HttpResponse(rw, r, res)

})

}


이제 HelloResource를 등록하려면 아래와 같이 단 한 줄만 넣으면 됩니다.


AddResource(router, new(HelloResource))



전체 소스는 다음과 같습니다.


package main


import (

"encoding/json"

"fmt"

"log"

"net/http"

"strconv"


"github.com/julienschmidt/httprouter"

)


type Response struct {

Code    int         `json:"code"`

Message string      `json:"message"`

Data    interface{} `json:"data"`

}


type Resource interface {

Uri() string

Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Post(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Put(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

Delete(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response

}


type (

GetNotSupported    struct{}

PostNotSupported   struct{}

PutNotSupported    struct{}

DeleteNotSupported struct{}

)


func (GetNotSupported) Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (PostNotSupported) Post(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (PutNotSupported) Put(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func (DeleteNotSupported) Delete(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{405, "", nil}

}


func abort(rw http.ResponseWriter, statusCode int) {

rw.WriteHeader(statusCode)

}


func HttpResponse(rw http.ResponseWriter, req *http.Request, res Response) {

content, err := json.Marshal(res)


if err != nil {

abort(rw, 500)

}


rw.WriteHeader(res.Code)

rw.Write(content)

}


func AddResource(router *httprouter.Router, resource Resource) {

fmt.Println("\"" + resource.Uri() + "\" api is registerd")


router.GET(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Get(rw, r, ps)

HttpResponse(rw, r, res)

})

router.POST(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Post(rw, r, ps)

HttpResponse(rw, r, res)

})

router.PUT(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Put(rw, r, ps)

HttpResponse(rw, r, res)

})

router.DELETE(resource.Uri(), func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {

res := resource.Delete(rw, r, ps)

HttpResponse(rw, r, res)

})

}


// user string array

var users = []string{

"user1", "user2", "user3",

}


// /user

type UserResource struct {

PutNotSupported

DeleteNotSupported

}


func (UserResource) Uri() string {

return "/user"

}


func (UserResource) Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

return Response{200, "", users}

}


func (UserResource) Post(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

user := r.FormValue("user")

if len(user) > 0 {

users = append(users, r.FormValue("user"))

return Response{200, "", nil}

} else {

return Response{400, "", nil}

}

}


// /user/:index

type UserEachResource struct {

PostNotSupported

}


func (UserEachResource) Uri() string {

return "/user/:index"

}


func (UserEachResource) Get(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

if index, err := strconv.Atoi(ps.ByName("index")); err == nil {

if len(users) > index && index > 0 {

return Response{200, "", users[index]}

}

}

return Response{400, "", nil}

}


func (UserEachResource) Put(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

user := r.FormValue("user")

if len(user) > 0 {

if index, err := strconv.Atoi(ps.ByName("index")); err == nil {

if len(users) > index && index > 0 {

users[index] = user

return Response{200, "", nil}

}

}

return Response{400, "", nil}

} else {

return Response{400, "", nil}

}

}


func (UserEachResource) Delete(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) Response {

if index, err := strconv.Atoi(ps.ByName("index")); err == nil {

if len(users) > index && index > 0 {

users = append(users[:index], users[index+1:]...)

return Response{200, "", nil}

}

}

return Response{400, "", nil}

}


func main() {

router := httprouter.New()


AddResource(router, new(UserResource))

AddResource(router, new(UserEachResource))


log.Fatal(http.ListenAndServe(":8080", router))

}


실행 해보록 하겠습니다.


소스를 수정하고 컴파일 후, 다시 서버를 구동해 줍니다.


[jklee@www simple_api]$ go install

[jklee@www simple_api]$ simple_api

"/user" api is registerd

"/user/:index" api is registerd


웹브라우저에서 확인해봅니다.



3.PNG


4.PNG


댓글목록

관리자님의 댓글

관리자 작성일

[jklee@www ~]$ curl -X GET --header 'Accept: application/json' 'http://localhost:5001/hello/Jinkwan'
hello, Jinkwan!
[jklee@www ~]$


개인정보취급방침 서비스이용약관 모바일 버전으로 보기 상단으로

TEL. 063-469-4551 FAX. 063-469-4560 전북 군산시 대학로 558
군산대학교 컴퓨터정보공학과

Copyright © www.leelab.co.kr. All rights reserved.