Go 애플리케이션 개발 가이드

1. 개요

1.1. 문서 개요

1.1.1. 목적

본 문서(Go 애플리케이션 개발 가이드)는 Open PaaS 프로젝트의 서비스팩(Mysql, MongoDB, RabbitMQ, Radis, GlusterFS)을 Go 애플리케이션과 연동하여 서비스를 사용하고 애플리케이션을 배포하는 방법에 대해 제시하는 문서이다.

1.1.2. 범위

본 문서의 범위는 Open PaaS 프로젝트의 Go 애플리케이션 개발과 서비스팩 연동에 대한 내용으로 한정되어 있다.

1.1.3. 참고 자료

2. Go 애플리케이션 개발가이드

2.1. 개요

Open PaaS에 등록된 다양한 서비스팩을 Go언어로 작성된 애플리케이션과 바인딩하고 해당 애플리케이션에 바인딩된 환경정보(VCAP_SERVICES)에서 각 서비스별 접속정보를 획득하여 애플리케이션에 적용하여 이용 할 수 있도록 Windows 환경에서 Go 애플리케이션을 작성 할 수 있도록한다.

2.2. 개발환경 구성

Go 애플리케이션 개발을 위해 다음과 같은 환경으로 개발환경을 구성 한다.
    OS : Windows 7 64bit
    Go : 1.5.2
    IDE : Intellij IDEA
    Intellij IDEA 는 Commnuity와 Ultimate 버전이 있는데, Community 버전은 Free이고, Ultimate 버전은 은 30-day trial버전이다.

2.2.1. Go SDK설치

    1.
    Go SDK 다운로드
    다운로드
Go SDK : go1.5.2.windows-amd64.msi
    1.
    Go SDK 설치
    2.
    go1.5.2.windows-amd64.msi 더블클릭하여 설치를 실행한다.
    “실행” 버튼 클릭
    “Next” 버튼을 클릭
    “Next” 버튼을 클릭
    Go SDK가 설치될 디렉토리 선택 후 “Next” 버튼 클릭
    “Install” 버튼 클릭
    “Finish” 버튼 클릭

2.2.2. IntelliJ IDEA설치

    1.
    IDEA 다운로드
    다운로드 : ideaIC-15.0.2.exe
    IntelliJ IDEA 설치
    ideaIC-15.0.2.exe 더블클릭하여 설치를 실행한다.
    “Next” 버튼 클릭
    설치위치 지정 후 “Next” 버튼 클릭
    “Next” 버튼 클릭
    “Install” 버튼 클릭
    “Run IntelliJ IDEA Community Edition” 체크 선택
    “Finish” 버튼 클릭

2.3. 개발

샘플 애플리케이션에의 데이터 관리는 MySQL, MongoDB 중에 하나를 이용하기 때문에 API 요청시 요청 본문에 DBType 값을 가지고 결정한다.

2.3.1. 샘플 애플리케이션 연동

    1.
    Go 샘플 프로젝트 연결
    “Import Project” 선택
    다운로드 받은 Go 샘플 어플리케이션의 위치를 지정
    “Ok” 버튼을 클릭
    “Next” 버튼 클릭 - “Next” 버튼 클릭
    “Next” 버튼 클릭
    “Finish” 버튼 클릭

2.3.2. 샘플 애플리케이션 환경설정

    IntelliJ IDEA 환경에 Go Plugin 설치 및 Go 샘플 어플리케이션의 환결설정
    File > Settings 선택
    Plugins > Browse repositories 선택
    “Go” 를 검색 후, 조회된 결과에서 Go 를 선택 후 Install 버튼을 클릭
    “Restart IntelliJ IDEA” 선택
    “Restart” 버튼 클릭
    File > Project Structure... 선택
    Project SDK 영역의 “New” 버튼 클릭
    “Go SDK” 선택
    “Go SDK” 설치된 디렉토리 선택 후 “OK” 버튼 클릭
    “OK” 선택
    go-sample-app 프로젝트에서 “main.go” 파일을 선택
    오른쪽 상단에 “Configure Go Libraries” 선택
    Global libraries 영역에는 Go SDK 디렉토리 선택
    Project libraries 영역에는 Go 샘플 어플리케이션이 위치한 디렉토리 선택
    “OK” 버튼 클릭
    오른쪽 상단의 “Change module type to Go and reload project” 선택
    “Reload project” 선택

2.3.3. VCAP_SERVICES 환경설정 정보를 통한 연동

개방형 플랫폼에 배포되는 애플리케이션이 바인딩된 서비스별 접속 정보를 얻기 위해서는 애플리케이션별로 등록되어있는 VCAP_SERVICES 환경설정 정보를 읽어들여 정보를 획득 할 수 있다.
1). 개방형 플랫폼의 애플리케이션 환경정보
    서비스를 바인딩하면 JSON 형태로 환경설정 정보가 애플리케이션 별로 등록된다.
1
{
2
"VCAP_SERVICES": {
3
"p-rabbitmq": [
4
{
5
"credentials": {
6
"dashboard_url": "https://pivotal-rabbitmq.10.244.0.34.xip.io/#/login/f8b12fcd-df98-4745-a6b2-61f01d20fe24/5lpn72rufsivfgnf3ft4l1p797",
7
"hostname": "10.244.9.50",
8
"hostnames": [
9
"10.244.9.50"
10
],
11
12
…..(중간 생략)…..
13
"ssl": true,
14
"uri": "amqps://f8b12fcd-df98-4745-a6b2-61f01d20fe24:[email protected]/a1aec425-d1ec-40b7-865f-4eaba371b9a2",
15
"uris": [
16
"amqps://f8b12fcd-df98-4745-a6b2-61f01d20fe24:[email protected]/a1aec425-d1ec-40b7-865f-4eaba371b9a2"
17
],
18
"username": "f8b12fcd-df98-4745-a6b2-61f01d20fe24",
19
"vhost": "a1aec425-d1ec-40b7-865f-4eaba371b9a2"
20
},
21
"label": "p-rabbitmq",
22
"name": "rabbitmq-service-instance",
23
"plan": "standard",
24
"tags": [
25
"rabbitmq",
26
"messaging",
27
"message-queue",
28
"amqp",
29
"stomp",
30
"mqtt",
31
"pivotal"
32
]
33
}
34
]
35
…..(이하 생략)…..
Copied!
    VCAP_SERVICES 정보 구조
2). VCAP_SERVICES 정보 추출 방법
    VCAP_SERVICE 정보 중 “uri” 정보를 추출하여 리턴한다.
    부모 엘리먼트 (VCAP_SERVICE)
      첫번째 엘리먼트 (p-rabbitmq) 서비스 이름 정보
      두번째 엘리먼트 (credentials) 정보
      세번째 엘리먼트 (uri) 정보
1
func sample_function_name() string {
2
3
args := os.Getenv("VCAP_SERVICES") //VCAP_SERVICES 엘리먼트 정보 (부모 엘리먼트)
4
var amqp_uri string
5
if args != "" {
6
var vcap_env map[string]interface{}
7
if err := json.Unmarshal([]byte(args), &vcap_env); err != nil {
8
log.Panic(err.Error())
9
}
10
11
//Service instance (not name) - for example : p-mysql - type check !!! []interface{}
12
if vcap_env["p-rabbitmq"] != nil { //첫번째 엘리먼트(서비스 이름) 정보
13
sub_env := vcap_env["p-rabbitmq"].([]interface{})
14
credentials := (sub_env[0].(map[string]interface{}))["credentials"].(map[string]interface{})
15
//두번째 엘리먼트 “credentials” 정보
16
amqp_uri = credentials["uri"].(string) //세번째 엘리먼트 “uri”정보
17
}
18
}
19
return amqp_uri
20
}
Copied!
    VCAP_SERVICES 구조 참조

2.3.4. config.ini 파일을 통한 Database, Redis, RabbitMQ 연동

1). config.ini
    mysql 연결정보 설정
1
#Go Sample Web Server port
2
server.port = 8080
3
#Mysql DB Info
4
mysql.dburl=”mysql\_server\_ip”:”mysql\_server\_port”/”database\_name”
5
mysql.userid=”user\_id”
6
mysql.userpwd=”user\_password”
7
mysql.maxconn=”max\_connection\_number”
8
9
MongoB Info
10
mongodb.dburl=”mongodb\_server\_ip”: ”mongodb\_server\_port”/”collection\_name”
11
mongodb.userid=”user\_id”
12
mongodb.userpwd=”user\_password”
13
14
# Redis Info
15
redis.addr=”redis\_server\_ip”: ”redis\_server\_port”
16
17
# RabbitMQ Info
18
rabbitmq.user=”rabbitmq\_id”
19
rabbitmq.pass=”rabbitmq\_password”
20
rabbitmq.addr=”rabbitmq\_server\_ip”: ”rabbitmq\_server\_port”
Copied!
2). 연동 샘플
1
import (
2
"bufio"
3
"fmt"
4
"io"
5
"log"
6
"os"
7
"strconv"
8
"strings"
9
10
"net/http"
11
12
"encoding/json"
13
"org/openpaas/sample/Godeps/_workspace/src/gopkg.in/redis.v3"
14
15
"org/openpaas/sample/datasource"
16
"org/openpaas/sample/handler"
17
"org/openpaas/sample/message"
18
)
19
20
type Config map[string]string
21
22
func main() {
23
// Datasource - MySql, Cubrid, MongoDB 타입별 처리
24
var dbconfig *datasource.DBConfig
25
var mgodbconfig *datasource.MgoDBConfig
26
var handlers http.Handler
27
28
fmt.Println("##### Go Sample Application start!!!")
29
//============================================
30
// dbtype 정보는 시스템 프로퍼티에서 가져온다.
31
dbtype := os.Getenv("dbtype")
32
//============================================
33
34
//============================================
35
// Sample VCAP_SERVICE INFO Parsing
36
os_args := os.Getenv("VCAP_SERVICES")
37
readOSEnvironment(os_args)
38
//============================================
39
40
//============================================
41
// 기본적인 프로퍼티 설정 정보 읽어오기
42
config, err := ReadConfig(`config.ini`)
43
if err != nil {
44
fmt.Println(err)
45
}
46
//============================================
47
48
// Default Redis Client Create - For Login & Logout
49
redis_client := initRedis(config["redis.addr"])
50
defer redis_client.Close()
51
52
// default dbytype = "mysql"
53
if dbtype == "" {
54
dbtype = "mysql"
55
}
56
57
// RabbitMQ
58
//endpoint := "amqps://929b5a98-6521-4da5-86c1-0fcaa2450a86:[email protected]:5671/3c14abdc-c922-428c-af70-d61eb91ef818"
59
endpoint := readOSEnvironment_mq()
60
fmt.Println("########## credentials-uri:", endpoint)
61
mq := message.NewRabbitMQ(endpoint)
62
if mq != nil {
63
defer message.CloseMQ(mq)
64
}
65
66
// DB Initialize -
67
// DB 타입별로 Connection 처리
68
if dbtype == "mysql" {
69
maxConnection, err := strconv.Atoi(config["mysql.maxconn"])
70
if err != nil {
71
log.Println(err)
72
os.Exit(-1)
73
}
74
75
dbconfig = datasource.NewDBConfig(config["mysql.userid"], config["mysql.userpwd"], config["mysql.dburl"], maxConnection)
76
defer dbconfig.CloseDb()
77
78
fmt.Println("dbmap:", dbconfig.DBMAP)
79
if dbconfig.DBMAP == nil {
80
log.Panic("Couldn't create Database Connection properly - MySQL!!!")
81
os.Exit(-1)
82
}
83
84
// Route Path 정보와 처리 서비스 연결
85
handlers = handler.NewHandler(dbconfig.DBMAP, redis_client, mq.Ch)
86
87
} else if dbtype == "mongodb" {
88
mgodbconfig = datasource.NewMgoDBConfig(config["mongodb.userid"], config["mongodb.userpwd"], config["mongodb.dburl"])
89
defer mgodbconfig.CloseDb()
90
91
fmt.Println("session:", mgodbconfig.SESSION)
92
if mgodbconfig.SESSION == nil {
93
log.Panic("Couldn't create Database Connection properly - MongoDB!!!")
94
os.Exit(-1)
95
}
96
97
// Route Path 정보와 처리 서비스 연결
98
handlers = handler.NewMgoHandler(mgodbconfig.SESSION, redis_client, mq.Ch)
99
100
} else {
101
fmt.Println("No database type found.")
102
os.Exit(-1)
103
}
104
105
if err := http.ListenAndServe(fmt.Sprintf(":%v", config["server.port"]), handlers); err != nil {
106
log.Fatalln(err)
107
}
108
}
109
110
/*
111
Read Config file
112
*/
113
func ReadConfig(filename string) (Config, error) {
114
// init with some bogus data
115
config := Config{
116
"server.ip": "127.0.0.1",
117
"server.port": "8080",
118
"mysql.dburl": "",
119
"mysql.userid": "",
120
"mysql.userpwd": "",
121
"mysql.maxconn": "",
122
}
123
124
if len(filename) == 0 {
125
return config, nil
126
}
127
file, err := os.Open(filename)
128
if err != nil {
129
return nil, err
130
}
131
defer file.Close()
132
133
reader := bufio.NewReader(file)
134
for {
135
line, err := reader.ReadString('\n')
136
// check if the line has = sign
137
// and process the line. Ignore the rest.
138
if equal := strings.Index(line, "="); equal >= 0 {
139
if key := strings.TrimSpace(line[:equal]); len(key) > 0 {
140
value := ""
141
if len(line) > equal {
142
value = strings.TrimSpace(line[equal+1:])
143
}
144
// assign the config map
145
config[key] = value
146
}
147
}
148
if err == io.EOF {
149
break
150
}
151
if err != nil {
152
return nil, err
153
}
154
}
155
return config, nil
156
}
157
158
159
func initRedis(addr string) *redis.Client {
160
client := redis.NewClient(&redis.Options{
161
Addr: addr,
162
})
163
return client
164
}
Copied!

2.3.5. GlusterFS 연동

1). 파일 Upload
    클라이언트에서 보낸 요청에서 파일을 읽어들여 GlusterFS Server로 전송
1
func (h *GlusterFSService) Upload(w http.ResponseWriter, r *http.Request) {
2
if err := r.ParseMultipartForm(MAX_MEMORY); err != nil {
3
log.Println(err)
4
http.Error(w, err.Error(), http.StatusForbidden)
5
}
6
index := 0
7
path := make([]string, len(r.MultipartForm.File))
8
for _, fileHeaders := range r.MultipartForm.File {
9
for _, fileHeader := range fileHeaders {
10
file, _ := fileHeader.Open()
11
buf, _ := ioutil.ReadAll(file)
12
thumb_img_path, err := ObjectPut(buf, fileHeader.Filename)
13
path[index] = "{\"thumb_img_path\":\"" + thumb_img_path + "\"}"
14
if err != nil {
15
log.Fatalln("!!! ObjectPut error:", err)
16
}
17
}
18
index++
19
}
20
js, err := json.Marshal(path)
21
if err != nil {
22
log.Fatalln("Error writing JSON:", err)
23
}
24
w.Header().Set("Content-Type", "application/json")
25
w.WriteHeader(http.StatusOK)
26
w.Write([]byte(js))
27
return
28
}
Copied!
2). GlusterFS Server 연결 및 파일전송
    GlusterFS Server로 연결을 맺고 파일을 전송
1
func ObjectPut(buf []byte, fileName string) (string, error) {
2
userName, password, authUrl, tenantName := readOSEnvironment_gf()
3
c := swift.Connection{
4
UserName: userName,
5
ApiKey: password,
6
AuthUrl: authUrl,
7
Tenant: tenantName,
8
}
9
// Authenticate
10
err := c.Authenticate()
11
if err != nil {
12
panic(err)
13
}
14
// Container
15
_, _, err = c.Container(CONTAINER_NAME)
16
if err != nil {
17
var meta = swift.Headers{"X-Container-Read": ".r:*"}
18
err = c.ContainerCreate(CONTAINER_NAME, meta)
19
if err != nil {
20
panic(err)
21
}
22
}
23
// Put object
24
headers := swift.Headers{}
25
headers["Content-Length"] = strconv.FormatInt(int64(len(buf)), 10)
26
err = c.ObjectPutBytes(CONTAINER_NAME, fileName, buf, "application/octet-stream")
27
if err != nil {
28
log.Fatalln("!!! ObjectPut error:", err)
29
}
30
// Fetch object info and compare
31
info, _, err := c.Object(CONTAINER_NAME, fileName)
32
if err != nil {
33
log.Println(err)
34
}
35
if info.Bytes != int64(len(buf)) {
36
log.Println("Bad length")
37
}
38
thumb_img_path := c.StorageUrl + "/" + CONTAINER_NAME + "/" + fileName
39
return thumb_img_path, err
40
}
Copied!
3). GlusterFS 연결정보
    VCAP_SERVICES에서 정보 추출
1
func readOSEnvironment_gf() (string, string, string, string) {
2
args := os.Getenv("VCAP_SERVICES")
3
var userName, password, authUrl, tenantName string
4
if args != "" {
5
var vcap_env map[string]interface{}
6
if err := json.Unmarshal([]byte(args), &vcap_env); err != nil {
7
log.Panic(err.Error())
8
}
9
//Service instance (not name) - for example : p-mysql - type check !!! []interface{}
10
if vcap_env["glusterfs"] != nil {
11
sub_env := vcap_env["glusterfs"].([]interface{})
12
credentials := (sub_env[0].(map[string]interface{}))["credentials"].(map[string]interface{})
13
userName = credentials["username"].(string)
14
password = credentials["password"].(string)
15
authUrl = credentials["auth_url"].(string)
16
tenantName = credentials["tenantname"].(string)
17
}
18
}
19
return userName, password, authUrl, tenantName
20
}
Copied!

2.4. 배포

    cf cli 명령어를 이용하여 Go 샘플 어플리케이션을 배포한다.
    cf cli가 설치되어 있고, cf login이 이미 되어 있다는 가정하에 진행한다.
1
### Go 샘플 어플리케이션 배포
2
$ cd “Go 샘플 어플리케이션” 디렉토리
3
$ source .envrc # GO_PATH를 지정하기 위해
4
5
$ cd src/org/openpaas/sample
6
7
$ cf push –f manifest.yml # Go 샘플 어플리케이션 배포
8
Using manifest file manifest.yml
9
Creating app go-sample in org org / space space as admin...
10
OK
11
Using route go-sample.os.paasxpert.com
12
Binding go-sample.os.paasxpert.com to go-sample...
13
OK
14
Uploading go-sample...
15
Uploading app files from: /home/ihocho/git/OpenPaaSSample/GoSample/go-sample-app/src/org/openpaas/sample
16
Uploading 854.2K, 185 files
17
Done uploading
18
OK
19
Binding service rabbitmq-service-instance to app go-sample in org org / space space as admin...
20
OK
21
Starting app go-sample in org org / space space as admin...
22
Creating container
23
Successfully created container
24
Downloading app package...
25
Downloaded app package (321.1K)
26
Downloading buildpacks (https://github.com/cloudfoundry/go-buildpack.git)...
27
Downloaded buildpacks
28
Staging...
29
-------> Buildpack version 1.7.0
30
https://pivotal-buildpacks.s3.amazonaws.com/concourse-binaries/godep/godep-v17-linux-x64.tgz
31
-----> Checking Godeps/Godeps.json file.
32
-----> Installing go1.4.2... done
33
Downloaded [https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz]
34
-----> Running: godep go install -tags cloudfoundry ./...
35
Exit status 0
36
Staging complete
37
Uploading droplet, build artifacts cache...
38
Uploading droplet...
39
Uploading build artifacts cache...
40
Uploaded build artifacts cache (60M)
41
Uploaded droplet (2.8M)
42
Uploading complete
43
1 of 1 instances running
44
App started
45
OK
46
App go-sample was started using this command `sample`
47
Showing health and status for app go-sample in org org / space space as admin...
48
OK
49
requested state: started
50
instances: 1/1
51
usage: 256M x 1 instances
52
urls: go-sample.os.paasxpert.com
53
last uploaded: Mon Dec 14 04:39:52 UTC 2015
54
stack: cflinuxfs2
55
buildpack: https://github.com/cloudfoundry/go-buildpack.git
56
state since cpu memory disk details
57
#0 running 2015-12-14 01:41:20 PM 0.0% 2.1M of 256M 0 of 1G
Copied!
Go 샘플 애플리케이션에의 데이터 관리는 MySQL, MongoDB 중에 하나를 이용하기 때문에 배포시 환경변수 정보(dbtype)에 데이터베이스 이름(mysql or mongodb)를 설정하여 배포한다..

2.5. 테스트

curl 명령어를 통해 command 창에서 직접 테스트 할 수 있다.
1
### 모든 조직정보 조회
2
curl -v http://”depolyed_sample_app_name”/orgs
3
4
### 특정 조직정보 조회
5
curl -v http:// ”depolyed_sample_app_name”/orgs/{org_id}
6
7
### 조직 생성
8
curl -X POST -v
9
–d '{"id":”org_id”,"label":"org_lable_info","desc":"org_description_info","url":"org_uri_info"}'
10
http:// ”depolyed_sample_app_name”/orgs
11
12
### 조직 수정
13
curl -X PUT -v
14
-d '{"label":" org_lable_info ","desc":"org_description_info","url":"org_url_info"}'
15
http:// ”depolyed_sample_app_name”/orgs/{org_id}
16
17
### 조직 삭제
18
curl -X DELETE -v http:// ”depolyed_sample_app_name”/orgs/{org_id}
Copied!
Last modified 1yr ago