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