NCMBでは各種言語向けにSDKを提供しています。公式ではない、コミュニティSDKを入れると10以上あるかと思います。
今回はGo言語でNCMBへのリクエストを送信してみました。問題になるのは署名生成プロセスなので、そこを関数化したことで、今後のSDK化も考えられそうです。
必要なライブラリ
今回利用したライブラリは以下の通りです。
import (
"fmt"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"net/url"
"sort"
"time"
"encoding/json"
"github.com/go-resty/resty/v2"
"github.com/tidwall/gjson"
)
定数の定義
利用した、基本となる定数です。
// 定数の定義
const (
ApplicationKey = "YOUR_APPLICATION_KEY"
SignatureMethod = "HmacSHA256"
SignatureVersion = "2"
FQDN = "mbaas.api.nifcloud.com"
ClientKey = "YOUR_CLIENT_KEY"
)
main関数の流れ
main関数は以下のようになっています。今回はobjectIdやdefinePathは空ですが、汎用的な署名生成時には使える予定です。
func main() {
t := time.Now()
where := map[string]string{
"testKey": "testValue",
}
bytes, _ := json.Marshal(where)
query := map[string]string{
"where": string(bytes),
}
objectId := ""
definePath := ""
path := getPath("TestClass", objectId, definePath)
signature, _ := createSignature("GET", "TestClass", t, query, objectId, definePath)
// URLを作成
partofUrl := &url.URL{
Scheme: "https",
Host: FQDN,
Path: path,
RawQuery: mapToQuery(query),
}
// Restyクライアントの生成
client := resty.New()
// リクエストの作成と送信
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetHeader("X-NCMB-Application-Key", ApplicationKey).
SetHeader("X-NCMB-Signature", signature).
SetHeader("X-NCMB-Timestamp", t.Format("2006-01-02T15:04:05.999Z0700")).
Get(partofUrl.String())
if err != nil {
fmt.Println(err)
}
// レスポンスの解析
value := gjson.Get(resp.String(), "results")
fmt.Println(value)
}
署名生成関数
createSignature
の内容は以下のようになります。処理内容については REST API リファレンス : シグネチャの生成方法 | ニフクラ mobile backend を参照してください。
// 署名を作成する関数
func createSignature(method, className string, time time.Time, queries map[string]string, objectId string, definePath string) (string, error) {
// パスの取得
var path = getPath(className, objectId, definePath)
// baseInfoの定義
baseInfoMap := map[string]string{
"X-NCMB-Application-Key": ApplicationKey,
"SignatureMethod": SignatureMethod,
"SignatureVersion": SignatureVersion,
"X-NCMB-Timestamp": time.Format("2006-01-02T15:04:05.999Z0700"),
}
// クエリが存在する場合は、それをbaseInfoMapに追加
if queries != nil {
for k, v := range queries {
baseInfoMap[k] = url.QueryEscape(v)
}
}
// 自然順序でソート
var keys []string
for k := range baseInfoMap {
keys = append(keys, k)
}
sort.Strings(keys)
// baseInfoの作成
var baseInfo string
for _, k := range keys {
if baseInfo != "" {
baseInfo += "&"
}
baseInfo += k + "=" + baseInfoMap[k]
}
// 署名文字列の作成
signatureString := method + "\n" + FQDN + "\n" + path + "\n" + baseInfo
// HMACエンコーディング
h := hmac.New(sha256.New, []byte(ClientKey))
h.Write([]byte(signatureString))
// Base64エンコーディング
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
その他
その他、用意した関数です。
// パスを取得する関数
func getPath(className string, objectId string, definePath string) string {
if definePath != "" {
return definePath
} else if objectId != "" {
return fmt.Sprintf("/2013-09-01/classes/%s/%s", className, objectId)
} else {
return fmt.Sprintf("/2013-09-01/classes/%s", className)
}
}
// map型のクエリをstring型に変換する関数
func mapToQuery(queries map[string]string) string {
var query string
for k, v := range queries {
if query != "" {
query += "&"
}
query += k + "=" + url.QueryEscape(v)
}
return query
}
コード全体
この流れで、NCMBのデータストアを検索する処理ができます。データ投稿などは未対応ですが、GETをPOSTに変えればいけるでしょう。
package main
import (
"fmt"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"net/url"
"sort"
"time"
"encoding/json"
"github.com/go-resty/resty/v2"
"github.com/tidwall/gjson"
)
// 定数の定義
const (
ApplicationKey = "YOUR_APPLICATION_KEY"
SignatureMethod = "HmacSHA256"
SignatureVersion = "2"
FQDN = "mbaas.api.nifcloud.com"
ClientKey = "YOUR_CLIENT_KEY"
)
// パスを取得する関数
func getPath(className string, objectId string, definePath string) string {
if definePath != "" {
return definePath
} else if objectId != "" {
return fmt.Sprintf("/2013-09-01/classes/%s/%s", className, objectId)
} else {
return fmt.Sprintf("/2013-09-01/classes/%s", className)
}
}
// map型のクエリをstring型に変換する関数
func mapToQuery(queries map[string]string) string {
var query string
for k, v := range queries {
if query != "" {
query += "&"
}
query += k + "=" + url.QueryEscape(v)
}
return query
}
// 署名を作成する関数
func createSignature(method, className string, time time.Time, queries map[string]string, objectId string, definePath string) (string, error) {
// パスの取得
var path = getPath(className, objectId, definePath)
// baseInfoの定義
baseInfoMap := map[string]string{
"X-NCMB-Application-Key": ApplicationKey,
"SignatureMethod": SignatureMethod,
"SignatureVersion": SignatureVersion,
"X-NCMB-Timestamp": time.Format("2006-01-02T15:04:05.999Z0700"),
}
// クエリが存在する場合は、それをbaseInfoMapに追加
if queries != nil {
for k, v := range queries {
baseInfoMap[k] = url.QueryEscape(v)
}
}
// 自然順序でソート
var keys []string
for k := range baseInfoMap {
keys = append(keys, k)
}
sort.Strings(keys)
// baseInfoの作成
var baseInfo string
for _, k := range keys {
if baseInfo != "" {
baseInfo += "&"
}
baseInfo += k + "=" + baseInfoMap[k]
}
// 署名文字列の作成
signatureString := method + "\n" + FQDN + "\n" + path + "\n" + baseInfo
// HMACエンコーディング
h := hmac.New(sha256.New, []byte(ClientKey))
h.Write([]byte(signatureString))
// Base64エンコーディング
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
func main() {
t := time.Now()
where := map[string]string{
"testKey": "testValue",
}
bytes, _ := json.Marshal(where)
query := map[string]string{
"where": string(bytes),
}
objectId := ""
definePath := ""
path := getPath("TestClass", objectId, definePath)
signature, _ := createSignature("GET", "TestClass", t, query, objectId, definePath)
// URLの一部を作成
partofUrl := &url.URL{
Scheme: "https",
Host: FQDN,
Path: path,
RawQuery: mapToQuery(query),
}
// Restyクライアントの生成
client := resty.New()
// リクエストの作成と送信
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetHeader("X-NCMB-Application-Key", ApplicationKey).
SetHeader("X-NCMB-Signature", signature).
SetHeader("X-NCMB-Timestamp", t.Format("2006-01-02T15:04:05.999Z0700")).
Get(partofUrl.String())
if err != nil {
fmt.Println(err)
}
// レスポンスの解析
value := gjson.Get(resp.String(), "results")
fmt.Println(value)
}
まとめ
Go言語はWebサービスであったり、CLIなどでも活用されています。NCMBと連携すれば、データを保存・取得できる場所として活用できるでしょう。
関数レベルではありますが、NCMBとGo言語を組み合わせる際の参考にしてください。