未分類

(昨日の続きです)

(Continuation from yesterday)

江端:「まあ、それはさておき、『一旗』って何だ?」

Ebata: "Well, aside from that, what's a 'the flag' ?"

嫁さん:「『故郷に錦を飾る』とも言うね」

Wife: "It's also called 'return to their hometown in glory'"

江端:「『一旗』から想起されるイメージって、ミュージシャン、アイドル、役者、くらいしか思い付かないんだけど」

Ebata: "The only images that 'the flag' reminds me of are musicians, idols and actors"

嫁さん「あるいは、政治家とか野球選手・・・いずれにしても、この程度が私達のイメージが限界かな」

Wife: "Or a politician or a baseball player... Anyway, I guess this is the limit of our image.

江端:「自分の望みのことが達成できれば、それは、『一旗上がった』ということにならないのかな? 自分なりの目標に対して、それを達成すれば、足るというような」

Ebata: "If they can achieve what they want, is that 'they could raise the flag' ? I suppose it's good enough for my own goals, if I achieve them."

嫁さん「・・・いや、それはちょっと違うかな」

Wife:"...no, that's not enough.

江端:「?」

Ebata:"?"

嫁さん:「『故郷に錦を飾る』と合わせて考えれば、それは、第三者にとって ―― 特に出身地の ―― 多くの人間が、共通して、"成功"と認識できる何かでなければ、成立しない」

Wife: "If they use the phrase of "return to their hometown in glory", the flag should be something that could be perceived as a "success." for many people, especially in their hometown."

江端:「・・・多くの人が共通して価値のあると認められるもの ―― つまり"金"か」

Ebata: "...what most people consider to be of common value -- 'money'"

嫁さん「あるいは、有名になること ―― "名声"も含まれるかな」

Wife: "Or to be famous -- I guess that includes 'fame'"

江端:「第三者から観測可能な"金"と"名声"というとになると ―― ああ、なるほど、『ミュージシャン』などは、分かりやすい」

Ebata: "When it comes to 'money' and 'fame' that can be observed by a third party - ah, I see, 'musicians' is easy to understand.

嫁さん「そうだねえ。そこに至るプロセスが、努力と能力だけでなく、運も必要であり、加えて"その可能性が恐しく小さい"という要件も必要だね」

Wife: "Yes, you're right. The process of getting there requires not only effort and ability, but also luck, and in addition, 'the possibility should be terribly small'.

-----

正直、私は、

Honestly, I thought that

―― 面倒くさいな

"It's troublesome"

と思いました。

私は、「自分の意志で目指す成功」であるならともかく、「他人の目に映る成功」なんぞの為に、自分が振り回されることなど、ゴメンです。

I don't want to be at the mercy of "success in the eyes of others", but "success of my own volition.

-----

最後にいらんことを書き残します。

Finally, I'll leave you with something unnecessary.

こういう、他人視点の"一旗"幻想を持っている方は、このページの一読をお勧めします。

If you have an illusion of the flag in the eyes of others, let you read this article.

文字を読むのが面倒であるなら

If you can't be bothered to read the words, it is O.K. to watch these figures.

この図と、この図と、この図、を見るだけでも、結構です。

参考くらいにはなると思います。

I think it's at least a reference.

未分類

  1. 内容:位置情報をサーバからクアイアント(Webブラウザ)に1回のみ一回だけ送信して、その情報を、適当にランダムウォークさせるだけのプログラム
  2. 方針 できるだけ、プログラムは汚いまま残す
/*
// server7.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 server7.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

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

	"github.com/gorilla/websocket"
)

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

//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はクライアント識別子)
			_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
			if err != nil {
				log.Println("read:", err)
				break
			}

			// JSON(位置情報)を無理やり入れてみた

			gl := GetLoc{
				ID:  1,
				Lat: 35.653976,
				Lng: 139.796842,
			}

			//log.Printf("recv_serv: %s", gl)
			//err = c.WriteJSON(mt, gl)
			err = c.WriteJSON(gl)
			if err != nil {
				log.Println("write:", err)
				break
			}
		}
	*/

	gl := GetLoc{
		ID:  1,
		Lat: 35.653976,
		Lng: 139.796842,
	}

	//log.Printf("recv_serv: %s", gl)
	//err = c.WriteJSON(mt, gl)
	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}

}

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 lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneCluster - Realworld 50k</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="http://kobore.net/PruneCluster.js"></script>           
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>      


</head>
<body>
<div id="map"></div>

<script>

	ws = new WebSocket("{{.}}"); // websocketの確立

	/*
	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;
	}

    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
    }).setView(new L.LatLng(35.654543, 139.795534), 18);

    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        detectRetina: true,
        maxNativeZoom: 18
    }).addTo(map);

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

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

	var markers = [];

	// 受信すると、勝手にここに飛んでくる
	ws.onmessage = function (event) {
		// データをJSON形式に変更
		var obj = JSON.parse(event.data);

		// データをマーカーとして登録
		var marker = new PruneCluster.Marker(obj.lat, obj.lng);
		markers.push(marker);
		leafletView.RegisterMarker(marker);

        // leafletView.ProcessView();  // 変更が行われたときに呼び出されなければなりません。		
	}


	/*
	// 100人分を登録する
    var size = 1;
    var markers = [];
    for (var i = 0; i < size; ++i) {
        var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);

        markers.push(marker);
		leafletView.RegisterMarker(marker);
		
	}
	*/
	
	/*
		ちなみにオブジェクトの削除は、以下の様らしい
		// Remove all the markers
		pruneCluster.RemoveMarkers();

		// Remove a list of markers 
		pruneCluster.RemoveMarkers([markerA,markerB,...]);  リスト単位で消去する、ことか → marker[0],maker[1] と指定して消していくのだと思われ

		// こんなのがあった random.10000-delete.html
		document.getElementById('delete').onclick = function () {
			if (size >= 1000) {
				var deleteList = markers.splice(0, 1000);
				leafletView.RemoveMarkers(deleteList);
				size -= 1000;
			}
			return false;
		};

	*/
	
	// ランダムウォークさせる
    window.setInterval(function () {
        for (i = 0; i < 1; ++i) {
			//var coef = i < size / 8 ? 10 : 1;
			var coef = 10;
            var ll = markers[i].position;
            ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
            ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
        }

        leafletView.ProcessView();  // 変更が行われたときに呼び出されれなければならない
	}, 500);


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


    map.addLayer(leafletView);
</script>



</body>
</html>
`))

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

最近、NHKの「ドキュメント72時間」を見ています。

Recently, I've been watching NHK's "documentary series 72 Hours".

ある一定の場所で、72時間、市井の人々の取材を放送する番組です。

It is a program that broadcasts 72 hours of coverage of the people of the city in a certain location.

■NHKスペシャルのように、『聞き漏らさずに視聴しなければ』というような緊張間もなく、

There's no need to be tense like the NHK specials, where I have to watch it without missing a beat.

■他の民放番組のように、特異な行動や言動をする人を、面白可笑しく編集するでもなく(自分の街をインタビューされたシーンは、かなり不快だった)

It's not like other commercial TV shows that edit out people who act and behave in a peculiar way for fun or funny.(The scene where they interviewed someone from my town was quite uncomfortable.)

肩の力を抜いて見られるので、結構気にいっています。

I like it a lot because I can relax to watch it.

もっとも、この「ドキュメント72時間」にしても、市井の人々の「いい感じのコメント」や「琴線にふれるコメント」だけを集めている、という感は否めません。

However, it cannot be denied that this "documentary series 72 Hours" is a collection of "good comments" and "heartful comments" from the people.

しかし、番組として構成する以上、それは仕方のないことです。

But as long as it's structured as a program, I think that it cannot be helped.

-----

先日、嫁さんと2人で、夕食を食べながら、「東京・隅田川 花火のない静かな夏に」を見ていました。

The other day, my wife and I were watching "Tokyo Sumida River, Quiet Summer without Fireworks" while eating dinner.

そこでは、「一旗上げようと東京にやって来て」「それが叶わずに今に至る」という高齢者の方のインタビューシーンが出てきました。

There was an interview scene with an elderly man who said that "he came to Tokyo to make a name for himself", and "he couldn't do it".

先日の日記にも記載したのですが、

I mentioned it in my diary the other day.

―― "一旗"って何だろう?

"What does "the flag" mean?"

と、ずっと考えています。

I keep thinking about it.

-----

江端:「『東京に出てくれば、何かチャンスがある』という発想が良く分からん。そもそも、東京に、語るほどのメリットがあるのか?」

Ebata: "I don't understand the idea that "there are opportunities if you come to Tokyo". In the first place, are there any advantages to Tokyo that are worth talking about?

嫁さん:「大学の数、仕事の数、出会う人間の数、娯楽の数、手に入れられる情報とその速度、その他もろもろ違うでしょう」

Wife: "The number of colleges, jobs, people you meet, entertainments, and the information you can get and the speed at which you can get it, and everything else will be different"

江端:「まあ、私の場合、ネットと通販があれば、十分に足るけど ―― まあ、その通りかなぁ。特に「人脈」については、都会は圧倒的に有利かもしれない」

Ebata: "Well, in my case, the internet and mail order are enough for me. However, I guess you're right. Especially when it comes to "networking", the city may have a huge advantage.

嫁さん:「まあ、パパの場合は『人間嫌い』だから、そのメリットはないと思うけど」

Wife: "Well, in your case, I don't think you have the benefit of that because you are a misanthrope.

(続く)

(To be continued)

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

ようやく動いた。疲れた。JSONを送り込むのは、明日以降ね。

忘備録

  • カレントディレクトリの中にdistというディレクトリを作って、LeafletStyleSheet.css PruneCluster.d.ts PruneCluster.js を放り込んだ
  • カレントディレクトリの中にLeafletStyleSheet.css,PruneCluster.d.ts,PruneCluster.js,examples.cssを放り込んでおいた
  • 上記はあまり意味なかったらしい(プログラムの下を御参照)。

 

使い方は、

>go run server5.go

http://localhost:8080

で起動する。

/*
// server5.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 server5.go      (適当なシェルから)
// http://localhost:8080  (ブラウザ起動)
*/

package main

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

	"github.com/gorilla/websocket"
)

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

//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はクライアント識別子)
		_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		if err != nil {
			log.Println("read:", err)
			break
		}

		// JSONを無理やり入れてみた

		gl := GetLoc{
			ID:  1,
			Lat: 35.653976,
			Lng: 139.796842,
		}

		//log.Printf("recv_serv: %s", gl)
		//err = c.WriteJSON(mt, gl)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", 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")
	homeTemplate.Execute(w, "")
}

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 lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneCluster - Realworld 50k</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="PruneCluster.js"></script>

    <link rel="stylesheet" href="examples.css"/>
</head>
<body>
<div id="map"></div>

<script>
    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
    }).setView(new L.LatLng(35.654543, 139.795534), 18);

    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        detectRetina: true,
        maxNativeZoom: 18
    }).addTo(map);

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

    var size = 100;
    var markers = [];
    for (var i = 0; i < size; ++i) {
        var marker = new PruneCluster.Marker(35.654543 + (Math.random() - 0.5) * 0.00001 * size, 139.795534 + (Math.random() - 0.5) * 0.00002 * size);

        markers.push(marker);
        leafletView.RegisterMarker(marker);
    }

    window.setInterval(function () {
        for (i = 0; i < size / 2; ++i) {
            var coef = i < size / 8 ? 10 : 1;
            var ll = markers[i].position;
            ll.lat += (Math.random() - 0.5) * 0.00001 * coef;
            ll.lng += (Math.random() - 0.5) * 0.00002 * coef;
        }

        leafletView.ProcessView();
    }, 500);

    map.addLayer(leafletView);
</script>
</body>
</html>
`))

ところが、今朝、PC断ち上げて再起動させてみたところ、昨日の状況に戻ってしまいました。

試しに、自分のサーバ(kobore.net)に、PruneCluster.jsexamples.cssをアップして、さらにdistというディレクトリ掘って、LeafletStyleSheet.cssを放りこんでみたら動きました。

<script src="PruneCluster.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script>

<link rel="stylesheet" href="examples.css"/>
→ <link rel="stylesheet" href="http://kobore.net/examples.css"/>

まあ、いずれ修正するとして、動くならなんでも良いので、このまま進めます。

 

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

なんで、JSON.parse()で、パースができないのだろう? と3時間くらい悩んだあげく、大文字を小文字変換されていることに、ようやく気がつきました。

ひどくない?

気がつかなかった私が悪いの?

/*
// server4.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"
)

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

//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はクライアント識別子)
		_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		if err != nil {
			log.Println("read:", err)
			break
		}

		// JSONを無理やり入れてみた

		gl := GetLoc{
			ID:  1,
			Lat: 35.653976,
			Lng: 139.796842,
		}

		//log.Printf("recv_serv: %s", gl)
		//err = c.WriteJSON(mt, gl)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", 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(1000);
		print("End sleep");
	}
}

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

	// test code  (JSON.parse()が大文字を無視することを確認するテスト)
	//const json = '{"ID":1, "Lat":35.653976, "Lng":139.796842}';
	//print("json:" + json);
	//var obj = JSON.parse(json);	

	print("obj:" + obj);  	
	print("ID:" + obj.id);     // obj.IDなら読み取れない
	print("Lat:" + obj.lat);   // obj.Latなら読み取れない
	print("Lng:" + obj.lng);   // obj.Lngなら読み取れない
}

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

</script>
</head>
<body>

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

</body>

</html>
`))

 

 

 

 

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

本日、東京証券取引所のシステムがダウンしました。

Today, the Tokyo Stock Exchange system is down.

原因については不明ですが、システムエンジニアとして申し上げることができるとしたら、

I'm not sure what caused this, but as a systems engineer, if I could offer something to you, it would be

――『各期、連休等の初日と最終日は、システムは停止するものと考えておくこと』が肝要

"It is essential to assume that the system will be shut down on the first and last day of each term, consecutive holidays, etc."

ということです。

システム保守や、システムリプレースは、期末に行われ、その起動は期の初日に行われることがあるからです。

This is because system maintenance and system replacement may take place at the end of the period, and its launch may take place on the first day of the period.

どんなに事前にチェックしても、「テスト環境」と「運用環境」は違います。

No matter how much you check in advance, there is a difference between a "test environment" and an "operational environment".

現実世界の、全ての状況を想定したテストというのは、夢物語です。

Testing for all situations is just a dream.

どんなシステムも、基本的には「ダウン」する運命にあるのです。

Any system is essentially doomed to go "down".

で、その「ダウン」のポテンシャルが最大化するのが、「各期や連休等の初日と最終日」ということです。

And that "down" potential is maximized on the first and last day of each period, holiday, etc.

-----

ところで、システムを様々なシナリオで攻撃することで、製品の弱点を見つけ出す役割の人を「テスター」と言います。

By the way, the person responsible for finding weaknesses in a product by attacking the system in various scenarios is called a "tester".

米国赴任中に、私、テスター用のプログラムを作り、各種の攻撃を仕掛け続けていたのですが、

While I was stationed in the U.S., I wrote a program for our testers that kept launching all sorts of attacks.

―― なんとなく、チームメンバから嫌な顔をされていました。

"Somehow, I was getting a bad look from my team members"

特に、開発者は、私がパーティションに顔を出すだけで、「あの日本人が、また来た」という顔をしていたように思います。

In particular, the developers seemed to get that "that Japanese guy is here again" look on their faces when I just showed up at the partition.

無理はありません。

It's reasonable.

そりゃ、作ったばかりのコードは、自分の創作物であり、自分の子どもです。

That's because the code they just created is their own creation and their own child.

それを、いきなり「苛められる」のですから、愉快であろうはずがありません。

It can't be pleasant because they are suddenly "tortured".

-----

逆に、私が作った開発物を、他のテスターによって攻撃されることがありましたが ―― 私も不快でした。

On the other hand, there have been times when I've been attacked by other testers on developments I've made -- and I've been uncomfortable, too.

それが、仕事であることは分かっていても、です。

Even though I know it's just a work.

そんな日は、『ダウンの再現条件を、ちゃんとスペックアウトしてこいよ!』などと愚痴を言いながら、強い酒を飲んでいました。

On a day like that, I said that "spec out the conditions for reproducing down" and I was drinking with complaining about such things.

まあ、仕事というのものは、理不尽なものである、ということです。

Well, I can say that works are unreasonable things

-----

ともあれ、システムエンジニアの一人として、

Anyway, as a systems engineer,

―― ダウンしないシステムなんて、存在しない

"Any system goes down"

ということだけは、申し上げたいのです。

I would like to say that.

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のこの記事。