20200624のGoに関する記事は8件です。

Goでint型のスライスを昇順と降順でソートする方法

はじめに

intのスライスをソートする方法が分からなかったので調べてみました。
今回使用するスライスは、こちら

s := []int{4, -1, 12, -26, 5}

昇順(小さいから大きい)でソート

sort.Ints(s)
fmt.Println(s)
// [-26 -1 4 5 12]

降順(大きいから小さい)でソート

sort.Sort(sort.Reverse(sort.IntSlice(s)))
fmt.Println(s)
//[12 5 4 -1 -26]

おまけ

スライスの中から一番大きい数字や小さい数字を見つけるコードも書いてみます。

一番大きい数字を出力

sort.Ints(s)
//合計の要素数から-1(5-1=4番目の数字)
fmt.Println(s[len(s)-1]) 
//12

一番小さい数字を出力

sort.Ints(s)
//スライスの0番目
fmt.Println(s[0])
//-26

さいごに

調べてみると、比較演算子を使うやり方も出てきましたが、個人的に <>を使うのが苦手なので(こんがらがる)、パッケージを使った方法を使っていきたいと思います。

参考にした記事など

sort - The Go Programming Language
配列 - ソートしたスライスの中身の数値だけ([]なし)で取り出す方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでintのスライスを昇順と降順でソートする方法

はじめに

intのスライスをソートする方法が分からなかったので調べてみました。
今回使用するスライスは、こちら

s := []int{4, -1, 12, -26, 5}

昇順(小さいから大きい)でソート

sort.Ints(s)
fmt.Println(s)
// [-26 -1 4 5 12]

降順(大きいから小さい)でソート

sort.Sort(sort.Reverse(sort.IntSlice(s)))
fmt.Println(s)
//[12 5 4 -1 -26]

おまけ

スライスの中から一番大きい数字や小さい数字を見つけるコードも書いてみます。

一番大きい数字を出力

sort.Ints(s)
//合計の要素数から-1(5-1=4番目の数字)
fmt.Println(s[len(s)-1]) 
//12

一番小さい数字を出力

sort.Ints(s)
//スライスの0番目
fmt.Println(s[0])
//-26

さいごに

調べてみると、比較演算子を使うやり方も出てきましたが、個人的に <>を使うのが苦手なので(こんがらがる)、パッケージを使った方法を使っていきたいと思います。

参考にした記事など

sort - The Go Programming Language
配列 - ソートしたスライスの中身の数値だけ([]なし)で取り出す方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hyperledger FabricのKeyゾンビ化を防ぐ(ソースコード掲載)

Hyperledger FabricでState DBのKeyを再利用させない(実はできない)

Hyperledger Fabric(以下HF)で一度削除(DelState)したKeyで再登録(PutState)したらどうなるんだろう?という疑問から、次のようなシナリオを考えました。またミラ・キータ(Mira Qiita)に登場してもらいます。

  1. Mira QiitaをKey="JMYMIRAGINO200302"で登録(PutState)
  2. 1のKeyでクエリ(GetState)
  3. 1のKeyで削除(DelState)
  4. 1のKeyでクエリ(GetState)
  5. 1のKeyで登録(PutState)
  6. 1のKeyで履歴を参照(GetHistoryForKey)

どうなるんだろう?

あと、この投稿にてGoで書いたソースコードやシェルスクリプトを掲載します。

環境について

動作環境については次の通りです。

  • Ubuntu 18.04.4 LTS
  • docker-compose 1.26.0
  • docker 19.03.11
  • HF 2.1.1
  • go 1.14.4
  • PostgreSQL(Dockerイメージ) 12.3(latest)

実際にやってみる

# ./CreateAsset.sh <----- 初期登録
2020-06-24 17:50:15.434 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- 一度クエリ
{"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"}
# ./DeleteAsset.sh <----- 削除
2020-06-24 17:53:11.100 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- 削除後のクエリ
Error: endorsement failure during query. response: status:500 message:"JMYMIRAGINO200302 does not exist"
# ./CreateAsset.sh <----- 同じKeyで登録
2020-06-24 17:53:26.301 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- またクエリ
{"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"}
#

Oh...
削除後のクエリではdoes not existが返ってきますが、削除したKeyで再登録ができてクエリもできちゃいました。履歴はどうなっているのでしょう?
見てみましょう!

# ./GetHistoryOfAsset.sh | jq
[
  {
    "TxId": "fed9df47d31bb4c6cec4be6c9bf154f8655f13d994eebfd709e564e36e011945",
    "Timestamp": "2020-06-24 08:53:26 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  },
  {
    "TxId": "7d2853ede65564e082ab81c56e7a12331493c841993caea4ad88261706f6da9c",
    "Timestamp": "2020-06-24 08:53:11 +0000 UTC",
    "IsDelete": true,
    "Record": {
      "year": "",
      "month": "",
      "mileage": 0,
      "battery": 0,
      "Location": ""
    }
  },
  {
    "TxId": "854ce2f67ecb11aba6ae103841da9cbf9ddb17ed412df661a8d76abc4b39a35c",
    "Timestamp": "2020-06-24 08:50:15 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  }
]
#

Oh...
履歴は下から読んでください。
廃車(DelState)前からの履歴が残って出力されています。
Keyやidの桁数が足りなくて、削除後に一定期間が過ぎると再利用というのはモノによってはあり(携帯電話の番号がそうでしたね)ですが、ブロックチェーンでは思想的になしだと思います。

対策を探してみる(なかった…)

残念ながらHFのAPIで解決できるものではなさそうです。調べた中でベストプラクティスはDelStateを使わないです。
同じ疑問を持った人がstackoverflowで質問していて、その回答が意外なものでした。
つまり、レコードを削除しないで「削除したフラグ」をレコードに持つ、ということですね。ちょっとだけ目からウロコ。HFがバージョンアップしたらできるようになるかもです。
もしかしたら調査不足かもしれません。偉い人教えて下さい:-)

ここまでのソースコード

先ずはHFのサンプルコードをインストールします。こちらにインストール方法が書いてあります。現時点でv2.1.1は最新版なのでcurl -sSL https://bit.ly/2ysbOFE | bash -sでインストールできます。
ディレクトリfabric-samples/に任意な名前(今回はassetで作っています)のディレクトリを作って、そこを作業場所にします。そこへfabcar/からnetworkDown.shstartFabric.shをコピーしてください。その他の環境については以前の投稿に書いてあります。
- startFabric.sh: docker-composeを使って必要なサービスを起動してくれます
- networkDown.sh: 全てを無にしてくれます

シェルスクリプトについて

invoke系の機能とquery系の機能で呼び出し方が違います。CreateAsset.shCreateAsset.shがあれば、他の機能を呼び出すシェルもコピペで作れます。function名と引数の違いだけです。

  • invoke系代表CreateAsset.sh
CreateAsset.sh
#!/bin/bash

pushd ../test-network > /dev/null

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

key='"JMYMIRAGINO200302"'
year='"2003"'
month='"02"'
mileage='"43871"'
battery='"100"'
location='"QIITA東京販売"'
args=${key},${year},${month},${mileage},${battery},${location}

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":['${args}']}'

popd > /dev/null
  • query系代表QueryAsset.sh
QueryAsset.sh
#!/bin/bash

pushd ../test-network > /dev/null

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CHANNEL_NAME=mychannel

key='"JMYMIRAGINO20030Z"'
peer chaincode query -C $CHANNEL_NAME -n asset -c '{"Args":["QueryAsset",'${key}']}'

popd > /dev/null

肝は必ずカレントディレクトリをtest-network/にすることです。pushdで移動してpopdで戻っています。

goソースコードについて

もともとはHF1.4の時に書いたコードでHF2.1.1にあわせて書き直しました。祖先はHF1.4のfabcar.goです。以下に掲載します。長いです。。
DBの初期化はInitLedgerからInitDBに移動しています。chaincodeのバージョンアップで毎回DBの初期化が走るのも嫌なので。。
UpdateAssetResetAssetが似ているようで機能を分けています。UpdateAssetには資産価値が上がるような変更にガードを入れています。ResetAssetは「工場でバッテリーを新品に交換する」みたいなシナリオを想定したものです。

コード中にコメントがなくてスミマセン:-(
個々でやってることは大したことないです。基本的には受け取った引数をHFのAPIへスルーパスしてるだけです。

メソッド一覧です。引数は全て文字列で渡します。

メソッド 引数 機能
InitLedger なし HFの初期化
InitDB なし State DBの初期登録。初期データはPostgre SQLから読み込んでいます
QueryAsset key keyからState DBの内容を検索・出力します
QueryAssetWithOwner key QueryAssetの出力に加え、Postgre SQLからの個人情報も検索・出力します
CreateAsset key, year, month, mileage, battery, location 新しいassetを作成・登録します
UpdateAsset key, mileage, battery, location assetの内容を更新します。値によっては更新できません
ResetAsset key, mileage, battery, location assetの内容を入力の値で強制的に更新します
QueryAllAssets なし State DB内の全レコードを検索します
QueryRangeAssets pagesize, key keyから始まるレコードをpagesize数分検索・出力します
DeleteAsset key keyのレコードを削除します。履歴は残ります
GetHistoryOfAsset key keyの取引履歴を検索・出力します
asset.go
package main

import (
        "encoding/json"
        "fmt"
        "strconv"
        "time"

        "github.com/hyperledger/fabric-contract-api-go/contractapi"
        "database/sql"
        _ "github.com/lib/pq"
)

// Define the Smart Contract structure
type SmartContract struct {
        contractapi.Contract
}

// データ構造の定義
type Asset struct {
        Year     string `json:"year"`     // 初度登録年
        Month    string `json:"month"`    // 初度登録月
        Mileage  int    `json:"mileage"`  // 走行距離(km)
        Battery  int    `json:"battery"`  // バッテリーライフ(%)
        Location string `jasn:"location"` // 位置
}
type AssetWithOwner struct {
        Name    string  // 名前
        Country string  // 国
        City    string  // 都道府県
        Addr    string  // 市区町村
        Record  *Asset
}
// クエリ結果(レーコード検索)
type QueryResult struct {
        Key     string          // レコードID(VINコード)
        Record  *Asset
}
// クエリ結果(履歴検索)
type GetHisResult struct {
        TxId      string        // トランザクションID
        Timestamp string        // タイムスタンプ
        IsDelete  bool          // 削除(廃車)フラグ
        Record    *Asset
}

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
        fmt.Println("InitLedger")

        return nil
}

func (s *SmartContract) InitDB(ctx contractapi.TransactionContextInterface) error {
        fmt.Println("InitDB")

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()

        if err != nil {
                return fmt.Errorf("sql.Open: %s", err.Error())
        }

        rows, err := db.Query("SELECT * FROM asset;")

        if err != nil {
                return fmt.Errorf("sql.Query: %s", err.Error())
        }

        var id string
        for rows.Next() {
                var asset Asset
                rows.Scan(&id, &asset.Year, &asset.Month, &asset.Mileage, &asset.Battery, &asset.Location)
                assetAsBytes, _ := json.Marshal(asset)
                ctx.GetStub().PutState(id, assetAsBytes)
        }

        return nil
}

func (s *SmartContract) QueryAsset(ctx contractapi.TransactionContextInterface, key string) (*Asset, error) {
        fmt.Println("QueryAsset")

        assetAsBytes, err := ctx.GetStub().GetState(key)

        if err != nil {
                return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
        }

        if assetAsBytes == nil {
                return nil, fmt.Errorf("%s does not exist", key)
        }

        asset := new(Asset)
        _ = json.Unmarshal(assetAsBytes, asset)

        return asset, nil
}

func (s *SmartContract) QueryAssetWithOwner(ctx contractapi.TransactionContextInterface, key string) (*AssetWithOwner, error) {
        fmt.Println("QueryAssetWithOwner")

        assetAsBytes, err := ctx.GetStub().GetState(key)

        if err != nil {
                return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
        }

        if assetAsBytes == nil {
                return nil, fmt.Errorf("%s does not exist", key)
        }

        asset := new(Asset)
        _ = json.Unmarshal(assetAsBytes, asset)

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()
        if err != nil {
                return nil, fmt.Errorf("sql.Open: %s", err.Error())
        }

        sql := "SELECT * FROM owner WHERE id = '" + key + "';"
        rows, err := db.Query(sql)
        if err != nil {
                return nil, fmt.Errorf("db.Query: %s", err.Error())
        }

        var id string
        awo := new(AssetWithOwner)
        for rows.Next() {
                rows.Scan(&id, &awo.Name, &awo.Country, &awo.City, &awo.Addr)
                awo.Record = asset
        }

        return awo, nil
}

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, key string, year string, month string, sMileage string, sBattery string, location string) error {
        fmt.Println("CreateAsset")

        mileage, _ :=strconv.Atoi(sMileage)
        battery, _ :=strconv.Atoi(sBattery)
        asset := Asset{
                Year:     year,
                Month:    month,
                Mileage:  mileage,
                Battery:  battery,
                Location: location,
        }
        assetAsBytes, _ := json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error {
        fmt.Println("UpdateAsset")

        assetAsBytes, _ := ctx.GetStub().GetState(key)
        asset := new(Asset)
        json.Unmarshal(assetAsBytes, &asset)


        if sMileage != "" {
                mileage, _ := strconv.Atoi(sMileage) // input mileage (km)
                if mileage < asset.Mileage {
                        return fmt.Errorf("Invalid argument.")
                }
                asset.Mileage = mileage
        }

        if sBattery != "" {
                battery, _ := strconv.Atoi(sBattery) // input battery (%)
                if battery > (asset.Battery + 5) {
                        return fmt.Errorf("Invalid argument.")
                }
                asset.Battery = battery
        }

        if location != "" {
                asset.Location = location
        }

        assetAsBytes, _ = json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) ResetAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error {
        fmt.Println("ResetAsset")

        assetAsBytes, _ := ctx.GetStub().GetState(key)
        asset := new(Asset)
        json.Unmarshal(assetAsBytes, &asset)


        if sMileage != "" {
                mileage, _ := strconv.Atoi(sMileage) // input mileage (km)
                asset.Mileage = mileage
        }

        if sBattery != "" {
                battery, _ := strconv.Atoi(sBattery) // input battery (%)
                asset.Battery = battery
        }

        if location != "" {
                asset.Location = location
        }

        assetAsBytes, _ = json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) QueryAllAssets(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
        fmt.Println("QueryAllAssets")

        startKey := ""
        endKey   := ""

        resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetStateByRange: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []QueryResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                queryResult := QueryResult{Key: queryResponse.Key, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func (s *SmartContract) QueryRangeAssets(ctx contractapi.TransactionContextInterface, sPageSize string, bookmark string) ([]QueryResult, error) {
        fmt.Println("QueryRangeAssets")

        startKey := ""
        endKey := ""
        var pageSize int32
        tmpSize, _ := strconv.Atoi(sPageSize)
        pageSize = int32(tmpSize)

        resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, pageSize, bookmark)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetStateByRangeWithPagination: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []QueryResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                queryResult := QueryResult{Key: queryResponse.Key, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, key string) error {
        fmt.Println("DeleteAsset")

        return ctx.GetStub().DelState(key)
}


func (s *SmartContract) GetHistoryOfAsset(ctx contractapi.TransactionContextInterface, key string) ([]GetHisResult, error) {
        fmt.Println("GetHistoryOfAsset")

        resultsIterator, err := ctx.GetStub().GetHistoryForKey(key)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetHistoryForKey: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []GetHisResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                t := time.Unix(queryResponse.Timestamp.Seconds, 0).String()
                //t := time.Unix(queryResponse.Timestamp.Seconds, int64(queryResponse.Timestamp.Nanos)).String()
                queryResult := GetHisResult{TxId:queryResponse.TxId, Timestamp:t, IsDelete:queryResponse.IsDelete, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func main() {

        chaincode, err := contractapi.NewChaincode(new(SmartContract))

        if err != nil {
                fmt.Printf("Error create asset chaincode: %s", err.Error())
                return
        }

        if err := chaincode.Start(); err != nil {
                fmt.Printf("Error starting asset chaincode: %s", err.Error())
        }
}

最後に

HFが1.4から2.1.1になってコードがシンプルに書きやすくなりました。1.4までは文字列バッファに自分でゴリゴリとデータを詰めていたのですが、その辺りがスマートになっています。引数も1つの文字列配列から引数ごとに分離しました。

参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hyperledger FabricのKeyゾンビ化を防ぐ(全ソースコード掲載)

Hyperledger FabricでState DBのKeyを再利用させない(実はできない)

Hyperledger Fabric(以下HF)で一度削除(DelState)したKeyで再登録(PutState)したらどうなるんだろう?という疑問から、次のようなシナリオを考えました。またミラ・キータ(Mira Qiita)に登場してもらいます。

  1. Mira QiitaをKey="JMYMIRAGINO200302"で登録(PutState)
  2. 1のKeyでクエリ(GetState)
  3. 1のKeyで削除(DelState)
  4. 1のKeyでクエリ(GetState)
  5. 1のKeyで登録(PutState)
  6. 1のKeyで履歴を参照(GetHistoryForKey)

どうなるんだろう?

あと、この投稿にてGoで書いたソースコードやシェルスクリプトを掲載します。

環境について

動作環境については次の通りです。

  • Ubuntu 18.04.4 LTS
  • docker-compose 1.26.0
  • docker 19.03.11
  • HF 2.1.1
  • go 1.14.4
  • PostgreSQL(Dockerイメージ) 12.3(latest)

実際にやってみる

# ./CreateAsset.sh <----- 初期登録
2020-06-24 17:50:15.434 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- 一度クエリ
{"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"}
# ./DeleteAsset.sh <----- 削除
2020-06-24 17:53:11.100 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- 削除後のクエリ
Error: endorsement failure during query. response: status:500 message:"JMYMIRAGINO200302 does not exist"
# ./CreateAsset.sh <----- 同じKeyで登録
2020-06-24 17:53:26.301 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
# ./QueryAsset.sh <----- またクエリ
{"year":"2003","month":"02","mileage":43871,"battery":100,"Location":"QIITA東京販売"}
#

Oh...
削除後のクエリではdoes not existが返ってきますが、削除したKeyで再登録ができてクエリもできちゃいました。履歴はどうなっているのでしょう?
見てみましょう!

# ./GetHistoryOfAsset.sh | jq
[
  {
    "TxId": "fed9df47d31bb4c6cec4be6c9bf154f8655f13d994eebfd709e564e36e011945",
    "Timestamp": "2020-06-24 08:53:26 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  },
  {
    "TxId": "7d2853ede65564e082ab81c56e7a12331493c841993caea4ad88261706f6da9c",
    "Timestamp": "2020-06-24 08:53:11 +0000 UTC",
    "IsDelete": true,
    "Record": {
      "year": "",
      "month": "",
      "mileage": 0,
      "battery": 0,
      "Location": ""
    }
  },
  {
    "TxId": "854ce2f67ecb11aba6ae103841da9cbf9ddb17ed412df661a8d76abc4b39a35c",
    "Timestamp": "2020-06-24 08:50:15 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  }
]
#

Oh...
履歴は下から読んでください。
廃車(DelState)前からの履歴が残って出力されています。
Keyやidの桁数が足りなくて、削除後に一定期間が過ぎると再利用というのはモノによってはあり(携帯電話の番号がそうでしたね)ですが、ブロックチェーンでは思想的になしだと思います。

対策を探してみる(なかった…)

残念ながらHFのAPIで解決できるものではなさそうです。調べた中でベストプラクティスはDelStateを使わないです。
同じ疑問を持った人がstackoverflowで質問していて、その回答が意外なものでした。
つまり、レコードを削除しないで「削除したフラグ」をレコードに持つ、ということですね。ちょっとだけ目からウロコ。HFがバージョンアップしたらできるようになるかもです。
もしかしたら調査不足かもしれません。偉い人教えて下さい:-)

ここまでのソースコード

先ずはHFのサンプルコードをインストールします。こちらにインストール方法が書いてあります。現時点でv2.1.1は最新版なのでcurl -sSL https://bit.ly/2ysbOFE | bash -sでインストールできます。
ディレクトリfabric-samples/に任意な名前(今回はassetで作っています)のディレクトリを作って、そこを作業場所にします。そこへfabcar/からnetworkDown.shstartFabric.shをコピーしてください。その他の環境については以前の投稿に書いてあります。
- startFabric.sh: docker-composeを使って必要なサービスを起動してくれます
- networkDown.sh: 全てを無にしてくれます

シェルスクリプトについて

invoke系の機能とquery系の機能で呼び出し方が違います。CreateAsset.shCreateAsset.shがあれば、他の機能を呼び出すシェルもコピペで作れます。function名と引数の違いだけです。

  • invoke系代表CreateAsset.sh
CreateAsset.sh
#!/bin/bash

pushd ../test-network > /dev/null

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

key='"JMYMIRAGINO200302"'
year='"2003"'
month='"02"'
mileage='"43871"'
battery='"100"'
location='"QIITA東京販売"'
args=${key},${year},${month},${mileage},${battery},${location}

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n asset --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"CreateAsset","Args":['${args}']}'

popd > /dev/null
  • query系代表QueryAsset.sh
QueryAsset.sh
#!/bin/bash

pushd ../test-network > /dev/null

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CHANNEL_NAME=mychannel

key='"JMYMIRAGINO20030Z"'
peer chaincode query -C $CHANNEL_NAME -n asset -c '{"Args":["QueryAsset",'${key}']}'

popd > /dev/null

肝は必ずカレントディレクトリをtest-network/にすることです。pushdで移動してpopdで戻っています。

goソースコードについて

もともとはHF1.4の時に書いたコードでHF2.1.1にあわせて書き直しました。祖先はHF1.4のfabcar.goです。以下に掲載します。長いです。。
DBの初期化はInitLedgerからInitDBに移動しています。chaincodeのバージョンアップで毎回DBの初期化が走るのも嫌なので。。
UpdateAssetResetAssetが似ているようで機能を分けています。UpdateAssetには資産価値が上がるような変更にガードを入れています。ResetAssetは「工場でバッテリーを新品に交換する」みたいなシナリオを想定したものです。

コード中にコメントがなくてスミマセン:-(
個々でやってることは大したことないです。基本的には受け取った引数をHFのAPIへスルーパスしてるだけです。

メソッド一覧です。引数は全て文字列で渡します。

メソッド 引数 機能
InitLedger なし HFの初期化
InitDB なし State DBの初期登録。初期データはPostgre SQLから読み込んでいます
QueryAsset key keyからState DBの内容を検索・出力します
QueryAssetWithOwner key QueryAssetの出力に加え、Postgre SQLからの個人情報も検索・出力します
CreateAsset key, year, month, mileage, battery, location 新しいassetを作成・登録します
UpdateAsset key, mileage, battery, location assetの内容を更新します。値によっては更新できません
ResetAsset key, mileage, battery, location assetの内容を入力の値で強制的に更新します
QueryAllAssets なし State DB内の全レコードを検索します
QueryRangeAssets pagesize, key keyから始まるレコードをpagesize数分検索・出力します
DeleteAsset key keyのレコードを削除します。履歴は残ります
GetHistoryOfAsset key keyの取引履歴を検索・出力します
asset.go
package main

import (
        "encoding/json"
        "fmt"
        "strconv"
        "time"

        "github.com/hyperledger/fabric-contract-api-go/contractapi"
        "database/sql"
        _ "github.com/lib/pq"
)

// Define the Smart Contract structure
type SmartContract struct {
        contractapi.Contract
}

// データ構造の定義
type Asset struct {
        Year     string `json:"year"`     // 初度登録年
        Month    string `json:"month"`    // 初度登録月
        Mileage  int    `json:"mileage"`  // 走行距離(km)
        Battery  int    `json:"battery"`  // バッテリーライフ(%)
        Location string `jasn:"location"` // 位置
}
type AssetWithOwner struct {
        Name    string  // 名前
        Country string  // 国
        City    string  // 都道府県
        Addr    string  // 市区町村
        Record  *Asset
}
// クエリ結果(レーコード検索)
type QueryResult struct {
        Key     string          // レコードID(VINコード)
        Record  *Asset
}
// クエリ結果(履歴検索)
type GetHisResult struct {
        TxId      string        // トランザクションID
        Timestamp string        // タイムスタンプ
        IsDelete  bool          // 削除(廃車)フラグ
        Record    *Asset
}

func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
        fmt.Println("InitLedger")

        return nil
}

func (s *SmartContract) InitDB(ctx contractapi.TransactionContextInterface) error {
        fmt.Println("InitDB")

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()

        if err != nil {
                return fmt.Errorf("sql.Open: %s", err.Error())
        }

        rows, err := db.Query("SELECT * FROM asset;")

        if err != nil {
                return fmt.Errorf("sql.Query: %s", err.Error())
        }

        var id string
        for rows.Next() {
                var asset Asset
                rows.Scan(&id, &asset.Year, &asset.Month, &asset.Mileage, &asset.Battery, &asset.Location)
                assetAsBytes, _ := json.Marshal(asset)
                ctx.GetStub().PutState(id, assetAsBytes)
        }

        return nil
}

func (s *SmartContract) QueryAsset(ctx contractapi.TransactionContextInterface, key string) (*Asset, error) {
        fmt.Println("QueryAsset")

        assetAsBytes, err := ctx.GetStub().GetState(key)

        if err != nil {
                return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
        }

        if assetAsBytes == nil {
                return nil, fmt.Errorf("%s does not exist", key)
        }

        asset := new(Asset)
        _ = json.Unmarshal(assetAsBytes, asset)

        return asset, nil
}

func (s *SmartContract) QueryAssetWithOwner(ctx contractapi.TransactionContextInterface, key string) (*AssetWithOwner, error) {
        fmt.Println("QueryAssetWithOwner")

        assetAsBytes, err := ctx.GetStub().GetState(key)

        if err != nil {
                return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
        }

        if assetAsBytes == nil {
                return nil, fmt.Errorf("%s does not exist", key)
        }

        asset := new(Asset)
        _ = json.Unmarshal(assetAsBytes, asset)

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()
        if err != nil {
                return nil, fmt.Errorf("sql.Open: %s", err.Error())
        }

        sql := "SELECT * FROM owner WHERE id = '" + key + "';"
        rows, err := db.Query(sql)
        if err != nil {
                return nil, fmt.Errorf("db.Query: %s", err.Error())
        }

        var id string
        awo := new(AssetWithOwner)
        for rows.Next() {
                rows.Scan(&id, &awo.Name, &awo.Country, &awo.City, &awo.Addr)
                awo.Record = asset
        }

        return awo, nil
}

func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, key string, year string, month string, sMileage string, sBattery string, location string) error {
        fmt.Println("CreateAsset")

        mileage, _ :=strconv.Atoi(sMileage)
        battery, _ :=strconv.Atoi(sBattery)
        asset := Asset{
                Year:     year,
                Month:    month,
                Mileage:  mileage,
                Battery:  battery,
                Location: location,
        }
        assetAsBytes, _ := json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error {
        fmt.Println("UpdateAsset")

        assetAsBytes, _ := ctx.GetStub().GetState(key)
        asset := new(Asset)
        json.Unmarshal(assetAsBytes, &asset)


        if sMileage != "" {
                mileage, _ := strconv.Atoi(sMileage) // input mileage (km)
                if mileage < asset.Mileage {
                        return fmt.Errorf("Invalid argument.")
                }
                asset.Mileage = mileage
        }

        if sBattery != "" {
                battery, _ := strconv.Atoi(sBattery) // input battery (%)
                if battery > (asset.Battery + 5) {
                        return fmt.Errorf("Invalid argument.")
                }
                asset.Battery = battery
        }

        if location != "" {
                asset.Location = location
        }

        assetAsBytes, _ = json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) ResetAsset(ctx contractapi.TransactionContextInterface, key string, sMileage string, sBattery string, location string) error {
        fmt.Println("ResetAsset")

        assetAsBytes, _ := ctx.GetStub().GetState(key)
        asset := new(Asset)
        json.Unmarshal(assetAsBytes, &asset)


        if sMileage != "" {
                mileage, _ := strconv.Atoi(sMileage) // input mileage (km)
                asset.Mileage = mileage
        }

        if sBattery != "" {
                battery, _ := strconv.Atoi(sBattery) // input battery (%)
                asset.Battery = battery
        }

        if location != "" {
                asset.Location = location
        }

        assetAsBytes, _ = json.Marshal(asset)

        return ctx.GetStub().PutState(key, assetAsBytes)
}

func (s *SmartContract) QueryAllAssets(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
        fmt.Println("QueryAllAssets")

        startKey := ""
        endKey   := ""

        resultsIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetStateByRange: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []QueryResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                queryResult := QueryResult{Key: queryResponse.Key, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func (s *SmartContract) QueryRangeAssets(ctx contractapi.TransactionContextInterface, sPageSize string, bookmark string) ([]QueryResult, error) {
        fmt.Println("QueryRangeAssets")

        startKey := ""
        endKey := ""
        var pageSize int32
        tmpSize, _ := strconv.Atoi(sPageSize)
        pageSize = int32(tmpSize)

        resultsIterator, _, err := ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, pageSize, bookmark)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetStateByRangeWithPagination: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []QueryResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                queryResult := QueryResult{Key: queryResponse.Key, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, key string) error {
        fmt.Println("DeleteAsset")

        return ctx.GetStub().DelState(key)
}


func (s *SmartContract) GetHistoryOfAsset(ctx contractapi.TransactionContextInterface, key string) ([]GetHisResult, error) {
        fmt.Println("GetHistoryOfAsset")

        resultsIterator, err := ctx.GetStub().GetHistoryForKey(key)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetHistoryForKey: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []GetHisResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                t := time.Unix(queryResponse.Timestamp.Seconds, 0).String()
                //t := time.Unix(queryResponse.Timestamp.Seconds, int64(queryResponse.Timestamp.Nanos)).String()
                queryResult := GetHisResult{TxId:queryResponse.TxId, Timestamp:t, IsDelete:queryResponse.IsDelete, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}

func main() {

        chaincode, err := contractapi.NewChaincode(new(SmartContract))

        if err != nil {
                fmt.Printf("Error create asset chaincode: %s", err.Error())
                return
        }

        if err := chaincode.Start(); err != nil {
                fmt.Printf("Error starting asset chaincode: %s", err.Error())
        }
}

最後に

HFが1.4から2.1.1になってコードがシンプルに書きやすくなりました。1.4までは文字列バッファに自分でゴリゴリとデータを詰めていたのですが、その辺りがスマートになっています。引数も1つの文字列配列から引数ごとに分離しました。

参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go のオレオレ linter を golang.org/x/tools/go/analysis 使って作成する

ゴール

  • x/tools/go/analysis package を使って、linter を実装できるようになる

背景

Go は言語本体の簡潔さ、強力さはもちろんのことですが、周辺のツール/環境が充実していることでも知られています。
linter についても、標準で go vet や、標準の linter の runnner として、golangci-lint というサードパーティのツールも利用されています。

さらに、Go ではそのような linter 、及び静的解析を実装するためのライブラリが準標準 (golang.org./x 以下)で公開されています。
今回は、そのライブラリを使ってユーザ独自の linter を作る方法を紹介します。

環境

  • go1.14.4
  • skeleton v1.2.1

gostaticanalysis/skeleton で linter のひな形を作る

go/analysis package を使った linter の作成は、以下のツールでスケルトンを作成することができます。

https://github.com/gostaticanalysis/skeleton

skeleton が出力するスケルトンの内容

以下のようにファイル一式が出力されます。

.
│  hogefuga.go
│  hogefuga_test.go
│
├─cmd
│  └─hogefuga
│          main.go
│          varinit.exe
│
├─plugin
│  └─hogefuga
│          main.go
│
└─testdata
    └─src
        └─a
                a.go

  • hogefuga.go/hogefuga_test.go へ、linter の実装とテストを記載する
  • cmd 以下は go vet 等から利用する実行体を作成するコマンド
  • plugin 以下は golangci-lint から利用するプラグインを作成するコマンド
  • testdata 以下へ hogefuga_test.go から参照されるテストデータを作成する

これらで、linter を作るための基本的な材料は全てそろっていて、
すぐに linter の作成を始めることができます。

独自 linter を実装する

というわけで、早速 linter を作っていきます。
今回は簡単なサンプルとして、識別子に hoge/fuga のような意味のない名前を与えている場合、
それを linter の指摘として出力するというものを作ってみます。

テストデータを作成する

まず、linter の指摘を受けるような Go のコードをテストデータとして用意します。

go/analysis package は、 go/analysis/analysistest package を使って、テストができます。

そのテスト package では、 // want "期待値" というコメントで、 その行にどんな linter の指摘を期待するかを記載してテストすることができます。

a.go
package a

import "fmt"

func a() {
    var hoge string = "hoge" // want "identifier is meaningless"

    fuga(hoge) // want "identifier is meaningless" "identifier is meaningless"
}

func fuga(s string) { // want "identifier is meaningless"
    fmt.Println(s)
}

なお、テストコード側は以下のようになっており、複数 package を試すなどの場合除いては、
スケルトンの出力したコードからの変更は不要です。

hogefuga_test.go
package varinit_test

import (
    "testing"

    "github.com/akif999/varinit"
    "golang.org/x/tools/go/analysis/analysistest"
)

// TestAnalyzer is a test for Analyzer.
func TestAnalyzer(t *testing.T) {
    testdata := analysistest.TestData()
    analysistest.Run(t, testdata, varinit.Analyzer, "a")
}

linter の処理を実装する

以下のように実装しました。
ユーザで書いているのは、ast node*ast.Ident (識別子) へ絞り込むフィルタを作成していることと、
そのフィルタで ast を走査して、各 *ast.Ident (識別子) の要素に対して、指摘を出力するかを判定します。

ちなみに、本記事では取り上げていませんが、指摘を出力するだけではなく suggest fix (= 修正案を提示) したり、
lint すると同時に修正したりということもできるようです。

hogefuga.go
package varinit

import (
    "go/ast"

    "golang.org/x/tools/go/analysis"
    "golang.org/x/tools/go/analysis/passes/inspect"
    "golang.org/x/tools/go/ast/inspector"
)

const doc = "varinit is ..."

// Analyzer is ...
var Analyzer = &analysis.Analyzer{
    Name: "varinit",
    Doc:  doc,
    Run:  run,
    Requires: []*analysis.Analyzer{
        inspect.Analyzer,
    },
}

func run(pass *analysis.Pass) (interface{}, error) {
    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

    nodeFilter := []ast.Node{
        (*ast.Ident)(nil), // nodefileter の定義
    }

    inspect.Preorder(nodeFilter, func(n ast.Node) {
        switch n := n.(type) {
        case *ast.Ident: // 識別子に対して処理を記載する
            if isHoge(n.Name) || isFuga(n.Name) { // hoge、fuga に識別子が該当するかを確認する
                pass.Reportf(n.Pos(), "identifier is meaningless") // 指摘ありの場合、指摘を出力する API をコールする
            }
        }
    })

    return nil, nil
}

func isHoge(s string) bool {
    return s == "hoge"
}

func isFuga(s string) bool {
    return s == "fuga"
}

独自 linter の動かし方

独自 linter は、 go vet から動かすことができます。
golangci-lint の plugin を作ることができるようになっていますが、
記事執筆時点では、Windows/amd64 環境では build ができませんでした。

ちなみに、 go vet 本体の linter も、ある時点で go/analysis package を使ってリライトされています。

C:\Users\aki01\work\src\github.com\akif999\hogefuga\cmd\hogefuga>go vet --vettool=hogefuga.exe ..\..\testdata\src\a
# _/C_/Users/aki01/work/src/github.com/akif999/hogefuga/testdata/src/a
..\..\testdata\src\a\a.go:6:6: identifier is meaningless
..\..\testdata\src\a\a.go:8:2: identifier is meaningless
..\..\testdata\src\a\a.go:8:7: identifier is meaningless
..\..\testdata\src\a\a.go:11:6: identifier is meaningless

まとめ

  • go/analysis package を使うと、他の linter と共通の I/F で linter をつくることができる
  • linter の作成/テストをライブラリが支援してくれるので作るのが簡単
  • gostaticanalysis/skeleton を使うとスケルトンを出力してくれて、更に作りやすい
  • 現場独自の linter を作って、自分たちのプロダクトの品質とスピードの向上に貢献しませんか??
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CORS(preflight request)にハマったけど解決した話

CORS(preflight request)にハマったので、解決方法を備忘録として残しておきます。

エラーが起きた場面

異なるドメインからHttpリクエストを送る場合は、CORSに注意だよなぁ。
サーバー側のレスポンスで、ヘッダーをつけてあげれば良いんだろう。
簡単じゃん。

クライアント側

何かしらのデータをjasonでPOSTする。

var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", API_ENDPOINT);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send(JSON.stringify(data));

APIサーバー側

サーバー側では、レスポンスのヘッダーを付けてあげる。

func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) {
    //略...

    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

    //略...

    fmt.Fprint(w, "ok")
}

結果、エラー...!

当然、結果が正常に返ってくると思いきや
プリフライトリクエスト?がどうのこうのといったエラーが出た。。。なんやこれ。。。

Access to XMLHttpRequest at 'http://localhost:8080/api' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

CORS(preflight request)エラー解決

preflight requestとは?

リクエストによっては CORS プリフライトを引き起こさないものがあります。これをこの記事では「単純リクエスト」と呼んでいますが、 (CORS を定義している) Fetch 仕様書ではこの用語を使用していません。 「単純リクエスト」は、以下のすべての条件を満たすものです。
・許可されているメソッドのうちの一つであること。
GET
HEAD
POST
・ユーザーエージェントによって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で「禁止ヘッダー名」として定義されているヘッダー) を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で「CORS セーフリストリクエストヘッダー」として定義されている以下のヘッダーだけです。
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の要件を満たすもの)
DPR
Downlink
Save-Data
Viewport-Width
Width
・Content-Type ヘッダーでは以下の値のみが許可されています。
application/x-www-form-urlencoded
multipart/form-data
text/plain
・リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。これらは正しく XMLHttpRequest.upload を使用してアクセスされます。
・リクエストに ReadableStream オブジェクトが使用されていないこと。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests

上記の条件を満たさないものはpreflight requestがメインのリクエストの前に行われます。
今回は、POSTですが、"Content-Type", "application/json"をヘッダーに付けているので、プリフライトリクエストが起きました。
画像の最初のリクエストがpreflight requestです。
ここでは、実際にはOPTIONSメソッドがpreflight requestとして走ります。
preflight_correct.png

解決方法

 OPTIONSメソッドの時は、http.StatusOKを返すようにしたら解決しました!

func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) {
    //略...

    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }
    //略...

    fmt.Fprint(w, "ok")
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go修行10日目 embeded

embeded

  • Go言語には継承がなくてその代わり
  • VertexをVertex3Dがembededしている

package main

import "fmt"

type Vertex struct{
    x, y int
}

func (v Vertex) Area() int{
    return v.x * v.y
}

func (v *Vertex) Scale(i int){
    v.x = v.x * i
    v.y = v.y * i
}

type Vertex3D struct{
    Vertex
    z int
}

func (v Vertex3D) Area3D() int{
    return v.x * v.y * v.z
}

func (v *Vertex3D) Scale3D(i int){
    v.x = v.x * i
    v.y = v.y * i
    v.z = v.z * i
}

func New(x, y, z int) *Vertex3D{
    return &Vertex3D{Vertex{x, y}, z}
}

func main() {
    v := New(3,4,5)
    v.Scale(10)
    fmt.Println(v.Area())
    fmt.Println(v.Area3D())
}
1200
6000
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fish shell + anyenvでのGo開発環境構築

fish shell + anyenvというパターンでのGoの環境構築の記事がなかったので、備忘録としてまとめます。

実行バージョン
- fish 3.0.2
- anyenv 1.1.1

anyenvでのgoenvのインストールと設定

まずanyenvでGoのバージョン管理システムのgoenvをインストールします。

$ anyenv instal goenv

そして、goenv用の環境変数やaliasの設定をfish shellの設定ファイル~/.config/fish/config.fishに追記します。この部分がzshやbashとは違うので注意です。

set -x GOENV_ROOT "$HOME/.anyenv/envs/goenv"
set -x PATH $PATH "$GOENV_ROOT/bin"

set -gx PATH "$GOENV_ROOT/shims" $PATH
set -gx GOENV_SHELL fish
source "$GOENV_ROOT/libexec/../completions/goenv.fish"
command goenv rehash 2>/dev/null
function goenv
  set command $argv[1]
  set -e argv[1]

  switch "$command"
  case rehash shell
    source (goenv "sh-$command" $argv|psub)
  case '*'
    command goenv "$command" $argv
  end
end

これで準備は出来たのでfish shellを再起動して、goの指定バージョンをインストールしましょう。

# インストール可能なバージョンを取得
$ goenv install --list

# 指定バージョンのインストール
$ goenv install 1.14.3

# デフォルト利用バージョンを指定
$ goenv global 1.14.3

一応go envで環境変数を出力して各種設定が正しく出来ていることを確認しましょう。

$ go env

無事出力されれば完了です。:tada:

VSCodeでの設定

VSCodeでのGoを開発する場合は以下エクステンションをインストールします。

Go with Visual Studio Code

anyenv利用の場合は環境変数が通常と異なり、このままでは動作しないのでVSCodeのsetting.jsonに以下を追記します。

go.gopathgo env | grep GOPATHの値、
go.gorootgo env | grep GOPATHの値です。

"go.gopath": "/Users/<ユーザー名>/go/<Goバージョン名>",
"go.goroot": "/Users/<ユーザー名>/.anyenv/envs/goenv/versions/<Goバージョン名>"

最後go関連の各種ツールをインストールします。
補完やデバッグ関連に必要です。

command + shift + pでコントロールパネルを開き、Go: Install/Update toolsを選択します。
ダイアログが開くので全てチェックを入れてOKでインストールが始まります。

スクリーンショット 2020-06-02 21.15.23.png

VSCodeでのGo開発環境構築も完了です。:tada:

参考

https://gist.github.com/patorash/b5a1033c08d2c4df103457866b2dcaa2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む