ABOUT ME

-

Total
-
  • Golang: gin으로 카카오 챗봇 서버 만들기
    컴퓨터/Go language 2021. 1. 1. 22:57
    728x90
    반응형

    gin

    gin 로고

     

    gin-gonic/gin

    Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin. - ...

    github.com

    카카오 챗봇 Python으로 만들기 링크

    카카오 챗봇 Rust언어로 만들기 링크

    카카오 챗봇 예제 서버 Python FastAPI 소스 링크

    카카오 챗봇 풀 예제 Github 링크

     

    1. gin

    gin은 Python의 Flask, FastAPI처럼 웹 서버를 만들 수 있는 웹 프레임워크이다.

    웹 프레임워크 벤치마크에 따르면, gin은 39위, FastAPI는 130위이다.

     

    Go 프레임워크로 젤 빠른 fiber나 echo, router, fasthttp 등이 있지만,

    자료가 제일 많은 gin으로 만들어보았다.

     

     

    2. Go 패키지 및 기본 설정

    go get -u github.com/gin-gonic/gin

    gin을 설치하고 

    아래 예제 코드처럼 만들면 GET /ping이 있는 서버가 열린다.

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
    }

     

    미들웨어 설정

    JSON 형식만 request 되기 때문에 그냥 항상 application/json으로 바꿔준다.

    (PureJSON, JSON...등을 보니 그 안에서도 content-type을 작성해주기 때문에 안해도 된다.)

    // JSONMiddleware is to set all types of requests are JSON.
    func JSONMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		c.Writer.Header().Set("Content-Type", "application/json")
    		c.Next()
    	}
    }
    
    func main() {
    	router := gin.Default()
    	router.Use(JSONMiddleware())
        ...
    }

     

    3. 카카오 챗봇 JSON 파싱

    nested 된 json을 어떻게 파싱 할지 고민하다가, FastAPI BaseModel처럼 형식을 짰다.

    구조

    KakaoJSON (베이스 모델)
        └---- Action (json:action)
        └---- Bot (json:bot)
        └---- Contexts (json:contexts)
        └---- Intent (json:intent)
        └---- UserRequest (json:userRequest)
    
    contexts는 input/output 없으면 왠만하면 [] 빈 배열이다.

     

    형식

    // KakaoJSON request main
    type KakaoJSON struct {
    	Action struct {
    		ID          string `json:"id"`
    		ClientExtra struct {
    		} `json:"clientExtra"`
    		DetailParams map[string]interface{} `json:"detailParams"`
    		Name         string                 `json:"name"`
    		Params       map[string]interface{} `json:"params"`
    	} `json:"action"`
    	Bot struct {
    		ID   string `json:"id"`
    		Name string `json:"name"`
    	} `json:"bot"`
    	Contexts []interface{} `json:"contexts"`
    	Intent   struct {
    		ID    string `json:"id"`
    		Extra struct {
    			Reason struct {
    				Code    int64  `json:"code"`
    				Message string `json:"message"`
    			} `json:"reason"`
    		} `json:"extra"`
    		Name string `json:"name"`
    	} `json:"intent"`
    	UserRequest struct {
    		Block struct {
    			ID   string `json:"id"`
    			Name string `json:"name"`
    		} `json:"block"`
    		Lang   string `json:"lang"`
    		Params struct {
    			IgnoreMe bool   `json:"ignoreMe,string"`
    			Surface  string `json:"surface"`
    		} `json:"params"`
    		Timezone string `json:"timezone"`
    		User     struct {
    			ID         string `json:"id"`
    			Properties struct {
    				BotUserKey  string `json:"botUserKey"`
    				BotUserKey2 string `json:"bot_user_key"`
    			} `json:"properties"`
    			Type string `json:"type"`
    		} `json:"user"`
    		Utterance string `json:"utterance"`
    	} `json:"userRequest"`
    }

     

    4. 서버 테스트

    route GET=("/") : 환영 메시지만 보여준다.

    localhost:8000

     

    route POST=("/json") : 카카오 챗봇 API json 형식이 잘 되는지 테스트하는 route

     

    아래 코드를 실행하면 localhost:8000에 서버가 열린다.

    # 파이썬에선 쉽게 바로 접근할 수가 있다.
    request["intent"]["extra"]["reason"]["code"]
    request["action"]["params"]["sys_text"]
    request["userRequest"]["utterance"]
    request["userRequest"]["user"]["id"]

     

    data.json에는 예제 카카오 챗봇 json이 담겨 있고, curl을 해서 post가 잘 되는지 확인한다.

    WIN10@DESKTOP:~$ curl http://localhost:8000/json -d "@data.json"
    
    {"message":"Reason:1 | Params['sys_text']:2021 | Utterance:2021 검색\n | UserID:idUser"}

     

    이후 AWS나 구름IDE를 이용해서 서버를 열고

    원하는 발화문, 블록 등을 만들어서 POST route를 만들면 끝이다.

    package main
    
    import (
    	"fmt"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    )
    
    // KakaoJSON request main
    type KakaoJSON struct {
    	Action struct {
    		ID          string `json:"id"`
    		ClientExtra struct {
    		} `json:"clientExtra"`
    		DetailParams map[string]interface{} `json:"detailParams"`
    		Name         string                 `json:"name"`
    		Params       map[string]interface{} `json:"params"`
    	} `json:"action"`
    	Bot struct {
    		ID   string `json:"id"`
    		Name string `json:"name"`
    	} `json:"bot"`
    	Contexts []interface{} `json:"contexts"`
    	Intent   struct {
    		ID    string `json:"id"`
    		Extra struct {
    			Reason struct {
    				Code    int64  `json:"code"`
    				Message string `json:"message"`
    			} `json:"reason"`
    		} `json:"extra"`
    		Name string `json:"name"`
    	} `json:"intent"`
    	UserRequest struct {
    		Block struct {
    			ID   string `json:"id"`
    			Name string `json:"name"`
    		} `json:"block"`
    		Lang   string `json:"lang"`
    		Params struct {
    			IgnoreMe bool   `json:"ignoreMe,string"`
    			Surface  string `json:"surface"`
    		} `json:"params"`
    		Timezone string `json:"timezone"`
    		User     struct {
    			ID         string `json:"id"`
    			Properties struct {
    				BotUserKey  string `json:"botUserKey"`
    				BotUserKey2 string `json:"bot_user_key"`
    			} `json:"properties"`
    			Type string `json:"type"`
    		} `json:"user"`
    		Utterance string `json:"utterance"`
    	} `json:"userRequest"`
    }
    
    // JSONMiddleware is to set all types of requests are JSON.
    func JSONMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		c.Writer.Header().Set("Content-Type", "application/json")
    		c.Next()
    	}
    }
    
    func main() {
    	router := gin.Default()
    	router.Use(JSONMiddleware())
    
    	router.GET("/", func(c *gin.Context) {
    		c.JSON(http.StatusOK, gin.H{
    			"message": "Server is running well.",
    		})
    	})
    
    	router.POST("/json", func(c *gin.Context) {
    		var json KakaoJSON
    		if err := c.BindJSON(&json); err != nil {
    			c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": err.Error()})
    			return
    		}
    
    		c.JSON(http.StatusOK,
    			gin.H{"json": json, "user entered": json.UserRequest.Utterance, "params": json.Action.Params})
    
    	})
        
        // SimpleText 만들고 보내는 과정 예제
    	router.POST("/simple", func(c *gin.Context) {
    		var json KakaoJSON
    		if err := c.BindJSON(&json); err != nil {
    			c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": err.Error()})
    			return
    		}
    
    		u := SimpleText{Version: "2.0"}
    		u.Template.Outputs.SimpleText.Text = fmt.Sprintf("Entered: %v", json.UserRequest.Utterance)
    
    		c.JSON(http.StatusOK,
    			gin.H{"message": u})
    
    	})
    
    	router.Run(":8000")
    }
    

     

    ex) simpleText 메시지 보내기

    ListCard나 다른 부분들은 꽤나 복잡해진다. 파이썬 FastAPI 버전이 정신 건강에 좋다.

    아니면 맨 아래 참고에 만든 모듈 참고

    모듈로 카톡 챗봇 JSON 만들기 링크

    // SimpleText for Kakao Response
    type SimpleText struct {
    	Template struct {
    		Outputs []struct {
    			SimpleText Text `json:"simpleText"`
    		} `json:"outputs"`
    	} `json:"template"`
    	Version string `json:"version"`
    }
    
    // BuildSimpleText ...
    func BuildSimpleText(msg string) *SimpleText {
    	stext := &SimpleText{Version: "2.0"}
    
    	var temp []struct {
    		SimpleText Text `json:"simpleText"`
    	}
    	simpleText := Text{Text: msg}
    
    	text := struct {
    		SimpleText Text `json:"simpleText"`
    	}{SimpleText: simpleText}
    
    	temp = append(temp, text)
    
    	stext.Template.Outputs = temp
    	return stext
    }
    
    c.JSON(http.StatusOK, gin.H{"message": BuildSimpleText(fmt.Sprintf("Entered: %v", json.UserRequest.Utterance))})
    // {"template":{"outputs":{"simpleText":{"text":"Entered: 2021 검색\n"}}},"version":"2.0"}

     

    ex) ListCard 메시지 보내기

    gin.H = map[string]interface{}

    아래는 2개의 카드와, 1개의 QuickReply를 추가했다.

    (아니면 맨 아래 참고에 만든 모듈 참고)

    // ListCard 만들고 보내는 과정 예제
    router.POST("/card", func(c *gin.Context) {
    	var kjson KakaoJSON
    	if err := c.BindJSON(&kjson); err != nil {
    		errorMsg := SimpleText{Version: "2.0"}
    		errorMsg.Template.Outputs.SimpleText.Text = err.Error()
    		c.JSON(http.StatusBadRequest, errorMsg)
    		return
    	}
    
    	// Card
    	items := []gin.H{}
    	header := gin.H{"title": "header title"}
    
    	// Card items
    	item := gin.H{"title": "card", "description": "desc", "imageUrl": "img", "link": gin.H{"web": "webhref"}}
    	item2 := gin.H{"title": "card2", "description": "desc2"}
    
    	// Add two cards
    	items = append(items, item)
    	items = append(items, item2)
    
    	// QuickReplies [Optional]
    	quickReplies := []gin.H{}
    
    	// Add one quick reply
    	quickReply := gin.H{"messageText": "안녕하세요", "action": "message", "label": "안녕"}
    	quickReplies = append(quickReplies, quickReply)
    
    	// Make a template
    	template := gin.H{"outputs": []gin.H{gin.H{"listCard": gin.H{"header": header, "items": items}}}}
    	template["quickReplies"] = quickReplies // Optional
    	listCard := gin.H{"version": "2.0", "template": template}
    
    	c.JSON(http.StatusOK, listCard)
    })
    
    // 결과
    // {"template":{"outputs":[{"listCard:":{"header":{"title":"header title"},"items":[{"description":"desc","imageUrl":"img","link":{"web":"webhref"},"title":"card"},{"description":"desc2","title":"card2"}]}}],"quickReplies":[{"action":"message","label":"안녕","messageText":"안녕하세요"}]},"version":"2.0"}

     

    ex) ListCard JSON marshal 테스트

    nested struct을 이용하고, anonymous struct 모음이라 파이썬을 쓰는게 더 나을 것 같다.

    listCard json tag를 만들기 위해 Outputs을 따로 anonymous 처리했음.

    package main
    
    import (
    	"fmt"
    
    	"github.com/clarketm/json"
    )
    
    type Template struct {
    	Outputs []struct {
    		ListCard ListCardJSON `json:"listCard"`
    	} `json:"outputs"`
    
    	QuickReplies []QuickReply `json:"quickReplies,omitempty"`
    }
    
    type QuickReply struct {
    	Action      string `json:"action"`
    	Label       string `json:"label"`
    	MessageText string `json:"messageText"`
    }
    
    type ListCardJSON struct {
    	Buttons []Button `json:"buttons"`
    	Header  Header   `json:"header,omitempty"`
    	Items   []Item   `json:"items"`
    }
    
    type Button struct {
    	Label  string `json:"label"`
    	Action string `json:"action"`
    }
    
    type Item struct {
    	Description string `json:"description"`
    	ImageUrl    string `json:"imageUrl,omitempty"`
    	Link        Link   `json:"link,omitempty"`
    	Title       string `json:"title"`
    }
    
    type Link struct {
    	Web string `json:"web"`
    }
    
    type Header struct {
    	Title string `json:"title"`
    }
    
    // KakaoListCard is main
    type KakaoListCard struct {
    	Template Template `json:"template"`
    	Version  string   `json:"version"` // 2.0
    }
    
    func main() {
    	data := KakaoListCard{Version: "2.0"}
    
    	// Card Buttons
    	buttons := []Button{{Label: "hey1", Action: "share"}, {Label: "hey2", Action: "share"}}
    
    	// Card Contents
    	items := []Item{
    		{Description: "desc1", ImageUrl: "img", Link: Link{Web: "web"}, Title: "title1"},
    		{Description: "desc2", Title: "title2"},
    	}
    	listCard := ListCardJSON{
    		Buttons: buttons,
    		Header:  Header{Title: "hi"},
    		Items:   items,
    	}
    
    	// Add outputs to template
    	var temp []struct {
    		ListCard ListCardJSON `json:"listCard"`
    	}
    
    	lc := struct {
    		ListCard ListCardJSON `json:"listCard"`
    	}{ListCard: listCard}
    
    	temp = append(temp, lc)
    	data.Template.Outputs = temp // To make a tag [{"listCard"}]
    
    	// Make QuickReplies
    	quickreplies := []QuickReply{{
    		MessageText: "어제 보여줘",
    		Action:      "message",
    		Label:       "어제",
    	}}
    
    	data.Template.QuickReplies = quickreplies
    
    	b, _ := json.MarshalIndent(data, "", "  ")
    	fmt.Println(string(b))
    }
    

     

     

    locust 15명 가상 유저 2개 POST, AWS EC2 free tier 서버 성능

     

    참고

    카카오톡 챗봇 API 응답 JSON 빌더 모듈 @Github 개인

    아래 모듈을 사용하시면 더 빠르고 간편하게 챗봇을 만들 수 있습니다.

     

    Golang: 카카오 챗봇 API 응답 JSON 빌더 헬퍼 모듈

    The Go Programming Language Download Go Binary distributions available for Linux, macOS, Windows, and more. // You can edit this code! // Click here and start typing. package main import "fmt" func..

    choiseokwon.tistory.com

    카카오톡 챗봇 서버 @Github 개인

     

    Alfex4936/KakaoChatBot-Golang

    Go언어 gin 웹 프레임워크를 이용한 카카오 챗봇 예제. Contribute to Alfex4936/KakaoChatBot-Golang development by creating an account on GitHub.

    github.com

    카카오톡 챗봇 서버 Python FastAPI 버전 @Github

    728x90

    댓글