- 投稿日:2020-04-30T21:50:14+09:00
社内Go PackageのためにGoDocで静的なHTMLを出力したい
はじめに
「社内で管理しているGoのパッケージのGoDocを誰でも閲覧できるようにしたい」というモチベーションによる記事です。
Wantedlyさんではこちらの記事のようにKubernetesでGoDocサーバ(gddo-server)を走らせることで社内GoDoc環境を構築しています。
今回はこのアプローチではなく、静的なHTMLをCI上で生成しそれをS3にホスティングすることで特にサーバを持たずに社内GoDocを運用する方針にしました。
環境・バージョン
Go:
go version go1.13.4 darwin/amd64
GoDoc(golang.org/x/tools/cmd/godoc):v0.0.0 (4b814e0)やったこと
CIでGoDocを元にHTMLを生成しそれを社内Amazon S3へアップロードします。
CI上でgodoc(golang.org/x/tools/cmd/godoc)のコマンドを取得した後、素直にgodocで静的HTMLを取得したかったのですが、現状(2020年4月30日時点)のgodocのバージョンにおいては、静的なHTMLを生成するオプション等は存在しないと下記のIssueページで知りました。
https://github.com/golang/go/issues/2381
このIssueページを読むと、wgetを使えばなんとか対応できるとの記載がありました。
- https://github.com/golang/go/issues/2381#issuecomment-66059483
- https://github.com/golang/go/issues/2381#issuecomment-66059484
試したらたしかにちゃんとHTMLを生成してくれたので、CIのスクリプトに組み込みます。
下記の例ではGitLab CIのYamlファイルからの抜粋ですが、シェルな部分はLinuxなCI環境なら同じように動くはずです。
.gitlab-ci.ymlの抜粋stage: generate-godoc-html variables: ARTIFACTS_DIR: artifacts GODOC_OUT: godoc GODOC_SERVER: localhost:6060 script: - godoc -http=${GODOC_SERVER} & # バックグラウンド実行 - sleep 1 # 一応1秒寝かす # ↓wgetでHTMLを生成する - wget -r -np -N -E -p -k http://${GODOC_SERVER}/pkg/【対象パッケージのPath】 # ↓(あってもなくてもホスティング先で表示はされる)HTML内の"localhost:6060"なPathをホスティング先のものに置換してやる - find ./${GODOC_SERVER}/ -name "*.html" -print0 | xargs -0 sed -i -e "s/http:\/\/${GODOC_SERVER}\/src\/【対象パッケージのPath】/https:\/\/【GitレポジトリのPath】" - find ./${GODOC_SERVER}/ -name "*.html" -print0 | xargs -0 sed -i -e "s/http:\/\/${GODOC_SERVER}/https:\/\/【S3のPath】\/${GODOC_OUT}\/master/" # ↓生成したHTMLらを移動 - mkdir -p ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} - mv ${GODOC_SERVER}/* ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} artifacts: paths: - ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} # 保存 expire_in: 3 days stage: upload-to-s3 script: - aws s3 cp ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} 【対象S3バケットのPath】 --recursive上記
wgetのオプションのクセが強いので解説です。-r : 再帰的にダウンロードする -np : 親ディレクトリを見ない -N : ローカルより新しいものでない限りファイルを取得しない -E : 拡張子.htmlをhtmlファイルに追加 (拡張子がない場合) -p : 各ページに必要なファイルをすべてダウンロード (css, js, 画像) -k : リンクを相対リンクに変換また、HTML内のリンクの整合を取るために上記のように、sedコマンドで
localhost:6060な部分を置換しています。これにより、GoDoc内のリンクやソースコード先に問題なく飛ぶことができます。詳細を把握するためにはローカル環境で検証することをおすすめします。これが走った後、対象のS3ホスティングのURLにアクセスすると、以下のようにGoDocを見ることができました。
参考
- 投稿日:2020-04-30T21:32:06+09:00
_cgo_export.c:3:10: fatal error: 'stdlib.h' file not found の 解決方法(Catalina版)
概要
go get したときに出たエラーです。macOSのバージョンによって解消方法が異なるようなので、Catalina 10.15での解決方法です。
結論
以下コマンドで、環境変数をセットして、該当コマンドを実行したところ、解消しました。
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"内容
DBから gorm structs を生成するツールを検証しようとローカルにインストールしようとしたところ発生しました。
go get -u github.com/smallnest/gen# runtime/cgo _cgo_export.c:3:10: fatal error: 'stdlib.h' file not found調べたこと
全く同じエラーのQiitaがあったのですが、macOS 10.14 で、Catalina 10.15 では解消しませんでした。
https://qiita.com/gold-kou/items/739958f996825cf33b89かなり昔から継続しているissueがあったので読み進めました。
途中の解決方法もOSのバージョンに依存していました。
最後まで読み進めて、Catalina 10.15での解決方法の記載がいくつかありました。
Cコンパイラの環境変数やllvmをアンインストールする方法の記載がありますが、影響範囲が読めなかったので、過去の解決方法でもSDKをopenすることで解消していることから、SDKのパスをCLIで釣って環境変数にセットする方法を採用しました。export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"https://github.com/golang/go/issues/27921
感想
めでたし、めでたし
- 投稿日:2020-04-30T20:47:09+09:00
Goで指定したバージョンのパッケージをインストールしたい
いざ自分がやろうとした際にちょっとだけ迷ったのでメモしときます。
バージョン指定してパッケージをインストール
go get <パッケージ名>@<指定するバージョン>例えば、 gqlgen の v0.10.2 をインストールしたい場合はこうなります。
go get github.com/99designs/gqlgen@v0.10.2本編はここまで。
go.mod でパッケージ管理しているときに自分がした勘違い
ここからはおまけです。
go.mod でパッケージ管理をしている場合、以下のようなファイルがあると思います。go.modmodule github.com/tcshy/hoge go 1.13 require ( github.com/99designs/gqlgen v0.10.2 github.com/aws/aws-sdk-go v1.28.9 github.com/gin-gonic/gin v1.5.0 ・・・ )このとき、 go.mod と同じディレクトリにいる状態で
go get github.com/99designs/gqlgenを打てば go.mod と同じバージョンでインストールされるのかなーと初めは思ったんですが、$ go get github.com/99designs/gqlgen $ gqlgen version v0.11.3見事に最新版になってる。
go.mod 内のバージョン情報も v0.11.3 にアップデートされちゃってる。go.modmodule github.com/tcshy/hoge go 1.13 require ( github.com/99designs/gqlgen v0.11.3 github.com/aws/aws-sdk-go v1.28.9 github.com/gin-gonic/gin v1.5.0 ・・・ )ってことなんでむやみに
go get <パッケージ名>でインストールすると知らない間にバージョンあがってる、って事がありますね。もし間違ったバージョンをインストールしてしまったら
一旦パッケージを削除してから、指定のバージョンを再インストールするといいと思います。
パッケージの削除方法としては、以下みたいにGOPATH配下にあるバイナリ(とソースコードもあれば)を手で消す感じらしいです。
(go uninstall パッケージ名とか使えるようになったらいいですね)$ cd $GOPATH $ rm -f bin/gqlgen $ rm -rf src/github.com/99designs # rm コマンドに -rf つけると確認されずにファイルが消えるんで、GOPAHTHとかパッケージ名などは各自確認してからコマンド打ってね!
- 投稿日:2020-04-30T20:28:20+09:00
WINDOWS OSでAWS LAMBDAにGOをデプロイ
目的
現在、WINDWOS10 OSでGOを用いてWEBサービスを開発しており、
AWS LAMBDAを活用する場面がありましたが、
実際にデプロイを行う際に、無駄にはまったところがあったので、
この度は、実際にデプロイしてみてはまったことや感想を共有したいと思います。AWS LAMBDAにGOをデプロイする方法
基本的でデプロイする方法はAWSの公式ドキュメントに記載されてあります。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html
特にデプロイパッケージ、Handlerがデプロイとは密接に関係があるので、
この辺はしっかりと読みましょう。僕は、しっかり読まずいくつが見逃してしまい、苦労しましたね、、、
僕がはまったところ
①ソースコードをLINUX用しなかったこと
問題
僕は以前、LAMBDAにPYTHON3をデプロイした経験があります。その際は、OSを気にせずデプロイしてもうまく動いてくれたので、
この度、GOをデプロイする際も、特に意識せず適当にアップロードしましたが、当然なことにエラーになりました。解決策
LINUXにビルドすれば問題解決です。
AWS LAMBDAは実際にはコンテナ環境のAmazon Linuxの上動作するので、
デプロイするものもこの環境に合わせてビルドしてアップロードしなければならないとのことです。WINDWOS環境でLINUX用にビルドする方法は、
cmdを用いて、デプロイ対象パスに移動し、
set GOOS=linux←ビルドオプションをLINUXに設定
go build -o main main.go←ビルド ※このファイル名をLAMBDA操作画面にてハンドラとして入力
build-lambda-zip.exe -output main.zip main←ビルドしたファイルをZIPに圧縮するbuild-lambda-zip.exeは
GOPATHから github.com/aws/aws-lambda-go/cmd/build-lambda-zip 入力するとダウンロードできます。②ハンドラ名入力ミス
問題
LINUX用にビルドして、ZIPファイルをアップロード完了してコードテストを行うと
下のようなエラーメッセージが表示されました。
関数の実行から返された結果が以下のエリアに表示されます。
関数から結果を返す方法の詳細については、こちらを参照してください。{ "errorMessage": "fork/exec /var/task/☆☆: no such file or directory", "errorType": "PathError" }解決策
ビルドしたファイルと、LAMBDA操作画面で入力したハンドラ名が異なるため発生するエラーでした。
例えば、go build -o main main.go でビルドした場合は、ハンドラにmainと入力しないとLambdaがパスを認識できず、エラーになります。
必ずビルドしたファイル名とハンドラ名を統一しましょう。
僕は「ハンドラ」と記載されてあったため、
lambda.Startに登録するハンドラを書くのかなと思い、
ビルドパス名と異なる値を入力して、はまりました。。。
- 投稿日:2020-04-30T17:34:35+09:00
NHK番組表API叩いてSlackで通知する
はじめに
なんか面白そうなAPIとか探していたら、NHK番組表APIなるものを発見しました。
NHKっていったら、紅白、高校野球、朝ドラ、大河、天才テレビくんとか忍たま乱太郎とか、昔からそんなイメージ。
最近だと、キングダムとか進撃の巨人なんかも放送してたり、昔のイメージを覆しはじめてますねー。
ちなみに最近見つけた僕のイチオシは「香川照之の昆虫すごいぜ!」です。ちなみに、NHKって不定期放送多くないですかね?
LIFE!とか、見逃しちゃうんですよね〜。
ウッチャンのコント番組見ると、笑う犬思い出しちゃいますよね〜。ミル姉さんとか。ということで、今回はLambda使ってサーバーレスな感じで、見たい番組・好きなタレントが出ている番組をSlackに通知する、お手軽アプリケーション作ろうかと思います。
言語はGo(勉強中)です。
環境など
- AWS Lambda
- Amazon CloudWatch Events(cron式)
- 言語:Go
- NHK番組表API(Program List API)
NHK番組表APIについて
APIを使用するにあたって、こちらから、ユーザ登録が必要です。
メールアドレスの登録をした後、アプリの登録をしてAPIキーを発行する必要があります。今回使用するProgram List APIの仕様確認やAPIの試打は、こちらから。
Goの実装
ディレクトリ構成
以下のような構成にしました。
% tree . └── nhk_api_test ├── README.md ├── cmd │ └── nhk_api_test │ └── main.go // main関数 ├── go.mod ├── go.sum ├── payload.go // Slackのwebhookで使う構造体 ├── proglamList.go // 番組表APIの構造体 └── webhook.go // メインロジックそれぞれの実装について、ざっくり説明します。
Go触ってて、学んだところとか。main.go
main.gopackage main import ( "github.com/aws/aws-lambda-go/lambda" nhk "github.com/tkl4230/nhk_api_test" ) func webhook() { nhk.Webhook() } func main() { lambda.Start(webhook) }特に主だった処理はしていません。
Lambdaの開始と
webhook.goのWebhook()を呼び出しているだけです。
goでは、javaとかみたくインスタンス.関数()ではなくpackage.関数()で呼び出します。
外部パッケージの関数を呼び出す時は、その関数の頭文字は大文字で宣言する必要があります。関数名が小文字の場合は、外部パッケージからは見えなくなり、同一パッケージからの呼び出しのみ可能となります。
同一パッケージ内の関数呼び出しの場合関数名で呼び出せます。あと、importで宣言しているlambdaライブラリを使用するためには以下のコマンド叩いておく必要があります。
go get github.com/aws/aws-lambda-go/lambdaproglamList.go
Program List APIで返ってくるjsonをマッピングするための構造体を宣言してます。
proglamList.gopackage nhk_api_test import "time" type Program struct { List List `json:"list"` } type List struct { E1 []E1 `json:"e1"` } type E1 struct { ID string `json:"id"` EventID string `json:"event_id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Area Area `json:"area"` Service Service `json:"service"` Title string `json:"title"` Subtitle string `json:"subtitle"` Content string `json:"content"` Act string `json:"act"` Genres []string `json:"genres"` } type Area struct { ID string `json:"id"` Name string `json:"name"` } type Service struct { ID string `json:"id"` Name string `json:"name"` LogoS Logo `json:"logo_s"` LogoM Logo `json:"logo_m"` LogoL Logo `json:"logo_l"` } type Logo struct { URL string `json:"url"` Width string `json:"width"` Height string `json:"height"` }各フィールドの後ろで
json:"xxx"と記述することで、jsonを解析した時にjsonのプロパティとgoのフィールドを紐づけてくれます。payload.go
Webhookに設定するPayloadの構造体を宣言して、Slackに通知するメッセージ内容の作成と設定をしています。
payload.gopackage nhk_api_test import ( "bytes" "fmt" "text/template" "time" ) const timeLayout = "15:04" // Payload.Text用のテンプレート var tpl = ` {{ range $key, $val := . -}} 日時: {{ $key }} {{ range $index, $var := $val }} 番組: {{ $var.Title }} 出演者: {{ $var.Act }} 時間: {{ timeFmt $var.StartTime }} 〜 {{ timeFmt $var.EndTime }} {{ end }} ------------------------------ {{ end }} ` type payload struct { Channel string `json:"channel"` UserName string `json:"username,omitempty"` Text string `json:"text"` IconUrl string `json:"icon_url,omitempty"` IconEmoji string `json:"icon_emoji,omitempty"` } // Payload.Textの作成と設定 func (p *payload) setText(m map[string][]E1) { // テンプレート内で使用する関数の設定 fMap := template.FuncMap{ "timeFmt": timeFmt, } // テンプレートの割当 tpl := template.Must(template.New("tpl").Funcs(fMap).Parse(tpl)) // テンプレートからデータ出力 var buffer bytes.Buffer if err := tpl.Execute(&buffer, m); err != nil { fmt.Println(err) } // 出力したデータを文字列化 p.Text = buffer.String() } // テンプレートに渡す関数。 // 番組表APIの日時の形式を hh:mm にしてくれる。 func timeFmt(date time.Time) string { return date.Format(timeLayout) }goにはクラスという概念がないため、構造体に属するメソッドを定義する時は、以下のように書き、レシーバ型に構造体を指定します。
func (レシーバ値 レシーバ型) 関数名()上記の書き方で、クラス内のメソッドを宣言するように、構造体に属したメソッドを宣言することになります。
なので、このファイルの
func (p *payload) setText()はpayload構造体に属したメソッドの宣言になります。普通の
func 関数名()は、パッケージに属する感じですかね。呼び出す時も
パッケージ名.関数()ではなく、構造体を生成して、構造体.関数()になります(具体的にはこちらの3-a(詳細はロジック内)を参照)。あと
*は、ポインタ渡すかどうかです。Javaでいう、参照渡しに近いかと思います(要勉強)。webhook.go
内容としては、主な処理を書いてます。
処理に必要な値は環境変数として外出ししているものもあります。
処理の流れとしては、以下の通りです。
- 現在日取得
- TZがUTCなので注意です。
- 取得するjsonがyyyy--mm-dd.jsonなので、そこで使用します。
- 以下の処理はループとなり、現在日を基準に環境変数:TERMに設定した回数、翌日以降のデータ取得を行います。
- NHK番組表APIをコール
- 取得したjsonの解析
- jsonデータの中に環境変数:PERFORMER, PROGRAM_NAMEに部分一致する番組があれば、その番組データをmapに格納。
- Webhookに設定するpayload構造体の生成。
- mapに番組データがある場合、テンプレートを元にメッセージ生成
- Webhook実行
webhook.gopackage nhk_api_test import ( "encoding/json" "fmt" "log" "net/http" "net/url" "os" "strconv" "strings" "time" ) var ( // 番組表APIのAPIキー apiKey = os.Getenv("API_KEY") // 探したい出演者(カンマ区切り) performer = os.Getenv("PERFORMER") // 探したい番組名(カンマ区切り) programName = os.Getenv("PROGRAM_NAME") // 探したい期間(Max7日) term = os.Getenv("TERM") // WebhookのURL webhookURL = os.Getenv("WEBHOOK_URL") // Slackのチャンネル名 channel = os.Getenv("CHANNEL") ) const ( // 日付のフォーマットProgram List APIのjson名で使用 dateLayout = "2006-01-02" // URL programListURL = "https://api.nhk.or.jp/v2/pg/list/130/e1/%s.json?key=%s" ) // Webhook メインロジック func Webhook() { // 今日の日付取得 // LambdaはUTCなので、9H補正 now := time.Now() now = now.Add(time.Duration(9) * time.Hour) // 期間設定を数値に変換 term, _ := strconv.Atoi(term) // 見たい番組表格納用マップ // K: 日付、V: E1構造体 programMap := make(map[string][]E1) // API実行(3日先まで) for i := 0; i < term; i++ { // URIに渡す日付を計算 d := now.AddDate(0, 0, i) date := d.Format(dateLayout) // 番組表APIの呼び出し reqURL := fmt.Sprintf(programListURL, date, apiKey) resp, err := http.Get(reqURL) if err != nil { log.Println("エラー発生") log.Println(fmt.Sprintf("Error:%s", err)) log.Println(fmt.Sprintf("Request:%s", reqURL)) return } if resp.StatusCode != 200 { log.Println("ステータスコード異常") log.Println(fmt.Sprintf("Status code:%v", resp.StatusCode)) log.Println(fmt.Sprintf("Request:%s", reqURL)) return } defer resp.Body.Close() // 番組表APIレスポンスのjson解析 var p Program if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { log.Println("json解析でエラー発生") log.Println(err) return } // 見たい番組名・出演者があれば、pListに格納 pList := make([]E1, 0) for _, e1 := range p.List.E1 { if isNoticeTarget(e1) { pList = append(pList, e1) } } // 見たい番組名・出演者がなければ、翌日へ。 l := len(pList) if l == 0 { log.Printf("%vに、探してる番組・出演者はありませんでした。\n", date) continue } log.Printf("%vに、%v件の番組が見つかりました。\n", date, l) // マップに格納 programMap[date] = pList } // payloadの生成 payload := payload{ Channel: channel, UserName: "nhk番組取得お知らせ", IconEmoji: ":ghost:", Text: "探している番組は見つかりませんでした。", } // 見たい番組が見つかれば、メッセージ(Text)を更新。 if len(programMap) > 0 { payload.setText(programMap) } // jsonのエンコード jsonByte, err := json.Marshal(payload) if err != nil { log.Println("jsonのエンコードでエラー発生") log.Println(err) return } // Webhookの呼び出し _, err = http.PostForm( webhookURL, url.Values{"payload": {string(jsonByte)}}, ) if err != nil { log.Println("Webhookの実行でエラー発生") log.Println(err) return } } // Webhookで通知する対象か判定 func isNoticeTarget(e1 E1) bool { // 出演者のチェック performerList := strings.Split(performer, ",") for _, v := range performerList { if strings.Contains(e1.Act, v) { return true } } // 番組のチェック programNameList := strings.Split(programName, ",") for _, v := range programNameList { if strings.Contains(e1.Title, v) { return true } } return false }所々出てくるアンスコのところは戻り値で不要なものを読み捨ててます。
変数宣言のところでos.Getenv("XXX")の記載がありますが、こちらで環境変数を読み込んでいます。AWSの設定
デプロイパッケージの作成
こちら、Lambdaの方に設定します。
GOOS=linux go build -o webhook main.go zip webhook.zip webhookLambda
nhk_program_webhookという関数名にしました。
トリガーにCloudWatch Eventsを設定しています。
ハンドラはwebhookとしています。
先ほど作成したデプロイパッケージはこちらからアップロードします。
環境変数6つ。
なんとなく思いついたものを設定してみました。CloudWatch Events
画面はLambdaの管理画面ですけど、CloudWatch Eventsの設定です。
毎日22:10(日本で7:10)に動くようにしてます。動かしてみた
CloudWatchではなく、テスト実行ですが、動きました。
(CloudWatchのトリガーからも動くことは確認済みです)なんと、昆虫すごいぜ! 再放送してます!!
すでに4回目っていうことで、乗り遅れてますが、要チェックです!!ということで、NHK番組表API叩いてSlack通知する、でした。
ありがとうございました。
- 投稿日:2020-04-30T17:29:29+09:00
GitHubのSecretsを活用して、GitHub Actionsで安全に機密情報を扱う
はじめに
GitHub repositoryにyamlファイルを書くことで、Commit時やPR反映時等色々な場面で利用できるGitHub Actions。
そんなGitHub Actionsに処理を書いていると、外部サービスと連携するためにアクセストークンのような機密情報を保持したくなります。
この記事では、その機密情報の扱うためのSecretsの使い方について記載します。GitHub repositoryのSecrets
使い方は簡単で、GitHub内のsecretを設定したいリポジトリのページを開き、下記Settingsから設定を行います。
例えばSecretSettingという設定を追加した場合は、以下のように
secrets.SecretSettingを変数に代入することで、run内に書いたスクリプトでsecretが扱えるようになります。- name: Access to secret env: SECRET_SETTING: ${{ secrets.SecretSetting }} run: | ##access to $SECRET_SETTING if this script is bash詳細は公式ページを参照ください。
注意
上記Settingsを扱えるのは、リポジトリのオーナーのみです。GitHub APIを利用してより汎用的に
上記の方法でも事足りるのですが、Secretsの設定はリポジトリごとにしかできず、さらにその権限はリポジトリのオーナーごととなっています。
大量にリポジトリがある場合は骨の折れるので、APIを利用して設定を行います。APIを利用する方法の一つとして、tokenを発行する方法があります。Private repositoryを使う場合は
repo Full control of private repositoriesが必要です。tokenが取得できたらあとはAPIを叩くだけ。
APIの仕様はこちら。Updateする際はpublic keyを取得した上でLibSodiumを利用してkeyを暗号化するのが特徴です。ざっくりシーケンスはこんな感じ
また、GitHub APIを利用するためのgolangライブラリが公開されていますので、プログラムで利用することも楽ちん。
上記を紹介した記事Github Actions Secrets API を使って Go で Secrets を更新するを参考に、golangでのサンプルコマンドを作成しています。参考
公式ページ
GitHub ヘルプ: 暗号化されたシークレットの作成と保存
GitHub Developer: REST API v3 Secrets
Libsodium documentationその他参考
Github Actions Secrets API を使って Go で Secrets を更新する
GitHub APIの仕組みを知り、APIでrepositoryを作成しよう
- 投稿日:2020-04-30T16:04:40+09:00
(Go言語・WebAssembly)Webページで並列処理はできるのか?
前書き
Go言語を勉強する中でgoルーチンをWebAsseblyにコンパイルした場合、
ブラウザ上で並列処理はきちんと行っているのか気になったので確認しました。結果
以降はソースコードの記述です。
コンパイルの方法などは以前の記事を参照してください。
(Go言語)WebAssemblyで簡単なWebサイトを作ってみた実行環境
OS:Windows 10 64bit
言語環境:go version go1.14.1
ブラウザ:Firefox バージョン: 74.0ソースコード
htmlファイル該当部分のみ抜粋 <body> <button type="submit" id="button1" >START</button><br> <div id="time1" >時刻1:</div> <div id="time2" >時刻2:</div> <div id="time3" >時刻3:</div> </body>時刻を取得し文字列を返すfunc Get_time() string { now := time.Now() str := strconv.Itoa(now.Hour()) + ":" + strconv.Itoa(now.Minute()) + ":" + strconv.Itoa(now.Second()) return str }メイン関数package main import ( "strconv" "syscall/js" "time" ) func main() { //イベントの管理- get_three_time("button1", "time1", "time2", "time3") <-make(chan struct{}, 0) }実装処理func get_three_time(id string, output1 string, output2 string, output3 string) { //idから対象のオブジェクトを取得 object := js.Global().Get("document").Call("getElementById", id) out1 := js.Global().Get("document").Call("getElementById", output1) out2 := js.Global().Get("document").Call("getElementById", output2) out3 := js.Global().Get("document").Call("getElementById", output3) //ゴールーチンで実行し action := js.FuncOf(func(this js.Value, args []js.Value) interface{} { go func() { time.Sleep(time.Duration(0) * time.Second)//0秒待機 out1.Set("innerText", "時刻1:"+Get_time()) }() go func() { time.Sleep(time.Duration(2) * time.Second)//2秒待機 out2.Set("innerText", "時刻2:"+Get_time()) }() go func() { time.Sleep(time.Duration(4) * time.Second)//4秒待機 out3.Set("innerText", "時刻3:"+Get_time()) }() return nil }) //アクションの呼び出し object.Call("addEventListener", "click", action) }結果・感想
Go言語+WebassemblyでGoルーチンによる並列処理を実装してみました。
もともとJavascritpには同期/非同期処理があり、それらと動作の違いはないかもしれません。CPU視点で見れば、マルチスレッドによる非同期処理が役に立つ場面が存在しますが、
はたしてブラウザ上のバイナリコードが、複数のコア上で動作するのか?
という疑問が残ります。仮説ですが、
スクリプトの実行がブラウザの1プロセス上として動作するならマルチスレッドとはなりませし、ブラウザやCPUアーキテクチャに依存する可能性があります。しかしながら、ソースコードで並列処理を実装できることはGo言語のメリットです。
フロントエンドにJavascriptやPHPなくGo言語を採用する理由の一つになるのではないでしょうか次はGo言語でAPIサーバを構築して、それをもとにSPAなwebページの構築を目指しています。
できる限りGo言語でWEBシステムを構築してくために少しづつですが前に進んでいる実感があります。
これからも頑張っていこうと思います。ここまで読んできただきありがとうございました。
- 投稿日:2020-04-30T12:06:00+09:00
Goで作成したc-sharedなライブラリでexportしたはずの関数が見つからない
- 投稿日:2020-04-30T11:51:25+09:00
【Go】echoでハマったこと
echoを使っていてハマったことまとめ
概要
Go言語のWebフレームワークであるechoを使っていてハマったことについて、メモとしてまとめていこうと思います。
middleware
CORS
CORSWithConfig()の戻り値とe.Use()の引数が不一致
事象
自分の作ったフロントエンドからのみアクセスできるようにカスタムCORS設定をしようとして以下のようなコードを作成しました。
package main import ( "net/http" "github.com/labstack/echo/middleware" "github.com/labstack/echo/v4" ) func main() { e := echo.New() e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"http://localhost:3000"}, AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}, })) e.GET("/public", public) e.Logger.Fatal(e.Start(":1323")) } func public(c echo.Context) error { return c.String(http.StatusOK, "hello public!") }すると、以下のようなエラーに遭遇しました。
cannot use middleware.CORSWithConfig(middleware.CORSConfig literal) (type "github.com/labstack/echo".MiddlewareFunc) as type "github.com/labstack/echo/v4".MiddlewareFunc in argument to e.Useどうやらe.Use()では
v4のMiddlewareFunを引数として要求しているようですが、
CORSWithConfig()の戻り値はv4ではないMiddlewareFunらしいです。解決策
最初は、キャストすればいいかと思ったのですが、うまくいきませんでした。
そこで、少し調べてみるととても簡単なことで、自分がGoのバージョン管理などに不慣れなことが原因でした。問題の箇所は、以下のimoprt文になります。
"github.com/labstack/echo/middleware" "github.com/labstack/echo/v4"ここで、
echoについてはv4を指定してインポートしているのですが、middlewareは何も指定しておりません。
そのため、インポートされたmiddlewareとechoの間でバージョンの不一致が起きてしまい先ほどのエラーが生じたようです。解決策は、簡単で以下のように
middlewareにもv4を付けてあげるだけ。"github.com/labstack/echo/v4/middleware"これでエラーは出なくなり、無事にCORSの設定ができました。
終わりに
今後も
echoを使う中で自分がハマったことや、コメントでいただいたハマりポイントについて書いていきたいと思います。
何かご指摘や、ハマりポイントなどありましたらコメントよろしくお願いします。
- 投稿日:2020-04-30T09:34:16+09:00
GoのCommonMistakes/Using reference to loop iterator variableに対応するC++/Rustのコード
に、
CommonMistakes/Using reference to loop iterator variable:
func main() { var out []*int for i := 0; i < 3; i++ { out = append(out, &i) } fmt.Println("Values:", *out[0], *out[1], *out[2]) fmt.Println("Addresses:", out[0], out[1], out[2]) }に対応するC++とRustのコードが書かれているけど、上記のGoコードでは別に
iの寿命はforループで切れるわけではないので、C++の未定義動作になるコードやRustの "borrowed value does not live long enough" エラーになるコードとは対応していないと思う(コメントにあるエラーの方が対応としては適切だと思う。多分)。
- 投稿日:2020-04-30T00:36:23+09:00
Golang TempDir,TempFile作成
Golang TempDir,TempFile作成する
main.gopackage main import ( "fmt" "io/ioutil" "os" ) func main() { dir, _ := ioutil.TempDir("", "aaa") fmt.Println(dir) fp, _ := ioutil.TempFile(dir, "xxx") fpath := fp.Name() fmt.Println(fpath) fp2, _ := ioutil.TempFile(dir, "yyy") fpath2 := fp2.Name() fmt.Println(fpath2) fp.Close() fp2.Close() os.RemoveAll(dir) } //C:\Users\####\AppData\Local\Temp\aaa661041139 //C:\Users\####\AppData\Local\Temp\aaa661041139\xxx642705078 //C:\Users\####\AppData\Local\Temp\aaa661041139\yyy013873821
- 投稿日:2020-04-30T00:09:30+09:00
ソートを勉強してみる by Go ②
ソートを勉強してみる by Go ① の続き。
クイックソートしてみました。意外とすんなりいきました。package main import ( "fmt" "math/rand" ) func main() { length := 20 list := make([]int, length) for i := 0; i < len(list); i++ { list[i] = (rand.Int() >> 56) } sort(list, 0, len(list)-1) for i := 0; i < len(list); i++ { fmt.Println(list[i]) } } func sort(list []int, start int, end int) { if start >= end { return } var stack int left := start right := end - 1 marker := end for { for ; left <= marker; left++ { if list[left] > list[marker] { break } if left == marker { sort(list, start, end-1) return } } for ; right >= left; right-- { if right == left { stack = list[right] list[right] = list[marker] list[marker] = stack sort(list, start, right-1) sort(list, right+1, end) return } if list[right] < list[marker] { stack = list[right] list[right] = list[left] list[left] = stack break } } } }










