【Go】templateの基礎的使い方

GoでWebプログラミングを学んでます。

今回はtemplateの使い方の紹介です。

基礎的な使い方

まずはソースコードから。
main.go

package main

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

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := "Helooooooo!!!" // <--- tmpl.html の {{ . }} に表示されるデータ
        t.Execute(w, m)
}

func main(){
        server := http.Server{
                Addr: "127.0.0.1:8080",
        }
        http.HandleFunc("/process", process)
        server.ListenAndServe()
}

tmpl.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Go template work</title>
  </head>
  <body>
    {{ . }}  <!-- <--- ここにデータが表示される-->
  </body>
</html>

これを実行して localhost:8080/process をブラウザで確認すると

Helooooooo!!!

っと表示されます。

でこれは、文字列以外に数値でも構造体でも配列でもハッシュでも m:= ... のところでデータを渡せば表示することができます。

template.Must

余談ですがtemplate.ParseFilesの結果をtemplate.Mustでラップしてます。
これ知らなかったのですが、エラー処理を省略するためです。
template - The Go Programming Language
ParseFilesの結果はエラー結果も返してしまいますが、そのエラー処理を省略させてしまうのがMustです。
template - The Go Programming Language

構造体を渡す

とりあえず構造体を渡す

そんでこのデータを渡すときは構造体を渡してやると何かと使いやすいので構造体を使うことが多いです。
以降修正箇所は部分的に提示します。

type Member struct {
        Id   int
        Name string
}

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := Member{   // <--- 構造体で渡す
                Id: 123,
                Name: "bob",
        }
        t.Execute(w, m)
}
  <body>
    {{ .Id }} <!-- 構造体MemberのIdを表示 -->
  </body>

構造体で配列を渡す

例えば下記のように構造体の中の配列を表示させるには、rangeを使うと表示できます。

type Member struct {
        Items   []Item
}
type Item struct {
        Id      int
        Name    string
}

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := Member{
                Items: []Item{
                        Item{Id: 123, Name: "bob"},
                        Item{Id: 456, Name: "tom"},
                        Item{Id: 789, Name: "mike"},
                },
        }
        t.Execute(w, m)
}
  <body>
    <ul>
    {{ range .Items }}
       <li>{{ .Id }} - {{ .Name }}</li>
    {{ end }}
    </ul>
  </body>
・123 - bob
・456 - tom
・789 - mike

range は indexも使える

そしてこのrangeはindexも使えます。
main.goは変えずにtmpl.htmlを修正します。

  <body>
    <ul>
    {{ range .Items }}
       <li>{{ .Id }} - {{ .Name }}</li>
    {{ end }}
    </ul>
    <ul>
    {{ range $i, $v := .Items }}
       <li>{{ index $.Items $i }} : {{ $v }}</li>
    {{ end }}
    </ul>
  </body>
・123 - bob
・456 - tom
・789 - mike

・{123 bob} : {123 bob}
・{456 tom} : {456 tom}
・{789 mike} : {789 mike}

なかなか使えますね。

Goのwebhdfsクライアントを修正してみた

HadoopへファイルをGo言語でインポートしたくて、webhdfs経由で入れようとしていました。
そこでGoのクライアントライブラリーを使ってやってみたんだけど、少し苦労したって話です。

Go言語のwebhdfsクライアントってあんまりない。

Go言語でアプリを作ってて、webhdfsのクライアントになんかいいのないかなーと探してたら下記を見つけたのでこれでやってみることにした。
github.com
なんかアフリカ系のすごい笑顔がでてきたな。。。

これを探すまでにちょっと驚いたんだけどGo言語でHadoopのクライアントライブラリーってほとんど無いみたいね。
ここで探してもたった2つしかない。。。
webhdfs - Go libraries and applications
f:id:suganoo:20181227095445p:plain

まあそうだろうね、webhdfsのREST apiがあるから別に誰かのクライアント使わなくても自分でURLとかちゃんと書いてnet/http使えばなんとかなりそうだからな。
でも面倒なことしたくなかったのでクライアント使うことにしました。

エラーがでる。。。

READMEをもとにして書いてやってみたんですが、なんかエラーがでる。

import os
import github.com/vladimirvivien/gowfs
fs, err := gowfs.NewFileSystem(gowfs.Configuration{Addr: "hoge.com:14000", User: "hdfs"})
if err != nil{
	log.Fatal(err)
}
fp, _ := os.Open("hoge.txt.gz")
ok, err := fs.Create(
	fp,
	gowfs.Path{Name:"/hoge/hoge.txt.gz"},
	false,
	0,
	0,
	0700,
	0,
)
invalid character '<' looking for beginning of value

ソースのエラーのところをfmt.Printlnでいろいろと表示させて調べたところ、content-typeが適切に入ってないことがわかってきた。

Content-Type:[text/html;charset=utf-8]
 <u>Data upload requests must have content-type set to 'application/octet-stream'</u><

修正

なので仕方がないので、forkして修正してPRしてみた。
github.com

fs.Create()の末尾にcontent-type 入れたらそれを設定できるというもの。

ok, err := fs.Create(
	fp,
	gowfs.Path{Name:"/hoge/hoge.txt.gz"},
	false,
	0,
	0,
	0700,
	0,
	"application/octet-stream"   // <--- これな!
)

本家のコミットを見てみると、3,4年更新されてないからPRも取り込まれることもないでしょう。
なので動作確認はしたけどテストコードは試してない。。。
テストコード書いた方がいいんだけどなあ....
f:id:suganoo:20181227095617g:plain
お疲れ様でした。

【Git】fatal: unable to access SSL connect error

git clone しようとしたらタイトルのようなエラーが出ました。

詳しく言うと、go get でgitのソースコードを落としてこようとしたら、なぜかSSL connect error が出ました。
その解消法です。

やったこと

go get やったところこんなエラーが出ました。

go get github.com/vladimirvivien/gowfs

# cd .; git clone https://github.com/vladimirvivien/gowfs /home/apdev/go/src/github.com/vladimirvivien/gowfs
Cloning into '/xxxxxxx/go/src/github.com/vladimirvivien/gowfs'...
fatal: unable to access 'https://github.com/vladimirvivien/gowfs/': SSL connect error
package github.com/vladimirvivien/gowfs: exit status 128

はて、SSL connect error とは.....。

じゃあsrc 配下のところで、git clone もしてみました。

git clone https://github.com/vladimirvivien/gowfs.git

Cloning into 'gowfs'...
fatal: unable to access 'https://github.com/vladimirvivien/gowfs.git/': SSL connect error

同じようなエラー。。。

ログを出す

export GIT_CURL_VERBOSE=1

を設定しておくと、git で操作するときに詳細なログがでてきます。

もう一回 git cloneしてみる。

git clone https://github.com/vladimirvivien/gowfs.git

.......
* Connected to xxx.xxxx.xxxx (11.22.33.44) port 8080 (#1)
* NSS error -12190
* Expire cleared
* Closing connection #1
fatal: unable to access 'https://github.com/vladimirvivien/gowfs.git/': SSL connect error

NSS error -12190 .... !?

解決方法

ググってみると、nss というものが古いのかもとかでてきます。
yum update したほうがいいそうです。

sudo yum update -y nss

そんでgit clone してみるとうまくいきました!
ついでにファイル消してgo get もやってみるとうまくいった!!

git clone https://github.com/vladimirvivien/gowfs.git

go get github.com/vladimirvivien/gowfs

GIT_CURL_VERBOSE これは使えるなあ。

【Go】Go言語で時間を扱う(pkg/time)機能を"ほぼ"まとめてみた

ファイル名にタイムスタンプを入れたいなと思ってtime パッケージを調べてたんですが、便利な機能が意外とあるなと気づいたのでまとめてみました。

ちょくちょく調べることがあるので、この際いっきにまとめてみることにした。

自分でも知らない機能があったりして、面白かったです。

まずはドキュメントを読んでみましょう。
time - The Go Programming Language
あ部分的にDocumentのExampleを参考にしてるところはあります。

基本的な使い方

package main

import (
        "fmt"
        "time"
)

func main() {
        fmt.Println("----- Basic usage -----")
        now := time.Now()
        fmt.Println("Now()        : " + now.String())
        time1 := time.Date(2018, 10, 15, 16, 48, 32, 12345, time.Local)
        fmt.Println("Date()       : " + time1.String())
        fmt.Println()
}
----- Basic usage -----
Now()        : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301
Date()       : 2018-10-15 16:48:32.000012345 +0900 JST

Now()で現在時刻、Date()で特定の時刻が取れます。


ここ以降は出力の関係上 "strconv"もimportしておいてください。
また説明が重複するため、package, import 部分は省略します。

値取得関係の関数

日時などの値を単純に取得する関数です。

fmt.Println("----- Functions -----")
fmt.Println("Year()       : " + strconv.Itoa(now.Year()))
fmt.Println("Month()      : " + now.Month().String())
fmt.Println("int(now.Month()) : " + strconv.Itoa(int(now.Month())))
fmt.Println("Day()        : " + strconv.Itoa(now.Day()))
fmt.Println("Hour()       : " + strconv.Itoa(now.Hour()))
fmt.Println("Minute()     : " + strconv.Itoa(now.Minute()))
fmt.Println("Second()     : " + strconv.Itoa(now.Second()))
fmt.Println("Nanosecond() : " + strconv.Itoa(now.Nanosecond()))
fmt.Println("Weekday()    : " + now.Weekday().String())
fmt.Println("int(Weekday()) : " + strconv.Itoa(int(now.Weekday())))
fmt.Println("Unix()       : " + strconv.FormatInt(now.Unix(), 10))
fmt.Println("UnixNano()   : " + strconv.FormatInt(now.UnixNano(), 10))

iso_year, iso_week := now.ISOWeek()

fmt.Println("ISOWeek() year : " + strconv.Itoa(iso_year))
fmt.Println("ISOWeek() week : " + strconv.Itoa(iso_week))
fmt.Println()
----- Functions -----
Year()       : 2018
Month()      : December
int(now.Month()) : 12
Day()        : 21
Hour()       : 18
Minute()     : 2
Second()     : 48
Nanosecond() : 238415200
Weekday()    : Friday
int(Weekday()) : 5
Unix()       : 1545382968
UnixNano()   : 1545382968238415200
ISOWeek() year : 2018
ISOWeek() week : 51

Month()だと英文字の月名になるのが要注意ですね。なんで数字じゃないんだ。
intでキャストする必要があります。Weekday()、お前は許す。。

またIOSWeek()は年の53週間中何週間目がわかるから便利ですね。

便利な関数

じゃあ年月日を取るために Year(), Month(), Day() を使うか~っと思ったら、ちょっと待って。
下記のDate(), Clock()が便利ですよ。

fmt.Println("----- Simple Functions -----")
fmt.Println("Date()  Clock()")
year, month, day := now.Date()
hour, min, sec   := now.Clock()
fmt.Println(year)
fmt.Println(int(month))
fmt.Println(day)
fmt.Println(hour)
fmt.Println(min)
fmt.Println(sec)
fmt.Println()

fmt.Println("YearDay()    : " + strconv.Itoa(now.YearDay()))
fmt.Println("UTC()        : " + now.UTC().String())
fmt.Println("Local()      : " + now.Local().String())

zoneName, zoneOffset := now.Zone()

fmt.Println("Zone() (name,offset) : " + "( " + zoneName + " , " + strconv.Itoa(zoneOffset) + " )")
fmt.Println()
----- Simple Functions -----
Date()  Clock()
2018
12
21
18
2
48

YearDay()    : 355
UTC()        : 2018-12-21 09:02:48.2384152 +0000 UTC
Local()      : 2018-12-21 18:02:48.2384152 +0900 JST
Zone() (name,offset) : ( JST , 32400 )

Date()、Clock()はいっきに取れるから便利ですね。
また年間で何日目なのか?がわかるYearDay()も便利ですね。
UTC←→JST変換も楽です。zone情報もわかりますね。

加減算

時間の加算減算です。

fmt.Println("----- Add -----")
fmt.Println("Now()           : " + now.String())

now = now.Add(time.Duration(-1) * time.Hour)
fmt.Println("time 1 hour ago : " + now.String())
fmt.Println()
----- Add -----
Now()           : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301
time 1 hour ago : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699

もちろんHour以外にもDayでもMinuteでも可能です。

フォーマット変換

指定のフォーマットから文字列時刻を読み取ります。

fmt.Println("----- Parse() Format()-----")
var timeParse01 = time.Time{}
timeParse01, _  = time.Parse("2006/01/02 15:04:05 (MST)", "2018/12/21 13:45:38 (JST)")

fmt.Println("Parse()          : " + timeParse01.String())
fmt.Println("Format(ANSIC)    : " + timeParse01.Format(time.ANSIC))
fmt.Println("Format(RFC1123Z) : " + timeParse01.Format(time.RFC1123Z))
fmt.Println()
----- Parse() Format()-----
Parse()          : 2018-12-21 13:45:38 +0900 JST
Format(ANSIC)    : Fri Dec 21 13:45:38 2018
Format(RFC1123Z) : Fri, 21 Dec 2018 13:45:38 +0900

パースするところで、2006とか01とか02ってなんなんだ?と思ってましたが、そういうフォーマットの指定方法なんですね。
ここを見るとわかります。
src/time/format.go - The Go Programming Language

const (
	ANSIC       = "Mon Jan _2 15:04:05 2006"
	UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
	RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
	RFC822      = "02 Jan 06 15:04 MST"
	RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
	RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
	RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
	RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
	RFC3339     = "2006-01-02T15:04:05Z07:00"
	RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
	Kitchen     = "3:04PM"
	// Handy time stamps.
	Stamp      = "Jan _2 15:04:05"
	StampMilli = "Jan _2 15:04:05.000"
	StampMicro = "Jan _2 15:04:05.000000"
	StampNano  = "Jan _2 15:04:05.000000000"
)

const (
	_                        = iota
	stdLongMonth             = iota + stdNeedDate  // "January"
	stdMonth                                       // "Jan"
	stdNumMonth                                    // "1"
	stdZeroMonth                                   // "01"
	stdLongWeekDay                                 // "Monday"
	stdWeekDay                                     // "Mon"
	stdDay                                         // "2"
	stdUnderDay                                    // "_2"
	stdZeroDay                                     // "02"
	stdHour                  = iota + stdNeedClock // "15"
	stdHour12                                      // "3"
	stdZeroHour12                                  // "03"
	stdMinute                                      // "4"
	stdZeroMinute                                  // "04"
	stdSecond                                      // "5"
	stdZeroSecond                                  // "05"
	stdLongYear              = iota + stdNeedDate  // "2006"
	stdYear                                        // "06"
	stdPM                    = iota + stdNeedClock // "PM"
	stdpm                                          // "pm"
	stdTZ                    = iota                // "MST"
	stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
	stdISO8601SecondsTZ                            // "Z070000"
	stdISO8601ShortTZ                              // "Z07"
	stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
	stdISO8601ColonSecondsTZ                       // "Z07:00:00"
	stdNumTZ                                       // "-0700"  // always numeric
	stdNumSecondsTz                                // "-070000"
	stdNumShortTZ                                  // "-07"    // always numeric
	stdNumColonTZ                                  // "-07:00" // always numeric
	stdNumColonSecondsTZ                           // "-07:00:00"
	stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
	stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted

	stdNeedDate  = 1 << 8             // need month, day, year
	stdNeedClock = 2 << 8             // need hour, minute, second
	stdArgShift  = 16                 // extra argument in high bits, above low stdArgShift
	stdMask      = 1<<stdArgShift - 1 // mask out argument
)

時間単位の変更

fmt.Println("----- ParseDuration() -----")
duration, _ := time.ParseDuration("2h35m10s")
fmt.Println("Duration String      : " + duration.String())
fmt.Println("Duration in minutes  : " + strconv.FormatFloat(duration.Minutes(), 'f', 0, 64))
fmt.Println("Duration in seconds  : " + strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
fmt.Println()
----- ParseDuration() -----
Duration String      : 2h35m10s
Duration in minutes  : 155
Duration in seconds  : 9310

X時間は何分/何秒なのか?とかの単位変換計算が楽ですね。

どれくらいの時間の隔たりがあるか

fmt.Println("----- Sub() Since() Until() -----")
time01 := time.Date(2018, 10, 10, 15,  0,  0, 0, time.Local)
time02 := time.Date(2018, 10, 25, 15, 30, 10, 0, time.Local)

fmt.Println("time01   : " + time01.String())
fmt.Println("time02   : " + time02.String())

time01Subtime02 := time01.Sub(time02)
time02Subtime01 := time02.Sub(time01)

fmt.Println("time01.Sub(time02)   : " + time01Subtime02.String())
fmt.Println("time02.Sub(time01)   : " + time02Subtime01.String())
fmt.Println()

time03 := time.Date(2018, 12, 10, 0, 0, 0, 0, time.Local)
time04 := time.Date(2018, 12, 25, 0, 0, 0, 0, time.Local)

fmt.Println("Now()    : " + now.String())
fmt.Println("time03   : " + time03.String())
fmt.Println("time04   : " + time04.String())

fmt.Println("time.Since(time03)   : " + time.Since(time03).String())
fmt.Println("time.Until(time04)   : " + time.Until(time04).String())
fmt.Println()
----- Sub() Since() Until() -----
time01   : 2018-10-10 15:00:00 +0900 JST
time02   : 2018-10-25 15:30:10 +0900 JST
time01.Sub(time02)   : -360h30m10s
time02.Sub(time01)   : 360h30m10s

Now()    : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699
time03   : 2018-12-10 00:00:00 +0900 JST
time04   : 2018-12-25 00:00:00 +0900 JST
time.Since(time03)   : 282h2m48.3541336s
time.Until(time04)   : 77h57m11.6458664s

二つの時間の差分がSub()、その時間から今までがSince()、今から未来の時間までがUntil()。
このへんも便利ですね。

どっちが新しい?古い?

自分はUnixタイムで比較してましたが、こんな関数もあるんですね。

fmt.Println("----- After() Before() -----")
date11m := time.Date(2018, 11, 1, 0, 0, 0, 0, time.Local)
date12m := time.Date(2018, 12, 1, 0, 0, 0, 0, time.Local)

fmt.Println(date11m)
fmt.Println(date12m)

isDate11mAfterDate12m := date11m.After(date12m)
isDate12mAfterDate11m := date12m.After(date11m)

fmt.Printf("date11m.After(date12m)  = %v\n", isDate11mAfterDate12m)
fmt.Printf("date12m.After(date11m)  = %v\n", isDate12mAfterDate11m)

isDate11mBeforeDate12m := date11m.Before(date12m)
isDate12mBeforeDate11m := date12m.Before(date11m)

fmt.Printf("date11m.Before(date12m) = %v\n", isDate11mBeforeDate12m)
fmt.Printf("date12m.Before(date11m) = %v\n", isDate12mBeforeDate11m)
fmt.Println()
----- After() Before() -----
2018-11-01 00:00:00 +0900 JST
2018-12-01 00:00:00 +0900 JST
date11m.After(date12m)  = false
date12m.After(date11m)  = true
date11m.Before(date12m) = true
date12m.Before(date11m) = false

After()、Before() の方がわかりやすい!可読性がよくなります。これまで時間の比較はエポックタイムで比較してたんですが、これならぱっと見わかりやすくなりますね。

同じ時間か?

時差があっても同じ時刻か比較してくれます。

fmt.Println("----- Equal() -----")
//時差が8時間の "Beijing Time" を作ります。
secondsEastOfUTC := int((8 * time.Hour).Seconds())
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)

timeBeijin := time.Date(2018, 2, 1, 20, 15, 45, 0, beijing)
timeUtc    := time.Date(2018, 2, 1, 12, 15, 45, 0, time.UTC)

fmt.Println(timeBeijin)
fmt.Println(timeUtc)

datesEqualUsingEqualOperator := timeBeijin == timeUtc
datesEqualUsingFunction      := timeBeijin.Equal(timeUtc)

fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)
fmt.Println()
----- Equal() -----
2018-02-01 20:15:45 +0800 Beijing Time
2018-02-01 12:15:45 +0000 UTC
datesEqualUsingEqualOperator = false
datesEqualUsingFunction = true

timeBeijinとtimeUtcは時刻は違いますが、地球上の上では時差を換算すると同じ時刻ですからTrueになりますね。
(UTC)12:15 + (Beijin時差)8hr = (Beijin Time)20:15

それにしても上記のように書けば時差ロケーションが作れるんですね。

四捨五入や切り下げ

時間の四捨五入ってめんどくさいですよね。でもこれなら簡単です。

fmt.Println("----- Round() Truncate() -----")
time10    := time.Date(2018, 12, 10, 14, 15, 45, 987654321, time.Local)

fmt.Println("time10                      : " + time10.String())
fmt.Println("time10.Round(time.Hour)     : " + time10.Round(time.Hour).String())
fmt.Println("time10.Round(time.Minute)   : " + time10.Round(time.Minute).String())
fmt.Println("time10.Round(time.Second)   : " + time10.Round(time.Second).String())
fmt.Println("time10.Truncate(time.Hour)  : " + time10.Truncate(time.Hour).String())
fmt.Println("time10.Truncate(time.Minute): " + time10.Truncate(time.Minute).String())
fmt.Println("time10.Truncate(time.Second): " + time10.Truncate(time.Second).String())
fmt.Println()
----- Round() Truncate() -----
time10                      : 2018-12-10 14:15:45.987654321 +0900 JST
time10.Round(time.Hour)     : 2018-12-10 14:00:00 +0900 JST
time10.Round(time.Minute)   : 2018-12-10 14:16:00 +0900 JST
time10.Round(time.Second)   : 2018-12-10 14:15:46 +0900 JST
time10.Truncate(time.Hour)  : 2018-12-10 14:00:00 +0900 JST
time10.Truncate(time.Minute): 2018-12-10 14:15:00 +0900 JST
time10.Truncate(time.Second): 2018-12-10 14:15:45 +0900 JST

Round()は四捨五入みたいな感じで、Truncate()は切り捨てですね。

Parse処理うまくいったかな?

IsZero() ってなんのためにあるのかな?と思ってましたが、確認のためのようですね。

fmt.Println("----- IsZero() -----")
timeRight, _  := time.Parse("2006/01/02 15:04", "2018/12/21 13:45")
// Parse処理で不適切な形式をしてして失敗させます。
timeFail, _   := time.Parse("2006/01/02 15:04", "2018/12/21 hoge 13:45")

fmt.Println("timeRight   : " + timeRight.String())
fmt.Println("timeFail    : " + timeFail.String())
fmt.Println("timeRight.IsZero()   : " + strconv.FormatBool(timeRight.IsZero()))
fmt.Println("timeFail.IsZero()    : " + strconv.FormatBool(timeFail.IsZero()))
fmt.Println()
----- IsZero() -----
timeRight   : 2018-12-21 13:45:00 +0000 UTC
timeFail    : 0001-01-01 00:00:00 +0000 UTC
timeRight.IsZero()   : false
timeFail.IsZero()    : true

なるほど。パースに失敗すると0001-01-01....になってしまうんだな。

サンプルソース全文

こちらです

Tips of Tim pkg

(2018/12/26)追記

qiitaにも書いてみました。
qiita.com

【Go】Go言語でgzファイルをまとめる

たまたまgzファイルを一つにまとめる(=コンカチする)必要があったのでやってみました。
読み込んだ時にバイナリーで扱ってるんで、そのまま書いてしまえばいいから簡単ですね。

準備

ファイル

こんな感じの適当なファイルを用意しました。

cat aaaaa.txt

aaaaaaaaaa
AAAAAAAAAA
1111111111
cat bbbbb.txt

bbbbbbbbbb
BBBBBBBBBB
2222222222
cat ccccc.txt

cccccccccc
CCCCCCCCCC
3333333333

それをそれぞれgzipに固めておきます。

gzip aaaaa.txt
gzip bbbbb.txt
gzip ccccc.txt
ls
aaaaa.txt.gz bbbbb.txt.gz ccccc.txt.gz

このgzファイルをtest_dirに入れておきます。

フォルダ構成

実行する前にフォルダ構成を提示しておきます。

pack_gz_files.go
test_dir
    aaaaa.txt.gz
    bbbbb.txt.gz
    ccccc.txt.gz

実行スクリプト

goのコード

pack_gz_files.go

concatenate gz files

os.OpenFile

https://golang.org/pkg/os/#OpenFile

func OpenFile(name string, flag int, perm FileMode) (*File, error)

ファイルのポインタが取得できます。

ReadFile

https://golang.org/pkg/io/ioutil/#ReadFile

func ReadFile(filename string) ([]byte, error)

読み込んだファイルはバイト型で返されます。

(*File) Write

https://golang.org/pkg/os/#File.Write

func (f *File) Write(b []byte) (n int, err error)

読み込んだバイトデータを引数にそのままバイト型を与えればいいわけですね。

実行

go run pack_gz_files.go test_dir

実行結果

実行すると output.txt.gzができます。
それを解凍してみると...。

gunzip output.txt.gz

cat output.txt
aaaaaaaaaa
AAAAAAAAAA
1111111111
bbbbbbbbbb
BBBBBBBBBB
2222222222
cccccccccc
CCCCCCCCCC
3333333333

うまくコンカチされてますね。

参考サイト

stackoverflow.com
qiita.com

【Go】multiple-value in single-value context

たまにこんなエラーが出て戸惑いました。
あんまり調べなかったせいか、ググってもよくわからなくて引数かな?とか考えてたら全然違ってました。

結論から言うと、返り値が2つなのに1変数しか受取ろうとしてないぞ!
っていうエラーでした。

アホ過ぎて、よく調べろよ!っと自分に言いたくなったので書いておきます....。

ちなみに状況

エラー状況

ちなみにですが、自分のエラーが出た状況は下記です。
ファイルを読み込みたかったんですね。

gzFile := ioutil.ReadFile(file)

こんなエラーが出ます

multiple-value ioutil.ReadFile() in single-value context

「一つの値の中で複数の値になってるよ!」って感じでしょうかね。

ReadFileのドキュメント

ReadFile
ioutil - The Go Programming Language
をよく見るとちゃんと書いてありますね。

func ReadFile(filename string) ([]byte, error)

返り値は 「[]byte, error」の2つです。

正解

ちゃんとerror も受け取るように修正しました。

gzFile, err := ioutil.ReadFile(file)

まとめ

ドキュメントよく読めよ(自分)。。。

まあでももうちょっとエラー出力の内容がわかりやすくなってたらなあ。。。

続「Goプログラミング実践入門」の写経がとてもいい勉強になった

サンプルソースを写経していて、どーしても気になるところがあったのでメモしておきます。
blog.suganoo.net

(※2018/12/06更新 全然ちがうところ修正していたので更新)

開始ユーザーが表示されない

f:id:suganoo:20181206103538p:plain

User.Nameって無いよね

ここの部分の .User.Name が無いせいで、スレッドを表示させる時に開始ユーザーが表示されません。
gowebprog/index.html at 46faa1da9ae329950ec5f94d903a3165646e9085 · mushahiroyuki/gowebprog · GitHub

      開始ユーザー {{ .User.Name }} - 開始日 {{ .CreatedAtDate }} - 投稿数 {{ .NumReplies }}。

上のリンクは翻訳者のかたのリンクなのですが著者の方にもありません。

UserとThreadはバインディングされていません

というのもUserの結果ってバインディングされてないように見えます。
gowebprog/user.go at 46faa1da9ae329950ec5f94d903a3165646e9085 · mushahiroyuki/gowebprog · GitHub

type User struct {
	Id        int
	Uuid      string
	Name      string
	Email     string
	Password  string
	CreatedAt time.Time
}

type Thread struct {
	Id        int
	Uuid      string
	Topic     string
	UserId    int       // <--- 単純に UserId だけでUserとはバインディングされていない
	CreatedAt time.Time
}

なのでサンプルをそのまま使っても、そりゃ表示されないです。

俺なりに修正してみた

自分なりにFolkして修正してみました。今日は疲れてしまったのでしっかり動作確認してないし、眠いので動かなかったらご勘弁。
なのでプルリクも出すつもりもありません。参考までにしてください。

修正箇所

全体修正箇所github.com

ThreadとUserについて

まずThreadについては下記のようにUser構造体を持つようにしました。
update UserofThread · suganoo/gowebprog@09b5348 · GitHub

type Thread struct {
	Id        int
	Uuid      string
	Topic     string
	UserOfThread  User    //<--- これ
	CreatedAt time.Time
}

そんでThreads()でThreadを取ってくる時に、ちゃんとidでUserのデータも取ってくるように修正。
ついでに UserById関数も作っておきました。
gowebprog/thread.go at 09b53482accc3750b04017ab4b6c0b33fcc62249 · suganoo/gowebprog · GitHub

func UserById(id string) (user User, err error) {
 	user = User{}
 	err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE id = $1", id).
 		Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt)
 	return
 }

スレッドを取ってくるところ

	for rows.Next() {
		conv := Thread{}
		userId := ""
		if err = rows.Scan(&conv.Id, &conv.Uuid, &conv.Topic, &userId, &conv.CreatedAt); err != nil {
			return
		}
		user := User{}
		user, err := UserById(userId)
		if err != nil {
			return
		}
		conv.UserOfThread = user
		threads = append(threads, conv)
	}

表示側のindex.htmlも .UserOfThread.Nameで表示できるようになります。たぶん。
update UserofThread · suganoo/gowebprog@09b5348 · GitHub

開始ユーザー {{ .UserOfThread.Name }} - 開始日 {{ .CreatedAtDate }} - 投稿数 {{ .NumReplies }}。

こんな風にユーザー名が取れました。
f:id:suganoo:20181206103617p:plain
テストコードまで直す気力はも無いので寝ます。。。。

最後

サンプルコードは参考までに読むものですね。
著者の方も結構issue出てるのに、全然答える気配がないようです。もう興味が薄れてしまったんでしょうかね。

あと運用上気になった所として下記の2つのアクセスが頻繁になると思われます。

  • ページ遷移ごとにsessionをsessionsテーブルに確認しに行ってる
  • 上述のUserを取得するところでスレッドを全部取得する時に毎回Usersテーブルにアクセスしてる

おそらく運用上はkey value的なredisを使うとかした方がDBのアクセスを減らせるでしょう。

「Goプログラミング実践入門」の写経がとてもいい勉強になった

先日読んだこの本について内容がよかったので写経に挑戦してます。
blog.suganoo.net

やっと9割がた終わったのですが、とてもよかったです。
写経するの実は今回初めてだったのですが、かなり学びになりますね。

ところどころ端折ってしまったところも多々あるのですが、8割?おおすじの内容は写経して、ローカル環境でchatサービスの一応動くところまで写経できたので良しとしてブログにここまでの感想を書いておこうと思います。

本について気づいたところ

  • 構造体の値をhtmlテンプレートに渡すところが書いてない
  • 僅かに一部値の受け渡しがどうなってるところがわからない
  • DBがpostgreなので、コマンドが戸惑った
  • logout処理ちょっとおかしくないか?
  • js、cssとかのパスを設定するところもうちょっと説明しておいてくれよ

構造体の値をhtmlテンプレートに渡すところが書いてない

本の中ではhtmlに値を渡すところは、一つの値を渡すところしか書いてなかったんですね。
「これ複数の値渡したいときどーしたらいいんだろ?」っと思ってましたが、第二章のchitchatのところをやることでわかりました。
DBデータ取得→構造体にバインディング→構造体を渡すことでhtmlに値を渡すことができます。
書いといてよ。。。

僅かに一部値の受け渡しがどうなってるところがわからない

細かいところですが、サンプルソースでUser.Nameを使ってるがどこから渡してるのか???となるところがありました。
詳しくはissueを見てみてください。
https://github.com/mushahiroyuki/gowebprog/issues/4
著者のgithubにも書いたけど、回答してくれそうにないね。。。
https://github.com/sausheong/gwp/issues/14

DBがpostgreなので、コマンドが戸惑った

DBはこれまでMySQLくらいしか使ったことなかったので、初めてPostgreSQL使ってみました。
DB一覧やテーブル一覧を表示させたいときコマンドが全然違うので戸惑いますね。
勉強になりました。

logout処理ちょっとおかしくないか?

そうサンプルのログアウト処理が、これでいいのかな?と思いました。
ログアウトするごとにwarningメッセージだしてたり、cookieのリミット何もしてなかったり。
同じ事考えてる人がいたようで、こっちのissueを参考にしました。
https://github.com/sausheong/gwp/issues/4

js、cssとかのパスを設定するところもうちょっと説明しておいてくれよ

これもせめて本の中で説明しておいてくれよと思ったところでした。
chitchatの章で書いてあるけど、1ページくらいで書いておいてほしかったなあと。
https://github.com/mushahiroyuki/gowebprog/blob/master/ch02/chitchat/main.go#L14

もうちょっとちゃんとやるところ

  • テストコードちゃんと写経する
  • Herokuにデプロイ

テストコードちゃんと写経する

今回は簡単なWebプログラミングを学んで動かしてみたかったのでほぼテストコードを書いてませんでした。
よくないですねー。。。
まあこれは自分でも動かしたときに「あれ?なんでこれちゃんと表示されないんだー?」っと思ったら、たいていがSQLのスペルミスでした。
"insert int "とか "created_at, FROM"とか。。。。
テストコードかかないですすめると、めちゃめちゃデバッグに時間がかかった。。

いつもはちゃんとテストコード書いてるからな。。。

Herokuにデプロイ

ちゃんとプラットフォームにデプロイまでしないと、できたって言わないからな。
うん、はしょりました。あとでやります。。。。

感想など

  • ファンクション、テンプレートの使い方がわかってよかった
  • 本の著者って大変だな
  • 次はフレームワークGinに挑戦したい

ファンクション、テンプレートの使い方がわかってよかった

テキストにあるように html/template モジュールつかって、テンプレートの使い方が学べたのが良かった。
そう思ったのはRailsをやったとき、いきなりerbとか使ってたりして、そもそもerbテンプレートはどう使うものなのか?とか解説がないままにすすめられることが多くて違和感を感じてしまったからです。
まあフレームワークを学んでから、内部がどうなってるかを学ぶのもありだとは思うけどもねえ。。。

とりあえず今後Goのフレームワークを使うとき、中身の実装が想像しやすくなっていいだろうと思います。

本の著者って大変だな

上述したんですが、サンプルソースわからないところgithubのissueで質問したんですが出版からしばらくたってもこんな質問をうけるなんて、すごい大変だなと思います。
どんな内容だったなんて覚えてられないよ。
その点著者のgithubでは、issueにまーったく答えてないのはまあそうだよなと思いましたw。

次はフレームワークGinに挑戦したい

ということでだいたいのGoによるWebプログラミングがわかったので次はGinに挑戦してみようかなと思います。
その前にテストコードしっかり書いてみたりしますが。

見返してみても、並行処理やjsonの扱い、テストコード、ベンチマークテストなども書かれてるのでかなり学びになった本でした。
かなりおすすめです。

Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ

Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ

(2018/12/06追記)chitchatサンプルソースを修正してみました。

サンプルソースを修正してみました。
blog.suganoo.net

GoのWebフレームワークginの日本語ドキュメントがでたらしい

Go言語にはRuby on RailsのようにデファクトのWebフレームワークがありません。

GoでWebプログラミングをしてて、次はどのWebフレームワークを使ってみようかなと考えていました。

echo? gin? beego? revel? どれがいいんだろうと迷ってました。
どれでもいいんだけど、どうせやるなら使ってる人が多いとかがいいんですよね。

こんな比較リストもあるようです。
github.com

そんな中にginの日本語ドキュメントを書いてくださった方がいるなんて!なんてありがたいんだっ!
Gin Web Framework

ぱっと見レンダリングとか簡単そうだな。
Gin Web Framework


ginやってみようかなと思います。
楽しみだーがんばろー



【PR】搾取されてない.....!?

客先常駐で働いているエンジニアの方お疲れ様です。
IT業界でSESとして働いている人は多いんですが、自分のマージンや月額単価を知らない人がけっこう多いみたいですね。
自分もそうでした。(^^;)
でもそれかなりもったいないですよ。理由はこちらリツアンSTCの紹介記事を見てみてください。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
blog.suganoo.net

文字化けファイルをinodeで消す

探してみたらけっこうやり方あるみたいですね。
でも備忘のため書いておきます。

そもそもなんでinode番号指定で消す必要があるのか?ですが、こんなことがありました。

  • 一行一行ファイル名を書いたリストを作る
  • そのリストを読み込む
  • 1行づつ書いてあるファイル名にもとづいてファイルを作る

ってことをやってました。

そんでこの最初のファイル名のリストを作るところで、誰かがasciiコードのBOMありでファイルを作られたことがあったんですね。
BOMは詳しくないのでここでは説明を省きます。
とにかくファイルの最初に数バイト入ってしまいます。

そのためプログラムで読み込むと最初の一行目のファイル名が文字化けしてしまいます。
その文字化けのままファイルを作るとファイル名も文字化けしたまま作られてしまい、rm でファイル名を指定することができなくなりました。

対処方法

ls -li でinode番号を取得

$ ls -li
合計 0
1057945 -rw-rw-r-- 1 hoge hoge 0 11月 28 16:07 hoge.txt

inodeでfindしてファイル探して削除

find -inum 1057945 -exec rm -f {} \+

これでOK

ちなみに {} は見つかったファイルが一つ一つ入ってきます。
\+ はその一つ一つが一度に羅列して入ってきます。

qiita.com

「確かな力が身につくPHP「超」入門」を読んでみた

確かな力が身につくPHP「超」入門 (Informatics & IDEA)

確かな力が身につくPHP「超」入門 (Informatics & IDEA)

GoでWebプログラミングを学んでいるんですが、webプログラミングで有名どこのPHPやRailsはふつーどんな書き方するんだろと思って読んでみました。

プログラミング初心者向けの本ですね。
内容は最初から丁寧にわかりやすいです。

ページ遷移からSQL、さらにログイン管理のところまで網羅しています。

でもWebプログラミングをほぼやったことないですが、やっぱり初心者向けでありとっかかりはいいですがちょっと物足りない感じはします。

疑問点としては、htmlの構文をphpのソースにechoでガーッと書いてたんですがPHPってそう書くものなんでしょうか?
サンプルの中でとかえらい数になってたし、html分けないでこんなメンテナンス性悪く書いてるけどいいのかなと思ってしまいました。

あと関数の記述も少なかったですね。PHPって関数なしなのかなと思ってしまったくらい。なわけない!?よね?

プログラミングをやったことない人にはいい本でした!

【Go】goでhttp.Requestを使ってみる

Goでwebプログラミングを勉強してます。
この本をもとにして写経してます。
suganoo.hatenablog.com

http.Requestの値は何が取ってこられるかやってみました。

URIパスは RequestURIでとってこられるんですね。


httpのrequest

http - The Go Programming Language


Goプログラミング実践入門はほんんといい本だな。

【Go】exec.Commandで外部コマンドを実行する

たまたま気になったので調べてみました。

ざっくりいうと
exec.Command("hoge")で実行コマンドを指定します。

  • .Run() : 返却値を無視
  • .Output() : 返却値を取得
  • .Start() : 完了を待たない
  • .Wait() : 完了を待つ

ソースコードは後述の参考Qiitaを参考にしています。

Go で外部コマンド実行
exec - The Go Programming Language
知らなかったんですけど、動的引数を渡す時って「args...」というように「...」も必要だったんですね。

これが無いとこんなエラーになります

# command-line-arguments
./do_command.go:36:36: cannot use args (type []string) as type string in argument to exec.Command

<参考ページ>
qiita.com
stackoverflow.com

インフラエンジニアならpythonだよね

f:id:suganoo:20181105181116p:plain
「インフラエンジニアならプログラミング知識とかいらないよね?」
「プログラミング苦手だからインフラやろうかな。」

たまにこんなことを耳にします。


そんなこと言う人がいるんだな~とちょっと驚きました。

たしかにインスタンスはawsとかでボタンポチポチ押せばいいし、デプロイも設定ファイルに設定内容書いてボチッと実行すればいいだけかもしれません。

ですが、これまでの自分の(偏った)経験を振り返るとそんなことないなーと思います。

障害調査

インフラエンジニアの仕事ってかなり地味だなーと思うことが多いです。

ssh設定だとか、ネットワークだとか、ミドルウエアの管理とか....

そんな作業をしてると「Rubyとかでアプリ作ってるのいいなー」とうらやましくなってきます


そんなインフラエンジニアにもおお!っと脚光をあびることがありますが、それは障害対応の時です。
しかも原因が特定できた時だけです!

原因特定できなかった時はなんともカッコ悪く、存在意義がなくなるので将来的にクビになるのではドキドキしてしまいます。


これまでの経験からそういった障害調査のためログから特定の文字列を抜き出したりするときにコーディングができるとかなり調査がはかどります。
っというかコーディングできないとお話になりません!

Pythonの方がいい

じゃどの言語がいいのかというと、どれでもいいのですがPythonがおすすめです。

ほんと別になんでもいいんですけどね、自分はPythonかなと思います。

なぜなら、Linux標準にPythonが入ってるからです。

たいていインフラで使ってるOSはLinuxのディストリビューションだと思います。
Red-Hat, CentOS, Ubuntuとか。

障害が起きた時はどのサーバーで障害が起きるかなんてわかりません。
そんな時に標準で入ってるPythonであれば、インストールなど関係なく使えるのでどこでもすぐ調査ができます。


障害が起きた時はたいてい緊急です。

そんな時RubyやJavaが得意だからと言って、インストールからなんてやってられません。
ましてやコンパイルなんて面倒です。

なのでPythonがおすすめです。

もちろん標準のPythonはバージョンが古かったりして使える関数が使えなかったりすることなどありますが、基本的な使い方は変わらないでしょう。

また標準で入ってるものとしてPerlもあります。
はい、Perlでもいいですよ。正規表現処理早いし。
好みの問題ですが、私はユーザーが多いのでPythonを使ってます。

シェルスクリプトでもいいじゃん!?とも思いつきますが、ちょっと複雑なことするとすぐコードが読みにくくなるんですよね。
なので使うのを避けてます。それにシェル芸人になってもねえ....。


ああとそうだ、一応書いておきますが本番サーバーとかの中でログを読み出したりすると、メモリがいっぱいになっちゃったりして影響を及ぼすことがあるんでローカルにコピーしてから障害調査するなど気を付けましょうね。

ツール作るよ

業務でなにかしらのツールを作ることがよくあるかと思います。
ファイルをインポートする、ノードを監視する...など、そういったときの便利ツールを作るときにもコーディングができると便利です。

そういう時しかコーディングの機会がないので、俄然やる気が入っちゃいますけどね。

またログが取れるってことはデータ解析もできるんで、anacondaとかも使えるからPythonは有利ですね。


思えばansibleはpythonでできてるから、テンプレートとか書くときに便利ですね。
あ、chefはRubyだな.....。

できればGoもできた方がいい

そしてまた便利ツールを作ってると、CPUコストや処理速度が気になってきます。
さらに手を伸ばしてGoを学んでみるのもいいでしょう。

ポインタ使ったり、処理速度が速くなることがあるんでGoを学ぶのもおすすめです。

Docker, KubernetesはGoでできてるのでソースコードを見るのも勉強になるでしょう。

最後

っというようにインフラでもプログラミングが必要になることは多いですよ。なので日々機会を見つけて勉強しましょう。

なによりやっぱプログラミングは楽しいですよね。

インフラやってると確かになかなかコーディングの機会を得るのは難しいかもしれません。
ですが、どーにかして仕事をさぼろうと考えればコーディングの機会もできてくるので考えてみましょう。

インフラ監視でフロントエンドから作ってくのも楽しいですよ。

以上です。

JenkinsのMaster-Slave間のオンライン/オフラインの関係

ちょっとこりゃ知らなかったなあというネタを書いておきます。


前回こんな記事を書きました。
suganoo.hatenablog.com


関連してジョブの切り替えをもう少し知らべてみたところ、1つわかったことがありました。

短い言葉で説明がむずかしいのですが、
Slaveノードのオンライン/オフラインの関係は、Master-Slaveの1対1のものなんだなということでした。


どいうことかというと、簡単に状況としては下記です。

  • MasterとしてのJenkinsサーバーが2台あるとします。(Master01, Master02とします)
  • Masterはジョブの内容として別々のことをやっています。
  • Slave側は両方からアクセスされることがあります。

この時にMaster01からSlaveがオフラインであっても、Master02からオンラインになっていればMaster02からジョブ実行できるということでした。

f:id:suganoo:20181022131627p:plain

オンライン/オフラインの状態は(すべてのMasterから)共通した状態ではないってことです。

知らんかったなあ。
ということは各MasterノードからSlaveにオンライン状態を確認しなきゃならないなあ。