- 投稿日:2020-03-29T21:50:26+09:00
「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に読み込ませて自動作成されています。
autorestは、Microsoft社が開発している汎用(Azure専用ではない)オープンソースのツールで、Open API 仕様 形式を読み込んでRESTful Webサービスにアクセスするクライアント ライブラリーを生成します。
OpenAPI 3.0をサポートしており、PowerShall、C#、Python、Java、TypeScript、Ruby、そしてGoのHTTPクライアント ライブラリーを生成できます。
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さん
- 投稿日:2020-03-29T20:58:45+09:00
Golang入門.2 -http.Handleの実装を見る-
はじめに
Golang入門.1 -http.HandleでHello World!-の続きです。
今回のテーマ
http.Handle
関数の処理を追いかけます。なお、今回も以下のコードを参照しますが、メインはhttp.Handle
関数のソースコードです。mux.gopackage 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 ServeMux
にhandler
とpattern
が登録されるという説明を行いました。今回は実際にソースコードを見ながら処理の流れや使用されている構造体について確認していきます。具体的な説明は全てコード編にまとめます。コード編
実際にソースコードを読む手順で解説を行います。
まず、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
です。実装から明らかなように、handler
とpattern
が組みになっている構造体です。この構造体を必要に応じて追加していくことが処理の肝になってくることが予想されます。
最後に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で実際に
pattern
とhandler
の情報を保持したmuxEntry
を登録しています。また、mux.m
がまだ存在しない場合はmake
関数を用いて初期化しています。以上が登録までの流れです。最後に
読んで頂きありがとうございました。
server.ListenAnderve
関数なども準備が出来次第記事にしたいと思います。
- 投稿日:2020-03-29T20:58:45+09:00
Golang入門.2 http.Handleの実装を見る
はじめに
Golang入門.1 -http.HandleでHello World!-の続きです。
今回のテーマ
http.Handle
関数の処理を追いかけます。なお、今回も以下のコードを参照しますが、メインはhttp.Handle
関数のソースコードです。mux.gopackage 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 ServeMux
にhandler
とpattern
が登録されるという説明を行いました。今回は実際にソースコードを見ながら処理の流れや使用されている構造体について確認していきます。具体的な説明は全てコード編にまとめます。コード編
実際にソースコードを読む手順で解説を行います。
まず、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
です。実装から明らかなように、handler
とpattern
が組みになっている構造体です。この構造体を必要に応じて追加していくことが処理の肝になってくることが予想されます。
最後に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で実際に
pattern
とhandler
の情報を保持したmuxEntry
を登録しています。また、mux.m
がまだ存在しない場合はmake
関数を用いて初期化しています。以上が登録までの流れです。最後に
読んで頂きありがとうございました。
server.ListenAnderve
関数なども準備が出来次第記事にしたいと思います。
- 投稿日:2020-03-29T17:33:31+09:00
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
は以下の型を返す関数なら中身はどうでもいいっぽい
type AuthFunc func(ctx context.Context) (context.Context, error)
AuthFunc
の中で、やりたいように認証処理を実装していく
AuthFromMD
は指定した文字列をキーにリクエスト認証ヘッダからバリューを取得してくれる
- MD は MetaData かと思われる
- 認証ヘッダを使わない場合は、普通にhttpリクエストヘッダを使ってバリューを取得する
- よければ context を返し、ダメならエラーを返すとライブラリ側でうまいこと
UnaryServerInterceptor
かエラーを返してくれるgithub.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.gofunc 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 }
- 投稿日:2020-03-29T16:13:38+09:00
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.gopackage 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.ddlCREATE 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するか否かの判定が必要になるので、もっとスマートな実装が必要だと思います。その辺については別に記事を執筆しようかと思います。
参考文献
この記事は以下の情報を参考にして執筆しました。
- 投稿日:2020-03-29T13:59:21+09:00
Golang コマンドラインツール
プログラム引数
- プログラム引数とは
- プログラム実行時に渡される引数
- プログラムに対して外から渡されるデータや情報
- コマンドライン引数 = プログラム引数
$ echo hello
ではhello
がプログラム引数となるプログラム引数を取得する
os.Args
を使用する
- プログラム引数が入った文字列型のスライス
- 要素の1つ目はプログラム名
sample.gofunc main() { fmt.Println(os.Args()) }$ ./main hello world [./main hello world]flagパッケージ
フラグ(オプション)を便利に使うパッケージ
sample.gofunc main() { msg := flag.String("msg", "unknown", "メッセージ") n := flag.Int("count", 0, "繰り返す回数") flag.Parse() fmt.Println(strings.Repeat(*msg, *n)) }
型名Var()
を使った場合、引数で渡した値にバインドされるsample.govar ( i int s string ) func main() { // flag.StringVar(<値を格納する変数のアドレス>, <パラメータ名>, <デフォルト値>, <パラメータの説明>) flag.StringVar(&s, "str", s, "文字列") flag.IntVar(&i, "int", 1, "回数") flag.Parse() fmt.Println(s, i) }
- 投稿日:2020-03-29T13:59:21+09:00
Golang コマンドラインツール
プログラム引数
- プログラム引数とは
- プログラム実行時に渡される引数
- プログラムに対して外から渡されるデータや情報
- コマンドライン引数 = プログラム引数
$ echo hello
ではhello
がプログラム引数となるプログラム引数を取得する
os.Args
を使用する
- プログラム引数が入った文字列型のスライス
- 要素の1つ目はプログラム名
sample.gofunc main() { fmt.Println(os.Args()) }$ ./main hello world [./main hello world]flagパッケージ
フラグ(オプション)を便利に使うパッケージ
sample.gofunc main() { msg := flag.String("msg", "unknown", "メッセージ") n := flag.Int("count", 0, "繰り返す回数") flag.Parse() fmt.Println(strings.Repeat(*msg, *n)) }
型名Var()
を使った場合、引数で渡した値にバインドされるsample.govar ( 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.Reader
とio.Writer
- 入出力を抽象化したioパッケージで提供される型
- それぞれ1つのメソッドしか持たないので実装が楽
- 入出力を抽象化し、様々な型を透過的に扱える
- ファイル、ネットワーク、メモリ、etc...
sample.gotype 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.gofunc main() { fmt.Fprintln(os.Stderr, "エラー") os.Exit(0) if err := f(); err != nil { log.Fatal(err) }ファイルを扱う
- osパッケージを用いる
sample.govar 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: fugafugamain.gopackage 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()) } } }
- 投稿日:2020-03-29T06:45:38+09:00
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
UML class diagram
□ 備忘録
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.gopackage iterator // Iterator is interface type Iterator interface { HasNext() bool Next() *Book }(2) ConcreteIterator(具体的な反復子)の役
Iterator
役で定めたインタフェースを実際に実装する役です。
サンプルプログラムでは、bookShelfIterator
構造体が、この役を努めます。
この役は、スキャンするために必要な情報を持っている必要があります。
サンプルプログラムでは、BookShelf
構造体のインスタンスを、メンバー変数bookShelf
で覚えており、注目している本をメンバー変数index
で覚えるようになっています。iterator/book.gotype 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.gopackage 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.gopackage 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
- 投稿日:2020-03-29T05:38:24+09:00
Golangで、デザインパターン「Command」を学ぶ
GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "Pythonで、デザインパターン「Command」を学ぶ"で取り上げた、Pythonベースの”Command”のサンプルアプリをGolangで実装し直してみました。■ Command(コマンド・パターン)
Command パターン(英: command pattern)はオブジェクト指向プログラミングにおけるデザインパターンの一つで、動作を表現するオブジェクトを示す。Command オブジェクトは、動作とそれに伴うパラメータをカプセル化したものである。
例として、印刷を行うライブラリが PrintJob クラスを備えているとする。ライブラリのユーザーは新たに PrintJob オブジェクトを作成し、パラメータ(印刷するドキュメント、印刷部数など)をセットし、最後にプリンターにジョブを送信するメソッドを呼び出す。UML class and sequence diagram
UML class diagram
■ "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.gopackage 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.gopackage 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