api.go raw
1 // Package doapi : DO APIクライアントモジュール
2 package doapi
3
4 import (
5 "bytes"
6 "crypto/hmac"
7 "crypto/sha1"
8 "crypto/sha256"
9 "crypto/tls"
10 "encoding/base64"
11 "encoding/json"
12 "fmt"
13 "hash"
14 "io"
15 "net/http"
16 "net/url"
17 "sort"
18 "strings"
19 "time"
20
21 log "github.com/sirupsen/logrus"
22 )
23
24 const (
25 HmacSHA1 = "HmacSHA1"
26 HmacSHA256 = "HmacSHA256"
27 SignatureVersion2 = "2"
28 APIVersion = "20140601"
29 EndpointJSON = "https://do.api.iij.jp/"
30 // EndpointJSON = "http://localhost:9999/"
31 TimeLayout = "2006-01-02T15:04:05Z"
32 PostContentType = "application/json"
33 )
34
35 // API の呼び出し先に関連する構造
36 type API struct {
37 AccessKey string
38 SecretKey string
39 Endpoint string
40 SignMethod string
41 Expires time.Duration
42 Insecure bool
43 }
44
45 // NewAPI API構造体のコンストラクタ
46 func NewAPI(accesskey, secretkey string) *API {
47 dur, _ := time.ParseDuration("1h")
48 return &API{AccessKey: accesskey,
49 SecretKey: secretkey,
50 Endpoint: EndpointJSON,
51 SignMethod: HmacSHA256,
52 Expires: dur,
53 }
54 }
55
56 func convert1(r byte) string {
57 passchar := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~.-"
58 if strings.ContainsRune(passchar, rune(r)) {
59 return string(r)
60 }
61 return fmt.Sprintf("%%%02X", r)
62 }
63
64 // CustomEscape escape string
65 func CustomEscape(v string) string {
66 res := ""
67 for _, c := range []byte(v) {
68 res += convert1(c)
69 }
70 return res
71 }
72
73 // String2Sign get string to calculate signature
74 func String2Sign(method string, header http.Header, param url.URL) string {
75 var keys []string
76 ctflag := false
77 for k := range header {
78 hdr := strings.ToLower(k)
79 if strings.HasPrefix(hdr, "x-iijapi-") {
80 keys = append(keys, hdr)
81 } else if hdr == "content-type" || hdr == "content-md5" {
82 keys = append(keys, hdr)
83 ctflag = true
84 }
85 }
86 sort.Strings(keys)
87 var target []string
88 target = append(target, method)
89 target = append(target, "")
90 if !ctflag {
91 target = append(target, "")
92 }
93 for _, k := range keys {
94 if k == "content-type" || k == "content-md5" {
95 target = append(target, header.Get(k))
96 } else {
97 target = append(target, k+":"+header.Get(k))
98 }
99 }
100 target = append(target, param.Path)
101 return strings.Join(target, "\n")
102 }
103
104 // Sign get signature string
105 func (a API) Sign(method string, header http.Header, param url.URL, signmethod string) http.Header {
106 header.Set("x-iijapi-Expire", time.Now().Add(a.Expires).UTC().Format(TimeLayout))
107 header.Set("x-iijapi-SignatureMethod", signmethod)
108 header.Set("x-iijapi-SignatureVersion", SignatureVersion2)
109 tgtstr := String2Sign(method, header, param)
110 var hfn func() hash.Hash
111 switch signmethod {
112 case HmacSHA1:
113 hfn = sha1.New
114 case HmacSHA256:
115 hfn = sha256.New
116 }
117 mac := hmac.New(hfn, []byte(a.SecretKey))
118 io.WriteString(mac, tgtstr)
119 macstr := mac.Sum(nil)
120 header.Set("Authorization", "IIJAPI "+a.AccessKey+":"+base64.StdEncoding.EncodeToString(macstr))
121 return header
122 }
123
124 // Get : low-level Get
125 func (a API) Get(param url.URL) (resp *http.Response, err error) {
126 return a.PostSome("GET", param, nil)
127 }
128
129 // PostSome : low-level Call
130 func (a API) PostSome(method string, param url.URL, body interface{}) (resp *http.Response, err error) {
131 cl := http.Client{}
132 if a.Insecure {
133 tr := &http.Transport{
134 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
135 }
136 cl.Transport = tr
137 }
138 log.Debug("param", param)
139 var buf *bytes.Buffer
140 if body != nil {
141 var bufb []byte
142 bufb, err = json.Marshal(body)
143 if err != nil {
144 return nil, err
145 }
146 if len(bufb) > 2 {
147 log.Debug("call with body", method, string(bufb))
148 buf = bytes.NewBuffer(bufb)
149 } else {
150 // string(bufb)=="{}"
151 log.Debug("call without body(empty)", method)
152 buf = bytes.NewBufferString("")
153 body = nil
154 }
155 } else {
156 log.Debug("call without body(nil)", method)
157 buf = bytes.NewBufferString("")
158 }
159 req, err := http.NewRequest(method, param.String(), buf)
160 if err != nil {
161 return nil, err
162 }
163 if body != nil {
164 req.Header.Add("content-type", PostContentType)
165 }
166 req.Header = a.Sign(method, req.Header, param, HmacSHA256)
167 log.Debug("sign", req.Header)
168 resp, err = cl.Do(req)
169 return
170 }
171