[Go] HandleFuncのhandlerをswitchでまとめて実装する

ルーティングによって処理を変えたいが、handlerの中身はほとんど一緒なため、handlerの中でswitch文を使って切り分ける
L html.EscapeString(r.URL.Path) でパスの値を取得できる

func apiHandler(w http.ResponseWriter, r *http.Request){

	var code string // 変数として宣言
	switch html.EscapeString(r.URL.Path) {
	case "/btc":
		code = "BTC_JPY" // ビットコイン
	case "/eth":
		code = "ETH_JPY" // イーサリアム
	case "/xrp":
		code = "XRP_JPY" // リップル
	case "/xlm":
		code = "XML_JPY" // ステラルーメン
	case "/mona":
		code = "MONA_JPY" // モナコイン
	default:
		code = "BTC_JPY"
	}

	uri := "https://api.bitflyer.com/v1/getticker?product_code=" + code
	req, _ := http.NewRequest("GET", uri, nil)

	// 省略
}


func main() {
	http.HandleFunc("/btc", apiHandler)
	http.HandleFunc("/eth", apiHandler)
	http.HandleFunc("/xrp", apiHandler)
	http.HandleFunc("/xlm", apiHandler)
	http.HandleFunc("/mona", apiHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}

code := “BTC_JPY” とすると定数になるので、 var code string と変数として宣言する必要がある
なるほど、後はテンプレート側の処理

bulma cssとtypescriptを使いたい

[Go] CoinMarketCapのAPIを操作しよう

CoinMarketCapとは?
-> 急速に成長している仮想通貨スペースにおける世界で最も参照されている価格追跡ウェブサイトです。

まずDeveloper向けのアカウントを作成し、管理画面でAPI_KEYをコピーして、Developer向けページのIntroductionを一通り目を通します。
https://pro.coinmarketcap.com/account
https://coinmarketcap.com/api/documentation/v1/#section/Introduction

$ curl -H “X-CMC_PRO_API_KEY: ${API_KEY}” -H “Accept: application/json” -d “start=1&limit=5000&convert=USD” -G https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest

おおおおおおおおおおお、なんかすげえ

go

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
)

func main(){
	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", nil)
	if err != nil {
		log.Print(err)
		os.Exit(1)
	}

	q := url.Values{}
	q.Add("start", "1")
	q.Add("limit", "5000")
	q.Add("convert", "USD")

	req.Header.Set("Accepts", "application/json")
	req.Header.Add("X-CMC_PRO_API_KEY", "${API KEY}")
	req.URL.RawQuery = q.Encode()

	resp, err := client.Do(req);
	if err != nil {
		fmt.Println("Error!")
		os.Exit(1)
	}
	fmt.Println(resp.Status)
	respBody, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(respBody));
}

取得する量が多すぎるな
1回のリクエストで25credit使うとなると、1日12回程度しかrequestできんな。流石にこれだと使い物にならんが、イメージは出来た。

[Go] Jsonで出力する

import (
	"encoding/json"
	"log"
	"net/http"
	"time"
)

func apiClockHandler(w http.ResponseWriter, r *http.Request){
	type ResponseBody struct {
		Time time.Time `json:"time"`
	}
	rb := &ResponseBody {
		Time: time.Now(),
	}

	w.Header().Set("Content-type", "application/json")

	if err := json.NewEncoder(w).Encode(rb); err != nil {
		log.Fatal(err)
	}
}

func main(){
	http.HandleFunc("/api/clock", apiClockHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}

http://192.168.34.10:8080/api/clock

OK, ある程度のところまで来た

[Go] Bitflyerで取得した値をHTMLで表示する

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

func bitflyerHandler(w http.ResponseWriter, r *http.Request){

	uri := "https://api.bitflyer.com/v1/getticker"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)

	var ticker Ticker
	json.Unmarshal([]byte(byteArray),&ticker)


	t := template.Must(template.ParseFiles("/home/vagrant/go/src/github.com/me/sample/src/bitflyer.html.tpl"))
	if err := t.ExecuteTemplate(w, "bitflyer.html.tpl", fmt.Sprintf("%.0f\n", ticker.Ltp)); err != nil {
		log.Fatal(err)
	}
}


func main() {
	// fmt.Printf("%.0f\n",ticker.Ltp)

	http.HandleFunc("/bitflyer/", bitflyerHandler)
	log.Fatal(http.ListenAndServe(":8080",nil))
}


type Ticker struct {
	Code string `json:"product_code"`
	Ltp float64 `json:"ltp"`
}

bitflyer.html.tpl

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	{{ . }} 
</body>
</html>

なるほど、これをcoin marketでやるか

[Go] サーバーを起動してHTMLファイルを表示

import (
	"log"
	"net/http"
)

func main(){
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

L /home/vagrant/go/src/github.com/me/sample/src にhtmlファイルを配置する

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	Hello world
</body>
</html>

$ go run server.go

ほう、Echo使わなくても表示できるやん
これを動的に表示したい

### 動的に表示

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func clockHandler(w http.ResponseWriter, r *http.Request){
	fmt.Fprintf(w, `
		<!DOCTYPE html>
        <html>
        <body>
            It's %d o'clock now.
        </body>
        </html>
		`, time.Now().Hour())
}

func main(){
	http.HandleFunc("/clock/", clockHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

Printfで渡してるだけやな

### データの渡し方

func clockHandler(w http.ResponseWriter, r *http.Request){
	t := template.Must(template.ParseFiles("/home/vagrant/go/src/github.com/me/sample/src/clock.html.tpl"))

	if err := t.ExecuteTemplate(w, "clock.html.tpl", time.Now()); err != nil {
		log.Fatal(err)
	}
}

func main(){
	http.HandleFunc("/clock/", clockHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/home/vagrant/go/src/github.com/me/sample/src"))))
	log.Fatal(http.ListenAndServe(":8080",nil))
}

echoでのtemplateと書き方はそんなに変わらんな

[Go] BitflyerのAPIを取得したい

### Bitflyerからの取得

package main

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

func main() {
	uri := "https://api.bitflyer.com/v1/gethealth"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(byteArray))
}

$ go run gethealth.go
{“status”:”NORMAL”}

### Jsonの扱い
GoでJsonの受け取り方
1. 構造体を定義

type User struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Job string `json:"job"`
}
func main() {
	userStr :=
	 `{
	    "name": "tokumaru",
	    "age": 20,
	    "job": "student"
	  }`

	var user User
	json.Unmarshal([]byte(userStr),&user)

	fmt.Println(user.Name)
}

// 構造体を定義
type User struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Job string `json:"job"`
}

$ go run test.go
tokumaru

↓上記をつなげる
### Bitcoinの価格を取得
bitcoinは小数点なので、float64で定義する。
表示する際は、fmt.Printf(“%.0f\n”,ticker.Ltp)として、小数点以下を切り捨てる

package main

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

func main() {
	uri := "https://api.bitflyer.com/v1/getticker"
	req, _ := http.NewRequest("GET", uri, nil)

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	byteArray, _ := ioutil.ReadAll(resp.Body)

	var ticker Ticker
	json.Unmarshal([]byte(byteArray),&ticker)

	fmt.Printf("%.0f\n",ticker.Ltp)
}

// 構造体を定義
type Ticker struct {
	Code string `json:"product_code"`
	Ltp float64 `json:"ltp"`
}

$ go run test.go
5014562

これをHTMLで表示させたい

[Go] Ubuntu20.04にインストール

GoのDownloadsページから最新版のバージョンを確認します。
https://golang.org/dl/

今回はgo1.17.1

$ sudo wget -c https://dl.google.com/go/go1.17.1.linux-amd64.tar.gz -O – | sudo tar -xz -C /usr/local
$ export PATH=$PATH:/usr/local/go/bin
$ source ~/.profile
$ go version
go version go1.17.1 linux/amd64

うむ、OK

[Go] MySQLに接続する

まずMySQLにデータを作ります。
mysql
create database go;
use go
create table users (
id int auto_increment primary key,
name varchar(255),
age int,
address varchar(255),
update_at datetime
);
mysql> describe users;
+———–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———–+————–+——+—–+———+—————-+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| age | int | YES | | NULL | |
| address | varchar(255) | YES | | NULL | |
| update_at | datetime | YES | | NULL | |
+———–+————–+——+—–+———+—————-+

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

func main(){
	_, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	} else {
		fmt.Println("DB connect success!")
	}
}

func sqlConnect()(database *gorm.DB, err error){
	DBMS := "mysql"
	USER := "hoge"
	PASS := "fuga"
	PROTOCOL := "tcp(localhost:3306)"
	DBNAME := "go"

	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
	return gorm.Open(DBMS, CONNECT)
}

$ /home/vagrant/go/bin/dep ensure
$ go run main.go
DB connect success!

### データ挿入

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	error := db.Create(&Users{
		Name: "山田太郎",
		Age: 20,
		Address: "東京都港区赤坂1丁目1-1",
		UpdateAt: getDate(),
	}).Error
	if error != nil {
		fmt.Println(error)
	} else {
		fmt.Println("data insert success!")
	}

}

func sqlConnect()(database *gorm.DB, err error){
	DBMS := "mysql"
	USER := "hoge"
	PASS := "fuga"
	PROTOCOL := "tcp(localhost:3306)"
	DBNAME := "go"

	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
	return gorm.Open(DBMS, CONNECT)
}

type Users struct {
	ID int
	Name string `json:"name"`
	Age int `json:"age"`
	Address string `json:"address"`
	UpdateAt string `json:"updateAt" sql:"not null;type:date"`
}

func getDate() string {
	const layout = "2006-01-01 15:05:30"
	now := time.Now()
	return now.Format(layout)
}

$ go run main.go
data insert success!

mysql> select * from users;
+—-+————–+——+———————————+———————+
| id | name | age | address | update_at |
+—-+————–+——+———————————+———————+
| 1 | 山田太郎 | 20 | 東京都港区赤坂1丁目1-1 | 2021-09-09 14:23:20 |
+—-+————–+——+———————————+———————+
1 row in set (0.00 sec)

### データの取得

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	result := []*Users{}
	error := db.Find(&result).Error
	if error != nil || len(result) == 0 {
		return
	}
	for _, user := range result {
		fmt.Println(user.Name)
	}
}

$ go run main.go
山田太郎

### データの更新

func main(){
	db, err := sqlConnect()
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	result := []*Users{}
	error := db.Find(&result).Error
	if error != nil || len(result) == 0 {
		return
	}
	for _, user := range result {
		fmt.Println(user.Name)
	}

	fmt.Println("update")

	error = db.Model(Users{}).Where("id = ?", 1).Update(&Users{
		Name: "佐藤はじめ",
		UpdateAt: getDate(),
	}).Error

	if error != nil {
		fmt.Println(error)
	}

	result = []*Users{}
	db.Find(&result)
	for _, user := range result {
		fmt.Println(user.Name)
	}
}

$ go run main.go
山田太郎
update
佐藤はじめ

### DELETE

	fmt.Println("Delete")

	error = db.Where("id = ?", 1).Delete(Users{}).Error
	

	if error != nil {
		fmt.Println(error)
	}

	result = []*Users{}
	db.Find(&result)
	for _, user := range result {
		fmt.Println(user.Name)
	}

$ go run main.go
佐藤はじめ
Delete

mysql> select * from users;
Empty set (0.00 sec)

OK、CRUDは一通りわかった🈴

[Go] HTMLテンプレートを使って表示

public/view/hello.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	{{define "hello"}}Hello, {{.}}!{{end}}
</body>
</html>

main.go

import (
	"html/template"
	"io"
	"net/http"

	"github.com/labstack/echo"
)

type Template struct {
	templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func Hello(c echo.Context) error {
	return c.Render(http.StatusOK, "hello", "World")
}

func main(){
	e := echo.New()
	t := &Template{
		templates: template.Must(template.ParseGlob("public/views/*.html")),
	}
	e.Renderer = t
	e.GET("/hello", Hello)

	e.Logger.Fatal(e.Start(":8000"))
}

うーむ、出来たのはわかったけど、イマイチ仕組みがわからん

[Go] Anacondaでtwitterのつぶやき取得

/home/vagrant/go/bin/dep init

twitterのconsumer key, access-tokenなどを用意します。

package main

import (
	"github.com/ChimeraCoder/anaconda"
)

func main() {
    anaconda.NewTwitterApiWithCredentials("your-access-token", "your-access-token-secret", "your-consumer-key", "your-consumer-secret")
}

$ /home/vagrant/go/bin/dep ensure

作成されています。

twitterAccount.json

{
  "accessToken": "",
  "accessTokenSecret": "",
  "consumerKey": "",
  "consumerSecret": ""
}
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"

	"github.com/ChimeraCoder/anaconda"
)

func main(){

	raw, error := ioutil.ReadFile("twitterAccount.json")

	if error != nil {
		fmt.Println(error.Error())
		return
	}

	var twitterAccount TwitterAccount
	json.Unmarshal(raw, &twitterAccount)

	api := anaconda.NewTwitterApiWithCredentials(twitterAccount.AccessToken, twitterAccount.AccessTokenSecret, twitterAccount.ConsumerKey, twitterAccount.ConsumerSecret)

	searchResult, _ := api.GetSearch(`スタートアップ`, nil)
	for _, tweet := range searchResult.Statuses {
		fmt.Println(tweet.Text)
	}
}

type TwitterAccount struct {
	AccessToken string `json:"accessToken"`
	AccessTokenSecret string `json:"accessTokenSecret"`
	ConsumerKey string `json:"consumerKey"`
	ConsumerSecret string `json:"consumerSecret"`
}

$ go run main.go
“厄介者”火山灰で排水処理を手助け、
巨大企業も目?
RT @ecoecoecho: 何をやっているか全く分からないが藤原竜也のイ
RT @Herlipto_info: 𝖲𝗁𝖺𝗋𝗂𝗇𝗀 𝗈𝗎𝗋 𝗅𝗈𝗏𝖾𝗅𝗒 𝗇𝖾𝗐 𝗉𝗂𝖾𝖼𝖾𝗌.

9/20(mon)20:00
RT @Herlipto_info: 𝖲𝗁𝖺𝗋𝗂𝗇𝗀 𝗈𝗎𝗋 𝗅𝗈𝗏𝖾𝗅𝗒 𝗇𝖾𝗐 𝗉𝗂𝖾𝖼𝖾𝗌.

// 省略

おおおおおおおおおおお

package main

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

	"github.com/labstack/echo"
	"github.com/ChimeraCoder/anaconda"
)

func main(){

	e := echo.New()
	e.Post("/tweet", search)
	e.Logger.Fatal(e.start(":1323"))
}

func search(c echo.Context) error {
	keyword := c.FormValue("keyword")
	api := connectTwitterApi()

	searchResult, _ := api.GetSearch(`"` +keyword+ `"`, nil)

	tweets := make([]*Tweet, 0)

	for _, data := range searchResult.Statuses {
		tweet := new(Tweet)
		tweet.Text = data.FullText
		tweet.User = data.User.Name

		tweets = append(tweets, tweet)
	}

	return c.JSON(http.StatusOK, tweets)
}

func connectTwitterApi() *anaconda.TwitterApi {
	raw, error := ioutil.ReadFile("twitterAccount.json")

	if error != nil {
		fmt.Println(error.Error())
		return
	}

	var twitterAccount TwitterAccount
	json.Unmarshal(raw, &twitterAccount)

	return anaconda.NewTwitterApiWithCredentials(twitterAccount.AccessToken, twitterAccount.AccessTokenSecret, twitterAccount.ConsumerKey, twitterAccount.ConsumerSecret)

}



type TwitterAccount struct {
	AccessToken string `json:"accessToken"`
	AccessTokenSecret string `json:"accessTokenSecret"`
	ConsumerKey string `json:"consumerKey"`
	ConsumerSecret string `json:"consumerSecret"`
}

type Tweet struct {
	User string `json:"user"`
	Text string `json:"text"`
}

type Tweets *[]Tweet

$ go build
go: inconsistent vendoring in /home/vagrant/go/src/github.com/me/twitter:
github.com/ChimeraCoder/anaconda@v2.0.0+incompatible: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/ChimeraCoder/tokenbucket@v0.0.0-20131201223612-c5a927568de7: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/azr/backoff@v0.0.0-20160115115103-53511d3c7330: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/dustin/go-jsonpointer@v0.0.0-20160814072949-ba0abeacc3dc: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/dustin/gojson@v0.0.0-20160307161227-2e71ec9dd5ad: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/garyburd/go-oauth@v0.0.0-20180319155456-bca2e7f09a17: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
github.com/labstack/echo/v4@v4.5.0: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt
golang.org/x/net@v0.0.0-20210917221730-978cfadd31cf: is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt

run ‘go mod vendor’ to sync, or use -mod=mod or -mod=readonly to ignore the vendor directory

なんでやろ
まあ 取得できるって事まではわかった。