- 投稿日:2021-01-11T23:53:15+09:00
GoLand IDEをUbuntuにインストール
無料バージョンではありますが、GoLandエディタを自分のUbuntu環境にダウンロードしてみました。
参考にしたのは以下のサイトです。
Install Jetbrains GoLand Go IDE on Ubuntu
在Ubuntu安装JetBrains Goland IDEsnapdを使ってダウンロードする方法もありますが、今回はサイトからダウンロードする方向でいきました。
前提
- 環境がUbuntu
- Goはローカルにすでにダウンロード済み
ダウンロード
Linux用のバージョンをこちらでダウンロードします。
https://www.jetbrains.com/ja-jp/go/download/#section=linuxダウンロードしたら展開して
opt
フォルダに移します。
もちろん他のところでもOKらしいです。
移動したらシェルファイルを実行して立ち上げます。$ cd Downloads $ sudo tar xzf goland-2020.3.1.tar.gz -C /opt $ ls -l /opt total 12 drwx--x--x 4 root root 4096 11月 20 01:33 containerd drwxr-xr-x 8 root root 4096 1月 11 23:19 GoLand-2020.3.1 drwxr-xr-x 3 root root 4096 11月 20 10:46 google $ /opt/GoLand-2020.3.1/bin/goland.shこのような画面が出てきますが、
Do not import settings
を選んで進みます。ライセンスは持っていないので、無料バージョンを使います。
Evaluate for free
を選んで持っていないほうで進めると、最終的にこの画面にたどりつきます。毎回シェルファイルを実行して立ち上げるのは面倒なので、以下のようにアプリケーションディレクトリにファイルを作成します。今回はバージョンが
2020.3.1
なので、以下のように記入します。バージョンが変わった場合は違うバージョン名を記入してください。$ cd /usr/share/applications/ $ sudo vi Goland.desktop [Desktop Entry] Name=Goland Comment=Goland IDE Exec=/opt/GoLand-2020.3.1/bin/goland.sh Icon=/opt/GoLand-2020.3.1/bin/goland.png Type=Application Terminal=false Encoding=UTF-8作ったファイルに権限を与えます。
$ sudo chown 自分のユーザー名 Goland.desktop $ sudo chmod 755 Goland.desktopこれでアプリケーション内から開けるようになります。
ちなみにDesktopでも開きたい場合は、Desktopに先程のファイルをコピればいいです。
$ cp Goland.desktop /home/user/Desktop/プログラムを書いてみる
New Projectを選びます。
すでに入っているGOROOTをもとにCreateします。
毎度おなじみの
Hello World
を作ります。上のRunメニューからRunを押すと
go build hello.go
が出てくるので、これをクリックします。Hello Worldが出力されました!
以上です。無料バージョンなんで30日しか使えないぽいんですけどね、、、
- 投稿日:2021-01-11T23:47:49+09:00
GoのHTTP実装を読んだ知見をまとめる~Channel編~
はじめに
前回に引き続き、GoのHTTP実装と絡めて書く。今日はChannel。
経緯はこちらChannel
Goにはunbuffered channelとbuffered channelの2種類があります。
unbuffered channelの場合、send/recvどちらかがreadyになっていない場合ブロックされることから可能な限りbuffered channelを使うことが好まれ、GoのHTTP実装でもbuffered channelが多く使われています。が、buffered channelの場合、send/recvの後に処理を続けても実際に相手側が処理をしているとは限らないため、注意しないといけない場合があります。
それをとても簡単に示した例が以下となります。
以下では、
resc: make(chan responseAndError)
が利用されており、unbuffered channelであるため、期待通り足し算の結果を受け取る(簡単のため1を足しているだけなので、1~10までの値を任意の順に受け取る)ことができます。仮に
resc: make(chan responseAndError, 1)
にて、buffered channelに変更した場合、recvでselectしているselect { case <-adder.close: return false case rc := <-adder.resc:のどちらが処理続行されるかは不定であり、約半分くらい失敗を受け取ることになります。
これはspecに以下のようにあるように処理続行可能になったものが複数ある場合はランダムに選ばれるためです。If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
package main import ( "fmt" "sync" "time" ) const debug = false type responseAndError struct { response int err error } type adder struct { // for cancel or timeout close chan struct{} resc chan responseAndError } func newAdder() *adder { return &adder{ close: make(chan struct{}), // must use unbuffered channel resc: make(chan responseAndError), // if use buffered channel, we would get FAILED log // resc: make(chan responseAndError, 1), } } func (adder *adder) handle(a int, b int) bool { adder.add(a, b) time.Sleep(time.Second * 1) select { case <-adder.close: return false case rc := <-adder.resc: if debug { fmt.Printf("result: %d, err: %v", rc.response, rc.err) } return true } } func (adder *adder) add(a int, b int) { go func(a int, b int) { defer func() { close(adder.close) }() res := a + b adder.resc <- responseAndError{res, nil} }(a, b) } func main() { wg := &sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { adder := newAdder() ok := adder.handle(i, 1) if !ok { fmt.Println("================FAILED============") } wg.Done() }(i) } wg.Wait() }ソースはgithubにあげてます。
HTTP実装
上の例は実はHTTPクライアント実装を参考にしています。
具体的には、
recv側(handleメソッド)はPersistent Connectionがレスポンスを受け取るのを別goルーチンで待機する部分
send側(addメソッド)はPersistent Connectionが無限ループでreadをする処理
をエッセンスだけ取り出して書いてます。補足すると、Persistent Connectionが無限ループでreadをしている部分では、connectionが再利用されないと判断された場合は今回のサンプル同様にcloseチャネルをcloseすることで通知します。その時にコネクションも実際にcloseします。
unbuffered channelにしなくてはいけないのはコメントにも記載されており、サンプルと同じ理由です。以下引用します。
rc.ch
は今回のサンプルのadder.resc
に該当します。// Put the idle conn back into the pool before we send the response // so if they process it quickly and make another request, they'll // get this same conn. But we use the unbuffered channel 'rc' // to guarantee that persistConn.roundTrip got out of its select // potentially waiting for this persistConn to close. alive = alive && !pc.sawEOF && pc.wroteRequest() && replaced && tryPutIdleConn(trace) if bodyWritable { closeErr = errCallerOwnsConn } select { case rc.ch <- responseAndError{res: resp}: case <-rc.callerGone: return } // Now that they've read from the unbuffered channel, they're safely // out of the select that also waits on this goroutine to die, so // we're allowed to exit now if needed (if alive is false) testHookReadLoopBeforeNextRead() continue気になるところ
上記サンプルでは、
time.Sleep(time.Second * 1)
を入れることでselect
時には
adder.close
、adder.resc
両方とも処理可能である状態を作り出しましたが、仮に両方ともブロックされる状態でselect
に到達した場合はどうなるんでしょう。気になっため、
time.Sleep(time.Second * 1)
をres := a + b
直後に移動しました。その場合、buffered channelにしてもエラーになることはありませんでした。つまり、buffered channelにsendした時、ブロックしていた
select
ではbuffered channelがrecv可能と判断され選択されていたことになります。10回ではなく10000000回実行しても同様であり、試しに
Gosched()
を仕込ませたりしましたが結果は変わりませんでした。unbuffered channelはsend/recv待ちでブロックされているものは対象のchannelに対してrecv/sendされれば必ずselectにて選択されます。今回のサンプルではbuffered channelも同様でした。
が、specに書いてませんし、調べた限り記載を見つけられなかったので、保証されない挙動と思っていた方がいいと思います。いつかGoのスケジューラの実装も読むつもりなのでその時に確認しようと思いますが、どなたかご存知の方いたら教えて頂けると幸いです。
- 投稿日:2021-01-11T18:17:59+09:00
サイト「Go から Azure 仮想マシンをデプロイする」をやってみた
サイト「Go 開発者向けの Azure | Microsoft Docs」の「クイックスタート」の「Go から Azure 仮想マシンをデプロイする | Microsoft Docs」をmacOS上にて実際に動作させてみました。
前提条件
本文中にて、IDなど具体的な値や項目は日本語テキストに置き換えたり省略してあります。
Azure Cloud Shell を起動する
私はAzure Cloud Shellを使用せず、ローカルにAzure CLIをインストールして動作させました。
Azure CLIのインストール手順は、次の記事にて投稿しています。
Azure CLIのバージョンを確認します。本チュートリアルでは2.0.28以降が必要です。
% az version { "azure-cli": "2.17.1", "azure-cli-core": "2.17.1", "azure-cli-telemetry": "1.0.6", "extensions": {} }Azure SDK for Go のインストール
Goのバージョンを確認します。Azure SDK for Goでは1.8(Azure Stack プロファイルだと1.9)以降が必要です。
% go version go version go1.15.6 darwin/amd64Azure SDK for Goの最新版を取得&更新します。
% go get -u -d github.com/Azure/azure-sdk-for-go/...サービス プリンシパルの作成
Azureへのログイン
作成コマンドを実行する前に、Azureにログインしておく必要があります。
ログインしているかどうか確認します。
% az account showログインしていない場合に表示されるメッセージ
Please run 'az login' to setup account.
ログインしている場合に表示されるメッセージ
{ "environmentName": "環境名", "homeTenantId": "テナントID", "id": "サブスクリプションID", ... "user": { "name": "メールアドレス", "type": "user" } }ログインしていない場合は、ログインします。Webブラウザーが起動してAzureへのログインが促されます。Azureアカウントとパスワードを入力してログインします。
% az login The default web browser has been opened at https://login.microsoftonline.com/common/oauth2/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`. You have logged in. Now let us find all the subscriptions to which you have access...ログインに成功すると、Webブラウザーではドキュメントサイトに遷移し、ターミナルではログイン情報が出力されます。Webブラウザーを閉じて大丈夫です。
[ { "cloudName": "クラウド名", "homeTenantId": "テナントID", "id": "サブスクリプションID", ... "user": { "name": "メールアドレス", "type": "user" } } ]サービス プリンシパルの作成
サービス プリンシパルの作成して、ファイル「quickstart.auth」に格納します。
% az ad sp create-for-rbac --sdk-auth > quickstart.authquickstart.authの内容を確認します。
% cat quickstart.auth { "clientId": "クライアントID", "clientSecret": "クライアントシークレット", "subscriptionId": "サブスクリプションID", "tenantId": "テナントID", ... }サービス プリンシパルの設定
環境変数「AZURE_AUTH_LOCATION」に「quickstart.auth」のファイルパスを設定します。
% export AZURE_AUTH_LOCATION=/Users/ユーザー名/quickstart.auth環境変数に値が格納されていることを確認します。
% echo $AZURE_AUTH_LOCATION /Users/ユーザー名/quickstart.authコードの入手
サンプル コードの入手は、ドキュメント通りです。
% go get -u -d github.com/azure-samples/azure-sdk-for-go-samples/quickstarts/deploy-vm/...コードの実行
サンプル コードのディレクトリーに移動します。
% cd $GOPATH/src/github.com/azure-samples/azure-sdk-for-go-samples/quickstarts/deploy-vmディレクトリーの内容は次の通りです。
% ls -1 README.md main.go vm-quickstart-params.json vm-quickstart-template.json私の場合、サンプル コードを実行する前に、取得したAzure SDK for Goとサンプルのコード群で正常にコンパイルできるか確認しました。
% go build main.gomain.goファイルにはmain()関数が含まれているため、コンパイルすると実行バイナリー「main」ファイルが生成されます。
% ls -1 README.md main main.go vm-quickstart-params.json vm-quickstart-template.jsonドキュメントの方法でサンプル コードを実行する場合は、次の通りです。
% go run main.go作成された実行バイナリーを実行する場合は、次の通りです。
% ./main実行ログが出力されます。
2021/01/10 14:38:43 Created group: GoVMQuickstart 2021/01/10 14:38:43 Starting deployment: VMDeployQuickstart 2021/01/10 14:39:49 Completed deployment VMDeployQuickstart: Succeeded 2021/01/10 14:39:49 Log in with ssh: quickstart@IPアドレス, password: パスワード
上記の結果は、2回目の実行で得られたものです。
1回目の時は、2行目の「Starting deployment」で数分待たされたため、[Ctrl]+[C]で停止しました。
どうやら2行目では、Marketplaceから仮想マシンを取得していたようで、そのせいで時間がかかっていたようです。
この後、4行目の情報を使いますので、誤ってターミナルを閉じたりしないように。
各構成要素の作成を確認
学習も兼ねて、各構成要素が作成されたかどうかは「Azure ポータル」にログインして、目視にて確認しました。
- リソース グループ
- サービス プリンシパル
- 仮想マシン
- 仮想ネットワーク
仮想マシンにログイン
仮想マシンが作成されたかどうか、実行ログの4行目の値を使って、SSH接続で仮想マシンにログインしてみます。途中にpasswordを聞枯れるのでパスワードを入力して[Enter]キーを押下してください。
% ssh quickstart@IPアドレス The authenticity of host 'IPアドレス (IPアドレス)' can't be established. ECDSA key fingerprint is SHA256:SHA256値. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'IPアドレス' (ECDSA) to the list of known hosts. quickstart@IPアドレス's password: パスワード Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.15.0-1098-azure x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage 0 packages can be updated. 0 updates are security updates. The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details.仮想マシンにてumaneコマンドを実行してみます。
quickstart@QuickstartVM:~% uname -a Linux QuickstartVM 4.15.0-1098-azure #109~16.04.1-Ubuntu SMP Wed Sep 30 18:53:14 UTC 2020 x86_64 x86_64 x86_64 GNU/LinuxローカルマシンはmacOSであり、Linuxの仮想マシンが作成されていることが確認できます。
仮想マシンからログアウト
仮想マシンからログアウトします。
quickstart@QuickstartVM:~% exit logout Connection to IPアドレス closed.
クリーンアップしています
リソース グループの削除
対象のリソース グループが存在していることを確認します。
% az group list --output table Name Location Status --------------------------------- ------------- --------- GoVMQuickstart eastus Succeededリソース グループの削除を実行します。
% az group delete -n GoVMQuickstart
対象のリソース グループが削除されていることを確認します。
% az group list --output table Name Location Status --------------------------------- ------------- ---------サービス プリンシパルの削除
ファイル「quickstart.auth」にて、項目「clientId」の値「クライアントID」をコピーします。
% cat ~/quickstart.auth { "clientId": "クライアントID", ... }環境変数「CLIENT_ID_VALUE」にclientIdの値を設定します。
% export AZURE_AUTH_LOCATION=クライアントID環境変数に値が格納されていることを確認します。
% echo $CLIENT_ID_VALUE クライアントIDサービス プリンシパルの削除を実行します。
% az ad sp delete --id ${CLIENT_ID_VALUE} Removing role assignmentsコードの詳細 以降
ソース コードには一通り目を通し、ココこそ力を入れて解説しようかと思ったのですが、公式ドキュメントのコード解説が詳しいので、出番なし!(素晴らしい!)
- 投稿日:2021-01-11T18:10:23+09:00
X-Roadを手軽に試せるSecurity Server Docker Imageが出たので試してみる1
はじめに
X-Roadは、エストニアの政府機関や、政府機関と連携を行う企業(銀行など)の間で使われている情報連携を行うための技術です。
X-Roadはソースコードはgithub.com/nordic-institute/X-Roadにあるものの、Docker Imageやダウンロードしてすぐ動くパッケージがなかったので使ってみた系の記事があまりありませんでした。
最近、Planetway社でX-Roadを手軽に試せるDocker Imageを公開しました[hub.docker.com]。
この記事では、X-Roadを知らない方向けに、事前知識を端折りながらまずはX-Roadを使ってみることから始めます。# full disclosure 私はPlanetway社の中の人です
docker-compose upでX-Road Security Serverを立ち上げる
Docker Imageが公開されているのです。
何はともあれローカルで立ち上げてみましょう。作業用のディレクトリを作り、Docker Hubのドキュメントにある内容をコピーして
docker-compose.yml
ファイルを作成します。% cat docker-compose.yml version: '3.7' services: ss01: image: planetway/xroad-securityserver:6.24.1-1 command: bash -c "/files/initdb.sh && /files/cmd.sh" depends_on: - postgres environment: # JP-TEST or JP - PX_INSTANCE=JP-TEST - PX_MEMBER_CLASS=COM - PX_MEMBER_CODE=0170121212121 - PX_SS_CODE=qiita-demo-01 - PX_TSA_NAME=TEST of Planetway Timestamping Authority 2020 - PX_TSA_URL=https://tsa.test.planetcross.net - PX_TOKEN_PIN=jXq+rlg2VS - PX_ADMINUI_USER=admin - PX_ADMINUI_PASSWORD=Secret222 - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - PX_SERVERCONF_PASSWORD=serverconf - PX_MESSAGELOG_PASSWORD=messagelog - PX_OPMONITOR_PASSWORD=opmonitor - PX_POPULATE_DATABASE=true - PX_ENROLL=true ports: - "2080:2080" - "4000:4000" - "5500:5500" - "5577:5577" - "5588:5588" - "8000:80" - "8443:443" volumes: # .p12 files and keyconf.xml - "px-ss-signer:/etc/xroad/signer" # mlog.zip files are stored here, and ./backup contains backups - "px-ss-xroad:/var/lib/xroad" postgres: image: postgres:10 environment: POSTGRES_PASSWORD: password ports: - "5432:5432" volumes: - "px-ss-postgres:/var/lib/postgresql/data" volumes: px-ss-postgres: px-ss-signer: px-ss-xroad:Docker Hubのドキュメントでは環境変数を...と省略しているところはとりあえず適当に埋めます。
PX_SS_CODE
はX-Roadメンバー(X-Roadに参加する組織)の中のSecurity Serverを表すユニークな文字列です。ここからコピペする際には、ユニークな文字列に変更してください。そして
docker-compose up
してSecurity ServerとPostgreSQLをまとめて起動します。% docker-compose up Creating network "px-ss-docker_default" with the default driver Creating px-ss-docker_postgres_1 ... done Creating px-ss-docker_ss01_1 ... done Attaching to px-ss-docker_postgres_1, px-ss-docker_ss01_1 postgres_1 | The files belonging to this database system will be owned by user "postgres". postgres_1 | This user must also own the server process. postgres_1 | postgres_1 | The database cluster will be initialized with locale "en_US.utf8". postgres_1 | The default database encoding has accordingly been set to "UTF8". postgres_1 | The default text search configuration will be set to "english". postgres_1 | postgres_1 | Data page checksums are disabled. postgres_1 | postgres_1 | fixing permissions on existing directory /var/lib/postgresql/data ... ok postgres_1 | creating subdirectories ... ok postgres_1 | selecting default max_connections ... 100 postgres_1 | selecting default shared_buffers ... 128MB postgres_1 | selecting default timezone ... Etc/UTC postgres_1 | selecting dynamic shared memory implementation ... posix postgres_1 | creating configuration files ... ok ss01_1 | 2021-01-11 09:00:06,566 Creating /var/lib/xroad/backup ss01_1 | 2021-01-11 09:00:06,655 Generating new internal.[crt|key|p12] files ss01_1 | /CN=0874c65254b7 -subj ss01_1 | Generating a RSA private key ss01_1 | ............................................................+++++ ss01_1 | ............+++++ ss01_1 | writing new private key to '/etc/xroad/ssl/internal.key' ss01_1 | ----- ss01_1 | 2021-01-11 09:00:07,004 Generating new proxy-ui-api.[crt|key|p12] files ... # しばらく待つと ss01_1 | {"timestamp":"2021-01-11T09:03:10.183Z","level":"INFO","thread":"main","logger":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer","message":"Tomcat started on port(s): 4000 (https) with context path ''","context":"X-Road Proxy Admin REST API"}↑の最後の行が出てきたら、
https://localhost:4000/ をブラウザで開いてみしょう。
X-Road Security Serverの管理画面です。Safariでは"接続はプライベートではありません", Chromeでは"この接続ではプライバシーが保護されません"と表示されますが、理解しているので先に進みます。
ログイン画面では環境変数の
PX_ADMINUI_USER
とPX_ADMINUI_PASSWORD
でログインします。X-Roadとは
今一体何を立ち上げたのでしょうか。
Security Serverとは何でしょうか。
図1 Deployment view of X-Road (赤い線は筆者による強調)
docker-compose.yml
に書いたplanetway/xroad-securityserver
Docker Imageは、上図のClient Security Server, Service Security Serverの青い箱に当たるソフトウェアです。X-Roadの情報連携とは、Service Clientという組織内のClient Information Systemから、Service Providerという組織内のService Information Systemが提供する機能を使うことを表します。
Security Serverは、Client Information Systemから見るとForward Proxyの役割を果たし、Service Information Systemから見るとReverse Proxyの役割を果たします。Security ServerはClient Information SystemとService Information Systemをインターネットを通してつなげるセキュアなトンネルを構築します。
Client Information SystemとClient Security Serverの間、そしてService Information SystemとService Security Serverの間は、SOAPとRESTプロトコルを話します。
X-Roadの情報連携をやってみましょう。
認証用証明書のActivate、Security Serverの登録とSubsystemの登録
planetway/xroad-securityserver
Docker Imageは、Security Server立ち上げ時のセットアップ手順をいくつか自動的に実行しますが、情報連携を行う=HTTPリクエストをSecurity Serverに送る、までにはまだいくつかステップがあります。認証用証明書をActivateしましょう。
https://localhost:4000/ を開き、ログインします。
"KEYS AND CERTIFICATES" をクリック -> "Token: softToken-0"の左の ">" をクリック -> "TEST of Planetway Intermediate L1 Organizations CA 2020 ..." をクリック -> "ACTIVATE" をクリックします。認証用証明書をActivateしました。
Security Serverの登録リクエストを送りましょう。バツを押してその画面を閉じ、"KEYS AND CERTIFICATES" をクリック -> "Register" ボタンを押します。
"127.0.0.1"と入力し "ADD" ボタンを押します。
これでSecurity Serverの登録リクエストを送信したことになります。図1のCentral Serverでは、X-Roadに参加する組織と、それぞれの組織が運用するSecurity Serverのカタログを管理しています。Security Serverの登録リクエストは、Central ServerのカタログにこのSecurity Serverを登録してもらうためのリクエストです。
次にSubsystemの登録を行います。
"CLIENTS" をクリック -> "Add Subsystem" ボタンを押します。"Subsystem Code"には"democlient"と入力し、"ADD SUBSYSTEM"ボタンを押します。
"CLIENTS"を押し以下のように表示されていれば(Docker Demo Company (Owner)とdemoclientの行に緑の円と"REGISTERED"が表示されていれば)Subsystemの登録は完了です。
図1におけるService Client組織に所属するClient Information System情報システムをdemoclientと言う名前で登録しました。
Security ServerのConnection TypeをHTTPに変更する
もうそろそろです。
Client Information SystemとClient Security Serverの間は、デフォルトではHTTPSを使用します。この記事では簡略化するためHTTPに変更します。
同管理画面で、"CLIENTS" をクリック -> "democlient" の文字のところをクリック -> "INTERNAL SERVERS" をクリックします。下図のように、"CONNECTION TYPE"を"HTTP"に変更してください。
HTTPリクエストをSecurity Serverに送る
Service Provider側は、この記事では、予めPlanetway社が提供するものを利用しましょう。Planetway社では、現在時刻を返すtimeというX-Roadサービスをデモとして提供しています。
以下のようにcurlのコマンドを実行してください。
% curl -v "http://localhost:8000/r1/JP-TEST/COM/0170368015672/demoprovider/time" -H "X-Road-Client: JP-TEST/COM/0170121212121/democlient" * Connected to localhost (127.0.0.1) port 8000 (#0) > GET /r1/JP-TEST/COM/0170368015672/demoprovider/time HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.64.1 > Accept: */* > X-Road-Client: JP-TEST/COM/0170121212121/democlient > < HTTP/1.1 200 OK < Date: Wed, 13 Jan 2021 12:33:54 GMT < Content-Type: text/plain;charset=utf-8 < x-road-id: JP-TEST-b3fcf843-4d50-4108-b25a-22b5993ad0c9 < x-road-client: JP-TEST/COM/0170121212121/democlient < x-road-service: JP-TEST/COM/0170368015672/demoprovider/time < x-road-request-id: f13d39d1-c5a7-4fc5-bd2f-ac002e2dc2e7 < x-road-request-hash: N02MYtDKDzpT1QN7uUQlgUa+I5Vu5ImpX1WKxj/fByjKCUITbWgAlySJwfSt0xQU1AzaPG0RiEqoONQBxXD0jQ== < Content-Length: 41 < {"now":"2021-01-13T12:33:54.692995982Z"}と表示されれば成功です!
このcurlコマンドを紐解いていきます。
localhost:8000
私の環境では、DockerはmacOS用のDocker Desktopで動作させています。この記事冒頭のdocker-compose.yml
ではports以下に- 8000:80
とありました。これはlocalhost:8000
をDocker containerの80番ポートに転送します。Docker container内のSecurity Serverはポート80番でlistenしています。
/r1/JP-TEST/COM/0170368015672/demoprovider/time
Information SystemとSecurity Serverの間のプロトコルは、X-Road: Message Protocol for RESTと言うプロトコルにしたがいます。URLのpath部分はMessage Protocol for REST文書の中でserviceId
として記載があります。
r1
はプロトコルのバージョン。
JP-TEST
はX-RoadインスタンスのIDです。X-Roadのインスタンスとは、図1に表される箱全部を含んだまとまりで、あるGoverning Authority/運営機関と、運営機関が提供する環境で情報連携を行う組織群を含みます。JP-TEST
はPlanetway社が運営機関を務めています。他にEE
(エストニア),FI
(フィンランド)などがあります。
COM
はX-Roadに参加する組織が法人であることを表します。他にGOV
などがあります。
0170368015672
はPlanetway社を表すX-Road Member IDです。0170121212121
はDocker Imageの使用者が使えるテスト用のX-Road Member IDです。
demoprovider
はPlanetway社の情報システムを表すSubsystem IDです。
time
はX-Road Serviceコードです。
X-Road-Client: JP-TEST/COM/0170121212121/democlient
というヘッダの値は、Client Information Systemを表しています。Security Serverはアクセスコントロールの機能も含みます。予めtimeサービスは、
JP-TEST/COM/0170121212121/democlient
から呼び出せるよう設定済みです。time X-Road Serviceの実体
timeサービスは以下のようなGolangのコードとして実装しています。
package main import ( "encoding/json" "net/http" "os" "time" ) func handler(w http.ResponseWriter, r *http.Request) { t := time.Now() enc := json.NewEncoder(w) err := enc.Encode(map[string]interface{}{ "now": t, }) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } } func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/", handler) http.ListenAndServe(":"+port, nil) }普通のJSONを返すAPIサーバですね。
http.HandleFunc
の第一引数が/
であることに注意。これが図1のService Information Systemに当たります。いくつか私が面白いと思う点を書きます。
Security ServerがInformation Systemから見るとシンプルなトンネルであること
このGolangのサーバをローカルで実行し、立ち上がったサーバにcurlでHTTPリクエストを送る手順と、インターネットのどこかでPlanetway社が提供するtimeサービスを呼び出す手順の違いが小さいこと。
X-Road-Client
リクエストヘッダ、URLのpath部分だけです。X-Roadのセキュリティ面はInformation Systemには見えない
よくある外部APIと比較するとどうでしょう。Bearer tokenのようなものもクライアント証明書も何も見えません。認証や認可はSecurity Serverが隠蔽しています。
curlのリクエスト送り先はSecurity Server
知る必要があるのは、組織のID、その中のサービスIDからなる
serviceId
だけです。相手のURLは知る必要がありません。インターネットのレイヤの上に綺麗にRPCのレイヤをのっけています。まとめ
DockerでX-Road Security Serverを立ち上げ、それを通して時刻を返すデモサービスを呼び出すcurlコマンドを実行しました。
続く
- 投稿日:2021-01-11T15:54:00+09:00
Go製のtcardgenでHugoで作ったブログのOGPを自動生成してみた
※これは自作ブログに投稿したものと同じ記事です。
tcardgenでHugoで作ったブログのOGPを自動生成してみたはじめに
ブログを作成しても読んでもらわないとあまり意味がないわけで、そういう意味でもTwitter CardなどのSNS共有時の目を引く画像は重要なわけです。
と、いうわけで、今回はGo製のtcardgen
というライブラリを使用して、OGP作成を行ってみました。
tcardgen
↑こんな感じのTwittterCardを作ってくれるやつですね。準備
install
go get
でインストールする$ go get github.com/Ladicle/tcardgen
README.md
がkinto
とうフォントを使うように言っているのでダウンロードする今回はこの画像作成に使用できればいいので、
static/fonts/Kinto_Sans
といった感じのディレクトリに保存しておくテンプレート
元となる背景画像的なテンプレートを作成します。
tcardgen
リポジトリのexample
にいろいろサンプル画像が入ってます。
サイズは
1200 x 628 (px)
で作成します。その後、自分はiPadの
Affinity Designer
で、サンプル画像を透過させながら、いい感じのデザインを作成しました。下準備
markdown設定
tcardgenは、その使用上
category
とtags
の項目をなにかしら設定する必要があります。
よって、作成したい記事にはこれらの項目を追加します。
また、作成したimagesのパスを描けるようにしておきます。(詳細は後述します。)# Twitter card gen用設定 author: ["いわし"] categories: ["Tech"] tags: ["tcardgen", "Hugo", "OGP"] # tag ogimage: "images/og/tcardgen-hugo.png" # tcardgenで生成した画像をOGP画像に設定する url: "/blog/tcardgen-hugo/" # tcardgenでの自動生成スクリプト用のパスを設定 ルーティング固定の意味もある carduse: true # TwitterCardを使用するかどうか falseの場合はデフォルトの画像が適用される最初、記事作成時に
$ hugo new ./content/blog/{日本語}.mdにしていたのですが、このままだとパスが日本語になってしまい、URLが汚くなるので、作成時は適当にアルファベットで
.md
を作成し、後からタイトルをに日本語に変える作業をすることにしました。この際、パスは(本来)この設定し直したタイトルを元に決定されるので、日本語に変えるとパスも日本語になってしまうのですが、以下のように指定するとこちらを優先してくれます。
また、このurlは記事作成時の{}.md
から自動生成しています。url: "/blog/tcardgen-hugo/"yaml設定
作成する画像のスタイルは、
tcardge.yaml
で設定できます。これはtcardgen
にサンプルがあるので使用できます。僕の場合は、サンプル画像を元に配置を決めたのでそのまま使用しました。
template: static/ogp/template.png title: start: px: 113 pY: 252 fgHexColor: "#FFFFFF" fontSize: 68 fontStyle: Bold maxWidth: 1000 lineSpacing: 10 category: start: px: 113 py: 211 fgHexColor: "#E5B52A" fontSize: 42 fontStyle: Regular info: start: px: 223 py: 120 fgHexColor: "#A0A0A0" fontSize: 38 fontStyle: Regular tags: start: px: 120 py: 500 fgHexColor: "#FFFFFF" bgHexColor: "#7F7776" fontSize: 22 fontStyle: Medium boxAlign: Left boxSpacing: 6 boxPadding: top: 6 right: 10 bottom: 6 left: 8作成
[Hugo] tcardgen を使って OGP 画像を自動生成する - michimani.net
この記事をを参考にスクリプトを作成して実行します。
if [ $# != 1 ] || [ $1 = "" ]; then echo "One parameters are required" echo "" echo "string: path to markdown file of target post" echo "" echo "example command" echo "\t$ sh ./makeogp.sh ./content/post/test/test.md" exit fi TARGET_POST_PATH=$1 tcardgen \ --fontDir ./static/fonts/Kinto_Sans \ --output static/images/og \ --template static/ogp/template.png \ --config tcardgen.yaml \ $TARGET_POST_PATH使用例コマンドは、
$ ./makeogp.sh ./content/blog/tcardgen-hugo.md引数で作成したい記事を指定します。
作成されたものは、shellで指定されている
--output static/images/og
に出力されます。作成できました!
まだ終わりではない
ogpとして登録する
さて、画像はできましたがこれだけで終わりではありません。そうです。headタグで指定しなければなりません。
というわけで、
layouts/partials/head.html
に次の記述を加えます。{{"<!-- blog用にTwitterCardを設定 -->" |safeHTML}} <meta name="twitter:image:src" content="https://biwashi.github.io/blog/images/iwashilong_w.jpg"> <meta property="og:image" content="https://biwashi.github.io/blog/images/iwashilong_w.jpg" /> {{ if eq true .Params.carduse }} {{"<!-- Blogなのでカスタムされたものを表示 -->" |safeHTML}} <meta property="og:image" content="{{ .Site.BaseURL }}{{ .Params.ogimage }}"> <meta name="twitter:image:src" content="{{ .Site.BaseURL }}{{ .Params.ogimage }}"> {{ else }} {{"<!-- Homeなのでデフォのやつを表示 -->" |safeHTML}} {{ end }}ここで、さっきmarkdownに設置したいろいろなパラメータを使用します。
順に説明します。
<meta name="twitter:image:src" content="https://biwashi.github.io/blog/images/iwashilong_w.jpg"> <meta property="og:image" content="https://biwashi.github.io/blog/images/iwashilong_w.jpg" />まずこれはデフォルトのogp画像です。ブログ以外のサイト用を普通に指定します。
{{ if eq true .Params.carduse }} {{"<!-- Blogなのでカスタムされたものを表示 -->" |safeHTML}} <meta property="og:image" content="{{ .Site.BaseURL }}{{ .Params.ogimage }}"> <meta name="twitter:image:src" content="{{ .Site.BaseURL }}{{ .Params.ogimage }}"> {{ else }} {{"<!-- Homeなのでデフォのやつを表示 -->" |safeHTML}} {{ end }}さて、ここでifです。
先ほどの
markdon
でcarduse: xxx
というのを指定しました。あれはここで条件分岐するためです。
carduse
がtrue
の場合はブログだと判断して、先ほど作成したogpを指定してます。この際のパスも、先ほど指定したogimage
です。再度先ほどmarkdownを見ておきます。# Twitter card gen用設定"] author: ["いわし"] categories: ["Tech"] tags: ["tcardgen", "Hugo", "OGP"] # tag ogimage: "images/og/tcardgen-hugo.png" # tcardgenで生成した画像をOGP画像に設定する url: "/blog/tcardgen-hugo/" # tcardgenでの自動生成スクリプト用のパスを設定 ルーティング固定の意味もある carduse: true # TwitterCardを使用するかどうか falseの場合はデフォルトの画像が適用されるというわけで、
carduse: false
の場合は、ブログ以外として再度指定しません。これで、ブログにogpを設定し、かつブログ以外と分けることができました。
終わりに
ブログ作成と同時にこの作業はしていましたが、なかなか記事を書かずにいました。
少しでも参考になれば幸いです。
参考
今回も、はじまりはさんぽしさんでした。
- 投稿日:2021-01-11T15:25:13+09:00
Azure SDK for Goのセットアップ(macOS編)
サイト「Go 開発者向けの Azure | Microsoft Docs」の「クイックスタート」を始めるにあたり、コマンドラインツールで実行しようとすると、
- Azure CLI(macOSの場合:要 Homebrew)
- Azure SDK for Go
のインストールが必要となります。本記事では、次のサイト記事を参考にして、これらのセットアップ手順を自分メモとしてまとめました。
事前準備
Azureアカウントは取得済です。取得手順は次の記事にして投稿しています。
Goはインストール済です。Azure SDK for GoではGo 1.8(Azure Stack プロファイルだと1.9)以降が必要です。
Goのバージョンを確認。
% go version go version go1.15.6 darwin/amd64サイト「Azure SDK for Go を使用する開発者向けのツール」の補足
コマンドラインツールではありませんが、コードエディター「Visual Studio Code」はインストール済です。
Azure DevOps プロジェクトについては、別記事で投稿します。
依存関係管理ツール「dep」は、プロジェクトが終了しているので使いません。現在、Azure SDK for Go自体も脱dep化に取り組んでいる最中です。
Azure CLI
Azure CLIのインストール手順は、次のサイトに用意されています。
私の開発環境はmacOSのため、サイト内の「Install on macOS」リンクをクリックして詳細を確認しました。
Homebrewのインストール
macOSでは、パッケージマネージャー「Homebrew(brewコマンド)」を使ってAzure CLIをインストールします。
- Homebrew公式サイト:macOS(またはLinux)用パッケージマネージャー — Homebrew
Homebrew公式サイトに記載されているインストールコマンドをコピーし、ターミナルにペーストしてインストールを開始します。
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"インストールには管理者権限が必要のため、macOSにログインしたユーザーのパスワードの入力が促されます。ログインユーザーのパスワードを入力して[Enter]キーを押下します。
Password:インストールを続けるか中止するか問われます。[RETURN]または[Enter]キーを押下すると続行、それ以外のキーを押下すると中断されます。[Enter]キーを押下します。
Press RETURN to continue or any other key to abort
インストールが完了したら、コマンドを使用できるかバージョン表示して確認します。
% brew -v Homebrew 2.7.2 Homebrew/homebrew-core (git revision b692fd85; last commit 2021-01-11)アンインストールする場合は、公式サイトのFAQドキュメントの該当項目をご確認ください。
Azure CLIのインストール
brewを使ってAzure CLI(azコマンド)をインストールします。
サイトに記載されているインストールコマンドをコピーし、ターミナルにペーストしてインストールを開始します。
% brew update && brew install azure-cliインストールが完了したら、コマンドを使用できるかバージョン表示して確認します。
% az version { "azure-cli": "2.17.1", "azure-cli-core": "2.17.1", "azure-cli-telemetry": "1.0.6", "extensions": {} }アンインストールする場合は、brewコマンドのuninstall指定で行います。
% brew uninstall azure-cliコマンドの最新化
Homebrew(brewコマンド)
本体を最新化するには、updateを指定します。
% brew updatebrewでインストールしたモジュール群を最新化するには、upgradeを指定します。
% brew upgradeAzure CLI(azコマンド)
本体を最新化するには、upgradeを指定します。
% az upgrade両方
brew本体と、az本体および関連するモジュールをまとめて最新化するには、次のコマンドを実行します。
$ brew update && brew upgrade azure-cliAzure SDK for Go
サイトに記載されているインストールコマンドをコピーし、ターミナルにペーストしてインストールまたは最新化を行います。
% go get -u -d github.com/Azure/azure-sdk-for-go/...この時、Azure/go-autorestもインストール&最新化されます。
- 投稿日:2021-01-11T13:26:24+09:00
Go言語-ポインタ
ポインタ
プログラミング基本からGoの理解を文字におこして行こうと思います!
ポインタとは
アドレスは値
整数や文字列のように操作したり変数に代入することができる
Goではアドレスをポインタと呼んで扱っている
ポインタが代入された変数をポインタ型変数というポインタ型変数の定義
func main() { name := "tanabe" //nameの値がstring型なので*string型になる var namePtr *string = &name fmt.Println(namePtr) //コンソール 0xc421100230 //ポインタが出力ポインタ型変数を定義するには
変数のデータ型に「*(アスタリスク)」をつけて宣言ポインタ型変数namePtrを定義し、&nameで取得したポインタを代入している
ポインタからアクセスする
func main() { name := "tanabe" var namePtr *string = &name fmt.Println(*namePtr) //namePtrが示す変数の値を出力 } //コンソール tanabe //値が出力されるポインタ型変数を定義するには
変数のデータ型に「*(アスタリスク)」をつけて宣言ポインタを使って値を更新する
func main() { name := "tanabe" var namePtr *string = &name *namePtr = "naito" fmt.Println(name) //namePtrが示す変数の値を出力 } //コンソール naito //更新した値が出力されるポインタ型変数を使用して、値を直接更新することもできる
更新するには「*変数名 = 更新する値」と記述「*namePtr = "naito"」で、変数nameを更新
引数にポインタを指定する
func main() { name := "tanabe" changeName(&name) //ポインタを引数に指定 } func changeName(namePtr *string) { //stringのポインタ型で受け取る }ポインタを別の関数に引数として指定する場合
受け取る関数には対応したポインタ型の引数を用意する必要がある別の関数でポインタを使用し更新する
func main() { name := "tanabe" changeName(&name) //ポインタを引数に指定 fmt.Println(name) } func changeName(namePtr *string) { //stringのポインタ型で受け取る *namePtr = "naito" } //コンソール naitoポインタを引数として指定することで
別の関数からでもポインタを使って元の変数を更新することができる変数を引数に指定する
func main() { totalScore := 0 fn(totalScore) ... } func fn(totalScore int) { ... }変数totalScoreを引数に指定する場合
その変数自体が渡されるわけではない変数totalScoreの値がコピーされ、新しい変数に代入(値渡し)
渡した値を更新する
func main() { totalScore := 0 fn(totalScore) fmt.Println(totalScore) //totalScoreの値を出力 ... } func fn(totalScore int) { totalScore += 10 //fn関数のtotalScoreのみ値を更新 } //コンソール 0 //元の値が出力されるmain関数とfn関数のそれぞれの変数totalScoreは、別の変数
そのため、fn関数のtotalScoreを更新しても
fn関数のtotalScoreのみ値が更新されるポインタを見比べる
func main() { func fn(totalScore int) { totalScore := 0 totalScore += 10 ❶fn(totalScore) ❷fmt.Println(totalScore) ❶fmt.Println(totalScore) fmt.Println(&totalScore) fmt.Println(&totalScore) } } //コンソール ❶fn関数のtotalScoreの値とポインタ 10 //元の値が出力される 0xc421100230 ❷main関数のtotalScoreの値とポインタ 0 0xc421100052main関数とfn関数のそれぞれのtotalScoreを出力すると
それらが実際に別の場所にある変数であることがわかる引数のまとめ
引数に整数などの値を指定した場合
func main() { a := 10 calculate(a) fmt.Println("引数に整数を指定した場合:", a) } func calculate(a int) { //整数などの値を受け取る a += 1 } //コンソール 引数に整数を指定した場合:10 //main関数の変数aはスコープが違うのでこのままcalculate関数は受け取った引数に1を足す関数
変数aを引数に指定した場合、値がコピーされ別の変数が生成
よってcalculate関数で値を更新しても、main関数の変数aは更新されない引数にポインタを指定した場合
func main() { b := 10 calculate(&b) //ポインタを渡す fmt.Println("引数にポインタを指定した場合:", b) } func calculate(bPtr*int) //ポインタを貰う *bPtr } //コンソール 引数に整数を指定した場合:11 //main関数の変数bは値が更新されるポインタを引数に指定すると、calculate関数から元の変数の値を更新できる
このとき、受け取る引数はポインタ型を指定することに注意
- 投稿日:2021-01-11T13:25:55+09:00
Go言語-アドレス
アドレス
プログラミング基本からGoの理解を文字におこして行こうと思います!
アドレスとは
コンピュータにはメモリと呼ばれる作業場所のようなものが存在
変数はそのメモリに記録され、その場所をアドレスという「0xc420010230」のように16進数で表現されることが多い
16進数とは、数字を数えるときに16で繰り上がるようにする数え方アドレスの取得
func main() { name := "tanabe" fmt.Println(name) //nameで値を取得 fmt.Println(&name) //&nameでアドレスを取得 //コンソール tanabe 0xc421100230変数のアドレスを取得するためには、「&変数名」とする
メモリ上の記録する場所はコンピュータによって変わるため
プログラムを実行する度に違うアドレスを出力する場合もある
- 投稿日:2021-01-11T13:25:25+09:00
Go言語-関数
関数
プログラミング基本からGoの理解を文字におこして行こうと思います!
関数とは
var input string fmt.Println("次の単語を入力してください:go") fmt.Scan(&input) fmt.Printf("%sと入力されました", input)関数(かんすう)とは、「いくつかの処理をまとめたもの」
処理を関数にまとめると、何度でも使える上記のコードをask関数にまとめる
関数の定義
func ask() { var input string fmt.Println("次の単語を入力してください:go") fmt.Scan(&input) fmt.Printf("%sと入力されました", input) }関数は、「func 関数名()」と書く
「{ }」の中にまとめたい処理を書くことで関数を用意することができる関数の呼び出し
func main() { //ask関数を呼び出す ask() } func ask() { var input string fmt.Println("次の単語を入力してください:go") fmt.Scan(&input) fmt.Printf("%sと入力されました", input) }関数を定義しただけでは、その中の処理は実行されない
「関数名()」と書くことで関数の中の処理を実行できる関数を複数回呼び出す
func main() { ask() ask() //複数回呼びだせる } func ask() { var input string fmt.Println("次の単語を入力してください:Go") fmt.Scan(&input) fmt.Printf("%sと入力されました", input) } //コンソール //次の単語を入力してください:go //go(コンソールで入力) //goと入力されました //次の単語を入力してください:go //go(コンソールで入力) //goと入力されました1度定義すれば同様の処理を何度でも実行できるのが関数のメリット
関数に値を渡す
func main() { ask("go") }関数を呼び出す際に「()」の中に値を書くことで、値を渡すことができる
この値のことを引数という引数を受け取る
func main() { ask("go") //Goを引数としてquestionに渡している } func ask(question string) { //question→変数名 ...渡した値を使うためには、受け取るための変数を用意する必要がある
文字列"cat"をstring型の変数questionで受け取っている引数を使う
func main() { ask("go") //goを引数としてquestionに渡している ask("ruby") //rubyを引数としてquestionに渡している } func ask(question string) { ... fmt.Printf("次の単語を入力してください: %s\n", question) //コンソール //次の単語を入力してください:go ... //次の単語を入力してください:ruby ...渡された値は受け取った変数名を用いて使うことができる
渡す値を変えることで問題文も変えられる変数の値を渡す
func main() { text := "Go" ask(text) func ask(question string) { ... fmt.Printf("次の単語を入力してください: %s\n", question) } //コンソール //次の単語を入力してください:go ...関数には変数の値を引数として渡すこともできる
変数textを引数として、ask関数の変数questionに渡している複数の引数の渡し方
func main() { ask(1,"go") ask(2,"ruby") ask(3,"python") } func ask(number int, question string) { fmt.Printf("[質問%d]次の単語を入力してください: %s\n", number, question) } //コンソール //[質問1]次の単語を入力してください:go //[質問2]次の単語を入力してください:ruby //[質問3]次の単語を入力してください:python複数の引数を使うとき、渡す順番に注意
ask関数の引数の順が(問題番号, 問題文)となっている戻り値とは
func ask(number int, question string) int { fmt.Printf("[質問%d]次の単語を入力してください: %s\n", number, question) ... return 10 //ask関数の戻り値戻り値(もどりち)を使うことで呼び出し元に値を返すことが出来る
ask関数が「1」「go」を受け取り、結果の「10」を呼び出し元に返している
戻り値にあたるのがこの「10」
1行目のintは戻り値の型戻り値の受け取り方
func main() { totalScore := ask(1, "go") //ask関数の戻り値は10 fmt.Println("スコア", totalScore) } func ask(number int, question string)int { ... return 10 //コンソール //[質問1]次の単語を入力してください:go //スコア 10関数の戻り値は変数に代入するなどして受け取ることができる
ask関数を呼び出して返ってきた戻り値10を変数totalScoreに代入戻り値を計算
func main() { totalScore := ask(1, "go") totalScore += ask(2, "ruby") totalScore += ask(3, "python") fmt.Println("スコア", totalScore) } func ask(number int, question string)int { ... return 10「+=」を使用することで、ask関数の戻り値をそのまま足すことができる
正解,不正解の分岐
func ask(number int, question string)int { fmt.Scan(&input) if question == input { fmt.Println("正解") }else{ fmt.Prantln("不正解") } }正解や不正解を判断する条件分岐
出題された問題文と入力された値が同じかどうかで判断戻り値を返す
func main() { totalScore := ask(1,"go") totalScore += ask(2,"ruby") totalScore += ask(3,"python") fmt.println("スコア",totalScore) } func ask(number int, question string)int { if question == input { fmt.Println("正解") return 10 }else{ fmt.Println("不正解") return 0 }正解のとき、returnで10を返す
不正解のとき、returnで0を返す
戻された値をmain関数のtotalScoreに足す関数に値を渡すときの注意点
func main() { totalScore := 0 ask(1,"go") ask(2,"ruby") ask(3,"python") fmt.Println("スコア",totalScore) } func ask(number int, question string) { if question == input { fmt.Println("正解") totalScore += 10 //ask関数内からmain関数のtotalScoreに直接足そうとするとエラー }戻り値を使わず、main関数の中で定義された変数totalScoreを
ask関数の中で使おうとしてもできない変数のスコープ
func main() { totalScore := 0 //変数totalScoreが使用できる範囲(スコープ内) } func ask(number int, question string) { //変数totalScoreが使用できない範囲(スコープ外) }main関数の中で定義された変数totalScoreは、main関数の中でしか使えないから
変数には使える範囲があり、その範囲を変数のスコープと呼ぶスコープはとても重要です
スコアを引数で渡す
func main() { totalScore := 0 ask(1,"go",totalScore) ... fmt.Println("スコア",totalScore) } ... func ask(number int, question string, totalScore int) { ... if question == input { fmt.Println("正解") totalScore += 10 ... } }エラーは発生しないが、正解してもスコアが加算されない
同じ名前の変数
func main() { totalScore := 0 //main関数の変数 totalScoreのスコープ } func ask(number int, question string, totalScore int) { totalScore += 10 //ask関数の引数 totalScoreのスコープ }main関数で使用しているtotalScoreと
ask関数で使用しているtotalScoreはスコープが違うため
同じ名前だが、別の変数値のコピー
main関数からask関数へ変数を渡しているように見えるが
実際は変数totalScoreの値をコピーしてask関数に渡している
よって、ask関数のtotalScoreに値を足しても、もとの値は変わらない
- 投稿日:2021-01-11T13:17:28+09:00
【環境変数】go getでパッケージがインストールされるディレクトリは$GOPATH/bin。設定していない場合は$HOME/go/binに入る。
どうも、たかふみです。
少し前から会社の同期とGoでLINEBotの開発を行なっています。開発で使うライブラリをインストールするため、
go get
をした後、コマンドを実行したのですがwire: not found
と表示されてしまいました。「
go get
でインストールしたものってどこにインストールされるんだっけ?」ということで、今回はgo get
によるインストール先や$GOPATHについて調べた内容をまとめておきたいと思います!■結論
$GOPATH
を設定していなければ$HOME/binにインストールされる。
go get
でインストールされたライブラリは$GOPATH/binにインストールされる。■
go get github.com/google/wire/cmd/wire
を実行DIライブラリ「wire」をインストールするために
go get
を実行。wireコマンドを実行したところ下記のように表示。/go/src/github.com/go-server-dev # wire ash: wire: not foundパスが通っていないのが原因と考えました。そこで出たのが「
go get
でインストールしたものってどこにインストールされるんだろう」という疑問。■
echo $GOPATH
で確認調べてみると
$GOPATH/bin
にインストールされることが分かったので、echo
で$GOPATHを確認。echo $GOPATH何も出ませんでした。確かに設定した覚えはありません。
■そもそも
$GOPATH
とは何かThe GOPATH environment variable specifies the location of your workspace.
引用:https://github.com/golang/go/wiki/SettingGOPATH
$GOPATH
とはワークスペースの場所とのこと。また、go help gopath
より、go get
でインストールしたパッケージについては$GOPATH
で設定されたディレクトリにインストールされるとありました。そうなると、
$GOPATH
を設定していない僕の環境ではどこへインストールされているのでしょうか。。。■設定していない場合は
$HOME/go
にインストールされるgithubのwikiに書いてありました。
If no GOPATH is set, it is assumed to be $HOME/go on Unix systems and %USERPROFILE%\go on Windows.
https://github.com/golang/go/wiki/SettingGOPATH設定していない場合は
$HOME/go
と見なされるようです。
確認したところ、今回インストールしたパッケージ「wire」が$HOME/go/bin
に入っていることを確認しました。/go/src/github.com/go-server-dev # echo $HOME /root /go/src/github.com/go-server-dev # ls /root/go/ bin pkg src /go/src/github.com/go-server-dev # ls /root/go/bin/ wire■
$GOPATH
を設定しておくきちんと
$GOPATH
を設定しておきます。/go/src/github.com/go-server-dev # vim ~/.bash_profile ↓vimで記述 --- export GOPATH=$HOME/go --- /go/src/github.com/go-server-dev # source ~/.bash_profile /go/src/github.com/go-server-dev # echo $GOPATH /root/go
echo $GOPATH
で設定したパスが表示されることが確認できました。まとめ:$GOPATHは明示的に設定しておいた方が良さそう
go get
でインストールされる場所を把握するためにも、開発に入る前に$GOPATH
は設定しておいた方が良さそうです。設定していない場合は$HOME/go
にインストールされることをお忘れなく!それでは!
参照
SettingGOPATH
https://github.com/golang/go/wiki/SettingGOPATHGOPATH は適当に決めて問題ない
https://qiita.com/yuku_t/items/c7ab1b1519825cc2c06fGOPATHの設定とgo get
http://kodama-tech.hatenablog.com/entry/2016/12/14/002115[備忘録]Go言語のGOPATHやパッケージについて
https://qiita.com/chano2/items/ea76cc503e651f07bfb0
- 投稿日:2021-01-11T03:29:33+09:00
【Golang】 よく使う関数のメモ
この記事について
完全に自分用に書いているので説明は少なめで簡単な処理のものが多いですが、備忘録的に使うので随時更新します。
stringからのキャスト
string
→int
func atoi(a string) (b int) { b, _ = strconv.Atoi(a) return }半角スペース区切りの
string
→[]string
func aToSlice(a string) (b []string) { b = strings.Fields(strings.TrimSpace(a)) return }半角スペース区切りの
string
→[]int
func aToIntSlice(a string) (b []int) { c := strings.Fields(strings.TrimSpace(a)) for _, v := range c { d := atoi(v) b = append(b, d) } return }半角スペース区切りの
string
→[]float64
func aToFloat64Slice(a string) (b []float64) { c := strings.Fields(strings.TrimSpace(a)) for _, v := range c { d, _ := strconv.ParseFloat(v, 64) b = append(b, d) } return }スライスから別の型のスライスへのキャスト
[]string
→[]int
func toIntSlice(a []string) (b []int) { for _, s := range a { i := atoi(s) b = append(b, i) } return }
[]int
→[]string
func toStrSlice(a []int) (b []string) { for _, i := range a { s := strconv.Itoa(i) b = append(b, s) } return }その他
絶対値を
int
で返すfunc abs(a int) (b int) { b = int(math.Abs(float64(a))) return }
map[int]int
からvalueの最大値を返すfunc findMaxValue(m map[int]int) (maxValue int) { var max int var maxIndex int for i, v := range m { if max <= v { max = v maxIndex = i } } maxValue = m[maxIndex] return }
map[int]int
から最大値を持つkeyのみのスライスを返すGoの連想配列(map)は、範囲ループを使うと順番が実行ごとに異なるので昇順でソート返す。
func findMaxValueKeys(m map[int]int) (maxKeys []int) { maxValue := findMaxValue(m) for i, v := range m { if v == maxValue { maxKeys = append(maxKeys, i) } } sort.Slice(maxKeys, func (i, j int) bool { return maxKeys[i] < maxKeys[j] }) return }
- 投稿日:2021-01-11T03:25:05+09:00
GoのHTTP実装を読んだ知見をまとめる~Slice編~
はじめに
業務での調査がきっかけにGoのHTTP実装を最近読み続けていて、やっとhttp実装の流れが大まかに理解できるようになった。
割と勉強になったことが多いのでその知見を複数回に分けて知見をまとめていく。
HTTP実装自体は意外にもかなり複雑なものとなっており、文面で説明づらいので、どちらかというと実装を理解した知見を汎化して書きたい。HTTP実装も紹介しやすい部分は適宜書きたい。GoのHTTP実装とは、クライアントとサーバ両方含む。
TLS関連の処理やHTTP/2の実装は完全に読み飛ばしているので全く理解していない。HTTP1.0、HTTP1.1を対象とする。初回はスライスである。
GoのSlice
GoのSliceはゼロ値がnilなので内部構造としてはポインタであるように思いがちであるが、構造体である。以下がその定義である。
type slice struct { array unsafe.Pointer len int cap int }要するに、sliceは
- len
- cap
- 配列のポインタ
の3組からなる。
これは図で理解するのが良いが、既に丁寧な説明や解説があるので、そちらを参照してここでは省略する。大事なことは、「sliceはポインタでなく構造体だ」と理解するのではなく、sliceはsliceであり、既出の3組からなることをちゃんと覚えておかないといけないということだ。
例えば、以下の挙動はsliceをちゃんと理解していない人には不思議な挙動になると思う。
最初の
s = append(s, 4)
のちに明示的にs = append(s, 4)
しなくてはいけないのを簡単に説明すると、appendした直後のsmap["a"]の構造体は以下のようになっているためである。
- len = 3 (4ではない)
- cap = 4
- 配列のポインタは「1,2,3,4」をさす
mapのvalueとしてsliceがある場合は常に明示的にvalueを更新しないといけない。
package main import "fmt" func main() { // len=0 cap=4 s := make([]int, 0, 4) s = append(s, 1, 2, 3) smap := map[string][]int{ "a": s, } // 1,2,3 fmt.Println(smap["a"]) s = append(s, 4) // 1,2,3 fmt.Println(smap["a"]) // We must write explicitly smap["a"] = s // 1,2,3,4 fmt.Println(smap["a"]) s[0] = 0 // 0,2,3,4 // We write implicitly fmt.Println(smap["a"]) s = s[:1] // 0,2,3,4 fmt.Println(smap["a"]) // We must write explicitly smap["a"] = s // 0 fmt.Println(smap["a"]) }HTTP実装
上記のmapの説明の元ネタは実は
src/net/http/transport.goでmapのvalueであるキューを更新するときのコメントである。繰り返しになるが、mapのvalueがsliceの場合は更新した場合は明示的に上書きしなければいけない。なお、このconnsPerHostWaitのキューの実装は興味深いので別記事で紹介する。
// q is a value (like a slice), so we have to store // the updated q back into the map. t.connsPerHostWait[key] = qここではさらに、Chunked_transfer_encodingのReadの実装を参考にする。なお、Chunked_transfer_encoding自体の説明はここでは省略するが、実装の流れを掴むためにもwikiepediaの例を引用する。
Encoded data
In the following example, three chunks of length 4, 6 and 14 (hexadecimal "E") are shown. The chunk size is transferred as a hexadecimal number followed by \r\n as a line separator, followed by a chunk of data of the given size.4\r\n (bytes to send) Wiki\r\n (data) 6\r\n (bytes to send) pedia \r\n (data) E\r\n (bytes to send) in \r\n \r\n chunks.\r\n (data) 0\r\n (final byte - 0) \r\n (end message)Decoded data
Wikipedia in chunks.https://github.com/golang/go/blob/59bfc18e3441d9cd0b1b2f302935403bbf52ac8b/src/net/http/internal/chunked.go#L23-L115
に実装があります。以下、適宜コメントを追記します。func NewChunkedReader(r io.Reader) io.Reader { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) } return &chunkedReader{r: br} } type chunkedReader struct { r *bufio.Reader n uint64 // unread bytes in chunk err error buf [2]byte checkEnd bool // whether need to check for \r\n chunk footer } // beginChunkは例での // 4\r\n (bytes to send) // を読み込む部分です func (cr *chunkedReader) beginChunk() { // chunk-size CRLF var line []byte // readChunkLineは引数の*bufio.Readerから\nまで読みます line, cr.err = readChunkLine(cr.r) if cr.err != nil { return } // 例でいうところの4 cr.n, cr.err = parseHexUint(line) if cr.err != nil { return } // 終わりを意味する if cr.n == 0 { cr.err = io.EOF } } // バッファに既にあり、\nが含まれている場合のみtrueを返却します func (cr *chunkedReader) chunkHeaderAvailable() bool { n := cr.r.Buffered() if n > 0 { peek, _ := cr.r.Peek(n) return bytes.IndexByte(peek, '\n') >= 0 } return false } func (cr *chunkedReader) Read(b []uint8) (n int, err error) { for cr.err == nil { if cr.checkEnd { if n > 0 && cr.r.Buffered() < 2 { // We have some data. Return early (per the io.Reader // contract) instead of potentially blocking while // reading more. break } // 例でいうと、Wiki\r\nの\r\nを読もうとします if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { if string(cr.buf[:]) != "\r\n" { cr.err = errors.New("malformed chunked encoding") break } } cr.checkEnd = false } if cr.n == 0 { // 呼び出すとblockされそうであれば追加でreadせずに結果を返してしまいます。 if n > 0 && !cr.chunkHeaderAvailable() { // We've read enough. Don't potentially block // reading a new chunk header. break } cr.beginChunk() continue } if len(b) == 0 { break } rbuf := b if uint64(len(rbuf)) > cr.n { rbuf = rbuf[:cr.n] } var n0 int // 例でいうと、rbufにWikiだけが格納されるように調整されてます n0, cr.err = cr.r.Read(rbuf) n += n0 // rbufにデータが格納されますが、bと配列のポインタを共有しているため、結果的に引数のbにもデータが格納されています b = b[n0:] cr.n -= uint64(n0) // If we're at the end of a chunk, read the next two // bytes to verify they are "\r\n". if cr.n == 0 && cr.err == nil { cr.checkEnd = true } } return n, cr.err }Sliceとは関係ないですが、blockされそうであれば無理に読まずにReturnしてしまう工夫が面白いですね。Chunked_transfer_encodingの場合はチャンクとチャンクの間(例えばWikiとpedia)に受信のタイムラグがあることもあるでしょう。
Slice関連でいうと、この実装を初見でちゃんと動いていると理解するのは慣れが必要な気がします。
cr.r.Read(rbuf)
のように長さを期待するものに調整したものにするのも参考になりますね。改めて思いましたが、Readのinterfaceがそもそも引数にデータを格納する形となるので
p
がポインタのように思ってしまうのも無理もないですね。type Reader interface { Read(p []byte) (n int, err error) }
- 投稿日:2021-01-11T01:20:39+09:00
Go言語(net/http)でWebサーバーを作成(2)
前回の記事からだいぶ時間が経ってしまいました。
前回の記事はこちらです
本記事では golangでのhttp通信でhtmlファイルの表示をしてみます。非常に説明が多いです。自分でサンプルコードを理解したのでそれを皆さんに共有できたらと思います。よろしくお願い致します。
今回の記事はあまり有益ではありません。執筆者自身もあまり良い記事だと思っていません。あくまで記録として執筆しました。
こちらのソースコードは書籍「Go言語によるWebアプリケーション開発」に載っています。
package main import ( "log" "net/http" //追加 "path/filepath" "sync" "text/template" ) type templateHandler struct { once sync.Once filename string templ *template.Template } func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.templ.Execute(w, nil) } func main() { http.Handle("/", &templateHandler{filename: "template1.html"}) log.Fatal(http.ListenAndServe(":8080", nil)) }前回に比べてimport部分が増えました。ざっくりではありますが、解説していきます。
path/filepath
・・・ファイルパスを操作します。これによってパス同士の連結や指定をします。sync
・・・実行を一度だけ行う関数を定義するために必要です。今回はhtmlファイルの読み込みを行う回数を一度にしたいのでこのパッケージを使います。text/template
・・・htmlを扱うために使用します。最初に紹介した 'prth/filepath' がパス操作ならばこのパッケージはそのパスで指定されたファイルを実際にwebページとして読み込む操作をします。type templateHandler struct { //一度だけ実行する関数を使うため once sync.Once filename string //ポインタで Templateを使う templ *template.Template }まずはじめに構造体を定義します。ここでimportしたパッケージ syncを使って一度だけ実行する関数、どのファイルを表示するのか決めるfilename、実際にhtmlファイルを表示するためのtemplateをポインタで作ります。
次に構造体へメソッド(関数)を追加します。構造体にメソッドを追加する時は以下のルールで追加します。
func(任意の変数, 構造体名(ポインタ)) メソッド名(引数){ }任意の変数部分は追加する関数内で構造体自身をどのような変数で表現するかを決めることができます。一度ではなかなか理解ができないのでソースコードを読み進めるなかで理解していきます。
func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.templ.Execute(w, nil) }それでは追加したメソッドを読んでいきましょう。
前回にご説明した「func handler」を「ServeHTTP」という名前で追加しています。しかし、今回は前回とは少し違い、構造体で onceという名前で宣言したものを使います。 onceにあるメソッド「Do」を使って一度だけ実行する関数を作成していきます。それがfunc(){ t.tmpl = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)) }) t.templ.Execute(w, nil) }この部分です。t.tmplで構造体templeHandlerにあるtmplにアクセスします。
メソッドを追加する際に(t, *templeHandler)なので「t」なんです。 ここを「s」に変えたならば s.tmplでアクセスします。template.Mustで絶対に実行するように設定し、template.ParseFilesでfilepath.Joinによって指定されたファイルへアクセスし、読み込みます。そして、最終的に構造体の tmplに格納します。
メソッドの最後は .Evecuteでhttp通信に書き込んでいきます。
それではmain関数を見ていきます。
func main() { http.Handle("/", &templateHandler{filename: "template1.html"}) log.Fatal(http.ListenAndServe(":8080", nil)) }main関数内は前回ご説明しましたね。
今回は &templeHandlerで要素を{filename:"ファイル名"}で指定します。このファイルはgoスクリプトが保存されているフォルダに「templates」フォルダを作成し、その中に.htmlファイルを保存してください。
- 投稿日:2021-01-11T00:27:40+09:00
Go言語-fmt.Scan
fmt.Scan
プログラミング基本からGoの理解を文字におこして行こうと思います!
fmt.Scan
var input string fmt.Scan(&input) //❶fmt.scan(&変数名)でコンソールに文字入力 } //コンソール Go //❷文字列を入力→❸変数inputに入力された値Goが代入fmt.Scan(&変数名)を使うとコンソールでの入力ができる
入力された値は変数inputに代入入力の流れ
func main() { var input string fmt.Println("次の単語を入力してください:go") //❶ fmt.Scan(&input) //❷ fmt.Printf("%sと入力されました", input) //❸ //コンソール //❶次の単語を入力してください:go //❷go(コンソールで入力) //❸goと入力されましたfmt.Scanを使うと入力ができるようになる