2020/10,江端さんの技術メモ

golangのtemplateを試してみたくて(javascriptに疲れてきて)、Webブラウザから一回だけメッセージを送信するプログラム(server2.go)を書いてみました。
# Echoプログラムはいままで使ってきたプログラムを使わせて貰っています。
「GolangでCUIでWebsocketを試したい」にドンピシャのソースコードはこちら

やっていることは、サーバを立ち上げて、ブラウザからhttp://localhost:8080を起動して、(1)WebSocketをオープンして、(2)テキストメッセージを"5つ"送付して、(3)サーバから戻ってきたメッセージを受信・表示して、(4)クローズしているだけ

/*
// server3.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// 使い方
// go run server2.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト

var upgrader = websocket.Upgrader{} // use default options

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv_serv: %s", message) // 受信したメッセージの表示
		err = c.WriteMessage(mt, message)    // 受信したメッセージの返送
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>  

var print = function(message) {
    var d = document.createElement("div");
    d.textContent = message;
    output.appendChild(d);
};


//引数にはミリ秒を指定します。(例:5秒の場合は5000)
function sleep(a){
  	var dt1 = new Date().getTime();
  	var dt2 = new Date().getTime();
  	while (dt2 < dt1 + a){
		dt2 = new Date().getTime();
	}
  	return;
}


ws = new WebSocket("{{.}}");

// 送信5発(3秒単位)を送信
ws.onopen = function (event) {
	for (let i = 0; i < 5; i++){
		ws.send("Ebata is great"); 
		print("send: Ebata is great");
	
		print("Start sleep");
		sleep(3000);
		print("End sleep");
	}
}

// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
   	//console.log(event.data);
   	print("RESPONSE: " + event.data);
}

// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
    print("CLOSE");
    ws = null;
}

</script>
</head>
<body>

<div id="output"></div>

</body>

</html>
`))

2020/09,江端さんの技術メモ

Access to XMLHttpRequest at 'http://localhost:8080/api/loc/2' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
test.html:30 GET http://localhost:8080/api/loc/2 net::ERR_FAILED

が取れなくて困っていたので、以下のアドインを入れて強制的に解決した(デフォルトで、"なんでもOK")としておけばよさそう)。

ちなみに、これを使うと、Twitterで通信障害「"問題が発生しました" "やりなおす"」が発生するようなので、実験が終ったら、解除しておいた方が良いです。

ちなみに、上記の問題はjavascriptで発生している問題ですが、Go連携でなんとかしようかと思っていたところで、Sさんから、以下の情報を頂きました。

[Golang] gorilla/muxのCORS対処法

多分、今日あたりからぶつかりそうな問題でした。

で、今ぶつかっている問題ですが、JSON.parseから、情報が取れないんですよね。なんでかなー、色々試しているんだけど、もう疲れてきたなぁ。VSCでデバッグしているんですけど、dataの中身がスカスカなんですよねー。うん困った。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0.1//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html lang="ja">
<head>
<meta http-equiv="Content-Type" Content="text/html;charset=Shift_JIS">
<meta http-equiv="Content-Script-Type" content="text/javascript">

<title>同期通信テスト</title>

<script>
    // (1)XMLHttpRequestオブジェクトを作成
    var xmlHttp = new XMLHttpRequest();


    // (2)onreadystatechangeイベントで処理の状況変化を監視
    //xmlHttp.onreadystatechange = function(){
    //    if(this.readyState == 4 && this.status == 200){
    //        //console.log(this.responseText);
    //        data = this.response
    //    }
    //}

    //var data;

    // (3)HTTPのGETメソッドとアクセスする場所を指定
    xmlHttp.open("GET", "http://localhost:8080/api/loc/2", true);
    //xmlHttp.onload = function(){
    //    if (xmlHttp.status >= 200 && xmlHttp.status < 200){
    //        data = JSON.parse(xmlHttp.responseText);
    //    } else {
    //        console.log("error");
    //    }
    //}

    //xmlHttp.responseType = 'json'

    // (4)HTTPリクエストを送信 
    xmlHttp.send();  

    var data = JSON.parse(xmlHttp.responseText);

    //alert(xmlHttp.responseText);
    console.log(xmlHttp.response);
    console.log(xmlHttp.responseText);
    JSON.parse(data.responseText);
    //console.log(data);
    //var user = JSON.parse(this.responseText);   
    //var user = JSON.parse(data);   
    //var user = JSON.parse(this.responseText);
    //alert(user);
</script>

2020/09,江端さんの忘備録

市役所から肝炎ウイルス検診の案内が来まして、現在、市の指定の診療所に来ています。

The city hall has sent me a guide to hepatitis virus screening, and I am currently at a clinic.

背景は良く分かっていませんが、「生涯1度だけの検診 + 無料」ということなので、受診してきました。

I don't know the background well, but I went to see it because it means "one-time examination + free of charge".

それに、セルシン(精神安定剤)の処方もして貰うことにしました。

I also decided to get a prescription for celsine (tranquilizer).

-----

私、結構長いこと、人間ドッグで「脂肪肝」を指摘されて続けていました。

I have been pointing out "fatty liver" in human dogs for quite a long time.

ダイエット(のコラム執筆)と、断酒によって、ようやく、これから逃れることができるようになりました。

By dieting (writing a column) and abstaining from alcohol, I was finally able to escape from this.

断酒の効果であるかかどうかは、はっきりと分からないのですが、「肩コリ」「腰痛」が、劇的に改善されました。

I'm not sure if it's the effect of abstinence, but "shoulder stiffness" and "backache" have improved dramatically.

「不眠」は、"アルコール"が"セルシン"に代わっただけ、という見方もできますが、大きな改善点は、『うたたね』ができるようになったことです。

It can be said that "insomnia" is just a replacement of "alcohol" with "selcin", but the major improvement is the ability to take a "nap".

これによって、仕事(特に連続10数時間のコラム執筆)中に、10分間程度の仮眠できるようになったことは、大きいです。

It is great that I was able to take a nap for about 10 minutes during work (especially writing a column for more than 10 hours in a row).

まあ、「アルコールを摂取することで睡眠時間を確保していた」という事実は、「アルコールに依存することで、無理矢理、眠りを得ていた」と言える訳で、つまるところ

Well, the fact that "I was able to secure sleep time by consuming alcohol" can be said to be "I was forced to sleep by depending on alcohol", after all, I can't deny

『アルコール依存症』

"Alcoholism"

を、否定することはできないでしょう。

-----

以前もお話しましたが、アルコール依存症であった私にとって、コンビニのお酒の棚の列は、今なお結構な「地獄ロード」です。

As I said before, for me, who was an alcoholic, the rows of liquor shelves at convenience stores are still quite a "hell road".

あれ、マジでキツイです。

That is really hard.

あのロードを止まらずに突っ切るのは、相当な精神力や胆力を必要とします。

It takes a great deal of mental strength and courage to pass through that road without stopping.

「酒が飲めないことは、人生の半分を損している」と言われますが ―― 全く同感です。

It is said that "No drink, no life". I totally agree.

お酒というのは、人類が生み出した至宝の飲料です。

Alcohol is a treasure drink produced by humankind.

それでも、私が酒を断っているのは、

Still, the reason I refuse to drink is

―― まだまだ、書きたい物(コラムとプログラム)が沢山残っているから

"There are still a lot of things I want to write (columns and programs)"

です。

-----

とは言え、始終、頭がクリアで居続けるのは、なかなか辛いものです。

However, it's hard to stay clear from beginning to end.

コラムを書き上げた時とか、プログラムが動いた時は「ご褒美に、一杯やりたい」です。

When I finish writing the column, or when the program works well, I want to give a drink as a reward.

それ故、私は、安全性が担保され、習慣性がなく、依存性もなく、一定時間後には完全に正気に戻る(合法であることは言うまでもない)という ―― そういう、薬や嗜好品の開発をやりたい。

Therefore, I I want to do development of kind of medicine or luxury item, with safe, non-addictive, non-dependent, and completely going back to sane after a period of time (not to mention legal).

------

リタイア後に、再度大学受験をして、化学科に入学するのもいいな、と考えています。

After retirement, I think it would be good to take the university entrance exam again and enroll in the Department of Chemistry.

志望動機を問われたら

If I am asked about my motive, I will answer

『安全性を完全に担保した、究極の覚醒剤の開発です』

"Development of the ultimate stimulant that completely guarantees safety"

と答える予定です。

2020/09,江端さんの忘備録,江端さんの技術メモ

https://www.prakharsrivastav.com/posts/from-http-to-https-using-go/ が原典

Goを使ってHTTPからHTTPSへ
2019-08-02 :: プラカール・スリバスタフ

序章
この記事では、Go で TLS 暗号化を設定する方法を学びます。さらに、相互にTLS暗号化を設定する方法を探っていきます。このブログ記事で紹介されているコードはこちらからご覧いただけます。この記事では、関連するスニペットを表示しています。興味のある読者は、リポジトリをクローンしてそれに従ってください。

まず、シンプルな Http サーバとクライアントを Go で書くことから始めます。次に、サーバで TLS を設定することで、両者間のトラフィックを暗号化します。この記事の最後に、両者間の相互 TLS を設定します。

シンプルなhttpサーバー
まず、Go で Http クライアント・サーバの実装を作成してみましょう。localhost:8080 に到達可能な Http エンドポイント /server を公開します。そして、http.Clientを使ってエンドポイントを呼び出し、その結果を表示します。

完全な実装はこちらを参照してください。

// Server code
mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprint(w, "Protect Me...")
})
log.Fatal(http.ListenAndServe(":8080", mux))


// Client code
if r, err = http.NewRequest(http.MethodGet, "http://localhost:8080/server", nil); err != nil {
	log.Fatalf("request failed : %v", err)
}

c := http.Client{
	Timeout:   time.Second * 5,
	Transport: &http.Transport{IdleConnTimeout: 10 * time.Second},
}

if data, err = callServer(c, r); err != nil {
	log.Fatal(err)
}
log.Println(data) // Should print "Protect Me..."

次のセクションでは、TLS を使用してクライアントとサーバ間のトラフィックを暗号化します。その前に、公開鍵インフラストラクチャ (PKI) をセットアップする必要があります。

PKI のセットアップ
ミニ PKI インフラストラクチャをセットアップするために、minica という Go ユーティリティを使用して、ルート、サーバ、クライアントの鍵ペアと証明書を作成します。実際には、認証局 (CA) またはドメイン管理者 (組織内) が鍵ペアと署名付き証明書を提供してくれます。私たちの場合は、minicaを使ってこれをプロビジョニングしてもらうことにします。

鍵ペアと証明書の生成
注: これらを生成するのが面倒に思える場合は、Github リポジトリでコミットされた証明書を再利用することができます。

以下の手順で証明書を生成します。

minicaをインストールする: github.com/jsha/minicaを取得してください。
minica --domains server-certを実行してサーバ証明書を作成します。
初めて実行すると4つのファイルが生成されます。
minica.pem(ルート証明書
minica-key.pem (root 用の秘密鍵)
server-cert/cert.pem (ドメイン「server-cert」の証明書、ルートの公開鍵で署名されています)
server-cert/key.pem (ドメイン「server-cert」の秘密鍵)
minica --domains client-certを実行してクライアント証明書を作成します。2つの新しいファイルが生成されます。
client-cert/cert.pem (ドメイン "client-cert "の証明書)
client-cert/key.pem (ドメイン "client-cert "の秘密鍵)
また、minicaでドメインの代わりにIPを使用して鍵ペアや証明書を生成することもできます。

etc/hosts にエイリアスを設定する
上記で生成したクライアント証明書とサーバ証明書は、それぞれドメイン server-cert と client-cert で有効です。これらのドメインは存在しないので、localhost(127.0.0.1)のエイリアスを作成します。これを設定すると、localhost の代わりに server-cert を使用して Http サーバにアクセスできるようになります。

Linux以外のプラットフォームを使っている場合は、OSに合わせた設定方法をググってみてください。私はLinuxマシンを使っていますが、ドメインエイリアスの設定はとても簡単です。etc/hostsファイルを開き、以下の項目を追加します。

127.0.0.1       server-cert
127.0.0.1       client-cert

この時点で、インフラストラクチャの設定は完了です。次のセクションでは、クライアントとサーバ間のトラフィックを暗号化するために、これらの証明書を使ってサーバを設定します。

サーバーでTLSを設定する
サーバ-certドメインに生成された鍵と証明書を使って、サーバにTLSを設定してみましょう。クライアントは先ほどと同じです。唯一の違いは、3つの異なるURLでサーバを呼び出すことで、何が起こっているのかを理解することです。

完全な実装はこちら

// Server configuration
mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "i am protected")
})
log.Println("starting server")
// Here we use ListenAndServerTLS() instead of ListenAndServe()
// CertPath and KeyPath are location for certificate and key for server-cer
log.Fatal(http.ListenAndServeTLS(":8080", CertPath, KeyPath, mux))

// Server configuration
c := http.Client{
    Timeout:   5 * time.Second,
    Transport: &http.Transport{IdleConnTimeout: 10 * time.Second,},
}

if r, err = http.NewRequest(http.MethodGet, "http://localhost:8080/server", nil); err != nil { // 1
//if r, err = http.NewRequest(http.MethodGet, "https://localhost:8080/server", nil); err != nil { // 2
//if r, err = http.NewRequest(http.MethodGet, "https://server-cert:8080/server", nil); err != nil { // 3
    log.Fatalf("request failed : %v", err)
}

if data, err = callServer(c, r); err != nil {
    log.Fatal(err)
}
log.Println(data)

http.ListenAndServeTLS()を使用してサーバを起動します。これにはポート、公開証明書へのパス、秘密鍵へのパス、そしてHttp-handlerの4つの引数が必要です。サーバからのレスポンスを見てみましょう。私たちは失敗しますが、私たちはどのようにHttp暗号化が動作するかについてのより多くの洞察を与える3つの異なる要求を送信します。

Attepmt 1 http://localhost:8080/server に送信すると、応答があります。

Client Error. Get http://localhost:8080/server: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03x01x00x02\x02"

サーバエラー: http: 127.0.0.1:35694 からの TLS ハンドシェイクエラー: tls: 最初のレコードが TLS ハンドシェイクのように見えません。

これは、サーバーが暗号化されたデータを送信していることを意味する良いニュースです。Http経由では誰も意味をなさないでしょう。

Attempt 2 to https://localhost:8080/server、レスポンスは以下の通りです。

クライアントエラーです。Get https://localhost:8080/server: x509: certificate is valid for server-cert, not localhost

サーバエラー: http: 127.0.0.1:35698 からの TLS ハンドシェイクエラー: リモートエラー: tls: 不正な証明書

これはまたしても朗報です。これは、ドメインサーバ証明書に発行された証明書を他のドメイン(ローカルホスト)で使用することができないことを意味します。

Attempt 3 to https://server-cert:8080/server、応答があります。

クライアントエラーです。Get https://server-cert:8080/server: x509: certificate signed by unknown authority

サーバエラー: http: 127.0.0.1:35700 からの TLS ハンドシェイクエラー: リモートエラー: tls: 不正な証明書

このエラーは、クライアントがその証明書に署名したことを信頼していないことを示しています。クライアントは証明書に署名した CA を認識していなければなりません。

このセクションの全体的な考えは、TLS が保証する 3 つの保証を実証することでした。

メッセージは常に暗号化されている。
サーバが実際に言っている通りのものであること。
クライアントはサーバの証明書を盲目的に信じてはいけない。クライアントは、CA を通じてサーバの身元を確認できるようにしなければなりません。

クライアントでCA証明書を設定する
クライアント側のCA証明書を設定して、ルートCAの証明書とサーバの身元を照合できるようにします。サーバ証明書はルートCAの公開鍵を使って署名されているので、TLSハンドシェイクが有効になり、通信が暗号化されます。

完全な実装はこちらにあります。

// create a Certificate pool to hold one or more CA certificates
rootCAPool := x509.NewCertPool()

// read minica certificate (which is CA in our case) and add to the Certificate Pool
rootCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
rootCAPool.AppendCertsFromPEM(rootCA)
log.Println("RootCA loaded")

// in the http client configuration, add TLS configuration and add the RootCAs
c := http.Client{
    Timeout: 5 * time.Second,
    Transport: &http.Transport{
        IdleConnTimeout: 10 * time.Second,
        TLSClientConfig: &tls.Config{RootCAs: rootCAPool},
    },
}

if r, err = http.NewRequest(http.MethodGet, "https://server-cert:8080/server", nil); err != nil {
    log.Fatalf("request failed : %v", err)
}

if data, err = callServer(c, r); err != nil {
    log.Fatal(err)
}
log.Println(data)

// server response
prakhar@tardis (master)? % go run client.go  
RootCA loaded
i am protected # response from server

これにより、先ほど説明した3つの保証がすべて保証されます。

相互TLSの設定
サーバーにクライアントの信頼を確立しています。しかし、多くのユースケースでは、サーバーがクライアントを信頼する必要があります。例えば、金融、医療、公共サービス業界などです。これらのシナリオのために、クライアントとサーバーの間で相互にTLSを設定して、双方がお互いを信頼できるようにします。

TLSプロトコルは、最初からこれをサポートしています。相互TLS認証を設定するために必要な手順は以下の通りです。

1.サーバはCA(CA-1)から証明書を取得します。クライアントは、サーバの証明書に署名したCA-1の公開証明書を持っている必要があります。
2.クライアントは CA (CA-2) から証明書を取得します。サーバは、クライアントの証明書に署名したCA-2の公開証明書を持っていなければなりません。簡単にするために、クライアント証明書とサーバ証明書の両方に署名するために同じ CA (CA-1 == CA-2) を使用します。
3.サーバは、すべてのクライアントを検証するためにCA証明書プールを作成します。この時点で、サーバはCA-2の公開証明書を含む。
4.同様に、クライアントは独自のCA証明書プールを作成し、CA-1の公開証明書を含む。
5.両者は、CA 証明書プールに対して受信要求を検証します。どちらか一方に検証エラーがあった場合、接続は中断されます。
実際に動作を見てみましょう。この機能の完全な実装はこちらを参照してください。

サーバーの設定

mux := http.NewServeMux()
mux.HandleFunc("/server", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "i am protected")
})

clientCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
clientCAPool := x509.NewCertPool()
clientCAPool.AppendCertsFromPEM(clientCA)
log.Println("ClientCA loaded")

s := &http.Server{
    Handler: mux,
    Addr:    ":8080",
    TLSConfig: &tls.Config{
        ClientCAs:  clientCAPool,
        ClientAuth: tls.RequireAndVerifyClientCert,
        GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
            c, err := tls.LoadX509KeyPair(CertPath, KeyPath)
            if err != nil {
                fmt.Printf("Error loading key pair: %v\n", err)
                return nil, err
            }
            return &c, nil
        },
    },
}
log.Fatal(s.ListenAndServeTLS("", ""))

この設定で注意すべき点がいくつかあります。

  1. http.ListenAndServeTLS() の代わりに server.ListenAndServerTLS() を使用します。
  2. サーバ証明書と鍵を tls.Config.GetCertificate 関数の中にロードします。
  3. サーバが信頼すべきクライアント CA 証明書のプールを作成します。
  4. tls.Config.ClientAuth = tls.RequireAndVerifyClientCertを設定し、接続しようとするすべてのクライアントの証明書を常に検証します。検証されたクライアントのみが会話を続けることができます。

クライアント設定
http.Clientの設定は、クライアントの設定も少し変わります。

rootCA, err := ioutil.ReadFile(RootCertificatePath)
if err != nil {
    log.Fatalf("reading cert failed : %v", err)
}
rootCAPool := x509.NewCertPool()
rootCAPool.AppendCertsFromPEM(rootCA)
log.Println("RootCA loaded")

c := http.Client{
    Timeout: 5 * time.Second,
    Transport: &http.Transport{
        IdleConnTimeout: 10 * time.Second,
        TLSClientConfig: &tls.Config{
            RootCAs: rootCAPool,
            GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
                c, err := tls.LoadX509KeyPair(ClientCertPath, ClientKeyPath)
                if err != nil {
                    fmt.Printf("Error loading key pair: %v\n", err)
                    return nil, err
                }
                return &c, nil
            },
        },
    },
}

サーバと比較した場合の設定の違いに注目してください。

  1. tls.Configでは、サーバー上のClientCAの設定に対して証明書プールをロードするためにRootCAを使用しています。
  2. tls.Config.GetClientCertificate を使用して、サーバー上の tls.Config.GetCertificate に対してクライアント証明書をロードしています。
  3. GitHub の実際のコードにはいくつかのコールバックがあり、これを使って証明書の情報を見ることもできます。

クライアントとサーバの相互TLS認証の実行

# Server logs
2019/08/01 20:00:50 starting server
2019/08/01 20:00:50 ClientCA loaded
2019/08/01 20:01:01 client requested certificate
Verified certificate chain from peer:
  Cert 0:
    Subject [client-cert] # Server shows the client certificate details
    Usage [1 2]
    Issued by minica root ca 5b4bc5 
    Issued by 
  Cert 1:
    Self-signed certificate minica root ca 5b4bc5

# Client logs
2019/08/01 20:01:01 RootCA loaded
Verified certificate chain from peer:
  Cert 0:
    Subject [server-cert] # Client knows the server certificate details
    Usage [1 2]
    Issued by minica root ca 5b4bc5
    Issued by 
  Cert 1:
    Self-signed certificate minica root ca 5b4bc5
2019/08/01 20:01:01 request from server
2019/08/01 20:01:01 i am protected

結論
TLS の設定は、実装の問題というよりも証明書の管理の問題が常にあります。TLS 設定における典型的な混乱は、実装というよりも正しい証明書の使用に関連していることが多いです。TLS プロトコルとハンドシェイクを正しく理解していれば、Go は箱から出してすぐに必要なものをすべて提供してくれます。

また、理論的な観点からTLSの暗号化とセキュリティを探求した以前の記事もチェックしてみてください。

参考文献
この記事は、Gophercon-2018でのLiz Riceの素晴らしいトークに大きく影響されていますので、ぜひチェックしてみてください。その他の参考文献は以下の通りです。

secure-connections: gophercon のためのレポ
minica 認証局
Eric Chiangによるこの驚くべき記事。必読です。
step-by-step-guide-to-mtls-in-go.
mediumのこの記事。

2020/09,江端さんの技術メモ

サーバ側 loc_rest_server.go というファイル名で保存して、
>go run loc_rest_server.go
で起動


package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
)

/*

type GetLoc struct {
	Message string `json:"message"`
	Name    string `json:"name"`
}
*/

/*
// GetLoc GetLoc
type GetLoc struct {
	ID  int64   `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}
*/

// GetLoc GetLoc
type GetLoc struct {
	ID  string `json:"id"`
	Lat string `json:"lat"`
	Lng string `json:"lng"`
	//Address string  `json:"address"`
}

// ErrorResponse error response
type ErrorResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// locService loc service
func locService(ctx context.Context, number string, tm time.Time) (*GetLoc, error) {

	if number == "1" {
		return &GetLoc{
			ID:  number,
			Lat: "35.653976",
			Lng: "139.796842",
		}, nil
	}

	if number == "2" {
		return &GetLoc{
			ID:  number,
			Lat: "35.653758",
			Lng: "139.794192",
		}, nil
	}

	return nil, nil
}

// AppHandler application handler adaptor
type AppHandler struct {
	h func(http.ResponseWriter, *http.Request) (int, interface{}, error)
}

func (a AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	encoder := json.NewEncoder(w)
	status, res, err := a.h(w, r)
	if err != nil {
		log.Printf("error: %s", err)
		w.WriteHeader(status)
		encoder.Encode(res)
		return
	}
	w.WriteHeader(status)
	encoder.Encode(res)
	return
}

/*
// GetLoc GetLoc
func (app *App) GetLoc(w http.ResponseWriter, r *http.Request) (int, interface{}, error) {
	res, err := locService(r.Context(), "", time.Now())
	if err != nil {
		app.Logger.Printf("error: %s", err)
		e := ErrorResponse{
			Code:    http.StatusInternalServerError,
			Message: "something went wrong",
		}
		return http.StatusInternalServerError, e, err
	}
	app.Logger.Printf("ok: %v", res)
	return http.StatusOK, res, nil
}
*/

// GetLocWithNumber GetLoc with number
func (app *App) GetLocWithNumber(w http.ResponseWriter, r *http.Request) (int, interface{}, error) {
	val := mux.Vars(r)
	//res, err := locService(r.Context(), val["id"], time.Now())
	res, err := locService(r.Context(), val["id"], time.Now())
	if err != nil {
		app.Logger.Printf("error: %s", err)
		e := ErrorResponse{
			Code:    http.StatusInternalServerError,
			Message: "something went wrong",
		}
		return http.StatusInternalServerError, e, err
	}
	app.Logger.Printf("ok: %v", res)
	return http.StatusOK, res, nil
}

// App application
type App struct {
	Host   string
	Name   string
	Logger *log.Logger
}

func main() {
	host, err := os.Hostname()
	if err != nil {
		log.Fatal(err)
	}

	app := App{
		Name:   "my-service",
		Host:   host,
		Logger: log.New(os.Stdout, fmt.Sprintf("[host=%s] ", host), log.LstdFlags),
	}
	// for gorilla/mux
	router := mux.NewRouter()
	r := router.PathPrefix("/api").Subrouter()
	//r.Methods("GET").Path("/loc").Handler(AppHandler{h: app.GetLoc})
	//r.Methods("GET").Path("/loc/staticName").Handler(AppHandler{h: app.GetLoc})
	r.Methods("GET").Path("/loc/{id}").Handler(AppHandler{h: app.GetLocWithNumber})

	if err := http.ListenAndServe(":8080", router); err != nil {
		log.Fatal(err)
	}
}

クライアント側
main.go で保存して、
>go run main.go
で起動

参考とさせて頂いたページ「Goでhttpリクエストを送信する方法

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	//url := "http://google.co.jp"
	url := "http://localhost:8080/api/loc/2"

	resp, _ := http.Get(url)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(byteArray)) // htmlをstringで取得
}
動作結果
>go run main.go
 {"id":"2","lat":"35.653758","lng":"139.794192"}

JSONで展開するにはどうしたらいいかな?

参考にさせて頂いたのは「goでjson apiを叩く

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  string `json:"id"`
	Lat string `json:"lat"`
	Lng string `json:"lng"`
	//Address string  `json:"address"`
}

func main() {
	//url := "http://google.co.jp"
	url := "http://localhost:8080/api/loc/2"

	resp, _ := http.Get(url)
	defer resp.Body.Close()

	/*
		byteArray, _ := ioutil.ReadAll(resp.Body)
		fmt.Println(string(byteArray)) // htmlをstringで取得
	*/

	var d GetLoc

	fmt.Printf("======Body (use json.Unmarshal)======\n")
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	err = json.Unmarshal(body, &d)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%v\n", d)

	fmt.Printf("ID:%v\n", d.ID)
	fmt.Printf("Lat:%v\n", d.Lat)
	fmt.Printf("Lng:%v\n", d.Lng)
}

出力結果

>go run main.go
======Body (use json.Unmarshal)======
{2 35.653758 139.794192}
ID:2
Lat:35.653758
Lng:139.794192

2020/09,江端さんの忘備録

本日は、コラムがリリースされた日なので、日記はお休みです。

Today, new my column is released, so I take a day off.

踊るバズワード ~Behind the Buzzword(6)量子コンピュータ(6):

ひねくれボッチのエンジニアも感動で震えた「量子コンピュータ至高の技術」

Dancing Buzzword-Behind the Buzzword (6) Quantum Computer (6)

Even a cynical lonely engineer was moved to tears by the "supreme technology of the quantum computer"

-----

今回、量子コンピュータの最終回です。

This is the final installment of the "Quantum Computer" series.

最終回なので、監修をして頂いた、ご自称「量子コンピュータオタクのTさん」に、コメントの寄稿をお願いしたのですが、

As this is the last episode, I asked the self-proclaimed "quantum computer geek, Mr. T", who supervised the article, to contribute a comment.

『少し考えてみましたが、いい文章が浮かばなかった』

"I gave it some thought, but I couldn't come up with a good sentence"

との理由でご辞退されました。

He declined to do so because of the reason.

まあ、突然寄稿を依頼されて、ホイホイ応じる人間は、私の知る限り、私だけです。

Well, as far as I know, I'm the only person who is suddenly asked to contribute and responds immediately.

-----

長い間、「無礼な後輩シリーズ」として、レビューをして貰っている後輩にも、コメントの寄稿について訊ねてみたのですが、

For a long time, I asked the juniors who have been reviewing it as a "rude junior series" about contributing comments. However he said to me.

「メリットがない」

"No merit"

と一蹴されました。

I was kicked off by him.

後輩:「江端さん、もう何年もコラム書き続けているのに、全然メジャーになっていないじゃないですか」

"Ebata-san, you've been writing a column for years now, but you haven't become a major player at all.

後輩:「江端さんにフリーライド(ただ乗り)しても、私にメリットがないですよ」

"There's no benefit to me in free-riding with Ebata-san"

-----

確かに ―― その通り。

Certainly -- that's right.

コラム執筆に、こんなに膨大な時間と労力を注いでいるのに、「日の当たるところにいる」という感覚が ―― "ゼロ"

I've put such an enormous amount of time and effort into writing the column, however, the sense of "being in the sun" is "zero"

何故だろう。おかしい。なぜ、私の努力はこんなにも空回りしてしまうのだろう。

I don't know why. It's funny. Why are my efforts so spinning out of control?

―― というような話を、嫁さんにしたところ、

I told my wife about the story, and she said

嫁さん:「そもそも『メジャーになりたい』と思っているの?」

Wife: "Do you want to 'be a major player' in the first place?

と、言われました。

-----

あ、どんどん、嫌なことを思い出してきた。

Oh, I'm starting to remember more and more of the bad stuff.

そういえば、大学院の入試勉強の際にも、

Come to think of it, when I was studying for my graduate school entrance exam.

―― エバちゃんってさぁ、「入試の目的(合格)」を忘れて、「勉強の為に勉強する」ようになるから、心配だよ

I'm worried about you, Eba-chan, because you're going to forget the purpose of the entrance exam (passing) and start "studying for the sake of studying".

と、下宿に閉じ籠って勉強している私に、手紙を送ってくれたゼミの友人(女性)のメッセージを思い出してしまいました。

It reminded me of a message from a seminar friend (a woman) who sent me a letter, when I was studying in the boarding house.

# 電子メールを使うだけで、オタク呼ばわりされた時代でした(BBS(nifty)経由のみ)

# It was an era when people were called nerds just by using e-mail (only via BBS (nifty))

-----

そういえば、私、"メジャー"というものも、よく分からないんですよ。

By the way, I don't really understand what the "major" is.

どのくらい分からないかと言えば、「東京に出てきて、一旗上げる」という、その「旗」の内容が分からない程度に、分からないです。

I don't know it like the contents of a "flag" of "raising in Tokyo".

2020/09,江端さんの忘備録

今日は、母の施術の立ち会いの為に、会社を休んで、病院に行ってきました。

Today, I took a day off from work to go to the hospital to witness my mother's treatment.

フェイスシールドを準備しての万端の体制だったのですが、病院では、看護師さんもフェースシールドをしていませんでした。

I was all set up with a face shield at the ready, but at the hospital, the nurses didn't have a face shield either.

悪目立ちするのも何なので、今回は、フェースシールドを使うのはやめました。

I didn't want to stand out, so I decided not to use the face shield this time.

それはさておき。

Aside from that.

-----

昨日お話しましたが、体中の筋肉痛がハンパなく酷いので、実家の方のスーパー銭湯にいって、サウナ3セットをやってきました。

As I told you yesterday, I went to the super public bath at my parents' house and did three sets of sauna sessions because the muscle pain in my body was so bad.

サウナの中で流れているテレビ番組を ―― 苦痛を感じながら聞いていました。

I listened to the TV show playing in the sauna -- painfully.

著名な女優さんが自殺するのは痛ましいし、元アイドルグループのメンバが、飲酒運転の上に事故を起こすのは非難されてしかるべきです。

It's tragic to see a famous actress kill herself and a former member of an idol group should be blamed for causing an accident on top of a drunk driver.

―― しかし、大人が数人も集って、何十分も語るようなネタか?

"However, is this the kind of story that a few adults can get together and talk about for 20 minutes?"

うん、そういうネタなんだろう。

I thought "Yes, it is".

そういうネタだから、真っ昼間から放送されている訳です。

That's why it's being broadcast from daytime.

それだけのリソースを投入するなら、ソリューション(解決法)の議論でもすればいいのに ―― と思ってしまうのは、私がエンジニアだからかもしれません。

I thought "If they want to invest that much resources, they can discuss the solution", however, it may be because I am an engineer.

-----

個人的意見としては、

In my personal opinion,

―― サウナ室は無声、無音でいい

"Sauna rooms can be silent"

と思うのですが、サウナ利用者としては、ボーッとするよりは、テレビが流れていた方が気が紛れるということもあるのかもしれません。

However, as a sauna user, it may be more distracting to have the TV on than to be dumb.

うん、無声、無音では、サウナ室が、座禅の修行所のような感じになってしまうだろう(私は、それでもいいと思うのですが)。

Yeah, the sauna room will look like a zazen training center(I think that's fine).

私としては、早いところ、

I do expect that

■サウナ対応(完全防水、上限温度120度)のタブレットの開発

- Development of tablet compatible with sauna (fully waterproof, maximum temperature 120 degrees)

と、

and

■そのタブレットとイヤホンの銭湯への持ち込み

- Bringing the tablet and earphones to the public bath

を期待したいところです。

"VRゴーグル"でも可です。

"VR goggles" are also acceptable.

2020/09,江端さんの忘備録

無人の実家 ―― 江端のセキュリティシステムがガッチリ守っていますが ―― に帰省して、実に半年ぶりの庭手入れをしました。

I went to uninhabited my parents house, and tended the garden for the first time in six months, though Ebata's security system is protecting my garden.

庭が、雑草でエラいことになっていました。

The garden was getting out of control because of the weeds.

草や木を、電動チェーンソーで裁断しまくり、その後に、50m平方メートル相当の防草シートを敷設しました。

We cut all the grass and trees with an electric chainsaw and then laid down 50m square meters of weed control sheeting.

裁断した草木は、回収用ゴミ袋を14袋を使いました。

For the plants and trees cut, I used 14 bags of garbage for collection.

今回、役に立ったのは、携帯用の蚊取り線香です。

What helped me this time was a portable mosquito coil.

これを2つ購入して、ズボンの前後に装着していました。

I bought two of these and wore them on the front and back of my pants.

原始的なモノほど効果がある、というのを実感しました。

I have found that the more primitive the object, the more effective it is.

前日の作業では、何箇所か薮蚊に刺されていましたが、本日は被害はゼロでした。

During the previous day's work, there were several bush mosquito bites, but today there was no damage.

で、それはさておき。

And that's beside the point.

-----

昨夜は、腕が震えて箸が使えませんでした。

Last night my arms were shaking and I couldn't use chopsticks.

本気で「アル中」になったのかと思いましたよ ―― 禁酒してかなり経つというのに。

I seriously thought I have been an "alcoholic" -- however, I've been sober for quite a while now.

どうやら電動チェーンソーの使いすぎが原因らしいです。今朝は直っていましたので。

Apparently it's due to overuse of the electric chainsaw. I was able to get it fixed this morning.

しかし、腰から背中への痛み(筋肉痛)が半端ではなく、午前中はなんども眩暈(めまい)を起こして、作業を中断しながらの作業でした ―― ちなみに、飲料水はリットルレベルで補給していましたが。

But the pain (muscle pain) of my back was so bad. In the morning, I got dizzy many times and had to stop working for a while. Even I was replenishing my drinking water at the liter level.

これまでに体験したことのない現象で、ちょっと衝撃受けています。

It's a phenomenon I've never experienced before and I'm a bit shocked.

コロナ自粛による運動不足か ―― もう本格的に「老化」かもしれません。

Lack of exercise due to Corona self-restraint -- it may be full-blown "aging" already.

2020/09,未分類,江端さんの技術メモ

WiMAX 2+ は、有線(USB)で使えないと思っていたけど、USBで表示されたドライブ(D:)をクリックしたら使えるようになった。本体の方で特別な設定はしなかった。とりあえずメモで。

2020/09,江端さんの忘備録

「俺の青春ラブコメは間違っている完」の最終話の感想の続きです。

This is next my impression after watching episode 12(final) of 懼My Youth Romantic Comedy is Wrong,As I Expected ? Final"

-----

そういえば、言い忘れていましたが、

By the way, I forgot to say,

この最終回のもっとも重要なフレーズは、

The most important phrase in this final episode is.

『彼女がいる人、好きになっちゃいけないなんて法律ありましたっけ?』

"Was there a law that you shouldn't love someone who has a boyfriend ?"

です。

それと

In addition,

『あの二人が長続きするわけないじゃないですか』

"There's no way those two are going to last"

という考察も、なかなかい鋭いです ―― 正直、この人物の実年齢を疑うレベルの達観度です。

This consideration also has a considerable impact. To be honest, I doubt the actual age of her.

-----

私が知る限り、ティーンエイジャの恋愛が、長期間続いたという事例 ―― 例えば、結婚に至った等 ―― は、レアケースです。

As far as I know, cases of teenage love that have lasted for long periods is extreme rare. -- e.g., ending in marriage.

加えて、「めんどうくさい人間」の恋愛コヒーレント時間は、"短い"ですが、

In addition, while the romantic coherent time for "messy people" is "short",

「めんどうくさい人間同士」の恋愛コヒーレント時間は、"恐しく短い"です。

The love coherent time between "messy humans" is "terribly short".

私は、このヒーローとヒロインの破局(第一回目)は、高校在学中に発生する、と確信しています、

I am convinced that this hero and heroine catastrophe (the first one) occurs while in high school, and

だかこそ、もう一人のヒロインの取り得る戦略は、明確です。

That's why the possible strategy of the other heroine is clear.

「破綻のさせない友人関係の維持」

"Maintaining friendships that won't break up.

です。

もう一人のヒロインは、長期的視野に基づく、実にクレバーな戦略を選択したのです。

She chose a really clever strategy based on a long-term view.

-----

私の場合、

In my case,

■彼氏のノロケ話/愚痴を聞かされても、

even if I hear her boyfriend bragging and complaining about it,

■見合いの報告をされても、

even if she reported her matchmaking to me.

■プロポーズを断わられても、

even if she says no to my proposal,

それでも、「友人」という位置だけはキープし続ける ―― という、戦略を採用しました。

Nevertheless, I adopted the strategy of keeping the position of "friend" -- that's all.

-----

もちろん、このような戦略においては「一途(な恋とか愛)」というのは、無理です。

Of course, you can't be "single-minded love" in such a strategy.

恋愛はエネルギーを消費する行動であり、「出力値 x 出力時間」は一定値だからです。

This is because love is an energy-consuming behavior, and "output value x output time" is a constant value.

故に、100%の「一途」は、この戦略には向きません。

Hence, 100% "single-mindedness" is not a good fit for this strategy.

―― 「一途」ではなく、「0.7途」とか「0.5途」くらい

So, "0.7-mindedness" or "0.5-mindedness" will be better than "single-mindedness".

がいいです。

他のエネルギーは、「趣味」とか「仕事」とかに当分に配分しておくと全体としても良いです。ポートフォリオですね。

Other energy can be allocated to "hobbies" or "work" for the time being, like portfolio.

それと、「一途」の人は、いい感じに利用される傾向もあります(いわゆる「キープ君」等)。

And people who are "single-minded" tend to get taken advantage of in a good way (such as the so-called "Mr. Keeper").

一方、この戦略は、「敢えてその役割(キープ君)を振る舞う(ように見せる)というものでもあることにも留意しておいて下さい

On the other hand, keep in mind that this strategy is also about "daring to act (or appear to act) in that role (Mr. Keep)

-----

まあ、この戦略の最大の難点は、「恋愛が成就しないこと」を、最初から計画に計上しなけばならない点にあります。

Well, the biggest difficulty with this strategy is that you have to measure "love not being fulfilled" in your plan from the beginning.

このような確率に基づく恋愛戦略が、ティーンエイジャたちに受け入れられるか ―― ということが、最大の障壁でもあります。

One of the biggest barriers is the acceptance of this probability-based relationship strategy by teenagers -- and that's the biggest barrier.