20210111のGoに関する記事は14件です。

GoLand IDEをUbuntuにインストール

無料バージョンではありますが、GoLandエディタを自分のUbuntu環境にダウンロードしてみました。
参考にしたのは以下のサイトです。
Install Jetbrains GoLand Go IDE on Ubuntu
在Ubuntu安装JetBrains Goland IDE

snapdを使ってダウンロードする方法もありますが、今回はサイトからダウンロードする方向でいきました。

前提

  • 環境が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を選んで進みます。

Screenshot from 2021-01-11 23-20-22.png

ライセンスは持っていないので、無料バージョンを使います。Evaluate for freeを選んで持っていないほうで進めると、最終的にこの画面にたどりつきます。

Screenshot from 2021-01-11 23-20-54.png

毎回シェルファイルを実行して立ち上げるのは面倒なので、以下のようにアプリケーションディレクトリにファイルを作成します。今回はバージョンが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

これでアプリケーション内から開けるようになります。

Screenshot from 2021-01-11 23-40-17.png

ちなみにDesktopでも開きたい場合は、Desktopに先程のファイルをコピればいいです。

$ cp Goland.desktop /home/user/Desktop/

プログラムを書いてみる

New Projectを選びます。

Screenshot from 2021-01-11 23-43-40.png

すでに入っているGOROOTをもとにCreateします。

Screenshot from 2021-01-11 23-43-57.png

毎度おなじみのHello Worldを作ります。

Screenshot from 2021-01-11 23-51-04.png

上のRunメニューからRunを押すと go build hello.goが出てくるので、これをクリックします。

Screenshot from 2021-01-11 23-49-16.png

Hello Worldが出力されました!

Screenshot from 2021-01-11 23-51-09.png

以上です。無料バージョンなんで30日しか使えないぽいんですけどね、、、

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

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.closeadder.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のスケジューラの実装も読むつもりなのでその時に確認しようと思いますが、どなたかご存知の方いたら教えて頂けると幸いです。

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

サイト「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/amd64

Azure 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.auth

quickstart.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.go 

main.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

コードの詳細 以降

ソース コードには一通り目を通し、ココこそ力を入れて解説しようかと思ったのですが、公式ドキュメントのコード解説が詳しいので、出番なし!(素晴らしい!)

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

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_USERPX_ADMINUI_PASSWORDでログインします。

スクリーンショット 2021-01-11 11.54.39.png

X-Roadとは

今一体何を立ち上げたのでしょうか。
Security Serverとは何でしょうか。

arc-g_deployment_view_of_x_road.png
図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" をクリックします。

スクリーンショット 2021-01-12 16.55.30.png

スクリーンショット 2021-01-12 16.52.40.png

認証用証明書をActivateしました。
Security Serverの登録リクエストを送りましょう。

バツを押してその画面を閉じ、"KEYS AND CERTIFICATES" をクリック -> "Register" ボタンを押します。

スクリーンショット 2021-01-12 16.34.20.png

"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"ボタンを押します。

スクリーンショット 2021-01-15 12.01.51.png

"CLIENTS"を押し以下のように表示されていれば(Docker Demo Company (Owner)とdemoclientの行に緑の円と"REGISTERED"が表示されていれば)Subsystemの登録は完了です。

スクリーンショット 2021-01-12 19.25.42.png

図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"に変更してください。

スクリーンショット 2021-01-13 14.26.27.png

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に当たります。

いくつか私が面白いと思う点を書きます。

  1. Security ServerがInformation Systemから見るとシンプルなトンネルであること

    このGolangのサーバをローカルで実行し、立ち上がったサーバにcurlでHTTPリクエストを送る手順と、インターネットのどこかでPlanetway社が提供するtimeサービスを呼び出す手順の違いが小さいこと。X-Road-Clientリクエストヘッダ、URLのpath部分だけです。

  2. X-Roadのセキュリティ面はInformation Systemには見えない

    よくある外部APIと比較するとどうでしょう。Bearer tokenのようなものもクライアント証明書も何も見えません。認証や認可はSecurity Serverが隠蔽しています。

  3. curlのリクエスト送り先はSecurity Server

    知る必要があるのは、組織のID、その中のサービスIDからなるserviceIdだけです。相手のURLは知る必要がありません。インターネットのレイヤの上に綺麗にRPCのレイヤをのっけています。

まとめ

DockerでX-Road Security Serverを立ち上げ、それを通して時刻を返すデモサービスを呼び出すcurlコマンドを実行しました。

続く

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

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.mdkintoとうフォントを使うように言っているのでダウンロードする

ookamiinc/kinto: 均等 — Kinto is a Japanese font family adapted to match size & balance with Latin characters in user interfaces. A project based off Google Noto fonts.

今回はこの画像作成に使用できればいいので、static/fonts/Kinto_Sansといった感じのディレクトリに保存しておく

テンプレート

元となる背景画像的なテンプレートを作成します。
tcardgenリポジトリのexampleにいろいろサンプル画像が入ってます。
サイズは
1200 x 628 (px)
で作成します。

その後、自分はiPadのAffinity Designerで、サンプル画像を透過させながら、いい感じのデザインを作成しました。

下準備

markdown設定

tcardgenは、その使用上categorytagsの項目をなにかしら設定する必要があります。
よって、作成したい記事にはこれらの項目を追加します。
また、作成した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です。

先ほどのmarkdoncarduse: xxxというのを指定しました。あれはここで条件分岐するためです。

cardusetrueの場合はブログだと判断して、先ほど作成した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を設定し、かつブログ以外と分けることができました。

終わりに

ブログ作成と同時にこの作業はしていましたが、なかなか記事を書かずにいました。

少しでも参考になれば幸いです。

参考

今回も、はじまりはさんぽしさんでした。

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

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公式サイトに記載されているインストールコマンドをコピーし、ターミナルにペーストしてインストールを開始します。

% /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 update

brewでインストールしたモジュール群を最新化するには、upgradeを指定します。

% brew upgrade

Azure CLI(azコマンド)

本体を最新化するには、upgradeを指定します。

% az upgrade

両方

brew本体と、az本体および関連するモジュールをまとめて最新化するには、次のコマンドを実行します。

$ brew update && brew upgrade azure-cli

Azure SDK for Go

サイトに記載されているインストールコマンドをコピーし、ターミナルにペーストしてインストールまたは最新化を行います。

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

この時、Azure/go-autorestもインストール&最新化されます。

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

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
0xc421100052

main関数と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関数から元の変数の値を更新できる
このとき、受け取る引数はポインタ型を指定することに注意

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

Go言語-アドレス

アドレス

プログラミング基本からGoの理解を文字におこして行こうと思います!

アドレスとは

コンピュータにはメモリと呼ばれる作業場所のようなものが存在
変数はそのメモリに記録され、その場所をアドレスという

「0xc420010230」のように16進数で表現されることが多い
16進数とは、数字を数えるときに16で繰り上がるようにする数え方

アドレスの取得

func main() {
  name := "tanabe"
  fmt.Println(name)   //nameで値を取得
  fmt.Println(&name)  //&nameでアドレスを取得

//コンソール
tanabe
0xc421100230

変数のアドレスを取得するためには、「&変数名」とする
メモリ上の記録する場所はコンピュータによって変わるため
プログラムを実行する度に違うアドレスを出力する場合もある

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

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に値を足しても、もとの値は変わらない

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

【環境変数】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/SettingGOPATH

GOPATH は適当に決めて問題ない
https://qiita.com/yuku_t/items/c7ab1b1519825cc2c06f

GOPATHの設定とgo get
http://kodama-tech.hatenablog.com/entry/2016/12/14/002115

[備忘録]Go言語のGOPATHやパッケージについて
https://qiita.com/chano2/items/ea76cc503e651f07bfb0

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

【Golang】 よく使う関数のメモ

この記事について

完全に自分用に書いているので説明は少なめで簡単な処理のものが多いですが、備忘録的に使うので随時更新します。

stringからのキャスト

stringint

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
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ファイルを保存してください。

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

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を使うと入力ができるようになる

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