20190524のGoに関する記事は4件です。

golang + vscode + bingo(gopls) でコード補完を実現する

ある日突然コード補完が効かなくなり調べていると、
gocode やめます(そして Language Server へ)
を見つけました。

コード補完が無いと辛いので、bingo(gopls) を導入しました。
本記事はその備忘録です。

前提条件

  • macOS High Sierra
  • fish
  • $GOPATH 以下で作業しています。
  • Go 1.12 以上
    • brew & goenv を使用している場合 1.12beta までしか対応していません。(2019/05/23時点)
      brew install(upgrade) go で直接インストールし、goenv では system を選択しました。
  • vscode と https://github.com/Microsoft/vscode-go はインストール済みです。
    • > Go: Install/Update Tools で各ツールもインストール済みです。
      元の gopls もインストールされますが、後ほど bingo に置き換えます。

環境変数の確認

fish の例です。
1行目は goenv の設定です。使用していない人は不要です。
Go1.13 以降で GO111MODULE=on がデフォルトになった場合は最終行も不要です。

config.fish
status --is-interactive; and source (goenv init -|psub)
set -x GOPATH ~/go
set -x PATH $GOPATH/bin $PATH
set -x GO111MODULE on

bingo(gopls) のインストール

https://github.com/saibing/tools#install に記載されているインストールコマンドを実行します。
go install すると、vscode 上からインストールした $GOPATH/bin/gopls が入れ替わります。

vscode の設定

settings.json に以下の設定を追加します。

https://github.com/saibing/tools#vscode-go

"gopls" を設定すると、vscode のエラーが出ますが、特に問題ないそうです :sweat_drops:

https://github.com/saibing/bingo/issues/181#issuecomment-485150495

VSCode will complain but it should still work. Once we have a stable set of configs for gopls, we will merge them with the VSCode extension so that you don't get that error message.

そのうち修正されるようです。

動作確認

vscode を再起動してください。
> Developer: Reload Window ではうまくいかない?ことがありそう?です。

image.png

このようにコード補完されます :smiley:

動作が少し重いので、適時 > Developer: Reload Window してください。
また、「出力」でエラーなども確認できます。gopls を選択してください。

image.png

hover 時にエラーが出ることがあります。

"Request textDocument/definition failed" エラーが出る場合

自身で作成したコードの補完が効かない事があります。
https://github.com/golang/go/issues/31492

リポジトリのトップに go.mod がないのが原因?のようです。
.code-workspace ファイルを設置して folders を設定します。

VS Codeのワークスペース」を参考にしました。

参考記事

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

SSOサービスKeycloakとgolangのHTTPサーバを連携する

TL;DR

  • golangでWebアプリケーションを開発するにあたり,SAML認証を組み込む方法を紹介します
    • Keycloakの機能を利用します
    • net/httpで作るWebアプリケーションに,認証を付けるサンプルを紹介します
  • crewjam/samlライブラリを使います

準備

環境想定

  • Webアプリケーションは,以下のような,超シンプルなものをつくります
    • URLにリクエストを発行すると,ログインが求められます
    • ログインするとユーザ名が表示されます
  • テスト環境なので,Webアプリケーションはローカルホストで稼働させます
    • keycloakへ認証の問い合わせを行うのでネットワークに接続されている必要があります

必要環境

  • go 1.12.5
go version
go version go1.12.5 darwin/amd64
go get github.com/crewjam/saml/samlsp

Keycloak設定

  • Keycloakに管理者権限でログインし,連携したいRealmを選択します
  • "Clients"から"Create"を選択します
    • ClientIDをhttp://localhost:8000/saml/metadataのように,サービスを稼働させるURLを設定します
    • Client Protocolを"saml"でClientを作成します.
    • Clientの各タブのうち,サンプルの段階で設定を要するのは以下のとおりです

Settingsタブ

フィールド名
Client Signature Required OFF
Root URL http://localhost:8000

Mappersタブ

  • usernameだけ設定します
    • usernameをマップします
Name Mapper Type Property Friendly Name SAML Attribute Name SAML Attribute NameFormat
username username username username Basic

SAML Keysタブ:x.509証明書の取得

  • ClientのSAML Keysタブから,Signing Keyを2つのファイルにコピーします

    • Private Key : myservice.key
    • Certificate : myservice.cert
    • キーが生成されていなければ,"Generate new keys"ボタンをクリックして生成します
      • 00_samlkeys.jpg
  • 各ファイルには,下記のように接頭行,末尾行を追加します

    • myservice.key
    • -----BEGIN PRIVATE KEY----- MII.....<snip>..... -----END PRIVATE KEY----- * myservice.cert * -----BEGIN CERTIFICATE----- MII.....<snip>..... -----END CERTIFICATE-----

プログラム

  • GitHub crewjam/samlGetting Started as a Service Providerのプログラムを参考に進めます
  • 以下のようなプログラムmain.goを作成します
    • mainメソッドの最初にある5行で,SAML設定に要する文字列を設定しています
    • confIDPMetadataURL: メタデータのURIを記載します.Keycloakの場合は,Keycloakのドメインに,realms/<Realm>/protocol/saml/descriptorのパスを加えたものです
      • 下記コードでは例としてKeycloak(https://sso.eample.com), Realm(dev)の場合を記載しています
      • https://sso.example.com/auth/realms/dev/protocol/saml/descriptor
    • x509certx509key: 前項で作った証明書ファイルです.main.goと同じディレクトリに配置して,プログラムで読み込みます
    • confRootURLconfPort: Webアプリケーションの設定です.KeycloakのClientIDにはconfRootURLと同じ文字列が設定されている必要があります.
//main.go
package main

import (
    "crypto/rsa"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "net/http"
    "net/url"

    "github.com/crewjam/saml/samlsp"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Printf("%+v", samlsp.Token(r.Context()))
    fmt.Fprintf(w, "Hello, %s!", samlsp.Token(r.Context()).Attributes.Get("username"))
}

func main() {
    confIDPMetadataURL := "https://sso.example.com/auth/realms/dev/protocol/saml/descriptor"
    x509cert    := "myservice.cert"
    x509key     := "myservice.key"
    confRootURL := "http://localhost:8000"
    confPort    := ":8000"

    keyPair, err := tls.LoadX509KeyPair(x509cert, x509key)
    if err != nil {
        panic(err) // TODO handle error
    }
    keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
    if err != nil {
        panic(err) // TODO handle error
    }

    idpMetadataURL, err := url.Parse(confIDPMetadataURI)
    if err != nil {
        panic(err) // TODO handle error
    }

    rootURL, err := url.Parse(confRootURL)
    if err != nil {
        panic(err) // TODO handle error
    }

    samlSP, _ := samlsp.New(samlsp.Options{
        URL:            *rootURL,
        Key:            keyPair.PrivateKey.(*rsa.PrivateKey),
        Certificate:    keyPair.Leaf,
        IDPMetadataURL: idpMetadataURL,
    })
    app := http.HandlerFunc(hello)
    http.Handle("/hello", samlSP.RequireAccount(app))
    http.Handle("/saml/", samlSP)
    http.ListenAndServe(confPort, nil)
}

動作確認

Webアプリの実行

  • 作成したmain.go, myservice.key, myservice.confを同じディレクトリに置いて,go runコマンドで実行します
    • go run main.go

ブラウザで確認

  • Webアプリケーションhttp://localhost:8000/helloにアクセスすると,Keycloakのログイン画面に遷移します
  • Realmに登録されたユーザでログインすると,Webアプリケーションの画面に戻り,ユーザ名を使用した表示が行われます
    • 01_webapp.jpg

参考

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

プログラミング言語Go 第7章 メモ

「インタフェース」

書籍:プログラミング言語Go
第7章 「インタフェース」 の要点と思われる箇所を自分のメモ用にまとめました。

7 インタフェース

  • 具象型をあるインタフェース型として扱いたい場合、具象型が満足する全てのインタフェース宣言(継承宣言的な?) をする必要はなく、インタフェース型を満たすメソッドが具象型に実装されていれば良い。以下の Any は io.Writer インタフェース型として扱うことができる。
  type Any struct { n int }
  func (a Any) Write(p []byte) (int, error) {
      a.n -= len(p)
      if a.n > 0 {
          return len(p), nil
      } else {
          return len(p), io.EOF
      }
  }

7.1 契約としてのインタフェース

  • インタフェースは、内部構造を公開していない振る舞いを一般化あるいは抽象化したもの。
  • インタフェース型の値がある場合、その値が何かは示されず、その値で何ができるか、その値でメソッドがどう振る舞うかが示される。
  • io.Writer インタフェースは Fprintf とその呼び出し元の間の契約を定義する。
  package fmt
  func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)

  package io
  type Writer interface {
          // Write は p から len(p) バイトの基底のデータストリームへ書き込みます。
          // p から書き込まれたバイト数 (0 <= n <= len(p)) と、書き込みを早く終わらせた原因となったエラーを返します。
          // Write は、n < len(p) であるような n を返す場合には nil ではない error を返さなければなりません。
          // Write は、たとえ一時的であってもスライスのデータを変更してはいけません。
          // 実装は、p を持ち続けてはいけません。
          Write(p []byte) (n int, err error)
  }
  • *os.File や *bytes.Buffer のような具象型は、適切な Write メソッドの振る舞いを呼び出し元に提供することが求められる。
  • fmt.Fprintf は io.Writer の内部表現は意識しない。io.Writer の契約で保証される振る舞いのみに依存するため、io.Writerの実体は代替可能。

7.2 インタフェース型

  • インタフェース型は具象型がそのインタフェースのインスタンスとして見なされるために持たなければならないメソッドの集まりの定義。
  • 既存の型の組み合わせとして新たなインタフェース型を宣言できる。構造体埋め込みに似た構文でインタフェースを埋め込みすることができる。
  package io

  type ReadWriteCloser interface { // 埋め込みによるインタフェース組み合わせ方法。
      Reader
      Writer
      Closer
  }
  type ReaderWriter interface {  // 埋め込みではないインタフェース組み合わせ方法。
      Read(p []byte) (n int, err error)
      Write(p []byte) (n int, err error)
  }

7.3 インタフェースを満足する

  • インタフェースに対する代入可能性の規則は単純。その型がインタフェースを満足して (インタフェースのメソッドが具象型に実装されて) いれば代入可能。
  • インタフェースは具象型とその型が保持する値を包み隠す。具象型が他のメソッドを持っていてもそのインタフェース型が公開しているメソッドしか呼べない。
  os.Stdout.Write([]byte("hello")) // OK: *os.File は Write メソッドあり
  os.Stdout.Close()                // OK: *os.File は Close メソッドあり

  var w io.Writer
  w = os.Stdout
  w.Write([]byte("hello")) // OK: io.Writer は Write メソッドあり
  w.Close()                // コンパイルエラー: io.Writer は Close メソッドなし
  • 空インタフェース型 (interface{}) は全くメソッドを持たない。それを満足する型に何も要求していないので、全ての値を空インタフェースに代入できる。
  var any interface{}
  any = true
  any = 12.34
  any = "hello"
  ...
  • 宣言の方法により型がインタフェースを満足することを強制することも可能。
  var w io.Writer = new(bytes.Buffer)    // *bytes.Buffer は io.Writer を満足しなければならない
  var _ io.Writer = (*bytes.Buffer)(nil) // *bytes.Buffer は io.Writer を満足しなければならない
  • 一つのクラスが満足するインタフェースの集まりをクラスで明示的に記述する言語と異なり、Go は具象型の宣言を修正することなく、必要な場合に新たな抽象化つまり、興味ある部分の新たなグループ化を行うことができる。(既に定義済みの具象型のメソッドをインタフェースとして切り出せるし、新たなインタフェースを満足したい場合は具象型にそのインタフェースのメソッドを追加実装すればOK。)

7.4 flag.Value によるフラグの解析

  • flag.Duration 関数は time.Duration 型のフラグ変数を生成し、String メソッドで表示されたものと同じ表記を含む、さまざまな使いやすい形式でユーザが期間を指定できる。

7.5 インタフェース値

  • Go では変数は常に定義された値へ初期化される。インタフェースも例外ではなく、インタフェースのゼロ値は nil。
  var w io.Writer // w は nil。
  • 具象型からインタフェースの暗黙的な変換を伴う代入も可能。
  w = os.Stdout // *os.File を io.Writer に変換し、代入している。
  • インタフェース値は任意の大きさの動的な値を保持することができる。
  var x interface{} = time.Now()
  • インタフェース値は ==, != を使って比較可能(map のキーや switch のオペランドとして使える)。
  • どちらも nil か、動的な型が同一でかつ動的な値がその型に対する == の通常の振る舞いに従って等しければ二つのインタフェース値は等しい。
  • ただし、スライスなど、インタフェース値が同じ動的な型を持つが、その型が比較できない (スライスなど) 場合、比較は失敗しパニックになる。
  • インタフェース値は比較可能であるが比較が失敗し、パニックが発生しうる点に注意。インタフェース値が比較可能な型の動的な値を含んでいると確証できるときのみ比較を利用すること。

7.5.1 警告:nil ポインタを含むインタフェースは nil ではない。

  • 値を含まない nil インタフェース値は、たまたま nil であるポインタを含むインタフェース値と異なる。この違いは Goプログラマをつまずかせる罠を生み出す。
  • 以下のコードで、f に渡す buf は nil だが、io.Writer としては byte.Buffer 型の、値として nil を持つインタフェースとなり、 out 自体が nil ではない。そのため out.Write() が呼び出されてパニックになる。
  var debug = false
  func main() {
      var buf *bytes.Buffer
      if debug {
          buf = new(bytes.Buffer)
      }
      f(buf)
      ...

  func f(out io.Writer) {
      if out != nil {
          out.Write([]byte("done!\n")
      }
  • 上記の解決方法は、呼び出し元の buf を io.Writer に変更すること。
  var buf io.Writer
  if debug {
      buf = new(bytes.Buffer)
  }
  f(buf) // OK

7.6 sort.Interface でのソート

  • sort は多くのプログラムで頻繁に使われる操作で、sort パッケージはどのような列に対しても任意の順序付け関数に従った列内でのソートを提供する。
  • Go の sort.Sort 関数は、列やその要素の表現についてはなにも想定しない。代りに汎用ソートアルゴリズムとソートできる個々の列型との間の契約を定めるインタフェースを使う。
  package sort
  type Interface interface {
      Len() int
      Less(i, j int) bool // i, j は列要素のインデックス
      Swap(i, j int)
  }
  • 利便性のため sort パッケージは []int, []string, []float64 に特化していて、それらの自然な順序付けを行う関数と方も提供している。

7.7 http.Handler インタフェース

  • ServeHTTP でパスごとに case を追加しなくてもよいよう、ServeMux (リクエストマルチプレクサ) が提供されている。
  • Go は Ruby の RailsやPython の Django のような標準フレームワークを持たない(存在しないわけではないがフレームワークが不要なほど柔軟なつくりである)。フレームワークは拡張により長期的保守が困難になりうる。
  • HandlerFunc 型の定義により、ServeHTTPと同じ引数、戻り値の関数を ServeHTTP として利用することが可能となる。HandlerFunc は関数値にインタフェースを満足させるアダプタ。HandlerFunc により異なる名前のメソッドを容易に ServeHTTP メソッドとして利用できる。
  type HandlerFunc func(w ResponseWriter, r *Request)
  func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
      f(w, r)
  }
  • ウェブサーバはそれぞれのハンドラを新たなごルーチンで起動する。同じハンドラに対する別リクエストも含めて他のゴルーチンがアクセスするかもしれない変数へハンドラがアクセスする際はロックなどが必要。

7.8 error インタフェース

  • error 型とはエラーメッセージを返す単一メソッドを持ったインタフェース型。
  type error interface {
      Error() string
  }
  • 新たな error は errors.New() で最も簡単に返せる。error の実体の文字列は string の type ではなく構造体。不用意に error の実体の文字列が書き換えられることを防ぐため。
  type errorString { text string }
  func (e *errorString) Error() string { return e.text }
  func New(text string) error { return &errorString{text} }
  • New で返される errorString はポインタ型のため、New 呼び出し毎に異なる error インスタンスが割り当てられる。
  fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"
  • syscall パッケージは error を満足する数値型 Errno を定義しており、Unix プラットフォーム上での errno に対応する文字列を返す error が定義されている。
  var err error := syscall.Errno(2) // 2 = ENOENT
  fmt.Println(err.Error()) // "no such file or directory"

7.9 例:式評価器

  • 式評価器を例として、多態的な要素のツリー構造などを表現する場合に適切なインタフェースを定義することで再起処理を実現できる。

7.10 型アサーション

  • 型アサーションの書き方は x.(T)。x が動的な型 T と一致するかどうかを検査し x を T として取りだす。(bool の第二戻り値を受け取らない場合)検査に失敗するとパニックになる。
  var w io.Writer
  w = os.Stdout
  rw := w.(io.ReadWriter) // 成功: *os.Stdout は Read と Write の両方を持っている

  w = new (ByteCounter)  // p.199 で定義
  rw = w.(io.ReadWriter) // パニック: *ByteCounter は Read メソッドを持たない
  • 型アサーションで bool の第二戻り値を受け取るとパニックにはならない。
  var w io.Writer = os.Stdout
  f, ok := w.(*os.File)      // 成功: ok,  f == os.Stdout
  b, ok := w.(*bytes.Buffer) // 失敗: !ok, b == nil
  • ok の場合、次に何をするか決まっていることが多いので if の拡張形式で簡潔に書ける場合が多い。
  if f, ok := w.(*os.File); ok {
      ....
  • 以下のようにオペランドの変数を再利用する書き方もできる。
  if w, ok := w.(*os.File); ok {
      ...

7.11 型アサーションによるエラーの区別

  • os.IsNotExist(error) などはエラーに対して型アサーションを利用し、エラーの具象型で判定する方法を用いている。
  • PathError が返されて fmt.Errorf によりエラーメッセージを詳細化した場合、PathError 構造体ではなくなるため os.IsNotExist(error) が機能しなくなる。操作失敗直後にエラーの区別する必要があるので注意。

7.12 インタフェース型アサーションによる振る舞いの問い合わせ

  • 特定のメソッドを持っているか検証するために型アサーションを利用することができる。
  type stringWriter interface {
      WriteString(string) (n int, err error)
  }
  if sw, err := w.(stringWriter); ok {
      ... // w は WriteString(string) メソッドを持っている。
  • fmt.Printf 内では、単一オペランドを文字列へ変換するために型アサーションを利用している。
  func formatOneValue(x interface{}) string {
      if err, ok := x.(error); ok {
          return err.Error()
      }
      if str, ok := x.(Stringer); ok {
          return str.String()
      }
      ...

7.13 型 switch

  • 型アサーションは、どのようなメソッドを持つか判別するためと、値がどのような型であるかを判別するために利用される。
  • interface{} はさまざまな具象型を値として保持でき、インタフェース値の型を動的に識別し、型ごとに処理を切り替えるために型アサーションは利用できる。
  • 型アサーションによる型の識別は switch を利用して以下のように表現できる。
  switch x.(type) {
  case nil:
  case int, uint:
  case bool:
  case string:
  default:
  }
  • case は上から順に比較されるため、x が複数インタフェースを持つ場合、case の順番が重要になるので注意。(fallthrough は許されていない。)
  • 以下の形式でswitch を記述すると、case 内では x は case の型として扱われる。ただし、複数の型の case では 元の型のままなので注意。
  func sqlQuote(x interface{}) {
      switch x := x.(type) {
      case int, uint: // 複数の型の case なので、この case 内では x は interface{} のまま。
      case string:    // 単一の型の case なので、この case 内では x は string として扱える。

7.14 例:トークンに基づく XML デコード

  • encoding/xml パッケージは、トークンに基づくAPIを提供している。トークンを型アサーションで判別することでそれぞれのトークンの種類に応じた処理を記述できる。

7.15 ちょっとした助言

  • 不必要な抽象化でインタフェースを増やしてはいけない。実行時コストがかかる。
  • インタフェースは統一的に扱わなければならない二つ以上の具象型が存在する場合にだけ必要。
  • 具象型が一つの場合でも、依存関係の問題で同一パッケージに具象型を定義できない場合はインタフェースを定義してもOK。
  • Goのインタフェースは複数の具象型が満足するためのもののため、インタフェース内のメソッドは単純で数が少ない定義であるべき。Goのインタフェース設計では "必要なものだけを求める" ということが重要。

練習問題解答例

https://github.com/tsuyuzaki/go/tree/master/GoProgrammingLanguage/chapter07

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

Go言語 配列の宣言の時に30秒間つまずいたこと

はじめに

記述するGo言語の配列については一部のみです。

配列の宣言方法

角括弧内[]に長さを宣言し、その後ろに型を宣言します。
配列の要素にアクセスするには、配列の変数の後ろに角括弧[]を付けその中に添字を記述します。
※ 添字は[0]から始まります。

package main

import (
    "fmt"
)

func main() {
    var number [3]string
    number[0] = "zero"
    number[1] = "one"
    number[2] = "two"

    fmt.Println(number)        // => [zero one two]
    fmt.Println(len(number))   // => 3
}

配列の宣言時に値を代入

変数に:=で代入できます。上記の宣言の後ろに{}を付け、その中に代入したい値を,で区切り記述します。

package main

import (
    "fmt"
)

func main() {
    number := [3]string{"zero", "one", "two"}

    fmt.Println(number)        // => [zero one two]
    fmt.Println(len(number))   // => 3
}

ここでつまずいた

{}の中の要素を見やすく複数行に分けて記述したいことがあると思います。しかし以下の場合はコンパイルエラーが発生します。

func main() {
    number := [3]string{
        "zero",
        "one",
        "two"
    }

    fmt.Println(number)
    fmt.Println(len(number))
}
// syntax error: unexpected newline, expecting comma or }

原因は{}の要素の最後にも,を付けないといけないからです。

package main

import (
    "fmt"
)

func main() {
    number := [3]string{
        "zero",
        "one",
        "two",    // <= , が必要
    }

    fmt.Println(number)
    fmt.Println(len(number))
}

;の自動挿入

Go言語は多言語によくある文末の;を省略できます。コンパイル時にGoコンパイラによって;を挿入されます。上記のコンパイルエラーはこの自動挿入によって発生しているようです。

package main;

import (
    "fmt"
);

func main() {
    number := [3]string{
        "zero",
        "one",
        "two";    // <= ここに ; が挿入される
    };

    fmt.Println(number);
    fmt.Println(len(number));
};

参考: (https://github.com/tsurubee/golang-study/issues/1)

最後に

参考サイトはスターティングGo言語についてのでした。
買ってみようかなぁ~。
スターティングGo言語

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