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

PruneMobile というソフトウェアを作っています。

クライアント側のプログラムで、Golang(Go言語)の 並列(通信)処理を始めて本格的に実装したのですが、サーバ側にエラーが乱発しています。

Golangの通信プログラムの仕様は今一つ分かなくて(C言語なら「私がマニュアルだ」と傲慢に言い放ちますが)、試行錯誤の最中です。

"Go"は"C"と、ポインタや配列も違って、勉強し直し状態です。

それはさておき、クライアント側のプログラムは、以下の通り。ポイントは、

go passenger(1)
go passenger(2)
go passenger(3)
go passenger(4)

でスレッド処理をしてみたところです。

// client6.go ペアは server12.go

package main

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

	"github.com/gorilla/websocket"

	"math/rand"
	"time"
)

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

var gl [100]GetLoc
var gl2 [100]GetLoc
var c [100](*websocket.Conn)

var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options

func random(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

func main() {
	rand.Seed(time.Now().UnixNano())

	flag.Parse()
	log.SetFlags(0)

	go passenger(1)
	go passenger(2)
	go passenger(3)
	go passenger(4)

	time.Sleep(25 * time.Second)
}

func passenger(i int) {

	/*
		gl[i] = GetLoc{
			ID:  0,
			Lat: 35.653976,
			Lng: 139.796821,
		}
	*/

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

	var err error
	c[i], _, err = websocket.DefaultDialer.Dial(u.String(), nil)
	//c[i] = cc
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c[i].Close()

	gl[i].ID = 0
	gl[i].Lat = 35.653976
	gl[i].Lng = 139.796821

	if i != -100 {
		log.Printf("before1 %d ID:%d", i, gl[i].ID)
		log.Printf("before1 %d Lat:%f", i, gl[i].Lat)
		log.Printf("before1 %d Lng:%f", i, gl[i].Lng)
	}

	//err = c.WriteJSON(mt, gl)
	err = c[i].WriteJSON(gl[i])
	if err != nil {
		log.Println("write:", err)
	}

	//&(gl2[i]) = new(GetLoc)
	err = c[i].ReadJSON(&(gl2[i]))

	if i != -100 {
		log.Printf("after1 %d ID:%d", i, gl2[i].ID)
		log.Printf("after1 %d Lat:%f", i, gl2[i].Lat)
		log.Printf("after1 %d Lng:%f", i, gl2[i].Lng)
	}

	if gl2[i].ID == 0 {
		// 強制停止
		os.Exit(1)
	}

	gl[i].ID = gl2[i].ID

	for k := 0; k < 20; k++ {
		gl[i].Lat += random(0.5, -0.5) * 0.00001 * 10 * 5
		gl[i].Lng += random(0.5, -0.5) * 0.00002 * 10 * 5

		if i != -100 {
			log.Printf("before2 %d ID:%d", i, gl[i].ID)
			log.Printf("before2 %d Lat:%f", i, gl[i].Lat)
			log.Printf("before2 %d Lng:%f", i, gl[i].Lng)
		}

		err = c[i].WriteJSON(gl[i])
		if err != nil {
			log.Println("write:", err)
		}
		//gl2[i] := new(GetLoc)
		err = c[i].ReadJSON(&(gl2[i]))
		if i != -100 {
			log.Printf("after2 %d ID:%d", i, gl2[i].ID)
			log.Printf("after2 %d Lat:%f", i, gl2[i].Lat)
			log.Printf("after2 %d Lng:%f", i, gl2[i].Lng)
		}
		time.Sleep(1 * time.Second) // 1秒休む
	}

	gl[i].ID = gl2[i].ID
	gl[i].Lat = 999.9
	gl[i].Lng = 999.9

	log.Printf("before3 %d ID:%d", i, gl[i].ID)
	log.Printf("before3 %d Lat:%f", i, gl[i].Lat)
	log.Printf("before3 %d Lng:%f", i, gl[i].Lng)

	err = c[i].WriteJSON(gl[i])

	err = c[i].ReadJSON(&(gl2[i]))
	log.Printf("after3 %d ID:%d", i, gl2[i].ID)
	log.Printf("after3 %d Lat:%f", i, gl2[i].Lat)
	log.Printf("after3 %d Lng:%f", i, gl2[i].Lng)

}

でもって、サーバ側に、

wsarecv: An existing connection was forcibly closed by the remote host.

がでてくるのですが、これがどうにも分からない。

websocket.DefaultDialer.Dialもスレッド単位で分離しているから、良さそうなんだけどなー

サーバ側の出力コンソールは、こんな感じ

first ID:0
117 after gl = <-chan2_1
first Lat:0.000000
94
first Lng:0.000000
126
read: read tcp 127.0.0.1:8080->127.0.0.1:50368: wsarecv: An existing connection was forcibly closed by the remote host.
138
write: write tcp 127.0.0.1:8080->127.0.0.1:50366: wsasend: An existing connection was forcibly closed by the remote host.
after gl2.id = 3
after gl2.lat = 35.653909
after gl2.lng= 139.796333
145
148
113 before gl = <-chan2_1
86
final ID:3
final Lat:35.653909
final Lng:139.796333
94
write: write tcp 127.0.0.1:8080->127.0.0.1:50365: wsasend: An existing connection was forcibly closed by the remote host

とりあえず、忘れそうなので、自分の為にメモを残しておきます。

websocket.DefaultDialer.Dialもスレッド単位ごとに発行しているから分離しているから、良さそうなんだけどなぁ。Websocketの複数のエンドポイントを作って並列処理につっこむには、もう一頑張り必要、ということでしょう。

ちなみに「誰か、私を助けてくれたっていいんだからね!」と申し上げておきます。

 

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

MarkdownをWord形式(docx)に変換する を参考にして、Pandoc をインストールしましたが、とてもキレイに表示されて便利です。

# PruneMobileとは

複数の人間や自動車等の移動体のリアルタイムの位置情報を、地図上に表示する、PruneClusterのアプリーケーションです。

PruneMobileのサーバに対して、任意のタイミングで位置情報を送り込むだけで、地図上にマーカーが表示されます。

## 使用環境

- golang(Go言語)のインストールされていれば良いです。私(江端智一)の環境では以下のようになっています。
```
$ PruneMobile\server>go version
$ go version go1.14 windows/amd64
```

- 実際に動かせば、コンパイラから、あれこれ言われますので、それに対応して下さい。基本的には、
```
$ go get github.com/gorilla/websocket
```
は必要になると思います。

## サンプルプログラムの環境

- Webブラウザで表示される地図は、東京都江東区の豊洲駅を中心にして作ってあります。
  - PruneMobile\server\serverX.go (Xは数字) の中にある、
```
var map = L.map("map", {
   attributionControl: false,
   zoomControl: false
}).setView(new L.LatLng(35.654543, 139.795534), 18);
```
の"35.654543, 139.795534"の座標を変更すれば、地図の中心位置が変わります。

- クライアントプログラムでは、豊洲駅を中心にランダムウォークさせています
  - PruneMobile\server\clientX.go (Xは数字)を起動すると、10秒間程、マーカーが移動して、その後消滅します。

- クライアントプログラム(clientX.go)は複数同時に起動させることができます。

## 現時点で確認している問題点で、いずれ直すもの

- マーカーの消滅のタイミングが、同時になってしまう

- Webブラウザの表示が、最初の1つめしか、正常に動作しない

- ローカルのjs(javascript)のローディングに失敗した為、江端のプライベートサーバ(kobore.net)からローディングしている。PruneMobile\server\serverX.goの以下を参照
```
	<script src="http://kobore.net/PruneCluster.js"></script>
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>
```

# サンプルプログラムの動作方法

## Step 1 サーバの起動

適当なシェルを立ち上げて
```
$ cd PruneMobile\server
$ go run serverX.go (Xは数字)
```
とすると、「Windowsセキュリティの重要な警告(windows10の場合)」が出てくるので、「アクセスを許可する」ボタンを押下して下さい。

## Step 2 ブラウザの起動
Chromoブラウザ(他のブラウザのことは知らん)から、
```
http://localhost:8080/
```
と入力して下さい。豊洲地区の地図が表示されます。

## Step 3 クライアントの起動
適当なシェルを立ち上げて
```
$ cd PruneMobile\client
$ go run clientX.go (Xは数字)
```
とすると、マーカが0.5秒単位でランダムに動きます。

# クライアントプログラムで使うI/F(データ形式)

## 前提

- サーバとwebsocketのコネクションを確立して下さい。方法は問いません。golangでの記述方法はclient/clientX.goを参考にして下さい。

- データ形式はJSONを用います。golangでの記載サンプルは以下の通りです。

```

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

## Step.1 マーカーの登録

IDを"0"にして、最初のマーカーの座標を入力したJSONを、サーバに送付して下さい。golangでの送信方法はは以下の通りです。
```
	gl := GetLoc{
		ID:  0,
		Lat: 35.653976,
		Lng: 139.796821,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```

返り値に入ってきたIDが、これからそのマーカで使うID番号となります。golangでの受信方法はは以下の通りです。

```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

以後、このID番号(整数)を使用して下さい。この番号と異なる番号を使用した場合、動作は保証されません。

## Step.2 マーカーの移動

指定されたIDを使って、移動後の座標を送付して下さい。
```
	gl := GetLoc{
		ID:  5,         // IDが"5"の場合
		Lat: 35.653923,
		Lng: 139.796852,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

## Step.3 マーカーの抹消

指定されたIDを使って、地球上の緯度経度の数値で現わせない座標を入力して下さい。具体的に、latに90.0より大きな値、またはlngに180より大きな値を入力することで、マーカが消去されます。
```
	gl := GetLoc{
		ID:  5,         // IDが"5"の場合
		Lat: 999.9,
		Lng: 999.9,
	}

	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
```
返り値は、入力と同じJSONが戻ってきますが、必ず受信して下さい。
```
	gl2 := new(GetLoc)
	err = c.ReadJSON(gl2)
	log.Printf("after ID:%d", gl2.ID)
	log.Printf("after Lat:%f", gl2.Lat)
	log.Printf("after Lng:%f", gl2.Lng)
```

以上

 

README.docx

 

 

 

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

取り敢えず、週末の成果(ちゃんと動くものではないので、そのまま使うことはお勧めしません)

"client1.go"の内容

package main

import (
	"flag"
	"log"
	"net/url"

	"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", "0.0.0.0:8080", "http service address") // テスト
//var addr = flag.String("addr", "localhost:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options

func main() {
	flag.Parse()
	log.SetFlags(0)
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
	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()

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

	log.Printf("ID:%d", gl.ID)
	log.Printf("Lat:%f", gl.Lat)
	log.Printf("Lng:%f", gl.Lng)
	//err = c.WriteJSON(mt, gl)
	err = c.WriteJSON(gl)
	if err != nil {
		log.Println("write:", err)
	}
}

"server8.go"の内容

/*
// 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 echo2(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はクライアント識別子)

		gl := new(GetLoc)

		err := c.ReadJSON(&gl) // クライアントからのメッセージの受信(mtはクライアント識別子)

		log.Printf("ID:%d", gl.ID)
		log.Printf("Lat:%f", gl.Lat)
		log.Printf("Lng:%f", gl.Lng)

		if err != nil {
			log.Println("read:", err)
			break
		}
	}
}

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("/echo2", echo2)           // echo関数を登録 (サーバとして必要)
	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"/>      <!-- これも、いずれローカルホストから取れるように換える -->

	<!-- goのテンプレートのローカルって、どこになるんだろう? -->

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

	// ランダムウォークさせる
    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,江端さんの技術メモ

ようやく動いた。疲れた。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,江端さんの技術メモ

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,江端さんの技術メモ

サーバ側 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,未分類,江端さんの技術メモ

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

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

私が今使っているWordPressは、現時点での最新バージョンなのですが、どうしても、Prism For WPのメニューが出てこなくて、ずっと悩んでいました。

で、色々しらべた結果、

をインストールする必要があるらしく、これを入れることでメニューがでるようになりました。

が今度は、画像イメージのコピペができなくなりました。

で、さらに、

をインストールして、この画面のようにコピペを張りつけるように戻すことができるようになりました。

なんか、やりたいことを実現していくと、どんどんWordPressのダウングレードをしているような気になってきました(プラグインもどんどん入れることになって、気持ち悪いです(経験上、プラグインの入れすぎは、トラブルの元になります))。