Jex’s Note

Golang - Native Package

fmt

輸出固定長度

fmt.Printf("ID : %-10s", id)    // 十個字元的長度, 向左對齊
fmt.Printf("ID : %10s", id)     // 十個字元的長度, 向右對齊
fmt.Printf("ID : %.10s", id)    // 印出頭十個字元

前面補 0

fmt.Sprintf("%02d:%02d", 5, 3)  // 05:03

接收 command 輸入的值

fmt.Print("Enter a number: ")
var input float64
fmt.Scanf("%f", &input)

印出顏色

fmt.Println("\x1b[31;1mHello, World!\x1b[0m")

log

儲存成 file

// open a file
f, err := os.OpenFile("dev.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
    log.Printf("Create log file error: %v", err)
}

// don't forget to close it
defer f.Close()

// assign it to the standard logger
log.SetOutput(f)

// 以下的 log 都會進到 log file 裡
log.Println("test")
log.Println("testr2")
log.Println("testr3")

將 log 內容暫存在 buffer

var buf bytes.Buffer
logger := log.New(&buf, "logger: ", log.Lshortfile)
logger.Print("Hello, log file!")

fmt.Print(&buf)

攔截 log 的內容

var buf bytes.Buffer
logger := log.New(&buf, "", log.Lshortfile)
go func() {
    for {
        if buf.Len() > 0 {
            fmt.Print(&buf)
            buf.Reset()
        }
    }
}()
fmt.Println(0)
logger.Print("Hello, log file!1")
logger.Print("Hello, log file!2")
logger.Print("Hello, log file!3")

dd := make(chan int)
<-dd

寫到 syslog

// Configure logger to write to the syslog. You could do this in init(), too.
logwriter, e := syslog.New(syslog.LOG_NOTICE, "myprog")
if e == nil {
    log.SetOutput(logwriter)
}

// Now from anywhere else in your program, you can use this:
log.Print("Hello Logs!")

/var/log/system.log :

Nov  4 08:19:33 Jexde-MacBook-Pro myprog[90449]: 2016/11/04 08:19:33 Hello Logs!

net

取得本機上的 Mac

interfaces, err := net.Interfaces()
if err != nil {
    panic("Poor soul, here is what you got: " + err.Error())
}
for _, inter := range interfaces {
    fmt.Println(inter.Name, inter.HardwareAddr)
}

header / status / etc.

header

resp, _ := http.Get(url)
// &{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] Server:[nginx] Accept-Ranges:[bytes] Content-Length:[994291] Date:[Sat, 25 Jan 2014 19:13:51 GMT] Etag:["4e28a21e-f2bf3"] Content-Type:[video/x-flv] Last-Modified:[Thu, 21 Jul 2011 22:03:10 GMT]] 0xf8400c2a00 994291 [] false map[] 0xf8400c0240}

Status

resp.Status             // 200 OK
resp.StatusCode         // 200

size

resp.Header.Get("Content-Length")
// 994291

body

source, _ := ioutil.ReadAll(resp.Body)
// source 為 131 239 216 100 96 184 2 221 171 162 131 49 17 33 17 39 152 176 194 18 19 62 40 124 93 230 48 58 9 (..略..)

len(source) 結果會和 header 的 Content-Length 一樣

net/url 解析 URL

s := "postgres://user:pass@host.com:5432/path?k=v#f"
u, err := url.Parse(s)
fmt.Println(u.Scheme)                   // postgres
fmt.Println(u.User)                     // user:pass
fmt.Println(u.User.Username())          // user
p, _ := u.User.Password()
fmt.Println(p)                          // pass
fmt.Println(u.Host)                     // host.com:5432
h := strings.Split(u.Host, ":")
fmt.Println(h[0])                       // host.com
fmt.Println(h[1])                       // 5432
fmt.Println(u.Path)                     // /path
fmt.Println(u.Fragment)                 // f
fmt.Println(u.RawQuery)                 // k=v
m, _ := url.ParseQuery(u.RawQuery)
fmt.Println(m)                          // map[k:[v]]
fmt.Println(m["k"][0])                  // v

u, _ := url.Parse("http://www.test.com/xxx.mp3?foo=bar&foo=baz#this_is_fragment")
fmt.Println("full uri:", u.String())  // full uri: http://www.test.com/xxx.mp3?foo=bar&foo=baz#this_is_fragment
fmt.Println("scheme:", u.Scheme)      // scheme: http
fmt.Println("opaque:", u.Opaque)      // opaque:
fmt.Println("Host:", u.Host)          // Host: www.test.com
fmt.Println("Path", u.Path)           // Path /xxx.mp3
fmt.Println("Fragment", u.Fragment)   // Fragment this_is_fragment
fmt.Println("RawQuery", u.RawQuery)   // RawQuery foo=bar&foo=baz
fmt.Printf("query: %#v\n", u.Query()) // query: url.Values{"foo":[]string{"bar", "baz"}}

url 組 query

var Url *url.URL
Url, err = url.Parse("https://" + hostname)
Url.Path += "/api/test"
url_p := url.Values{}
url_p.Add("a", 123)
url_p.Add("b", 456)
Url.RawQuery = url_p.Encode()

urlencode

params := url.Values{}
params.Add("p", p_64)
params.Add("iv", iv_64)
fmt.Println(params.Encode())

自組 post body 要 escape

Post body 規則像 url, 要 url encode, 否則 + 等等的符號會被當成是 space

body := fmt.Sprintf("p=%s&iv=%s", url.QueryEscape(p_64), url.QueryEscape(iv_64))

net/http Download file

out, err := os.Create("output.txt")
defer out.Close()
resp, err := http.Get("http://example.com/")
defer resp.Body.Close()
n, err := io.Copy(out, resp.Body)

只取得 response 的 Header

res, _ := http.Head(url)
maps := res.Header

它連完線馬上就會斷了,不需要手動關掉

Get filename

import (
    "net/url"
    "path/filepath"
)

s := "https://talks.golang.org/2012/splash/appenginegophercolor.jpg"
u, _ := url.Parse(s)
fmt.Println(filepath.Base(u.Path))

GET method

import (
  "http"
  "io/ioutil"
  "os"
)

response, _, err := http.Get("http://golang.org/")
if err != nil {
    fmt.Printf("%s", err)
    os.Exit(1)
} else {
    defer response.Body.Close()
    contents, err := ioutil.ReadAll(response.Body)
    if err != nil {
        fmt.Printf("%s", err)
        os.Exit(1)
    }
    fmt.Printf("%s\n", string(contents))
}

ref : https://gist.github.com/ijt/950790

Get 另一種寫法,指定 header

client := http.Client{
  Timeout: time.Second * 5,
}
req, err := http.NewRequest("GET", "http://www.google.com/dd", nil)
req.Close = true        // Note !!  避免發生 POST EOF 問題
req.Header.Set("Content-Type", "text/plain")
req.Header.Add("Content-Type", "text/plain")
resp, err := client.Do(req)
if err != nil {
    return err.Error()
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
  err = errors.New("Server return non-200 status")
  return
}
contents, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(contents))
return

post :

http.NewRequest("GET", "http://www.google.com/dd", strings.NewReader("name=cjb"))

Http 建立 API

http.HandleFunc("/", Index)
err := http.ListenAndServe(":5555", nil)
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, string("index"))
    // w.Write([]byte("Time: " + time.Now().Format(time.RFC1123)))
}

也可以改由這樣連線, 先建立 linstener

http.HandleFunc("/", Index)
s := &http.Server{}
l, err := net.Listen("tcp", ":5555")
if err != nil {
    panic(err)
}
panic(s.Serve(l))

ConnState 可以截取 connection 的狀態

總共有這五種狀態 StateNew StateActive StateIdle StateHijacked StateClosed

func main() {
    http.HandleFunc("/", myHandler)
    s := &http.Server{
        Addr:           ":8081",
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
        ConnState:      ConnStateListener,
    }
    panic(s.ListenAndServe())
}

func ConnStateListener(c net.Conn, cs http.ConnState) {
    fmt.Printf("CONN STATE: %v, %v\n", cs, c)
    // 如果想要直接結束 connection : c.Close()
}

ref : http://siddontang.com/2015/01/25/stop-server-gracefully/

handle connection (created at 27 Dec 2013)

ln, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Fatal("Starting error", err)
}
conn, err := ln.Accept() // this blocks until connection or error
if err != nil {
    fmt.Println("Create connection failure!")
}
handleConnection(conn)

func handleConnection(conn net.Conn) {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("Please enter a message : ")
        str, err := reader.ReadString('\n')
        if err != nil {
            break
        }
        if str != "" {
            str := fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\nContent-Type: text/html\n\n%s", len(str) - 1 , str)
            conn.Close() // shut down the connection
            break
        }
    }
    bufio.NewReader(os.Stdin).ReadBytes('\n')
}

API Custom Handler

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  th := &timeHandler{format: time.RFC1123}
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

net/http - x509: certificate signed by unknown authority

Post 到一個 https 的 URL 得到 x509: certificate signed by unknown authority

會發生這個原因主要是 golang 的 client 端會自動地對 server 傳來的 certificate 做驗證,但因為它是由不知名的 CA 簽發的,所以有 error

網路其中解法之一 : To fix this problem, you must add certificates of “COMODO RSA Domain Validation Secure Server CA” to your certififcate served from server, also see full report at https://www.ssllabs.com/ssltest/analyze.html?d=catchchat.catchchatchina.com

另一個解法是不要去驗證它

import ("net/http"; "crypto/tls")

tr := &http.Transport{
    TLSClientConfig: &tls.Config{InsecureSkipVerify : true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://someurl:443/)

更好的解法是對 server 的 certificate 做驗證, 這邊有點超越我的能力了, 請參考這

net/http client request 得到 EOF 原因

Post https://xxxxx.com/wait/: EOF

有可能是這個 request 需要比較久的時間,即使你的 http client 的 timeout 設很長, 也會因為 server timeout 太短先把你踢掉,所以你會得到這個 Error

Get lan ip

func GetLocalIP() string {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return ""
    }
    for _, address := range addrs {
        // check the address type and if it is not a loopback the display it
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String()
            }
        }
    }
    return ""
}

Proper way to test CIDR membership of an IP 4 or 6 address example

ref: https://www.socketloop.com/tutorials/golang-proper-way-to-test-cidr-membership-of-an-ip-4-or-6-address-example

package main

import (
        "fmt"
        "net"
        "os"
)

func main() {
        // generate a range of IP version 4 addresses from a Classless Inter-Domain Routing address
        ipAddress, ipNet, err := net.ParseCIDR("123.45.67.64/27")

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        // generate a range of IPv4 addresses from the CIDR address
        var ipAddresses []string

        for ipAddress := ipAddress.Mask(ipNet.Mask); ipNet.Contains(ipAddress); inc(ipAddress) {
                //fmt.Println(ipAddress)
                ipAddresses = append(ipAddresses, ipAddress.String())
        }

        // list out the ipAddresses within range
        for key, ipAddress := range ipAddresses {
                fmt.Printf("[%v] %s\n", key, ipAddress)
        }

        //test for IP version 4 address for membership

        // WRONG WAY!!
        fmt.Println("Contains 123.45.67.69 : ", ipNet.Contains([]byte("123.45.67.69")))

        // CORRECT / PROPER WAY!
        fmt.Println("Contains 123.45.67.69 : ", ipNet.Contains(net.ParseIP("123.45.67.69")))

}

func inc(ip net.IP) {
        for j := len(ip) - 1; j >= 0; j-- {
                ip[j]++
                if ip[j] > 0 {
                        break
                }
        }
}

output :

[0] 123.45.67.64
[1] 123.45.67.65
...略...
[9] 123.45.67.73
...略...
[31] 123.45.67.95
Contains 123.45.67.69 :  false
Contains 123.45.67.69 :  true

io/ioutil/bufio

將下載的資料轉成 io.Reader 型態

var source io.Reader
source = resp.Body
buffer := make([]byte, 1024)
for {
    cBytes, _ := source.Read(buffer)
    (...略...)

等同於

buffer := make([]byte, 1024)
for {
    cBytes,_ := resp.Body.Read(buffur)
    (...略...)

Read File

var img64 []byte
img64, _ = ioutil.ReadFile("/home/ubuntu/mygo/src/pushImage/google.png")

Read last line of file (ref: here)

func readLastLine(filename string) {
    var previousOffset int64 = 0

    file, err := os.Open(filename)
    if err != nil {
            panic(err)
    }

    defer file.Close()

    reader := bufio.NewReader(file)

    // we need to calculate the size of the last line for file.ReadAt(offset) to work

    // NOTE : not a very effective solution as we need to read
    // the entire file at least for 1 pass :(

    lastLineSize := 0

    for {
            line, _, err := reader.ReadLine()

            if err == io.EOF {
                    break
            }

            lastLineSize = len(line)
    }

    fileInfo, err := os.Stat(filename)

    // make a buffer size according to the lastLineSize
    buffer := make([]byte, lastLineSize)

    // +1 to compensate for the initial 0 byte of the line
    // otherwise, the initial character of the line will be missing

    // instead of reading the whole file into memory, we just read from certain offset

    offset := fileInfo.Size() - int64(lastLineSize+1)
    numRead, err := file.ReadAt(buffer, offset)

    if previousOffset != offset {

            // print out last line content
            buffer = buffer[:numRead]
            fmt.Printf("%s \n", buffer)

            previousOffset = offset
    }

}

Get last-modified time of file

// Get file list from dir
files, err := ioutil.ReadDir("/tmp")
if err != nil {
    return
}

for _, file := range files {
    // Skip dir
    if file.IsDir() {
        continue
    }

    fmt.Printf("%s %s\n", file.Name(), file.ModTime())
}

Line counter

func lineCounter(r io.Reader) (int, error) {
    buf := make([]byte, 32*1024)
    count := 0
    lineSep := []byte{'\n'}

    for {
        c, err := r.Read(buf)
        count += bytes.Count(buf[:c], lineSep)

        switch {
        case err == io.EOF:
            return count, nil

        case err != nil:
            return count, err
        }
    }
}

file, err := os.Open("/tmp/test.log")
if err != nil {
    return
}
defer file.Close()
count, err := lineCounter(file)

有效率的 monitor file 並取得最新內容 (類似 tail 指令)

  1. 先 new File
  2. 用兩個 int 變數記錄上一次及偵測到檔案變更的位置 (也就是檔案大小, 用 os.Stat 取得)
  3. 使用 fsnotify/fsnotify 偵測變更, 有變更就取最新內容 (用 file.ReadAt 在上一次的位置開始讀取, 讀取的長度用目前的size減上一次size

code:

for {
        select {
        case event := <-watcher.Events:
                if event.Op&fsnotify.Write == fsnotify.Write {
                        // log.Println("modified file:", event.Name)
                        info, err := file.Stat()
                        if err != nil {
                                log.Println("failed to get file size, err: ", err)
                                break
                        }
                        curr_size = info.Size()
                        last_line, err := readLastLine(file, prev_size, curr_size)
                        if err != nil {
                                log.Println("failed to get last line, err: ", err)
                                continue
                        }
                        prev_size = curr_size
                        if last_line != "" {
                                log.Println(last_line)
                        }
                }
        case err := <-watcher.Errors:
                log.Println("error:", err)
                return
        }
}

func readLastLine(file *os.File, prev int64, curr int64) (last_line string, err error) {
        // start reading from the end of the file
        buf := make([]byte, curr-prev)
        n, err := file.ReadAt(buf, prev)
        if err != nil && err != io.EOF {
                return "", err
        }
        return string(buf[:n]), nil
}

string

基本處理

String to Array

strings.Split("a/b/c",  "/")

Array to String (join)

s := []string{"a", "b", "c"}
fmt.Println(strings.Join(s, "/"))

os

外部參數

  • command : go run t.go dd
  • t.go : fmt.Println(os.Args[1])

“main.main” 函數並沒有返回值

如果想返回一個出錯信息,可用系統調用強制退出:

os.Exit(1)

# Stop
os.Exit(0)

檔案 / 目錄 是否存在

檔案或目錄是否存在

if _, err := os.Stat(filename); os.IsNotExist(err) {
    fmt.Printf("no such file or directory: %s", filename)
    return
}

檔案資訊

func GetFileInfo(path string) (isExistent bool, fileInfo os.FileInfo) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false, nil
    }
    if fileInfo.IsDir() {
        // it's a directory
        return false, nil
    }
    // it's a file
    return true, fileInfo
}

目錄是否存在

func DirExists(path string) (bool) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false
    }
    if fileInfo.IsDir() {
        // it's a directory
        return true
    }
    // it's a file
    return false
}

檔案是否存在

func FileExists(path string) (bool) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false
    }
    if fileInfo.IsDir() {
        // it's a directory
        return false
    }
    // it's a file
    return true
}

目前位置

dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
        log.Fatal(err)
}
fmt.Println(dir)

file - 指定寫入位置

file, _ := os.OpenFile("tt", os.O_RDWR|os.O_APPEND, 0660)
n, err := file.WriteAt([]byte("中Start"), 3)
if err != nil {
    fmt.Println(err.Error())
}
fmt.Println(n)

返回的 n 是寫入的字節大小, 中文字佔 3 btyes, 英文 1 bytes

檔案 tt 寫入後的結果 : ^@^@^@中Start

其中 ^@ 為空字元, 共有 3 組 ^@ 是因為 WriteAt 指定從 3 開始寫入,

但檔案位置是從 0 開始算, 所以會有 3 組 ^@

寫入改成 n, err := file.WriteAt([]byte("678"), 6)

結果為 : ^@^@^@中678rt

註 : ^@ 佔 1byte, 佔 3 bytes

Create folder

 import (
   //"fmt"
   "os"
   "path/filepath"
 )

 func main() {
   // create a TestDir directory on current working directory
   os.Mkdir("." + string(filepath.Separator) + "TestDir",0777)
 }

在 windows 下執行可執行檔(.exe)後視窗停留不關閉

引入 :

import (
    "bufio"
    "os"
)

最後一行執行 :

bufio.NewReader(os.Stdin).ReadBytes('\n')

當執行 .exe 檔後視窗就不會關閉了

或用另一種寫法

b := make([]byte, 1)
os.Stdin.Read(b)

不同作業系統取得不同 path separator (slash, 斜線)

string(filepath.Separator)

os/exec

取得執行中的 binary 路徑
[binary path]
os.Args[0]                      # /home/apps/go/src/test/test

[dir path]
filepath.Dir(os.Args[0])        # /home/apps/go/src/test
執行外部指令
output, err := exec.Command("git", "rev-parse", "HEAD").Output()
if err != nil {
    return "unknown"
}
fmt.Println(string(output))

output 最後會含一個 ascii: 10 (hex: 0A) 的換行字元, 它並不是 \n 字元


signal

接收中止訊號(kill signal)

當程式執行後會停在 <-sigc,如果收到 kill 指令則會繼續往下走

sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
<-sigc
log.Println("Abort!")

發送 signal

重啟 signal

syscall.Kill(syscall.Getpid(), syscall.SIGHUP)

Kill process signal

syscall.Kill(syscall.Getpid(), syscall.SIGTERM)

time

time

Datetime

time.Now()                  // 2016-12-29 17:33:23.784617257 +0800 CST

Format

 time.Now().Format(time.RFC3339)                            // 2017-02-14T06:59:21Z   可以以 string 型態被解到 time.Time 型態
 time.Now().Local().Format("2006-01-02 15:04:05 +0800")     // 2014-06-30 14:23:38 +0800
 time.Now().Format("2006-01-02 15:04:05")                   // 2015-02-03 04:16:54
 time.Now().String()                                        // 2017-01-10 06:51:14.271079336 +0000 UTC
 time.Now().UTC().String()                                  // 轉成 UTC+0

Timestamp

time.Now().Unix()           // 1483004003

Convert timestamp to time

tm := time.Unix(1484032609, 0)

Convert string to time

today := "2017-01-10 16:57:28"
t, err := time.Parse("2006-01-02 15:04:05", today)

Convert string to time in timezone

// 將指定的時間跟 timezone 轉成 time.Time
l, err := time.LoadLocation("Asia/Taipei")
t, err := time.ParseInLocation("2006-01-02 15:04:05", str, l)       // 2017-01-10 16:57:28 +0800 CST

UTC: time.LoadLocation(“UTC”)

Convert string to time in RFC3339

ts, err := time.Parse(time.RFC3339, src)

Convert time to time in timezone

t := time.Now()
loc, err := time.LoadLocation("Asia/Taipei")
fmt.Println(t.String())             // 2017-01-10 08:41:19.833220416 +0000 UTC
fmt.Println(t.In(loc))              // 2017-01-10 16:41:19.833220416 +0800 CST
fmt.Println(t.In(loc).Format("2006-01-02 15:04:05"))

year, month, day, hour, min, sec

year, month, day := t2.Date()
fmt.Printf("Date : [%d]year : [%d]month : [%d]day \n", year, month, day)
// Date : [2017]year : [1]month : [10]day

hr, min, sec := t2.Clock()
fmt.Printf("Clock : [%d]hour : [%d]minutes : [%d] seconds \n", hr, min, sec)
// Clock : [16]hour : [57]minutes : [28] seconds

Parse time 的非預期狀況

timeTest := "20:36"
timezone := "Asia/Tokyo"
loc, err := time.LoadLocation(timezone)
if err != nil {
    log.Fatal(err)
}
t, err := time.ParseInLocation("15:04", timeTest, loc)
if err != nil {
    log.Fatal(err)
}
fmt.Println(t.Format("15:04"))
fmt.Println(t.UTC().Format("15:04"))

在某些主機會是預期結果

20:36
11:36

某些會是:

20:36
11:17

似乎也不是 go 版本不同的問題, 不知道確切發生的原因, 但如果將指定的時間用完整一點可以解決此問題

timeTest := time.Now().Format("2006-01-02") + " " + "20:36"
timezone := "Asia/Tokyo"

loc, err := time.LoadLocation(timezone)
if err != nil {
    log.Fatal(err)
}
t, err := time.ParseInLocation("2006-01-02 15:04", timeTest, loc)
if err != nil {
    log.Fatal(err)
}
fmt.Println(t.Format("15:04"))
fmt.Println(t.UTC().Format("15:04"))

Comapare

After / Before

time1 := "2016-05-15 12:22:00"
time2 := "2017-05-15 12:22:00"
t1, err := time.Parse("2006-01-02 15:04:05", time1)
t2, err := time.Parse("2006-01-02 15:04:05", time2)
if err == nil && t1.Before(t2) {
    fmt.Println("true")
}

延遲

time.Sleep(3 * time.Second)

time.Sleep(500 * time.Millisecond)

speed := 500
time.Sleep(time.Duration(speed) * time.Millisecond)

時間相加/相減

兩時間相減

startTime := time.Now()                     // 2014-01-26 18:17:22.185125 +0800 CST
time.Sleep(3 * time.Second)
endTime := time.Now()                       // 2014-01-26 18:17:25.186307 +0800 CST

var durationTime time.Duration = endTime.Sub(startTime)
fmt.Println(durationTime.String())

加減(日時秒)

timein := time.Now().Add(time.Hour * time.Duration(1))      // Add 1 hour
timein := time.Now().Add(time.Minute * time.Duration(1))    // Add 1 minute
timein := time.Now().Add(time.Second * time.Duration(1))    // Add 1 second

// 減的話一樣是用 Add 但在 Duration 上加上負號
timein := time.Now().Add(time.Minute * time.Duration(-10))


then := time.Now().AddDate(0, 0, 1)     // +1 day
then := time.Now().AddDate(0, 0, -1)    // -1 day
then := time.Now().AddDate(1, 1, 0)     // +1 year and 1 month

timestamp plus 30 seconds

time.Now().Unix() + 30

Week

time.Now().Weekday()            // Tuesday
int(time.Now().Weekday())       // 2 (Day of the week)

相減算時間差

time_start := time.Now()
// do something
log.Printf("elasped time: %s", time.Since(time_start))

3 * time.Second

如果前面的 int 從 string 轉型,是會噴錯的,要改成 :

time.Duration(params["idle_timeout"].(int)) * time.Second

印出到小數的 timestamp/datetime (e.g. 2017-06-28 02:58:46.452 +0000 UTC)

func main() {
        fmt.Println(unixMilli(time.Unix(0, 123400000)))
        fmt.Println(unixMilli(time.Unix(0, 123500000)))
        m := makeTimestampMilli()
        fmt.Println(m)
        fmt.Println(time.Unix(m/1e3, (m%1e3)*int64(time.Millisecond)/int64(time.Nanosecond)))
}

func unixMilli(t time.Time) int64 {
        return t.Round(time.Millisecond).UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
}

func makeTimestampMilli() int64 {
        return unixMilli(time.Now())
}

Result:

123
124
1498618861845
2017-06-28 03:01:01.845 +0000 UTC

Timestamp with three decimal places

fmt.Sprintf("%f", float64(time.Now().UnixNano())/1000000000) // 1483004003.785

math/rand

產生 0~7 (每一次產生不一樣的變數)

rand.Seed(time.Now().UnixNano())
r := rand.Intn(8)

等於

r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
f := r.Intn(8)

產生一串隨機數值 (ex: 7572000213564998818)

r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
f := r.Int63()

Unique key

32 bytes HEX

key := make([]byte, 16)       // 16 會產生 32 個字元
_, err := rand.Read(key)
if err != nil {
    log.Fatal(err)
}
mk := fmt.Sprintf("%X", key)  //小寫
mk := fmt.Sprintf("%X", key)  // 大寫
fmt.Println(mk)

sync

Pool (First In, First out)

var pool sync.Pool
type Item struct {
    Order string
}

v := pool.Get() // You get nothing if pool is empty.
if v == nil {
    v = &Item{Order: "first"}
}
pool.Put(v)                      // Put the first item
pool.Put(&Item{Order: "second"}) // Put the second item
q := pool.Get()                  // You'll get the first item
q = pool.Get()                   // You'll get the second item
fmt.Println(q.(*Item).Order)     // "second"

Sync WaitGroup

當同一時間同步做很多事,但希望全部 goroutine 做完事才進行下一步

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for q := 0; q < 10; q++ {
        wg.Add(1)               # flag
        go func(i int) {
            fmt.Println(i)
            defer wg.Done()     # 通報這個 goroutine 做完了
        }(q)
    }
    wg.Wait()                   # 程式會在這裡等全部 goroutine 做完
}

encoding

組出 SOAP

package main

import "fmt"
import "encoding/xml"

type MyRespEnvelope struct {
    XMLName xml.Name
    Body    Body
}

type Body struct {
    XMLName     xml.Name
    GetResponse completeResponse `xml:"activationPack_completeResponse"`
}

type completeResponse struct {
    XMLName xml.Name `xml:"activationPack_completeResponse"`
    Id      string   `xml:"Id,attr"`
    MyVar   string   `xml:"activationPack_completeResult"`
}

func main() {

    Soap := []byte(`<?xml version="1.0" encoding="UTF-8"?>
                    <soap:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
                    <soap:Body>
                    <activationPack_completeResponse Id="http://tempuri.org/">
                    <activationPack_completeResult xsi:type="xsd:string">Active</activationPack_completeResult>
                    </activationPack_completeResponse>
                    </soap:Body>
                    </soap:Envelope>`)

    res := &MyRespEnvelope{}
    if err := xml.Unmarshal(Soap, res); err != nil {
        fmt.Println(err.Error())
    }

    var val completeResponse = res.Body.GetResponse
    fmt.Println(val.MyVar)
}

ref : http://play.golang.org/p/957GWzfdvN

Parse xml into struct

Person.xml :

<Person>
    <FullName>Grace R. Emlin</FullName>
    <Company>Example Inc.</Company>
    <Email where="home">
        <Addr>gre@example.com</Addr>
    </Email>
    <Email where='work'>
        <Addr>gre@work.com</Addr>
    </Email>
    <Group>
        <Value>Friends</Value>
        <Value>Squash</Value>
    </Group>
    <City>Hanga Roa</City>
    <State>Easter Island</State>
</Person>

code :

package main

import (
    "encoding/xml"
    "fmt"
    "io/ioutil"
    "os"
)

type Email struct {
    Where string `xml:"where,attr"`
    Addr  string
}

type Address struct {
    City, State string
}

type Result struct {
    XMLName xml.Name `xml:"Person"`
    Name    string   `xml:"FullName"`
    Phone   string
    Email   []Email
    Groups  []string `xml:"Group>Value"`
    Address
}

func main() {

    var v Result
    xmlFile1, err := ioutil.ReadFile("Person.xml")
    if err != nil {
        fmt.Println("Error opening file: ", err)
        return
    }

    err1 := xml.Unmarshal(xmlFile1, &v)
    if err1 != nil {
        fmt.Printf("error: %v", err)
        return
    }
    fmt.Println(v)

    // 用 streaming 形式解析, 較容易處理大數據的 xml
    xmlFile, err := os.Open("Person.xml")
    if err != nil {
        fmt.Println("Error opening file: ", err)
        return
    }
    defer xmlFile.Close()
    decoder := xml.NewDecoder(xmlFile)
    for {
        t, _ := decoder.Token()
        if t == nil {
            break
        }
        switch se := t.(type) {
        case xml.StartElement:
            if se.Name.Local == "Person" {
                var d Result
                decoder.DecodeElement(&d, &se)
                fmt.Println(d)
            }
        }
    }

    fmt.Println(v.XMLName)
    fmt.Printf("Name=%s \n", v.Name)
    fmt.Printf("Where=%s   Addr=%s  \n", v.Email[0].Where, v.Email[0].Addr)
    fmt.Printf("Where=%s   Addr=%s  \n", v.Email[1].Where, v.Email[1].Addr)
    fmt.Printf("Groups : %s,  %s\n", v.Groups[0], v.Groups[1])
    fmt.Printf("City=%s,  State=%s  \n", v.Address.City, v.Address.State)
}

ref : http://www.cnblogs.com/yuanershi/archive/2013/01/29/2881192.html

Parse xml into map - clbanning/mxj

var xml = `
    <Person>
        <FullName>Grace R. Emlin</FullName>
        <Company>Example Inc.</Company>
        <Email where="home">
            <Addr>gre@example.com</Addr>
        </Email>
        <Email where='work'>
            <Addr>gre@work.com</Addr>
        </Email>
        <Group>
            <Value>Friends</Value>
            <Value>Squash</Value>
        </Group>
        <City>Hanga Roa</City>
        <State>Easter Island</State>
    </Person>`
res, err := mxj.NewMapXml([]byte(xml))
if err != nil {
    fmt.Println(err)
}
fmt.Println(res)
fmt.Println(res["Person"].(interface{}).(map[string]interface{})["FullName"])
fmt.Println(res["Person"].(interface{}).(map[string]interface{})["Email"].([]interface{})[1].(map[string]interface{})["Addr"])

result :

map[Person:map[FullName:Grace R. Emlin Company:Example Inc. Email:[map[-where:home Addr:gre@example.com] map[Addr:gre@work.com -where:work]] Group:map[Value:[Friends Squash]] City:Hanga Roa State:Easter Island]]
Grace R. Emlin
gre@work.com

Parse json (struct, map)

方法1 : Json 放進預先定義好的 Struct 裡

type Test struct {
    JsonTag string `json:"field1"` // 利用 Tag mapping 到欄位名稱
    Field2  []struct {
        SubField1 string
        SubField2 string
    }
}

res := &Test{}
if err := json.Unmarshal([]byte(jsonString), res); err != nil {
    fmt.Println(err)
}
fmt.Println(res.JsonTag)
fmt.Println(res.Field2[0].SubField1)
fmt.Println(res.Field2[1].SubField2)

方法2 : 以 map 方式將 json 讀進來

var res2 map[string]interface{}
if err := json.Unmarshal([]byte(jsonString), &res2); err != nil {
    fmt.Println(err)
}
fmt.Println(res2["field1"])
fmt.Println(res2["field2"].([]interface{})[0].(map[string]interface{})["subField1"])
fmt.Println(res2["field2"].([]interface{})[1].(map[string]interface{})["subField2"])

json unmarshal 預設數字讓它是 int64 而不是 float64

json 字串裡面有 "visible_at":1483079819,但 unmarshal 完 int 變成 1.483079819e+09

解決方法 :

var data = `{
    "id": 12423434,
    "Name": "Fernando"
}`
d := json.NewDecoder(strings.NewReader(data))
d.UseNumber()
var x interface{}
if err := d.Decode(&x); err != nil {
    log.Fatal(err)
}
fmt.Printf("decoded to %#v\n", x)

base64 加解密

data := "abc123!?$*&()'-=@~"

sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println(sEnc)           # YWJjMTIzIT8kKiYoKSctPUB+

sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println(string(sDec))   # abc123!?$*&()'-=@~

POST base64 with form-data

body := fmt.Sprintf("p=%s&iv=%s", url.QueryEscape(crypt.Base64Encode(p_bytes)), url.QueryEscape(crypt.Base64Encode(iv_bytes)))

json.Marshal 不要 escape 某些字元

當欄位裡面有 url 時,json marshal 會自動地將某些字元(e.g. &) escape 為 \\u0026,為了避免此情形改用 :

type Search struct {
        Query string `json:"query"`
}
data := &Search{Query: "http://google.com/?q=stackoverflow&ie=UTF-8"}
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err = enc.Encode(data)
fmt.Println(string(buf.Bytes()))            // 注意 Encode 會在字尾加上 `\n`

[output]
    {"query":"http://google.com/?q=stackoverflow&ie=UTF-8"}

或用取代的方式將被脫逸的符號還原

b, err := json.Marshal(v)
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)

crypto

md5

"crypto/md5"

h := md5.New()
io.WriteString(h, "Hello World!")
fmt.Printf("%x", h.Sum(nil))

AES CBC encrypt

plaintext 必須為 aes.Blocksize (16) 的倍數


import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "io"
)

func main() {
    key := []byte("1234567890123456")
    plaintext := pad([]byte("secret data12345"))

    if len(plaintext)%aes.BlockSize != 0 {
        panic("plaintext is not a multiple of the block size")
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
    fmt.Println(base64.StdEncoding.EncodeToString(iv))              // iv base64: VGnqCX2VQonnNUL/Hlmk1w==
    fmt.Println(base64.StdEncoding.EncodeToString(ciphertext[16:])) // encrypt base64: 5ThQKq6RnAiGsLHU/BWm7A==
}

// plaintext 無法被 aes.Blocksize (16) 要先補位數湊滿
func pad(in []byte) []byte {
    padding := aes.BlockSize - (len(in) % aes.BlockSize)
    if padding == 0 {
        padding = aes.BlockSize
    }
    for i := 0; i < padding; i++ {
        in = append(in, byte(padding))
    }
    return in
}

另種寫法

text := []byte(`secret data12345`)
sharekey := []byte(`1234567890123456`)
block, err := aes.NewCipher(sharekey)
if err != nil {
    panic(errors.New("AESEncrypt Create Cipher error: " + err.Error()))
}

paddingText := pad(text, block.BlockSize())
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
    panic(err)
}

mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(paddingText, paddingText)
fmt.Println(base64.StdEncoding.EncodeToString(iv))
fmt.Println(base64.StdEncoding.EncodeToString(paddingText))

// 補滿 16 位
func pad(src []byte, blocksize int) []byte {
    pdSize := blocksize - (len(src) % blocksize)
    padBytes := bytes.Repeat([]byte{0x00}, pdSize)
    src = append(src, padBytes...)
    return src
}

AES CBC decrypt

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
)

func main() {
    key := []byte("1234567890123456")
    iv, _ := base64.StdEncoding.DecodeString("7/JbVL/5cMvqf9sslD6qdQ==")                // iv base64
    ciphertext, _ := base64.StdEncoding.DecodeString("6AFagV3iLEAqbBuSqvL19Q==")        // encrypt base64

    block, _ := aes.NewCipher(key)
    if len(ciphertext) < aes.BlockSize {
        panic("ciphertext too short")
    }
    if len(ciphertext)%aes.BlockSize != 0 {
        panic("ciphertext is not a multiple of the block size")
    }
    aes := cipher.NewCBCDecrypter(block, iv)
    aes.CryptBlocks(ciphertext, ciphertext)
    fmt.Printf("%s\n", ciphertext)
}

UUID (crypto/rand)

// 一般
uuid := make([]byte, 16)
io.ReadFull(rand.Reader, uuid)

// 64 encode
uuid := Gen()
ret := base64.URLEncoding.EncodeToString(uuid)
ret = strings.Replace(ret, "=", "", -1)

import uuid "github.com/satori/go.uuid"
uuid.NewV4()

defer

關於 defer 如何運作

package main

import (
    "fmt"
)

func main() {
    defer Function3()
    Function1()
}

func Function1() {
    fmt.Println("Function1 開始")
    defer Function2()
    fmt.Println("Function1 結束")
}

func Function2() {
    fmt.Println("Function 2")
}

func Function3() {
    fmt.Println("Function 3")
}


執行結果 :

Function1 開始
Function1 結束
Function 2
Function 3

ref : http://ithelp.ithome.com.tw/question/10153473


error

擲出自定義錯誤訊息

err = errors.New("fail")

比對 error

在另一個 package 用全域變數定義

package fruits

var NoMorePumpkins = errors.New("No more pumpkins")

就可以比對了

package shop

if err == fruits.NoMorePumpkins {
     ...
}

runtime

detect OS

switch runtime.GOOS {
    case "windows":
    case "darwin":       //Mac OS
    case "linux":
    default :
}

記憶體使用量

var m0 runtime.MemStats
runtime.ReadMemStats(&m0)
fmt.Printf("Memory: %.2f mb", float64(m0.Sys)/1024/1024)

channel

把 Channel 當成 Queue 使用

它是先進先出,如果要做一個 worker 很好用,直接宣告它的大小是多少,但塞值不需要管它的 index,一直丟就對了

var worker_ch = make(chan string, 10000)

// 用 Goroutine 跑,一個 Worker,它會一直 wait,直到 worker_ch 有值
for {
    file_name := <-worker_ch
    DoSomething(file_name)
}

// 另一個 Goroutine,假設它是 API 負責塞值給 worker_ch
func NonBlcok(ctx *fasthttp.RequestCtx, _ fasthttprouter.Params) {
    file_name := string(ctx.FormValue("filename"))
    worker_ch <- file_name
}

reflect

Call dynamic func by name

func Call(m map[string]interface{}, name string, params ...interface{}) (result []reflect.Value, err error) {
    f := reflect.ValueOf(m[name])
    if len(params) != f.Type().NumIn() {
        err = errors.New("The number of params is not adapted.")
        return
    }
    in := make([]reflect.Value, len(params))
    for k, param := range params {
        in[k] = reflect.ValueOf(param)
    }
    result = f.Call(in)
    return
}

用法 :

// 宣告
funcs := map[string]interface{}{
    "qq": curl.Get,  // index 隨便取, 後面的是 func name  e.g. func QQ() 就填  QQ, 如果是 curl pacakge 的 Get func 就填 curl.Get
}
Call(funcs, "qq", params)  // 如果有參數就傳入 params

Call dynamic struct.function by name (string)

import "fmt"
import "reflect"

type T struct{}

func (t *T) Test() string {
    fmt.Println("test...")
    return "ok"
}

func main() {
    var t T
    res := reflect.ValueOf(&t).MethodByName("Test").Call([]reflect.Value{})
    fmt.Println(res[0]) // ok
}

res 是一個陣列,即使你的 func 只回傳一個參數,它也是放在陣列裡


flag

auto flag

config := struct {
    Name    string        `flag:"queue,queue name"`
}{
    Name:    "",
}
autoflags.Define(&config)
flag.Parse()

image

image/jpg Convert jpg to png

file, err := os.Open("test.jpg")
if err != nil {
    log.Fatal("File error")
}

img, err := jpeg.Decode(file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

out, err := os.Create("test.png")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
err = png.Encode(out, img)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
fmt.Println("Done!")

image/jpg Convert jpg to bmp

// Convert jpg to bmp
img_file, err := os.Open("test.jpg")
if err != nil {
    log.Fatal("File error")
}
defer img_file.Close()

img, err := jpeg.Decode(img_file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
out_file, err := os.Create("test.bmp")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
err = bmp.Encode(out_file, img)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
defer out_file.Close()

image/jpg Convert bmp to jpg

// Convert jpg to bmp
img_file, err := os.Open("test.bmp")
if err != nil {
    log.Fatal("File error")
}
defer img_file.Close()

img, err := bmp.Decode(img_file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
out_file, err := os.Create("test.jpg")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

options := &jpeg.Options{Quality: 50}
err = jpeg.Encode(out_file, img, options)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
defer out_file.Close()

unsafe

看變數佔多數記憶體 (bytes)

a1 := "xxxdflasjdfl;daskjfdsalfkjasdlfjas"
a2 := int64(66666)
a3 := int32(5555)
a4 := float32(2222.4)
fmt.Println(unsafe.Sizeof(a1))  // 16
fmt.Println(unsafe.Sizeof(a2))  // 8
fmt.Println(unsafe.Sizeof(a3))  // 4
fmt.Println(unsafe.Sizeof(a4))  // 4

byte

String 相同, 同長度及 byte 不同 invalid character '\x00' after top-level value

可能是 AES 加解密後發生的情況,可能在過程中被塞入 padding, 導致解出來後的 byte 數量不一樣

加密前 :

2561953d9ec389715498d44b0c150fec
len = 32
[50 53 54 49 57 53 51 100 57 101 99 51 56 57 55 49 53 52 57 56 100 52 52 98 48 99 49 53 48 102 101 99]

加密後 :

len = 48
[50 53 54 49 57 53 51 100 57 101 99 51 56 57 55 49 53 52 57 56 100 52 52 98 48 99 49 53 48 102 101 99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

ASCII 對應

50 => 2
99 => c
0 => 空字元(Null)

解決方法: 將多餘的 \x00 刪除

b = bytes.Trim(b, "\x00")

ref: 類似問題


Comments