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

https://github.com/gorilla/websocket/tree/master/examples/echo

この"server.go"は、client.goからの通信だけではなく、ブラウザの画面も提供します(スゴい!)。

というか、Goプログラムの中に、html書けるなんて知らんかった。

 
// server.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.

//go:build ignore
// +build ignore

package main

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

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost: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)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %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)
	http.HandleFunc("/", home)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>  
window.addEventListener("load", function(evt) {

    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;

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

    document.getElementById("open").onclick = function(evt) {
        if (ws) {
            return false;
        }
        ws = new WebSocket("{{.}}");
        ws.onopen = function(evt) {
            print("OPEN");
        }
        ws.onclose = function(evt) {
            print("CLOSE");
            ws = null;
        }
        ws.onmessage = function(evt) {
            print("RESPONSE: " + evt.data);
        }
        ws.onerror = function(evt) {
            print("ERROR: " + evt.data);
        }
        return false;
    };

    document.getElementById("send").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        print("SEND: " + input.value);
        ws.send(input.value);
        return false;
    };

    document.getElementById("close").onclick = function(evt) {
        if (!ws) {
            return false;
        }
        ws.close();
        return false;
    };

});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server, 
"Send" to send a message to the server and "Close" to close the connection. 
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output" style="max-height: 70vh;overflow-y: scroll;"></div>
</td></tr></table>
</body>
</html>
`))
// client.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.

//go:build ignore
// +build ignore

package main

import (
	"flag"
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

func main() {
	flag.Parse()
	log.SetFlags(0)

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
	log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	done := make(chan struct{})

	go func() {
		defer close(done)
		for {
			_, message, err := c.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				return
			}
			log.Printf("recv: %s", message)
		}
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-done:
			return
		case t := <-ticker.C:
			err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
			if err != nil {
				log.Println("write:", err)
				return
			}
		case <-interrupt:
			log.Println("interrupt")

			// Cleanly close the connection by sending a close message and then
			// waiting (with timeout) for the server to close the connection.
			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("write close:", err)
				return
			}
			select {
			case <-done:
			case <-time.After(time.Second):
			}
			return
		}
	}
}

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

私は、一人で仕事をするのが好きです。

I like to work alone.

ですから、コラムの執筆は、私の性癖にあっているようです。

So, writing the column seems to be in my nature.

しかし、現在のコラムの執筆に関しては ――

But as for writing the current column,

編集担当者のMさん、量子コンピュータの監修者のTさん、そして、いわゆる「無礼な後輩」の御三方の支援なくしては、成立しません。

It wouldn't be possible without the support of Ms. M. as the editor, Mr. T as the supervisor of quantum computer, and the three so-called "rude junior".

ところが、Mさんを除いては、無償でご協力頂いている(クレジットの表示もご辞退されています)ので、大変心苦しいと思っています。

However, with the exception of Mr. M., I am very distressed by the fact that they has cooperated with me free of charge (and have declined to show credit).

だから、監修や査読のお願いに対して、締切等を申し上げられない立場です。

So it is hard for me ask them for deadline about supervision and peer review

-----

今回、「無礼な後輩」に対して、3回ほど、立て続けにフォローメールをしたところ、

This time, I sent three follow-up emails in a row to the "rude junior, and the call came back and

開口一番、

His first words are

『江端さん、嫌がらせですか』

"Ebata-san, are you harassing me?"

と、電話がかかってきました。

-----

そんな、ボランティアでご協力頂いている恩人に、事もあろうに『嫌がらせ』なんて、そんな不遜な気持ち ――

I don't like the idea of "harassment" for a benefactor who has volunteered to help me. Such irreverence is

「半分くらい」

"only the half".

しか、ありません。

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

最近、睡眠不足気味で、ちょくちょく仮眠を取るようにしています。

Lately, I've been sleep-deprived and I've been trying to take a nap every so often.

私の体の中にある「乾電池」は、とても性能のよいやつで、

The "dry cell" in my body is a very good performing one.

「10分間の仮眠で、5時間連続稼動」

"I'll take a 10-minute nap and run for five hours straight"

できます、

これ、一見、良さそうに見えますが、「性(たち)の悪い不眠症の原因」となっています。

This may seem like a good one, but it is the "cause of our bad insomnia".

-----

昨日も、タイマーに「10分間」セットして、横になりました。

Yesterday, I set the timer for "10 minutes" and laid down.

残り8分34秒のところで目が覚めました。

I woke up with 8:34 to go.

2分弱しか仮眠が取れていないのに、その割には、体調がスッキリしています ―― 最近、経験したことがないくらい。

I've only been able to take a nap for less than two minutes. However I was feeling better than I'd have experienced in recent years.

変だなーと思って、時計を確認したら、

I thought it was odd, so I checked my watch.

セットしたタイマーの時間は、"10分間"ではなく、"10時間"でした。

The timer was set for "10 hours", not "10 minutes".

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

「特許明細書のロジック」の説明の仕方

「特許明細書のロジック」の説明の仕方

2010/09/17

1. 背景と目的

(1)後僚より、『知財担当者から、「特許明細書作成のロジックを説明しろ」と、言われたけど、なんのことか分からない』と相談された。

(2)そこで、江端が独断と偏見に基いて確立した「特許明細書のロジック」を開示する。

(3)なお、本内容は、知財担当者(弁理士を含む)にも開示したが、少なくともクレームは受けなかった(ように思う)。

従って、内容的には大きく外れてはいないのだろう、と考えている。

2. 知財担当者からの要請

知財担当者殿より、『「特許明細書のロジック」を説明する場合には、以下のように説明して欲しい』との要請を受けている。

====================================================================== 
先ほど、「背景技術→背景技術の問題点→本発明」と書きましたが、
詳細には、下記の点についてご説明頂きたくお願い致します。

(Step.1)発明のバックグラウンドとなる分野の説明
   	↓
(Step.2)その分野における一般的な課題の説明
    	↓
(Step.3)公知例がどのように上記課題を解決するかの説明
    	↓
(Step.4)その公知例でも解決できない課題の説明
    	↓
(Step.5)それをどう解決するかの説明(*本発明のロジック)

*本発明について、代表となる図を記載して頂けると助かります。
====================================================================== 

3. 江端の付帯説明

(面倒なので、以下メールより抜粋)

知財担当者殿のフローチャートの内容は完璧なので、そこに、私が具体例を書きます。

私はこういう風に書いてきて、発明検討会を何十回も突破して、(分割と共同を含めて)100本以上の明細書に関わってきたのですから、

先ずは、黙って、私を信じろ

(1)発明のバックグラウンドとなる分野の説明

 
  (ポイント:ここは「技術」を書くな)
 
   ○有線でやっていたメータリングを無線でやろうとする試みはあった。
   ○が、「メータおばさん」のコストの方が圧倒的に安かった。
   ○近年、無線のリソースが滅茶苦茶安くなってきた。
   ○「メータおばさん」の産業構造が崩れる可能性がでてきた。
   ○アドホックに関する技術も、かなり溜ってきた。
   ○実験ネットワークで、華々しい報告もある。
   ○太陽光発電とか、未知のシステム構成要素が入ってきている。

(2)その分野における一般的な課題の説明

 
   (ポイント:「技術」を書いても良いが、基本的には「金」「不安」で良い)

   ○事業的困難性
        インフラコストが高い、設置面倒、メンテ面倒、保守コスト試算困難、
        事業主体が不明瞭、金主が不明、ビジネスモデルが作れん

   ○技術的困難性
        無線に対する信頼性がない(本当にない)。実績がない。

   ○失敗時のインパクト
       課金不公平→暴動→政権倒れる→革命(というのは冗談だが)              
       深刻な社会不安、インフラに対する信頼性の下落

(3)公知例がどのように上記課題を解決するかの説明

 
   (ポイント:他人の文献公知発明を褒め称える)

    ○A社の特許文献1は、アドホックを自由に構築できる。
      上記の技術的困難性を見事に解決できる。素晴しい(と褒め称える)。

    ○B社の特許文献2は、無線をこんなに高信頼にして、社会インフラレベル
      にもっていける。
      上記の失敗時のインパクトを見事に回避できる。
      実に素晴しい(と褒め称える)。

    ○C社の非特許文献1は、これらを低コストで製品化している。誠に素晴
      しい(と褒めちぎる)
      上記の事業的困難性を解決できるではないか。
      涙が出そうな程、見事である(と褒め称える)。

【特許文献1】特開2007-278XXX号公報
【特許文献2】特開2002-132XXX号公報
【非特許文献1】「XXXXX」、Ebata Inc..[online][平成20年4月7日検索]、
                インターネット

(4)その公知例でも解決できない課題の説明

 
   (ポイント:褒め称えた発明を、掌を返して、鬼のように非難する)

    ○しかし、よくよく見るとA社の特許文献1は、×××はできないし、△△
      △はできない。カスな発明である(と、罵しる)

    ○また、B社の特許文献2は、★★★★★はできないし、■■■■■はでき
      ない。こんなものが現実世界で使える訳がない(と、嘲笑う)

    ○それに、C社の非特許文献1は、安いだけで、絶対必要となる○○○と
      いう機能がなく、お話になりゃしない(と、コケにする)

(5)それをどう解決するかの説明(*本発明のロジック)

 
   (ポイント:自分の発明を絶賛する(弱点については黙っている))

   ○ {俺の/あたしの}発明は、こういう内容だ(*本発明のロジック)。
      (ここで技術の話が始めて登場する)

   ○A社の特許文献1の問題点である、×××を解決し、△△△を可能とする。
      この発明は完璧だ(と、自己陶酔する)

   ○またB社の特許文献2の弱点である、★★★★★もできるし、■■■■■
     もできる。この発明は凄すぎる(と、自画自賛する)

   ○加えて、C社の非特許文献1が具備していない機能を、{俺の/あたしの}
     発明は持っていて、産業上の利用性も完璧!(と、誇大妄想する)

   ○以下、その理屈をお前達に、説明してやるから、実施例を見ろ!!!
     (以下実施例に続く)
とりあえず、こんな風に乱暴に理解して下さい。 ロジックとは、ようするに「物語」です。

(6)その他

江端が発明を評価する場合の評価方法は3つです。

(1)コスト(製造、メンテ、人材)が 1/10になる。

(2)性能(速度、メモリ、ハードディスク等)が10倍になる。

(3)上記(1)(2)に該当しなくても、(こじつけでも)「億円」の単位で儲かる。

これを、私は『特許発明 10分の1、10倍の法則』と名づけています。

『5%向上』程度なら出願やめとけ、と言っています。学生の研究ではないのだから。

また、ソフトウェアのアルゴリズムで、到底外部から発見できないようなものならやめとけ、とも。

書くだけ無駄です。侵害を立証できませんから。

# 山程書いてきた私が言っているのだから間違いない。

では、頑張って下さい。

以上

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

先日、「NHKスペシャル 東京リボーン (5)「渋谷 迷宮大改造」」を視聴しました。

I watched NHK Special Tokyo Reborn (5) "Shibuya Labyrinth" the other day.

■5月末の夜。駅の機能を止めることなく、54時間のうちにホームを350メートル移動させ、線路を500メートルにわたって軌道修正する大工事。

- The night at the end of May. Major works to move the platform 350 meters and correct the track for 500 meters in 54 hours without stopping the station's functioning.

■計画準備に2年間。工事作業員1000人超。

- It took two years to prepare the plan. More than 1,000 construction workers.

■1分1秒を争う時間との闘い。準備を尽くしてもなおも起こる想定外の事態。

- A race against time, with every minute counted. The unexpected continues to happen even after all the preparation.

十何年か前に、私も、似たような案件(「時間」に関してはもっと厳しい条件だったように思う)に関わらせて頂いたことがあり、思わず、そのことを思い出して ――

Some ten years ago, I was involved in a similar case (I think the conditions were more stringent as far as "time" is concerned), and I couldn't help but think back to it -- and,

涙が出そうになりました。

I was almost in tears.

これこそが「ドラマ」と言うのです。

This is what I call "drama".

-----

ちなみに、その時期に、ロンドンに出張していたことがあったのですが、メトロの駅前で、

Incidentally, I was on a business trip to London during that period. In front of the Metro station,

―― 地下鉄、まるまる2週間停止

"Subway shut down for a full two weeks"

という、なんとも豪快な輸送インフラの停止を目の当りにして、『ああ、そういうやり方もあるんだ』と、気がつきました。

When I witnessed the shutdown of the transportation infrastructure in a very dynamic manner, I realized that there was a way to do it.

まさに、「コロンブスの卵」でした。

It was truly a "Columbus' egg".

-----

渋谷を経由する全ての鉄道(山手線、埼京線、湘南新宿ライン、井の頭線、東横線、田園都市線、銀座線、半蔵門線、副都心線)を、1週間、完全に停止する ――

All railways through Shibuya (Yamanote, Saikyo, Shonan Shinjuku, Inokashira, Toyoko, Denentoshi, Ginza, Hanzomon and Fukutoshin lines) are to be completely shut down for one week.

という集中工事は『難しいだろう』と思います。

The centralized construction of the project "will be difficult".

でも、もし、関東大震災が発生したら、1週間どころか、半年後でも全線開通には至れないでしょう。

But if the Great Kanto Earthquake strikes, we won't be able to open the entire line in six months, not one week.

都市機能を1週間まるまる止める ―― 今こそ、そういう「豪快な避難訓練」が必要かもしれません。

Stopping the city from functioning for a week -- maybe now is the time for such a "bold evacuation drill".

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

$ pacman -Su
エラー: mingw32: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" は不明です
エラー: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" をリモートで検索できませんでした
エラー: mingw64: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" は不明です
エラー: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" をリモートで検索できませんでした
エラー: msys: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" は不明です
エラー: キー "4A6129F4E4B84AE46ED7F635628F528CF3053E04" をリモートで検索できませんでした
エラー: データベース 'mingw32' は無効です (無効または破損したデータベース (PGP 鍵))
エラー: データベース 'mingw64' は無効です (無効または破損したデータベース (PGP 鍵))
エラー: データベース 'msys' は無効です (無効または破損したデータベース (PGP 鍵))

てな感じのことが続き、

$pacman -S tree // treeのインストール

すら、できない有様。

で、1時間くらい回遊し続けて、この記事「MSYS2でPGP鍵が不明とか」の内容を試してみました。

$ wget http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz
$ wget http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig
$ pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz{.sig,}
$ pacman -U --config <(echo) msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz
$ pacman -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz

インストールするか? の問いには、"Y(es)"を連打していました。

全く理由は分かりませんが、PGP鍵の交換はできたようで、"tree"のインストールできました。

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

Dockerコンテナのサーバ化には、目処がついたので、今度はクライアント化の方を検討中です。

まずは、mkdir ~/go_echo/client を作って、そこにclient.goを放り込みました。

現時点での、Dockerfileは以下です。

# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/client
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/client
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/client

# gorillaのパッケージを入れる
RUN go get github.com/gorilla/websocket

現時点での、docker-compose.ymlは以下です。

version: '3' # composeファイルのバーション指定
services:
  client: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化

    volumes:
      - .:/go/src/client # マウントディレクトリ指定
    #ports:
    #   - "8080:8080"
    #expose:
    #   - "8080"

で、$ docker-compose build
$ docker-compose up -d
$ (winpty) docker container exec -it client_client_1 bash を実施して、

root@d561a99d0f67:/go/src/client# go run client.go

を実施したら、

connecting to ws://localhost:8080/echo
dial:dial tcp 127.0.0.1:8080: connect: connection refused
exit status 1

となりました。このlocalhostは、コンテナ内のホストだから、コンテナの外にあるホストOSや、別のコンテナの中にあるサーバには、繋らないんだろうなぁ、と。

その証拠に、

root@d561a99d0f67:/go/src/client# ping kobore.net
PING kobore.net (49.212.198.156) 56(84) bytes of data.
64 bytes from 49.212.198.156 (49.212.198.156): icmp_seq=1 ttl=37 time=12.0 ms

と、外部には、バッチリ繋っている(でも、これは、ちょっと凄いと思う)


では、ここからトライアル。

docker-compose.ymlの最後に、

extra_hosts:
- "local_dev:192.168.0.1"

を追加 → 失敗。 ちなみに、"127.0.0.1"も失敗しました。

もしからしたら、と思い、 extra_hosts:をコメントアウトして、client.goの方のアドレスを直書きしてみました。今、PC本体のアドレスが、

C:\Users\ebata>ipconfig

Windows IP 構成

イーサネット アダプター イーサネット:

接続固有の DNS サフィックス . . . . .:
IPv4 アドレス . . . . . . . . . . . .: 192.168.0.8
サブネット マスク . . . . . . . . . .: 255.255.255.0
デフォルト ゲートウェイ . . . . . . .: 192.168.0.1

なので、client.goの一行を以下のように換えてみました。

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

これで動きましが、正直、なんとも気持ち悪いです。

ただ、これを解決するためには、dockerでIPフォワーディングとかやらなければならないのだと思うと、結構ウンザリした気分になりましたので、このままで放置することにします(動けばいいんですよ。ここは踏んばるところではありません)

以上

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

前提の記事はこちら

このサーバをさらにDockerのコンテナに搭載します。

サーバも何も入っていないが、Golang開発環境だけが入っているコンテナを作ります。DockerでGoの開発環境を構築するを参考させて頂きました。

mkdir work # workディレクトリ作成
cd work # 作成したworkディレクトリに移動

ここに、Dockerfileとdocker-compose.ymlを作ります。

(Dockerfileの内容)
# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/work
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/work
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/work

上記の、/go/src/work は、ローカル(PC)のディレクトリではなくて、コンテナの中のディレクトリなので、パスの構成は気にしなくてよい。

(docker-compose.yml
の中身)
version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/work # マウントディレクトリ指定

次に、

docker-compose build

を実行する。

$ docker-compose build
Building app
Step 1/4 : FROM golang:latest
 ---> a794da9351a3
Step 2/4 : RUN mkdir /go/src/work
 ---> Using cache
 ---> 094b928e9bfb
Step 3/4 : WORKDIR /go/src/work
 ---> Using cache
 ---> b7a938d02446
Step 4/4 : ADD . /go/src/work
 ---> 9d95f42d64e3
Successfully built 9d95f42d64e3
Successfully tagged work_app:latest

さらに、docker-compose up -d を実行する。

$ docker-compose up -d
Creating network "work_default" with the default driver
Creating work_app_1 ... done

コンテナができているか同化を確認します。

$ docker-compose ps
   Name      Command   State   Ports
------------------------------------
work_app_1   bash      Up

現在、~/go_echo にある、server.goを、~/go_echo/workにコピーします。

これで、server.go が、ローカル(PC)と、コンテナの中で共有されるようになります。

では、コンテナの中に入ります。

$ winpty docker container exec -it work_app_1 bash # 江端のMSYS2 のシェルでは、"winpty"を付ける必要があるが、通常のシェルでは不要

root@abe44622eccf:/go/src/work# ls
'#main.go#'   Dockerfile   Dockerfile~   server.go   docker-compose.yml   docker-compose.yml~   main   main.go   main.go~   websocket-server.go

ここで、Goプログラムを実行します。

root@abe44622eccf:/go/src/work# go run server.go
server.go:21:2: cannot find package "github.com/gorilla/websocket" in any of:
        /usr/local/go/src/github.com/gorilla/websocket (from $GOROOT)
        /go/src/github.com/gorilla/websocket (from $GOPATH)

ふむ、やはり、こうなりますね。では、パッケージをインストールしましょう。

root@abe44622eccf:/go/src/work#  go get github.com/gorilla/websocket

何のメッセージも出さずに20秒後くらいに静かにプロンプトが戻ってきました(ちょっと不安になる)。

root@abe44622eccf:/go/src/work# go run client_multi_agent.go
connecting to ws://localhost:8080/echo
dial:dial tcp 127.0.0.1:8080: connect: connection refused
exit status 1

うーん、やっぱりネットワーク回りの設定をしていないから当然か。ポート開けていないし。docker-compose downして、docker-compose.ymlに以下を追加してみました。

ports:
  - "8080:8080"
$ more docker-compose.yml
version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/work # マウントディレクトリ指定
    ports:
      - "8080:8080"

それと、"go get github.com/gorilla/websocket"を毎回実行しない為には、Dockerfileに "RUN go get github.com/gorilla/websocket"を1行加えれば良いみたい。

# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/work
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/work
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/work

# gorillaのパッケージを入れる
RUN go get github.com/gorilla/websocket

そんでもって、再びdocker-compose build → docker-compose up -d を実施して、docker container exec -it work_app_1 bash をして、コンテナの中に入って、go run server.goをやったんだけど、ブラウザで、http://localhost:8080やっても表示されない。

C:\Users\ebata\go_echo\work>curl http://localhost:8080/
curl: (52) Empty reply from server

となっているところを見ると。8080ポートは外側に見えていないみたい。

でも、

C:\Users\ebata\go_echo\work>netstat -a | grep 8080
  TCP         0.0.0.0:8080           DESKTOP-P6KREM0:0      LISTENING
  TCP         [::]:8080              DESKTOP-P6KREM0:0      LISTENING

にはなっているんだよなぁ。

試しに、コンテナの中で"curl http://localhost:8080/"をやってみたら、

oot@b30e685f9cc6:/go/src/work# curl http://localhost:8080/

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;

てな感じで、ばっちり見えます。

うーん、変だなぁ。"8080:8080"ってポートフォワードしているじゃないのか? WebSocketの場合は、特殊な設定がいるのか?分かりません。

もう6時間くらい闘ったので、引き上げようとして、最後に、"curl: (52) Empty reply from server" でググってみたら、ドンピシャな感じの記事を見つけました。

docker上のアプリにlocalhostでアクセスしたらERR_EMPTY_RESPONSEが出る

まさに、同じ現象が表われていて、『そう! そう!』と言いながら読み進めていき、結果として、

(解決) アプリの設定を0.0.0.0でLISTENするよう変更する

と記載されていましたので、server.goのソースコードを、

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

と変更してみたところ、http://localhost:8080 でWebもcurlも表示されるようになりました。

ああ、これで、サーバのコンテナ化にメドがついた ―― と思ったら、どっと疲れが出てきました。

『Dockerのコンテナは、江端が作れ』と、指示されていましたので。


Dockerfileの最後に、

CMD ["go", "run", "server.go"]

をつけると、dockerコンテナをサーバ化にできる。

ただし、

# docker-compose build, 
# docker-compose up (-d ← これを付けたらダメ。バックグラウンドで起動するとDockerが終了してしまう)

で起動すること。