// stop_json.go
// 横浜市交通局 バス停情報 / Bus stop information of Transportation Bureau, City of Yokohama
// https://ckan.odpt.org/dataset/b_busstop-yokohamamunicipal
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
type stopJSON struct {
Id string `json:"@id"`
Type string `json:"@type"`
Lng_Title struct {
En string `json:"en"`
Ja string `json:"ja"`
Ko string `json:"ko"`
Zh string `json:"zh"`
Ja_Hrkt string `json:"ja-Hrkt"`
} `json:"title"`
Date string `json:"dc:date"`
Lat float64 `json:"geo:lat"`
Long float64 `json:"geo:long"`
Context string `json:"@context"`
Title string `json:"dc:title"`
Kana string `json:"odpt:kana"`
SameAS string `json:"owl:sameAS"`
Operator []interface{} `json:"odpt:operator"`
BusroutePattern []interface{} `json:"odpt:busroutePattern"`
BusstopPoleNumber string `json:"odpt:busstopPoleNumber"`
// BusstopPoleTimetable
}
func main() {
// JSONファイルから読み取る場合
//file, err := ioutil.ReadFile("route.json")
//file, err := ioutil.ReadFile("odpt_BusroutePattern.json")
file, err := ioutil.ReadFile("odpt_BusstopPole.json")
if err != nil {
// エラー処理
}
/*
// Webアクセスで取得する場合
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusstopPole?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
}
file, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
*/
var data []stopJSON
err = json.Unmarshal(file, &data)
if err != nil {
fmt.Println("Unmarshal")
log.Fatal(err)
}
//fmt.Println(string(file))
// ループによる取得
for _, e := range data {
fmt.Println(e) // 全情報表情
stops := e.BusroutePattern
fmt.Println("len_stop", len(stops))
for _, stop := range stops {
fmt.Println(stop)
}
}
}
横浜市交通局 バス停情報のJSONパーサー
横浜市交通局 バス路線情報の取得方法 golangによるJSONパーサー
https://ckan.odpt.org/dataset/b_busroute-yokohamamunicipal
■動かなくて悩んでいたコード
// xml_parse.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type testXML struct {
Date string `json:"dc:date"`
Busroute string `json:"odpt:BusroutePattern"`
}
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Println(body)
var data testXML
err = json.Unmarshal(body, &data)
if err != nil {
fmt.Println("Unmarshal")
log.Fatal(err)
}
fmt.Println(data)
}
出力結果
> go run xml_parse.go
Unmarshal
2023/05/01 18:05:31 json: cannot unmarshal array into Go value of type main.testXML
exit status 1
悩むこと1時間、("type testXML"スキーマを色々いじったりしていた)
『そういえば、このjsonのデータは、違う路線データも入っているはずだよな』
→ 『ということは、複数のデータが入っていることになるよな』
→ 『 For文で取らなければ不味くないか?』
で、ググってみたら、どうやらループになるように仕込んでおかないといけないらしいことが分かりました(XMLの先頭の情報が取れるだけだと思っていた)
■動いたコード
// xml_parse.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type testXML struct {
Date string `json:"dc:date"`
Busroute string `json:"odpt:BusroutePattern"`
}
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
x
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Println(body)
var data []testXML
err = json.Unmarshal(body, &data)
if err != nil {
fmt.Println("Unmarshal")
log.Fatal(err)
}
for _, e := range data {
fmt.Println(e.Date)
}
}
go run xml_parse.go
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00"当たり"だったようです。
バス路線情報のJSONをブラウザで読みとってみました。
ここから、酷いハマり方をします(10時間くらい)。
上記のルートの位置情報の表示のコーディングがどうしても分からない。
以下は、上記のJSONを簡略化したものです(route.json)。
[
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"007",
"ug:region":
{
"type":"Triangle",
"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]
},
"owl:sameAs":"test1"
},
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"008",
"ug:region":
{
"type":"LineString",
"coordinates":[[139.667765,35.416456],[139.668006,35.416708],[139.668116,35.416788],[139.668276,35.416841]]
},
"owl:sameAs":"test2"
}
]
問題は、ネストの中に入っていて、タグの名前のついていない、
"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]
の部分です。
10時間くらいは戦ったかなぁ ―― もう疲れ果てて、質問サイトに投稿する為に、(上記の)JSONファイルと、このパーサープログラムを成形していました。
で、そのプログラムをテストランさせたら ―― これが動いてしまったのです。
// route_json.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
type testXML struct {
Date string `json:"dc:date"`
Title string `json:"dc:title"`
Note string `json:"odpt:note"`
Owl string `json:"owl:sameAS"`
Regions struct {
Type string `json:"type"`
Coordinates []interface{}
//Coordinates []struct {
// Longitude float64 `json:"longitudes>longitude"`
// Latitude float64 `json:"latitudes>latitude "`
//} `json:"coodtinates"`
} `json:"ug:region"`
}
func main() {
file, err := ioutil.ReadFile("route.json")
//file, err := ioutil.ReadFile("odpt_BusroutePattern.json")
if err != nil {
// エラー処理
}
var data []testXML
err = json.Unmarshal(file, &data)
if err != nil {
fmt.Println("Unmarshal")
log.Fatal(err)
}
fmt.Println(string(file))
// ループによる取得
for _, e := range data {
fmt.Println(e) //
mm := e.Regions.Coordinates
fmt.Println(len(mm))
for _, m := range mm {
fmt.Println(m)
}
}
}
実行結果は以下の通りです。
> go run route_json.go
[
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"007",
"ug:region":
{
"type":"Triangle",
"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]
},
"owl:sameAs":"test1"
},
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"008",
"ug:region":
{
"type":"LineString",
"coordinates":[[139.667765,35.416456],[139.668006,35.416708],[139.668116,35.416788],[139.668276,35.416841]]
},
"owl:sameAs":"test2"
}
]{2023-04-03T00:00:00+09:00 007 test1 {Triangle [[139.6249873 35.4648941] [139.6237514 35.4648862] [139.623672 35.4650137]]}}
3
[139.6249873 35.4648941]
[139.6237514 35.4648862]
[139.623672 35.4650137]
{2023-04-03T00:00:00+09:00 008 test2 {LineString [[139.667765 35.416456] [139.668006 35.416708] [139.668116 35.416788] [139.668276 35.416841]]}}
4
[139.667765 35.416456]
[139.668006 35.416708]
[139.668116 35.416788]
[139.668276 35.416841]
ずっとエラーが出ていたんですが、突然表示されるようになりました。
Coordinates []interface{}
JSONタグが付いていない問題は、これで解消できるようです。
で、(簡易版ではなく)実体のJSONファイルを使ってみても動きました。
こういうことがあるから、本当にコーディングというのは厄介なんですよね。
パースしたデータを表示してみました。
通路が(多分)順番に表示されることが確認できました。
だいたいこれで完成です。
// route_json.go
// 横浜市交通局 バス路線情報 / Bus route information of Transportation Bureau, City of Yokohama
// https://ckan.odpt.org/dataset/b_busroute-yokohamamunicipal
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
type testJSON struct {
Id string `json:"@id"`
Type string `json:"@type"`
Date string `json:"dc:date"`
Context string `json:"@context"`
Title string `json:"dc:title"`
Note string `json:"odpt:note"`
Regions struct {
Type string `json:"type"`
Coordinates []interface{}
} `json:"ug:region"`
Owl string `json:"owl:sameAS"`
Pattern string `json:"odpt:pattern"`
Busroute string `json:"odpt:busroute"`
Operator string `json:"odpt:operator"`
Direction string `json:"odpt:direction"`
BusstopPoleOrder []struct {
Note string `json:"odpt:note"`
Index int `json:"odpt:index"`
BusstopPole string `json:"odpt:busstopPole"`
} `json:"odpt:busstopPoleOrder"`
}
func main() {
// JSONファイルから読み取る場合
//file, err := ioutil.ReadFile("route.json")
file, err := ioutil.ReadFile("odpt_BusroutePattern.json")
if err != nil {
// エラー処理
}
/*
// Webアクセスで取得する場合
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
}
file, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
*/
var data []testJSON
err = json.Unmarshal(file, &data)
if err != nil {
fmt.Println("Unmarshal")
log.Fatal(err)
}
//fmt.Println(string(file))
// ループによる取得
for _, e := range data {
fmt.Println(e) // 全情報表情
coors := e.Regions.Coordinates
fmt.Println("len_mm", len(coors))
for _, coor := range coors {
fmt.Println(coor)
}
stops := e.BusstopPoleOrder
fmt.Println("len_ss", len(stops))
for _, stop := range stops {
fmt.Println(stop.Note)
fmt.Println(stop.Index)
fmt.Println(stop.BusstopPole)
}
}
}
fast apiの設定
1 目的
すぐに環境やらコード(の場所)やらを忘れるので、自分の為にメモを残す
2 簡単なfastapiの環境とコード
私の環境では、c:\users\ebata\fastapi1,2,3,4,5あたりにある。
2.1. 最新のPIPをアップグレードする
> pip install --upgrade pip
> python -m pip install --upgrade pip
> python3 -m pip install --upgrade pip
のいずれかでできる。
2.2. uvicorn, fastapi, pydantic, pandas, requests, jsonのモジュールをインストールする
> pip3 install fastapi
> pip3 install uvicorn[standard]
は必須だが、後はプログラムを動かせば、プログラムの方から、インストールを指示されると思うので大丈夫(だろう)。
2.3. uvicornを起動する
> uvicorn index:app --reload
これで、自動的に"index.py"が動き出す。
2.4. クライアントを起動する
> python request.py
で、起動を確認する。
以上
3. ソースコード
3.1. index.py
# https://miseruit.com/2022/07/18/post-2939/
from fastapi import FastAPI
from pydantic import BaseModel
import pandas
class Item(BaseModel):
ID: str
Name: str
Class: str
app = FastAPI() #インスタンスを作成
User_list =[
{"ID":"M001","Name":"Takashi","Class":"A"},
{"ID":"M002","Name":"Hanako","Class":"A"},
{"ID":"M003","Name":"Hiroshi","Class":"B"},
{"ID":"M004","Name":"Kyoko","Class":"B"},
]
# joson_normalize関数を用いてJsonデータをDataFrame型に変換します。
df = pandas.json_normalize(User_list)
# Get /Users/ : 全件取得
# Get /Users/{ID} :特定のIDのみ取得
# POST /Users/ :ユーザの登録
# DELETE /Users/{ID} :ユーザの削除
# curl http://localhost:8000/Users/
@app.get("/Users/")
async def users():
return User_list
# curl http://localhost:8000/Users/M003
@app.get("/Users/{u_id}")
async def users(u_id:str):
return list(filter(lambda item : item['ID']==u_id, User_list))
# Windowsの場合
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/
@app.post("/Users/")
async def users(user: Item):
User_list.append({"ID": user.ID,"Name":user.Name,"Class":user.Class})
return User_list
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003
@app.delete("/Users/{u_id}")
async def users(u_id:str):
global df
df = df.drop(df.index[df["ID"]==u_id])
return df.to_json(orient='records')
3.2. request.py
import requests
import json
# テスト
#url = 'https://wp.kobore.net/'
#response = requests.get(url)
#print(response.text)
#print()
print("-----start of get(1)")
# curl http://localhost:8000/Users/ と同じ機能を実施
r = requests.get('http://localhost:8000/Users/')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(1)")
print()
# curl http://localhost:8000/Users/M003 と同じ機能を実施
print("-----start of get(2)")
r = requests.get('http://localhost:8000/Users/M003')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(2)")
print()
print("-----start of delete")
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003と同じ機能を実施
r = requests.delete('http://localhost:8000/Users/M003')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of delete")
print()
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/ と同じ機能を実現
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
json_payload = '{"ID":"M005","Name":"Aya","Class":"C"}' # ここで100回くらいのパターンを試したぞ
print("headers:",headers)
print("payload:",json_payload)
#r = requests.post('http://localhost:8000/Users/',headers=headers, params=payload)
#r = requests.post('http://localhost:8000/Users/', headers=headers,params=json.loads(payload))
r = requests.post('http://localhost:8000/Users/', headers=headers, data=json_payload)
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
{'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]} が取れない!
結論から先に言うと
json_payload ='{"ID":"M005","Name":"Aya","Class":"C"}'
Python | FastAPIでAPI作成 ~その6:DELETEでデータ削除 & Pandas活用
を参考(というかコピペ)させて頂きました。
# https://miseruit.com/2022/07/18/post-2939/
from fastapi import FastAPI
from pydantic import BaseModel
import pandas
class Item(BaseModel):
ID: str
Name: str
Class: str
app = FastAPI() #インスタンスを作成
User_list =[
{"ID":"M001","Name":"Takashi","Class":"A"},
{"ID":"M002","Name":"Hanako","Class":"A"},
{"ID":"M003","Name":"Hiroshi","Class":"B"},
{"ID":"M004","Name":"Kyoko","Class":"B"},
]
# joson_normalize関数を用いてJsonデータをDataFrame型に変換します。
df = pandas.json_normalize(User_list)
# Get /Users/ : 全件取得
# Get /Users/{ID} :特定のIDのみ取得
# POST /Users/ :ユーザの登録
# DELETE /Users/{ID} :ユーザの削除
# curl http://localhost:8000/Users/
@app.get("/Users/")
async def users():
return User_list
# curl http://localhost:8000/Users/M003
@app.get("/Users/{u_id}")
async def users(u_id:str):
return list(filter(lambda item : item['ID']==u_id, User_list))
# Windowsの場合
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/
@app.post("/Users/")
async def users(user: Item):
User_list.append({"ID": user.ID,"Name":user.Name,"Class":user.Class})
return User_list
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003
@app.delete("/Users/{u_id}")
async def users(u_id:str):
global df
df = df.drop(df.index[df["ID"]==u_id])
return df.to_json(orient='records')
C:\Users\ebata\fastapi4> uvicorn index:app --reload
import requests
import json
# テスト
#url = 'https://wp.kobore.net/'
#response = requests.get(url)
#print(response.text)
#print()
print("-----start of get(1)")
# curl http://localhost:8000/Users/ と同じ機能を実施
r = requests.get('http://localhost:8000/Users/')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(1)")
print()
# curl http://localhost:8000/Users/M003 と同じ機能を実施
print("-----start of get(2)")
r = requests.get('http://localhost:8000/Users/M003')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(2)")
print()
print("-----start of delete")
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003と同じ機能を実施
r = requests.delete('http://localhost:8000/Users/M003')
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
print("-----end of delete")
print()
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/ と同じ機能を実現
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
json_payload = '{"ID":"M005","Name":"Aya","Class":"C"}' # ここで100回くらいのパターンを試したぞ
print("headers:",headers)
print("payload:",json_payload)
#r = requests.post('http://localhost:8000/Users/',headers=headers, params=payload)
#r = requests.post('http://localhost:8000/Users/', headers=headers,params=json.loads(payload))
r = requests.post('http://localhost:8000/Users/', headers=headers, data=json_payload)
print(r.url)
print(r.status_code)
print(r.text)
print(r.json())
C:\Users\ebata\fastapi4> python request.py
{'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]}{"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}エラー番号422
pythonでFastAPIを試す
一回纏めて再学習
FastAPI入門https://zenn.dev/sh0nk/books/537bb028709ab9/viewer/f1b6fc
パラメタの違い
- パスパラメタ http://127.0.0.1:8000/items/3
- クエリパラメタ http://127.0.0.1:8000/items/?skip=0&limit=10
- リクエストボディ ???
環境構築
pip install fastapi
と
pip install sqlalchemy uvicorn
を実施。途中でpythonのバージョンアップしろと言われたので、素直に従いました。
run.py (サーバ立ち上げ用)
urls.py (URLのルーティング用)
controllers.py (レスポンス処理用)
を作りましたが、以下のエラーが出てきました。
> python run.py
Traceback (most recent call last):
File "run.py", line 1, in <module>
from urls import app
File "C:\Users\ebata\fastapi\urls.py", line 1, in <module>
from controllers import *
File "C:\Users\ebata\fastapi\controllers.py", line 3, in <module>
app = FastAPI(
NameError: name 'FastAPI' is not defined
さて、python環境構築の経験もなく、python使うの数年ぶり、ということで、まあ、「ご挨拶」でしょう。
試しに、 https://wp.kobore.net/江端さんの技術メモ/post-7450/ を入れて実行してみたとこ、ちゃんと動くようなので、環境側の問題 と特定しました。
ーーーー
PS C:\Users\ebata\fastapi> uvicorn main:app --reload
ERROR: Error loading ASGI app. Attribute "app" not found in module "m
が出てきたので、いろいろ調べたら、なんとプログラムがセーブされていなかったという間抜けなオチでした。
実施した(履修した)ページ
- https://www.youtube.com/watch?v=vVNCVgYb6Xw&t=2844s (Youtube)
- https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-environment
- https://fastapi.tiangolo.com/ja/tutorial/first-steps/
- https://cpp-learning.com/fastapi/#Web_API
現時点までの実施事項(続き)
https://fastapi.tiangolo.com/ja/tutorial/body/ の、「クエリパラメータ」まで履修完了。續きは、「リクエストボディ」から。
現在、履修に使っているホルダは、~/fastapi, ~/fasapi2
所感
外部インタフェースについては、Webや他の手段でAPIを叩くことができると思うが、モジュール間の関数にfastapiを使うことは可能だろうか。
原理的には、curl等で送り込めばば、何でも可能と思うが、相互通信を考えるとwebsocketを剥き出しにした方がよいのではないだろうか?
ちなみに、FastAPIでPostgresqlのDBアクセスをやろうと思ったけど、
$ pip install psycopg2
がどうしてもインストールできずに、断念(くやしい)
以上
golang map 取り扱い 再履修
package main import ( "fmt" ) // (1) var m = make(map[int]int) // (2)をコメントアウトするならこっちを使う func main() { // (2) //m := map[int]int{} // (1)をコメントアウトするならこっちを使う m[3124] = 9 m[1992] = 2 m[2020] = 3 // キーのみ取り出す for key := range m { fmt.Println(key) } //3124 //1992 //2020 fmt.Println() // キーと値 for key, value := range m { fmt.Println(key, value) } fmt.Println() //1992 2 //2020 3 //3124 9 // 値のみ必要な場合 for _, value := range m { fmt.Println(value) } //9 //2 //3 fmt.Println() // ループの回数を数える i := 0 for key, value := range m { fmt.Println(key, value) i++ } //3124 9 //1992 2 //2020 3 fmt.Println() fmt.Println("delete(m, 1992)") delete(m, 1992) for key, value := range m { fmt.Println(key, value) } //delete(m, 1992) //2020 3 //3124 9 fmt.Println() fmt.Println("add as m[2999] = 2") m[2999] = 2 for key, value := range m { fmt.Println(key, value) } //add as m[2999] = 2 //3124 9 //2999 2 //2020 3 _, ok := m[100] if ok { fmt.Println("OK") } else { fmt.Println("NG") } // NG _, ok = m[2999] if ok { fmt.Println("OK") } else { fmt.Println("NG") } // OK fmt.Println(m) // map[2020:3 2999:2 3124:9] m[2020]++ fmt.Println(m) // map[2020:4 2999:2 3124:9]
}
A5:SQL Mk-2 トライアル(解決)
問題
現在、192.168.0.23/postgresにアクセスを確認
で、A5:SQL Mk-2は接続成功している様子
だが、publicの下の「テーブル」「ビュー」などを叩いても何も出てこない。
念のためpgAdAdmin4 で試してみたが、こちらも問題なし
解決(T研究所のKさんに教えて貰いました)
『データベースを登録する際に、データベース名に「agent_db」 を指定していただく必要があります』
参考文献
"cannot parse invalid wire-format data"
研究室の学生さんたちに負荷テストに協力してもらっています。
今、"cannot parse invalid wire-format data" のエラーでGTFS_HUBのダウンを確認しました。
PrumeMobileの方は、そのままにして、GTFS_HUBの再起動をかけました。
(以下、後日対応)
repeated read on failed websocket connection (一応解決)
■PruneMobile側(sever.go)
■gtfs_hub側(gtfs_hub.go)
■対策
PruneMobile側(sever.go)の方が先に落ちる(確認) → で書き込みできなくなってgtfs_hub側(gtfs_hub.go)も落ちる、と、多分、そんな感じ。
"repeated read on failed websocket connection"を普通に読めば、「間違ったWebsocket接続で、*何度*も読み込みを繰り返している」なので、read errorが発生したら、即コネクションクローズして、クライアントも落してしまえばいいのかな、と考えました。→ ダメでした。サーバが落ちて処理が止まります。
で、以前、
で、
"http.HandleFunc()"は、クライアントがやってくるごにに一つづつ立ち上があるfork()のようなものである
てなことを書いていたので、read errorqを検知したら、forループから直ちにbreakして、クライアントをクラッシュさせてしまうこと(ができるものと)しました。→ ダメでした。サーバが落ちて処理が止まります。
for (){
(色々)
err = webConn.ReadJSON(&locMsg2) // ここでPanic serving ... repeated read on failed websocket connectionが発生している可能性あり
fmt.Printf("c6: ")
if err != nil{
fmt.Printf("Before webConn.close(), After c6:")
webConn.Close()
break → ダメ
}
}
javascript (onload.js) の方にも、終了理由がでるように、以下のコードを追加しました。
// サーバを止めると、ここに飛んでくる
socket.onclose = function (event) {
console.log("socket.onclose");
let obj = JSON.parse(event.data);
console.log("socket.onclose: obj.id:", obj.id);
console.log("socket.onclose: obj.lat:", obj.lat);
console.log("socket.onclose: obj.lng:", obj.lng);
console.log("socket.onclose: obj.type:", obj.type);
console.log("socket.onclose: obj.popup:", obj.popup);
socket = null;
// 切断が完全に完了したかどうか
if(event.wasClean){
var closed = "完了";
} else {
var closed = "未完了";
}
info.innerHTML += "切断処理:" + closed + "<br>";
info.innerHTML += "コード:" + event.code + "<br>";
info.innerHTML += "理由:" + event.reason + "<br>";
}
window.onunload = function(event){
// 切断
ws.close(4500,"切断理由");
}
上記の「ダメ」の対策中
https://ja.stackoverflow.com/questions/12389/golang%E3%81%A7%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E5%86%8D%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%99%E3%82%8B%E3%81%A8websocket-server%E3%81%8C%E8%90%BD%E3%81%A1%E3%82%8B
■問題解決編
色々問題があったのですが、順番に説明していきます。
var addr = flag.String("addr", "192.168.0.8:8080", "http service address") // テスト
....
.....
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
log.Fatal(http.ListenAndServeTLS(*addr, "./cert.pem", "./key.pem", nil)) // localhost:8080で起動をセット
}
まず第一に、http.HandleFunc()の誤解がありました。私は、これを、fork()のようにプロセスかスレッドを発生さるものと思っていましたが、これは、一言で言えば、単なるコールバック関数でした。
乱暴に言えば、Webからアクセスがあると、echo()というファンクションに吹っ飛ばされる、という現象を発生させる"だけ"で、それ意外のことは何にもしてくれないのです。
では、echo()の方はどうなっているかというと、
func echo(w http.ResponseWriter, r *http.Request) { // JavaScriptとの通信
webConn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("websocket connection err:", err)
return
}
defer webConn.Close()
とすることで、webSocket通信の準備ができて、その通信路が"webConn"にできる、ということです。
で、それ意外のことは何もしてくれないのです。
つまり、2つのWebブラウザからアクセスがくると、その度、echo()
に飛んできて、その度に、異なるwebConnを生成する、ということです。
ここまでの説明で分かると思いますが、つまり、Webブラウザがくる度に、それをどっかに格納しておかないと、通信路の情報が上書きされてしまいます。
なので、基本的には、以下のwebConnを、配列に格納しておきます。
var clients = make(map[*websocket.Conn]bool) // 接続されるクライアント
// クライアントを新しく登録(だけ)
m1Mutex.Lock() // 同時書き込みを回避するため、ミューテックスロックで囲っておく
clients[webConn] = true // これで、Webからのアクセスがある度に、通信路情報が動的に追加される
m1Mutex.Unlock()
というように、Webブラウザとの通信路を別々に管理しておきます。
もし、fork()みたいなことがしたいのであれば、goroutineを起動して、そのgoroutineにWebブラウザの通信路を明け渡す必要があります。それでも、通信路の全体管理は、echo()が握っているので、webConnを消滅されたい場合は、echo()の方でやって貰う必要があります。
この方法を実現する方法として、GO言語のサンプルプログラムで良く登場するのが、以下のような方法です。
// 江端のPCでは、c:\users\ebata\test20230412\main.go
package main
(中略)
func handleConnections(w http.ResponseWriter, r *http.Request) {
// 送られてきたGETリクエストをwebsocketにアップグレード
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// 関数が終わった際に必ずwebsocketnのコネクションを閉じる
defer ws.Close()
// クライアントを新しく登録
clients[ws] = true
for {
var msg Message
// 新しいメッセージをJSONとして読み込みMessageオブジェクトにマッピングする
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// 新しく受信されたメッセージをブロードキャストチャネルに送る
broadcast <- msg
}
}
func handleMessages() {
for {
// ブロードキャストチャネルから次のメッセージを受け取る
msg := <-broadcast
// 現在接続しているクライアント全てにメッセージを送信する
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
上記の例では、 http.HandleFunc("/ws", handleConnections) で、handleConnectionsをコールバック関数にしておき、こちらにWebブラウザからのアクセスを全部任せます
でもって、ついでにWebからやってくる通信の全てを受けつけています(ReadJSON())。さらに、webブラウザが突然閉じられた場合などは、通信エラーとして検知して、それをクローズした後に、webConnの配列から取り除いています。これで、このブラウザからの通信路は閉じらて、消されます。
で、この通信の内容を、チャネルを使って、go handleMessager
で作った、fork()みたいなgoroutineに流し込んでいます。
結論として、fork()みたなことがやりたければ、「自力で作れ」ということになります。
しかし、WebSocket単位で別々のgoroutine作るのも面倒くさいです。それに、もしそのようなgoroutineを作ったとすれば、ブロードキャストを実現しなければなりませんが、チャネルには、ブロードキャスト機能がありません(どうしても使わなければならない時は、私はredisを使っています)。
ですので、私、チャネルの配列を作って疑似的なブロードキャストを実現しようとしたのですが、このサンプルプログラムが見るからなくて、困っていました。
『echo()関数の中で、全てのWebコネクションを相手にできないかな』と考え始めました。
基本形はこんな形
for {
message <- channel // 外部から送られてきたデータ
for client := range clients{ //clientsはWebConnのリスト
client.WriteJSON(message)
client.ReadJSON(message2}
}
}
問題は、Webブラウザは終了処理などなどをせずに、閉じられてしまうことにあります(私たちが普通にやっていることです)。
とすれば、echo()の中でコネクションの切断を検知して、それをWebConnのリスト
から取り除く必要があります。
で、こんなことやってみたんですよ。
for {
message <- channel // 外部から送られてきたデータ
for client := range clients{ //clientsはWebConnのリスト
err = client.WriteJSON(message)
if err != nil{
client.Close()
delete(client, clients)
err = client.ReadJSON(message2}
if err != nil{
client.Close()
delete(client, clients)
}
}
これでは、for ルーチンの中で、回している変数を減らすという処理が不味かったようで、この場合、 echo()が終了してしまうようでした。当然、repeated.....も含めて、エラーの嵐になりました。
なので、こんな風に変更しました。
var delete_client *websocket.Comm
for {
delete_client = nil
message <- channel // 外部から送られてきたデータ
for client := range clients{ //clientsはWebConnのリスト
err = client.WriteJSON(message)
if err != nil{
delete_client = client
}
err = client.ReadJSON(message2}
if err != nil{
delete_client = client
}
}
if delete_client != nil{
delete_client.Close()
delete(clients, delete_client)
}
つまり、ループの外でコネクション処理を行う、ということです。
ただ、この方法では、ループを回っている途中に2つ以上のWebブラウザが落された場合にどうなるか、という問題が残ります。
この場合、「次のループでエラーを検知するのを待つ」という処理で対応する、と腹を括りました。
なぜなら、repeat..... のエラーは、積り積って発生することを経験的に知っていましたので、それまで通信障害したまま走って貰えばいい、と割り来って考えることにしました。
結論としては、
(1)http.HandleFunc()は、Webからの接続要求時に、飛される先の関数を記述するもの(コールバック関数)であり、
(2)そのコールバック関数の中だけで、入出力処理がしたいのであれば、ループを壊さないような工夫をして、上手く運用すれば、上手くいく(ことがある)
という結論になりそうです。
cert.pem = fullchain.pem key.pem = privkey.pem ということで良いのだろう
公開回、秘密鍵の対応
log.Fatal(http.ListenAndServeTLS(*addr, "./cert.pem", "./key.pem", nil)) // localhost:8080で起動をセット
if httpErr = http.ListenAndServeTLS(*addr, "./fullchain.pem", "./privkey.pem", nil);
ということで、 cert.pem = fullchain.pem key.pem = privkey.pem で良いのだろう