20200329のGoに関する記事は9件です。

「autorest」および「go-autorest」とは?

Azure SDK for Go documentation」サイトの「Install the Azure SDK for Go」ページを読み進めています。

Include the Azure SDK for Go in your project」セクションにて、GoでAzure サービスを使うには Azure/azure-sdk-for-go とは別に Azure/go-autorest もインポートする必要があるとの記述がありましたので、autorestについて少し調べてみました。

ちなみに Azure/go-autorest は Azure/azure-sdk-for-go をgo getした時に一緒にダウンロードされます。

  • go get -u -d github.com/Azure/azure-sdk-for-go/...

AzureのREST API

AzureにはREST APIが用意されており、各プログラミング言語はAzure SDKで用意されているHTTPクライアントを通じてこのAPIにアクセスし、Azureとの連携を図ります。

AzureのREST API 仕様はOpen API 仕様 形式で書かれており、また、公開されています。

Azure SDKで用意されているHTTPクライアントは、この仕様書をautorestに読み込ませて自動作成されています。

Azure/autorest

autorestは、Microsoft社が開発している汎用(Azure専用ではない)オープンソースのツールで、Open API 仕様 形式を読み込んでRESTful Webサービスにアクセスするクライアント ライブラリーを生成します。

OpenAPI 3.0をサポートしており、PowerShall、C#、Python、Java、TypeScript、Ruby、そしてGoのHTTPクライアント ライブラリーを生成できます。

Azure/go-autorest

go-autorestは、autorestで作成されたAzure SDK for Goのクライアント ライブラリーに対して、ゴルーチンを使ってHTTPリクエストを並列処理できる機能を追加するパッケージで、

  • Azure Active Directory 認証 ライブラリー (autorest/adal)
  • 日付型を正確にパースするパッケージ (autorest/date)
  • 型アサーション(autorest/to)
  • モック (autorest/mock)
  • バリデーション (autorest/validation)
  • ロガー (logger)

なども含まれています。

例:autorest/adal(Active Directory Authentication Library

クライアント サービスでは、毎回ログイン画面からログインするのはわずらわしいため、リソースを操作するためのアプケーション IDである「サービス プリンシパル」を作成してアクセスします。

autorest/adal はサービス プリンシパルのトークンを扱うためのパッケージです。

今回で「Azure SDK for Go のインストール / Install the Azure SDK for Go」ページを読み終えたことになります。

P.S. これまで通りja-jpでアクセスしたのですが日本語ドキュメントが表示されなくなっていました... > Microsoftさん

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

Golang入門.2 -http.Handleの実装を見る-

はじめに

 Golang入門.1 -http.HandleでHello World!-の続きです。

今回のテーマ

 http.Handle関数の処理を追いかけます。なお、今回も以下のコードを参照しますが、メインはhttp.Handle関数のソースコードです。

github

mux.go
package main
import (
        "fmt"
        "net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
}

func main() {
        hello := HelloHandler{}
        server := http.Server{
                Addr: "127.0.0.1:8080",
        }
        http.Handle("/", &hello)
        server.ListenAndServe()
}

概念編

 前回、type ServeMuxhandlerpatternが登録されるという説明を行いました。今回は実際にソースコードを見ながら処理の流れや使用されている構造体について確認していきます。具体的な説明は全てコード編にまとめます。

コード編

 実際にソースコードを読む手順で解説を行います。
 まず、Handle関数の実装を確認します。

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

 どうやらDefaultServeMuxにおけるHandle関数を呼び出すために定義されているようです。次にDefaultServeMuxの実装を確認します。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

 ここだけ見ても特にわかりませんね。type ServeMuxが鍵になりそうです。

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

 ようやく構造体にたどり着きました。次に示すHandle関数を見るとわかるのですが、今回注目しているhandlerの登録に関わっているのはServeMux.mです。より直接的にはServeMux.mに格納されているtype muxEntryです。実装から明らかなように、handlerpatternが組みになっている構造体です。この構造体を必要に応じて追加していくことが処理の肝になってくることが予想されます。
 最後にHandle関数の実装を確認します。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

序盤では同期処理やエラー処理を行なっています。その後、

    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

ではpatternが既出であるか確認しています。そして、

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e

で実際にpatternhandlerの情報を保持したmuxEntryを登録しています。また、mux.mがまだ存在しない場合はmake関数を用いて初期化しています。以上が登録までの流れです。

最後に

読んで頂きありがとうございました。server.ListenAnderve関数なども準備が出来次第記事にしたいと思います。

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

Golang入門.2 http.Handleの実装を見る

はじめに

 Golang入門.1 -http.HandleでHello World!-の続きです。

今回のテーマ

 http.Handle関数の処理を追いかけます。なお、今回も以下のコードを参照しますが、メインはhttp.Handle関数のソースコードです。

github

mux.go
package main
import (
        "fmt"
        "net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
}

func main() {
        hello := HelloHandler{}
        server := http.Server{
                Addr: "127.0.0.1:8080",
        }
        http.Handle("/", &hello)
        server.ListenAndServe()
}

概念編

 前回、type ServeMuxhandlerpatternが登録されるという説明を行いました。今回は実際にソースコードを見ながら処理の流れや使用されている構造体について確認していきます。具体的な説明は全てコード編にまとめます。

コード編

 実際にソースコードを読む手順で解説を行います。
 まず、Handle関数の実装を確認します。

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

 どうやらDefaultServeMuxにおけるHandle関数を呼び出すために定義されているようです。次にDefaultServeMuxの実装を確認します。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

 ここだけ見ても特にわかりませんね。type ServeMuxが鍵になりそうです。

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

 ようやく構造体にたどり着きました。次に示すHandle関数を見るとわかるのですが、今回注目しているhandlerの登録に関わっているのはServeMux.mです。より直接的にはServeMux.mに格納されているtype muxEntryです。実装から明らかなように、handlerpatternが組みになっている構造体です。この構造体を必要に応じて追加していくことが処理の肝になってくることが予想されます。
 最後にHandle関数の実装を確認します。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

序盤では同期処理やエラー処理を行なっています。その後、

    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

ではpatternが既出であるか確認しています。そして、

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e

で実際にpatternhandlerの情報を保持したmuxEntryを登録しています。また、mux.mがまだ存在しない場合はmake関数を用いて初期化しています。以上が登録までの流れです。

最後に

読んで頂きありがとうございました。server.ListenAnderve関数なども準備が出来次第記事にしたいと思います。

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

gRPC で認証処理を実装する

grpc で認証処理を実装する

  • interceptorを使って実装すると、良さそう
    • interceptor 自体は middleware 的な立ち回りをしてくれる

実装

  • grpc-ecosystem にライブラリが用意されているのでそれを使っていく
    • go-grpc-middleware/auth
    • 基本的には Client に PW を付与して、それをリクエスト時に投げてもらい、interceptor 内でチェックします
    • 実際に書くのは以下のようなコード
grpcServer = grpc.NewServer(
            grpc_middleware.WithUnaryServerChain(
                grpcMetrics.UnaryServerInterceptor(),
                grpc_recovery.UnaryServerInterceptor(opts...),
                                // ここでChain
                grpc_auth.UnaryServerInterceptor(AuthFunc),
            ),
        )

// AuthFunc は go-grpc-middleware/auth で使用する認証用の関数
// この関数自体は別のパッケージに切り出しても良い
func AuthFunc(ctx context.Context) (context.Context, error) {
    // ここでClientから投げられた認証ヘッダの値を取得
    key, err := grpc_auth.AuthFromMD(ctx, "bearer")
    if err != nil {
        return nil, err
    }

    // ここでは"password"ハードコーディングだけど、この手前でDBアクセスしたりして任意のパスを取得したりしても良い
    if key != "password" {
        return nil, grpc.Errorf(codes.Unauthenticated, "invalid api password")
    }

    // context に Client の情報を詰めて返す
    newCtx := context.WithValue(ctx, "result", "ok")
    return newCtx, nil
}
  • grpc_auth.UnaryServerInterceptor(AuthFunc)に渡す AuthFunc は以下の型を返す関数なら中身はどうでもいいっぽい
  • AuthFuncの中で、やりたいように認証処理を実装していく
    • AuthFromMDは指定した文字列をキーにリクエスト認証ヘッダからバリューを取得してくれる
      • MD は MetaData かと思われる
    • 認証ヘッダを使わない場合は、普通にhttpリクエストヘッダを使ってバリューを取得する
    • よければ context を返し、ダメならエラーを返すとライブラリ側でうまいことUnaryServerInterceptorかエラーを返してくれる
github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
        var newCtx context.Context
        var err error
        if overrideSrv, ok := info.Server.(ServiceAuthFuncOverride); ok {
            newCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod)
        } else {
            newCtx, err = authFunc(ctx)
        }
        if err != nil {
            return nil, err
        }
        return handler(newCtx, req)
    }
}

使ってみる

  • grpcurl してみて、ちゃんと認証処理が挟まるかチェック
    • grpcurl -plaintext -import-path . -proto proto/hello.proto -d '{"name": "hoge"}' -H "Authorization: bearer password" localhost:5000 hello.HelloService/Hello

所感

  • grpc.UnaryServerInterceptor を返す部分と、認証ヘッダからバリューを取得する処理を自前で書かなくていい分楽かな
  • とはいえ全部自分で実装してもそんなに手間でもない気がする

特定メソッドだけ認証をスキップしたい

  • 例えばヘルスチェックは認証をスキップしたい、みたいな場合はServiceAuthFuncOverrideが使える
    • 以下の interface の実装が grpc server に実装されていれば、AuthFuncより優先的にこちらがコールされる(オーバーライドと書いてあるが、実際には実装があるか否かの分岐が入っているっぽい)
type ServiceAuthFuncOverride interface {
    AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error)
}
  • fullMethodNameが grpc のサービスとメソッドの情報を持っているので、少なくともメソッドの粒度で何かしらの分岐が可能
func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
    // HealthCheck がコールされた場合は認証のチェックを skip する
    if fullMethodName == "/hoge_proto.HogeService/HealthCheck" {
        return ctx, nil
    }

    // 認証チェック処理
    // ~~省略~~

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

Go言語でDBアクセス(database/sql)

はじめに

本記事はGo言語でのDBアクセス(select/insert/updateとトランザクション制御)に関するメモです。DBMSとしてpostgreSQL、ライブラリは標準のdatabase/sqlを使っています。

database/sql

database/sqlは、Go言語のデータベースアクセス用の標準パッケージです。
database/sqlでは、アプリケーションに対して、DBを操作するためのインターフェースを提供しています。
下図に示したとおり、DBへの実際の操作を行うのはdbDrvierで、アプリケーションからドライバを直接扱うことはしません。

database/sqlを使ったDB接続

database/sqlでDB接続するには、database/sqlとDBドライバをimportして、sql.Openを実行します。
sqlOpenの第1引数はドライバ名称で、今回はpostgreSQLを使うので「postgres」を設定します。第2引数は接続文字列です。こちらは使用するDBMSや環境に合わせて設定してださい。

main.go
package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq"
)

func setupDB(dbDriver string, dsn string) (*sql.DB, error) {
    db, err := sql.Open(dbDriver, dsn)
    if err != nil {
        return nil, err
    }
    return db, err
}

func main() {
    dbDriver := "postgres"
    dsn := "host=127.0.0.1 port=5432 user=user password=password dbname=dbname sslmode=disable"
    db, err := setupDB(dbDriver, dsn)
    defer db.Close()

    if err != nil {
        log.Fatal(err)
    }
}

database/sqlでクエリー発行

クエリーを発行するメソッドにはQuery,QueryRow,Execなどがあります。概要は下表のとおりです。

メソッド名 定義 用途
Query func (db DB) Query(query string, args ...interface{}) (Rows, error) 複数行取得
QueryRow func (db *DB) QueryRow(query string, args ...interface{}) *Row 単一行取得。複数行返却された場合はエラー
Exec func (db *DB) Exec(query string, args ...interface{}) (Result, error) insert/update

他にもQueryContextのように~Contextと付いているメソッドがあります。こちらのメソッドはクエリーにcontextを設定でき、タイムアウトなんかを指定できるようです。

今回は、Query,QueryRow,Execを使って、以下のように定義したユーザ情報を格納するテーブルt_userを操作してみます。

t_user.ddl
CREATE SEQUENCE public.t_user_id_seq
    INCREMENT 1
    START 1
    MINVALUE 1
    MAXVALUE 9223372036854775807
    CACHE 1;

ALTER SEQUENCE public.t_user_id_seq
    OWNER TO postgres;

CREATE TABLE public.t_user
(
    id bigint NOT NULL DEFAULT nextval('t_user_id_seq'::regclass),
    name character varying(30) COLLATE pg_catalog."default" unique NOT NULL,
    profile character varying(600) COLLATE pg_catalog."default" ,
    created timestamp with time zone NOT NULL,
    updated timestamp with time zone NOT NULL,
    CONSTRAINT t_user_pkey PRIMARY KEY (id)
)

TABLESPACE pg_default;

ALTER TABLE public.t_user
    OWNER to postgres;

テーブルの各項目の役割は次のとおりです。

項目名 物理名 用途
ユーザID id ユーザを一意に特定するためのキー。insert時に自動で採番されるようにしています。
ユーザ名 name ユーザ名。他のユーザ名との重複を許可しません。
プロファイル profile ユーザが任意に設定できるユーザの情報。未設定(null)を許容しています。
登録日時 created レコードを登録した日時。
更新日時 updated レコードを更新した日時。

次にクエリーの結果を格納する構造体Userを以下のように定義します。構造体の定義は、テーブルかselectした結果を設定するのに都合の良いように定義にしておきます。
今回はテーブルt_userに合わせて構造体を定義しています。

type User struct {
    ID      int64
    Name    string
    profile sql.NullString
    Created time.Time
    Updated time.Time
}

profileフィールドの型がstringではなく、sql.NullStringを指定しているのは、profileにnullが設定される可能性があるためです。
database/sqlでテーブルをselectして結果を取得するにはQuery系のメソッドで得た結果をScanしますが、profileをstringで定義していた場合、nullが入っているとScan時にエラーとなります。ここではprofileの型にsql.NullStringを指定することで、Scan時に発生するエラーを回避しています。

database/sql Query

テーブルから最大10行取得するサンプルです。QueryメソッドのパラメータにSQLを直接記述します。

rows, err := db.Query("select * from t_user limit 10")
defer rows.Close()

if err != nil {
    log.Fatal(err)
    os.Exit(1)
}
for rows.Next() {
    u := &User{}
    if err := rows.Scan(&u.ID, &u.Name, &u.Profile, &u.Created, &u.Updated); err != nil {
        log.Fatal(err)
        os.Exit(1)
    }
    fmt.Println(u)
}

以下のように$1をつけることでプレースホルダーを指定することもできます。

rows, err := db.Query("select * from t_comments where id=$1", 1)

上記の場合は、id(PrimaryKey)を指定して検索しているので、取得結果が1行になります。したがってQueryRowを使って書き換えることもできます。

func selectUser(db *sql.DB, id int64) (*User, error) {
    u := &User{}
    if err := db.QueryRow("select * from t_user where id=$1", id).Scan(&u.ID, &u.Name, &u.profile, &u.Created, &u.Updated); err != nil {
        log.Fatal(err)
        return nil, err
    }
    return u, nil
}

selectUserの実行結果を表示したものは以下のとおりです。

&{1 taka23kz { false} 2020-03-28 23:59:18.898586 +0900 JST 2020-03-28 23:59:18.898586 +0900 JST} <nil>

profileの取得内容が{ false}のようになっているのは、sql.NullStringのString部分とValid部分が表示されているためです。

type NullString struct {
String string
Valid bool // Valid is true if String is not NULL
}

database/sql Exec

update/insertにはExecメソッドを使っています。
updateUser、insertUserのprofileパラメータがsql.NullStringではなくて、stringとしているのは、単にメソッドの使い勝手を優先してのことです。
Execメソッドにprofileパラメータを渡す際には、newNullStringメソッドでstringからsql.NullStringに変換しています。

func updateUser(db *sql.DB, id int64, profile string) error {
    if _, err := db.Exec("update t_user set profile = $1, updated = $2 where id=$3", profile, time.Now(), id); err != nil {
        log.Fatal(err)
        return err
    }
    return nil
}

func insertUser(db *sql.DB, name string, profile string) error {
    if _, err := db.Exec("insert into t_user (name, profile, created, updated) values($1, $2, $3, $4)", name, profile, time.Now(), time.Now()); err != nil {
        log.Fatal(err)
        return err
    }
    return nil
}

func newNullString(s string) sql.NullString {
    if len(s) == 0 {
        return sql.NullString{}
    }
    return sql.NullString{
        String: s,
        Valid:  true,
    }
}

テーブルから値をScanするということについては、これで問題ありませんが、Userの内容をJSONとして出力する場合にはこのままでは使えません。この問題についての対応方法については、脇道に逸れるため割愛しますが、参考文献のみんなのGo言語に記載があります。是非参考にしてみてください。

トランザクション

database/sqlでトランザクション制御をするには、Beginでトランザクションを開始し、RollbackまたはCommitでトランザクションを終了します。

tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
    os.Exit(1)
}
// tx.Rollback() or tx.Commit()

既に作成したselect/insert/updateに関するメソッドもトランザクション制御の対象とするために、以下のように、sql.DBだった第1引数をsql.Txに置き換えています。

func selectUser(tx *sql.Tx, id int64) (*User, error) {
    u := &User{}
    if err := tx.QueryRow("select * from t_user where id=$1", id).Scan(&u.ID, &u.Name, &u.profile, &u.Created, &u.Updated); err != nil {
        log.Fatal(err)
        return nil, err
    }
    return u, nil
}

func updateUser(tx *sql.Tx, id int64, profile string) error {
    if _, err := tx.Exec("update t_user set profile = $1, updated = $2 where id=$3", newNullString(profile), time.Now(), id); err != nil {
        log.Fatal(err)
        return err
    }
    return nil
}

func insertUser(tx *sql.Tx, name string, profile string) error {
    if _, err := tx.Exec("insert into t_user (name, profile, created, updated) values($1, $2, $3, $4)", name, newNullString(profile), time.Now(), time.Now()); err != nil {
        log.Fatal(err)
        return err
    }
    return nil
}

ただ、このままの実装では、個々のクエリーの成否を毎度判定して、Rollbackするか否かの判定が必要になるので、もっとスマートな実装が必要だと思います。その辺については別に記事を執筆しようかと思います。

参考文献

この記事は以下の情報を参考にして執筆しました。

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

Golang コマンドラインツール

プログラム引数

  • プログラム引数とは
    • プログラム実行時に渡される引数
      • プログラムに対して外から渡されるデータや情報
      • コマンドライン引数 = プログラム引数

$ echo helloではhelloがプログラム引数となる

プログラム引数を取得する

  • os.Argsを使用する
    • プログラム引数が入った文字列型のスライス
    • 要素の1つ目はプログラム名
sample.go
func main() {
    fmt.Println(os.Args())
}
$ ./main hello world
[./main hello world]

flagパッケージ

フラグ(オプション)を便利に使うパッケージ

sample.go
func main() {
    msg := flag.String("msg", "unknown", "メッセージ")
    n := flag.Int("count", 0, "繰り返す回数")

    flag.Parse()
    fmt.Println(strings.Repeat(*msg, *n))
}

型名Var()を使った場合、引数で渡した値にバインドされる

sample.go
var (
    i int
    s string
)

func main() {
    // flag.StringVar(<値を格納する変数のアドレス>, <パラメータ名>, <デフォルト値>, <パラメータの説明>)
    flag.StringVar(&s, "str", s, "文字列")
    flag.IntVar(&i, "int", 1, "回数")

    flag.Parse()
    fmt.Println(s, i)
}

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

Golang コマンドラインツール

プログラム引数

  • プログラム引数とは
    • プログラム実行時に渡される引数
      • プログラムに対して外から渡されるデータや情報
      • コマンドライン引数 = プログラム引数

$ echo helloではhelloがプログラム引数となる

プログラム引数を取得する

  • os.Argsを使用する
    • プログラム引数が入った文字列型のスライス
    • 要素の1つ目はプログラム名
sample.go
func main() {
    fmt.Println(os.Args())
}
$ ./main hello world
[./main hello world]

flagパッケージ

フラグ(オプション)を便利に使うパッケージ

sample.go
func main() {
    msg := flag.String("msg", "unknown", "メッセージ")
    n := flag.Int("count", 0, "繰り返す回数")

    flag.Parse()
    fmt.Println(strings.Repeat(*msg, *n))
}

型名Var()を使った場合、引数で渡した値にバインドされる

sample.go
var (
    i int
    s string
)

func main() {
    // flag.StringVar(<値を格納する変数のアドレス>, <パラメータ名>, <デフォルト値>, <パラメータの説明>)
    flag.StringVar(&s, "str", s, "文字列")
    flag.IntVar(&i, "int", 1, "回数")

    flag.Parse()
    fmt.Println(s, i)
}

入出力

入出力の抽象化

  • io.Readerio.Writer
    • 入出力を抽象化したioパッケージで提供される型
    • それぞれ1つのメソッドしか持たないので実装が楽
    • 入出力を抽象化し、様々な型を透過的に扱える
    • ファイル、ネットワーク、メモリ、etc...
sample.go
type Reader interface {
    Read(p []byte) (n int, err, error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

標準入力と標準出力

  • osパッケージで提供されている*os.File型の変数
    • io.Readerとio.Writerを実装している
    • 様々な関数やメソッドの引数として渡せる
種類 関数
標準入力 os.Stdin
標準入力 os.Stdout
標準エラー出力 os.Stderr
sample.go
// 標準エラー出力に表示
fmt.Fprintf(os.Stderr, "エラー")

プログラムの終了

  • os.Exit(code int) / os 終了
    • 終了コードを指定してプログラムの終了
    • プログラムの呼び出し元に終了状態を伝えられる
    • 0: 成功(デフォルト)
  • log.Fatal / os(エラー)終了
    • 標準エラー出力にエラーメッセージを表示
    • os.Exit(1)で以上終了させる
sample.go
func main() {
    fmt.Fprintln(os.Stderr, "エラー")
    os.Exit(0)

    if err := f(); err != nil {
    log.Fatal(err)
}

ファイルを扱う

  • osパッケージを用いる
sample.go
var src = "/Users/kanaiyuki/go/src/github.com/Kanai-Yuki/sample"

func main() {
    // 読み込み用にファイルを開く
    sf, err := os.Open(src)
    if err != nil {
    log.Fatal(err)
    }

    defer sf.Close()

    // 書き込み用にファイルを開く
    df, err := os.Create(src)
    if err != nil {
        log.Fatal(err)
    }

    defer func() {
    if err := df.Close(); err != nil {
         log.Fatal(err)
    }
    }()
}

入出力関連の便利パッケージ

パッケージ名 詳細
encoding JSON, XML, CSVなどのエンコードを扱うことができる
stinrgs 文字列周りの処理がある
bufio 標準入力を受付、出力させるなど, scannerが便利
strconv 文字列への変換を行う関数を提供
unicode Unicode周りの処理を提供
path/filepath osに寄らないファイルパスの操作が可能
sample.go
// bufio
func main() {
    // 標準入力から読み込む
    scanner := bufio.NewScanner(os.Stdin)
    // 1行ずつ読み込んで繰り返す
    for scanner.Scan() {
        //1行分を出力する
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "読み込みに失敗しました:", err)
    }
}

実践:Go言語でcatコマンドもどきを自作してみる

  • 作成するcatコマンドの仕様
    • 引数でファイルパスの一覧を受け、そのファイルを与えられた順に標準出力でそのまま出力するコマンドを作る
    • また、-nオプションを指定すると、行番号を各行につけて表示される
    • なお、行番号のすべてのファイルで通し番号する
$ mycat -n hoge.txt fuga.txt
1: hoge
2: hoge hoge
3: fuga
4: fugafuga
main.go
package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
    "path/filepath"
)

var (
    number   bool
    strCount int
    files    []string
)

// プログラム引数を取得
func init() {
    flag.BoolVar(&number, "number", number, "通し番号を付与する")
    flag.IntVar(&strCount, "count", strCount, "各ファイルにおける出力する文字数")
    flag.Parse()
    files = flag.Args()
}

func main() {
    // 実行ファイルのパスを取得
    path, err := os.Executable()
    if err != nil {
        log.Fatal(err)
    }
    path = filepath.Dir(path) // 実行ファイルのディレクトリを取得

    for i, file := range files {
        // 行番号、ファイル名を表示する
        if number {
            fmt.Printf("%v: %v\n", i, file)
        } else {
            fmt.Println(file)
        }

        // 読み込み
        path, err := os.Open(filepath.Join(path, file)) // ディレクトリにファイルネームを結合
        if err != nil {
            fmt.Fprintln(os.Stderr, "読み込みに失敗しました", err)
        }

        // 出力
        scanner := bufio.NewScanner(path)
        // TODO: 変数strCountによる出力文字数制限
        for scanner.Scan() {
            fmt.Println(scanner.Text())
        }
    }
}

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

Golangで、デザインパターン「Iterator」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Iterator」を学ぶ"

今回は、Pythonで実装した”Iterator”のサンプルアプリをGolangで実装し直してみました。

■ Iterator(イテレータ・パターン)

Iteratorパターン(イテレータ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。コンテナオブジェクトの要素を列挙する手段を独立させることによって、コンテナの内部仕様に依存しない反復子を提供することを目的とする。

UML class and sequence diagram

W3sDesign_Iterator_Design_Pattern_UML.jpg

UML class diagram

iterator.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

Iteratorパターンとは、何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのものだそうです。Iteratorは、日本語で、反復子と呼ばれることもあるそうです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。

■ "Iterator"のサンプルプログラム

実際に、Iteratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

  • 本棚に、Aroun d the World in 80 daysの本を追加する
  • 本棚に、Bibleの本を追加する
  • 本棚に、Cinderellaの本を追加する
  • 本棚に、Daddy-Long-Legsの本を追加する
  • 現在、本棚に存在する本のタイトルを表示する
$ go run Main.go 
Aroun d the World in 80 days
Bible
Cinderella
Daddy-Long-Legs

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Iterator

  • ディレクトリ構成
.
├── Main.go
└── iterator
    ├── aggregate.go
    ├── book.go
    └── iterator.go

(1) Iterator(反復子)の役

要素を順番にスキャンしていくインタフェースを定める役です。
サンプルプログラムでは、Iteratorインタフェースが、この役を努めます。

iterator/iterator.go
package iterator

// Iterator is interface
type Iterator interface {
    HasNext() bool
    Next() *Book
}

(2) ConcreteIterator(具体的な反復子)の役

Iterator役で定めたインタフェースを実際に実装する役です。
サンプルプログラムでは、bookShelfIterator構造体が、この役を努めます。
この役は、スキャンするために必要な情報を持っている必要があります。
サンプルプログラムでは、BookShelf構造体のインスタンスを、メンバー変数bookShelfで覚えており、注目している本をメンバー変数indexで覚えるようになっています。

iterator/book.go
type bookShelfIterator struct {
    bookShelf *BookShelf
    index     int
}

func newBookShelfIterator(bookShelf *BookShelf) *bookShelfIterator {
    return &bookShelfIterator{
        bookShelf: bookShelf,
        index:     0,
    }
}

// HasNext func for checking condition
func (b *bookShelfIterator) HasNext() bool {
    if b.index < b.bookShelf.getLength() {
        return true
    }
    return false
}

// Next func for fetching book
func (b *bookShelfIterator) Next() *Book {
    book := b.bookShelf.getBookAt(b.index)
    b.index++
    return book
}

(3) Aggregate(集合体)の役

Iterator役を作り出すインタフェースを定める役です。そのインタフェースは、「私が持っている要素を順番にスキャンしてくれる人」を作り出す抽象化メソッドということになります。
サンプルプログラムでは、Aggregateインタフェースが、この役を努めます。

iterator/aggregate.go
package iterator

// Aggregate is interface
type Aggregate interface {
    Append(book *Book)
    Iterator() Iterator
}

(4) ConcreteAggregate(具体的な集合体)の役

Aggregate役が定めたインタフェースを実際に実装する役です。具体的なIterator役、すなわちConcreteIterator役のインスタンスを作り出します。
サンプルプログラムでは、BookShelf構造体が、この役を努めます。

iterator/book.go
// BookShelf is struct
type BookShelf struct {
    last  int
    books []*Book
}

// NewBookShelf func for initializing NewBookShelf
func NewBookShelf() *BookShelf {
    return &BookShelf{
        last: 0,
    }
}

func (b *BookShelf) getBookAt(index int) *Book {
    return b.books[index]
}

// Append func for adding book
func (b *BookShelf) Append(book *Book) {
    b.books = append(b.books, book)
    b.last++
}

func (b *BookShelf) getLength() int {
    return b.last
}

// Iterator func for iterating BookShelfIterator
func (b *BookShelf) Iterator() Iterator {
    return newBookShelfIterator(b)
}

(5) Client(依頼人)の役

サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
package main

import (
    "fmt"

    "./iterator"
)

func startMain(bookShelf iterator.Aggregate) {
    bookShelf.Append(iterator.NewBook("Aroun d the World in 80 days"))
    bookShelf.Append(iterator.NewBook("Bible"))
    bookShelf.Append(iterator.NewBook("Cinderella"))
    bookShelf.Append(iterator.NewBook("Daddy-Long-Legs"))
    it := bookShelf.Iterator()
    for it.HasNext() {
        book := it.Next()
        fmt.Println(book.GetName())
    }
}

func main() {
    startMain(iterator.NewBookShelf())
}

(6)その他

本のタイトルを管理します。

iterator/book.go
// Book is struct
type Book struct {
    name string
}

// NewBook func for initializing Book
func NewBook(name string) *Book {
    return &Book{
        name: name,
    }
}

// GetName func for getting bookname
func (b *Book) GetName() string {
    return b.name
}

■ 参考URL

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

Golangで、デザインパターン「Command」を学ぶ

GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "Pythonで、デザインパターン「Command」を学ぶ"で取り上げた、Pythonベースの”Command”のサンプルアプリをGolangで実装し直してみました。

■ Command(コマンド・パターン)

Command パターン(英: command pattern)はオブジェクト指向プログラミングにおけるデザインパターンの一つで、動作を表現するオブジェクトを示す。Command オブジェクトは、動作とそれに伴うパラメータをカプセル化したものである。
例として、印刷を行うライブラリが PrintJob クラスを備えているとする。ライブラリのユーザーは新たに PrintJob オブジェクトを作成し、パラメータ(印刷するドキュメント、印刷部数など)をセットし、最後にプリンターにジョブを送信するメソッドを呼び出す。

UML class and sequence diagram

W3sDesign_Command_Design_Pattern_UML.jpg

UML class diagram

command.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "Command"のサンプルプログラム

実際に、Commandパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

  • ファイル権限"777"のtest1.txtファイルを作成する
  • ファイル権限"600"のtest2.txtファイルを作成する

なお、サンプルプログラムは、第一引数:作成したいファイル名、第二引数:付与したいファイル権限とします。

$ go run Main.go test1.txt 777
% touch test1.txt
% chmod 777 test1.txt
$ go run Main.go test2.txt 600
% touch test2.txt
% chmod 600 test2.txt

ファイル一覧を確認します。

$ ls -l|grep test
-rwxrwxrwx  1 ttsubo  staff    0  3 29 05:28 test1.txt
-rw-------  1 ttsubo  staff    0  3 29 05:29 test2.txt

想定どおり、ファイルが生成できました。

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Command

  • ディレクトリ構成
.
├── Main.go
└── command
    └── command.go

(1) Command(命令)の役

命令のインタフェースを定める役です。
サンプルプログラムでは、commandインタフェースが、この役を努めます。

command/command.go
package command

import (
    "fmt"
    "os"
)

type command interface {
    execute()
    display()
}

(2) ConcreteCommand(具体的命令)の役

Command役のインタフェースを実際に実装している役です。
サンプルプログラムでは、FileTouchCommand構造体と ChmodCommand構造体が、この役を努めます。

command/command.go
// FileTouchCommand is struct
type FileTouchCommand struct {
    filename string
    receiver *FileOperator
}

// NewFileTouchCommand func for initializing FileTouchCommand
func NewFileTouchCommand(filename string, receiverObj *FileOperator) *FileTouchCommand {
    return &FileTouchCommand{
        filename: filename,
        receiver: receiverObj,
    }
}

func (f *FileTouchCommand) execute() {
    f.receiver.createFile(f.filename)
}

func (f *FileTouchCommand) display() {
    fmt.Printf("%% touch %s\n", f.filename)
}
command/command.go
// ChmodCommand is struct
type ChmodCommand struct {
    filename   string
    permission uint64
    receiver   *FileOperator
}

// NewChmodCommand func for initializing ChmodCommand
func NewChmodCommand(filename string, permission uint64, receiverObj *FileOperator) *ChmodCommand {
    return &ChmodCommand{
        filename:   filename,
        permission: permission,
        receiver:   receiverObj,
    }
}

func (c *ChmodCommand) execute() {
    c.receiver.changeFileMode(c.filename, c.permission)
}

func (c *ChmodCommand) display() {
    fmt.Printf("%% chmod %o %s\n", c.permission, c.filename)
}

(3) Receiver(受信者)の役

ConcreteCommand役の命令を実行するときに対象となる役です。命令の受け取り手と呼んでもよいでしょう。
サンプルプログラムでは、FileOperator構造体が、この役を努めます。

command/command.go
// FileOperator is struct
type FileOperator struct {
}

// NewFileOperator func for initializing FileOperator
func NewFileOperator() *FileOperator {
    return &FileOperator{}
}

func (f *FileOperator) createFile(filename string) {
    os.Create(filename)
}

func (f *FileOperator) changeFileMode(filename string, permission uint64) {
    os.Chmod(filename, os.FileMode(permission))
}

(4) Invoker(起動者)の役

命令の実行を開始する役です。Command役で定義されているインタフェースを呼び出す役になります。
サンプルプログラムでは、CompositeCommand構造体が、この役を努めます。

command/command.go
// CompositeCommand is struct
type CompositeCommand struct {
    cmds []command
}

//NewCompositeCommand func for initializing CompositeCommand
func NewCompositeCommand() *CompositeCommand {
    return &CompositeCommand{}
}

// AppendCmd func for appending command
func (c *CompositeCommand) AppendCmd(cmd command) {
    c.cmds = append(c.cmds, cmd)
}

// Execute func for executing command
func (c *CompositeCommand) Execute() {
    for _, cmd := range c.cmds {
        cmd.execute()
    }
}

(5) Client(依頼人)の役

ConcreteCommand役を生成し、その際にReceiver役を割り当てる役です。
サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
package main

import (
    "flag"
    "strconv"

    "./command"
)

func startMain(filename string, permission uint64) {
    recv := command.NewFileOperator()
    cc := command.NewCompositeCommand()
    cc.AppendCmd(command.NewFileTouchCommand(filename, recv))
    cc.AppendCmd(command.NewChmodCommand(filename, permission, recv))
    cc.Execute()
    cc.Display()
}

func main() {
    flag.Parse()
    perm32, _ := strconv.ParseUint(flag.Arg(1), 8, 32)
    startMain(flag.Arg(0), perm32)
}

■ 参考URL

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