Skip to content

deepkh/Golang-Notes

Repository files navigation

Index


Golang playground

-> Golang Playground


References


  • 布林值(真或假):

    • bool
  • 字串:

    • string
  • 整數(Integer)

    • 有號數
      • int int8 int16 int32 int64
    • 無號數
      • uint uint8 uint16 uint32 uint64 uintptr
  • 符點數(float)

    • float32 (含小數點 7 位)
       package main
       import "fmt"
       func main() {
       	var f1 float32 = 0.1234567123 * 10;
       	var f2 float32 = 0.1234567123;
       	fmt.Printf("%v\n", f1)
       	fmt.Printf("%v\n", f2)
       }
    • float64 (含小數點 15 位)
       package main
       import "fmt"
       func main() {
       	var f1 float64 = 0.1234567890123456789 * 10;
       	var f2 float64 = 0.1234567890123456789;
       	fmt.Printf("%v\n", f1)
       	fmt.Printf("%v\n", f2)
       }
  • byte // alias for uint8

  • rune // alias for int32, represents a Unicode code point




package main
import "fmt"

type Point struct {
	x float64
	y float64
}

func PassSliceByReference(slice  []Point) {
	fmt.Printf("%v %v\n", len(slice ), slice);
}

func PassSliceWithPointerByReference(slice []*Point) {
	fmt.Printf("%v %v\n", len(slice ), slice);
}

func PassArrayByPoint(parray *[1]Point) {
	fmt.Printf("%v %v\n", len(parray), parray);
}

func main() {
	slice := []Point {
		{1, 2}, {3,4},
	};
	slice = append(slice, Point{1,2});
	//slice[1:2] is meaning i=1 && i < 2
	slice = append(slice, slice[1:2]...);
	PassSliceByReference(slice);
	
	slice_with_pointer := []*Point {
		{1, 2},
	};
	slice_with_pointer[0] = &Point{3,4};
	PassSliceWithPointerByReference(slice_with_pointer );

	//Array need to specified the length of entity 
	array := [1]Point {
		{1, 2},
	};
	PassArrayByPoint(&array);
}


  • 字串 key 與字串 value 的映射
     package main
     import "fmt"
    
     //https://stackoverflow.com/a/31064737/11474144
     func MapInit() map[string]string {
     	fmt.Printf("\nMapInit\n");
    
     	//case 1
     	// Initializes a map with space for 15 items before reallocation
     	// the first 15 items added to the map will not require any map resizing
     	m1 := make(map[string]string, 15);
     	fmt.Printf("len:%d %v\n", len(m1), m1);
    
     	//case 2
     	// Initializes a map with an entry relating the name "bob" to the string "bbb"
     	m2 := map[string]string{"bob": "bbb"};
     	fmt.Printf("len:%d %v\n", len(m2), m2);
    
     	//case 3
     	// Initializes a map with three entry
     	m3 := map[string]string {
     		"aaa": "AAA",
     		"bbb": "BBB",
     		"ccc": "CCC",
     	};
     	fmt.Printf("len:%d %v\n", len(m3), m3);
     	return m3;
     }
    
     func MapIterate(m map[string]string) {
     	fmt.Printf("\nMapIterate\n");
     	for key,value := range m {
     		fmt.Printf("key:%v value:%v\n", key, value);
     	}
     }
    
     func MapRemoveByKey(m map[string]string, key string) {
     	fmt.Printf("\nMapRemoveByKey %v\n", key);
     	delete(m, key);
     }
    
     func MapInsertKeyValuePair(m map[string]string, key string, value string) {
     	fmt.Printf("\nMapInsertKeyValuePair %v %v\n", key, value);
     	m[key] = value;
     }
    
     func MapGetValueByKey(m map[string]string, key string) {
     	fmt.Printf("\nMapGetValueByKey %v\n", key);
     	value, ok := m[key];
     	if ok {
     		fmt.Printf("the value is %v\n", value);
     	} else {
     		fmt.Printf("the key is not found %v\n", key);
     	}
     }
    
     func main() {
     	m := MapInit();
     	MapIterate(m);
    
     	MapRemoveByKey(m, "bbb");
     	MapIterate(m);
    
     	MapInsertKeyValuePair(m, "ddd", "DDD");
     	MapIterate(m);
    
     	MapGetValueByKey(m, "aaa");
     	MapGetValueByKey(m, "aaA");
     }

package main
import "fmt"

type Point struct {
	x float64
	y float64
}

func pass_by_pointer(p *Point, new_x float64) {
	p.x = new_x;
}

func pass_by_value(p Point, new_x float64) {
	p.x = new_x;
}

func main() {
	var p *Point = &Point{x: 1.0, y: 2.0};
	pass_by_pointer(p, 12.0);
	fmt.Printf("%v\n", *p); //{12 2}

	var p2 Point = Point{x: 1.0, y: 2.0};
	pass_by_value(p2, 12.0);
	fmt.Printf("%v\n", p2); //{1 2}
}

package main
import "fmt"

//代入一個值與回傳一個值
func test1(n1 int) int {
	return n1;
}

//代入二個值與回傳二個值
func test2(n1 int, n2 int) (int, int) {
	return n1, n2;
}

func main() {
	//宣告變數但不指定型別
	var n1, n2 = test2(1, 2);
	fmt.Printf("a: %v %v\n", n1, n2);

	//https://stackoverflow.com/questions/53404305/when-to-use-var-or-in-go/53404332
	//宣告變數與指定型別
	var n3 int;
	fmt.Printf("b: %v\n", n3);

	n3 = test1(3);
	fmt.Printf("c: %v\n", n3);

	// := 不使用 var 宣告
	n4 := 123;
	n4 = test1(4);
	fmt.Printf("d: %v\n", n4);

	// 省略第一個回傳值
	_, n5 := test2(4, 5);
	fmt.Printf("e: %v\n", n5);
}

  • 物件最大的意途是抽象化與封裝,將重覆的程序、業務邏輯與附加邏輯隱藏在物件裡讓其他的物件可以重覆呼叫與使用。與函數不同點在於物件裡有成員變數(member variable),並且物件有繼承可以延伸父類別的行為、介面提供附加邏輯的具體實作。
  • 但 Golang 並沒有提供繼承,只有介面。其原因為繼承會增加耦合的成本,無法在動態時期改變具現實例。
package main
import "fmt"

type PointX struct {
	x float64
}

func (p *PointX) X() float64 {
	return p.x;
}

func (p *PointX) SetX(x float64) {
	p.x = x;
}

type PointY struct {
	y float64
};

func (p *PointY) Y() float64 {
	return p.y;
}

func (p *PointY) SetY(y float64) {
	p.y = y;
}

//embbeded struct of PointX and PointY
type PointXYZ struct {
	PointX
	PointY
	z float64
};

//override PointX's SetX function
func (p *PointXYZ) SetX(x float64) {
	p.PointX.x = x * x;
}

func (p *PointXYZ) Z() float64 {
	return p.z;
}

func (p *PointXYZ) SetZ(z float64) {
	p.z = z;
}

func main() {
	pxy := &PointXYZ{};
	pxy.SetX(3.0);
	pxy.SetY(6.0);
	pxy.SetZ(9.0);
	fmt.Printf("%v %v %v\n", pxy.X(), pxy.Y(), pxy.Z());
}

  • 業務邏輯可以透過介面將附加邏輯給隔開,而實現達到開放封閉原則。最好的例子就是外掛(Plugin-In)。譬如 Foobar2000 音樂播放器的業務邏輯為
    • 檔案讀取模組 (Source Reader) -> 解碼模組 (Decoder) -> 放音模組 (Audio Playback)
  • 業務邏輯的主要流程盡量不會變更,變更的為上述三個附加邏輯的模組,當模組化後面對需求,對程式碼的改動是透過增加新程式碼進行的,而不是更改現有的程式碼。檔案讀取模組可以是"檔案讀取"或"串流讀取"或未來的新模組,解碼模組可以是"MP3解碼模組"或"AAC解碼模組或未來的新模組。
  • 模組式的設計就是透過介面提供一致性的接口讓業務邏輯去使用。
package main
import "fmt"

type Reader interface {
	Read(buf *Buf) error
}

type Decoder interface {
		Decode(buf *Buf) error;
}

type Playback interface {
		Playback(buf *Buf) error;
}

type Buf struct {
	in_buf []byte;
	in_len int;
	out_buf []byte;
	out_len int;
}

func NewBuf(in_alloc_len int, out_alloc_len int) *Buf {
	return &Buf{
		make([]byte, in_alloc_len),
		0,
		make([]byte, out_alloc_len),
		0,
	};
}

func (p *Buf) InBuf() []byte {
	return p.in_buf;
}

func (p *Buf) SetInLen(n int) {
	p.in_len = n;
}

func (p *Buf) InLen() int {
	return p.in_len;
}

func (p *Buf) OutBuf() []byte {
	return p.out_buf;
}		

func (p *Buf) OutLen() int {
	return p.out_len;
}

func (p *Buf) SetOutLen(n int) {
	p.out_len = n;
}

func ReverseBuf(buf *Buf) {
	n := buf.InLen();
		for i := 0; i < n/2; i++ {
				buf.OutBuf()[i] = buf.InBuf()[n-1-i];
		buf.OutBuf()[n-1-i] = buf.InBuf()[i];
	}
	buf.OutBuf()[n/2] = buf.InBuf()[n/2];
	buf.SetOutLen(buf.InLen());
}

type FileReader struct {

}

func NewFileReader() *FileReader {
	return &FileReader{};
}

func (p *FileReader) Read(buf *Buf) error {
	for i:=0; i< len(buf.InBuf()); i++ {
		buf.InBuf()[i] = 'A' + byte(i);
	}
	buf.SetInLen(len(buf.InBuf()));
	return nil;
}


type Mp3Decoder struct {

}

func NewMp3Decoder() *Mp3Decoder {
	return &Mp3Decoder{};
}

func (p *Mp3Decoder) Info() string {
	return "128kbps,16bit,2ch";
}

func (p *Mp3Decoder) Decode(buf *Buf) error {
	ReverseBuf(buf);
	return nil;
}

type DirectSoundPlayback struct {

}

func NewDirectSoundPlayback() *DirectSoundPlayback {
	return &DirectSoundPlayback{};
}

func (p *DirectSoundPlayback) Playback(buf *Buf) error {
	fmt.Printf("Read from reader %v %v\n", buf.InBuf(), string(buf.InBuf()));
	fmt.Printf("Decode from decoder %v %v\n", buf.OutBuf(), string(buf.OutBuf()));
	return nil;
}


func main() {
	var buf *Buf = NewBuf(26, 26);

	var reader Reader = NewFileReader();
	var decoder Decoder = NewMp3Decoder();
	var playback Playback = NewDirectSoundPlayback();

	reader.Read(buf);
	decoder.Decode(buf);
	playback.Playback(buf);
}

interface{} variable

  • interface{} 與 golang interface 並不是同一件事, interface{} 類似於 C 語言裡的 void * 變數,用於承接任何指標類型的實例。
package main
import "fmt"

type A struct {

}

func (p *A) Name() string {
	return "A";
}

type B struct {

}

func (p *B) Name() string {
	return "B";
}

type C struct {

}

func (p *C) Name() string {
	return "C";
}

func main() {
	interface_map :=  map[string]interface{} {
		"A": &A{},
		"B": &B{},
		"C": &C{},
	};

	//casting with error code
	vlaue_a, ok_a:= interface_map["A"];
	if ok_a {
		a, oka_2 := vlaue_a.(*A);
		if oka_2 {
			fmt.Printf("%v\n", a.Name());
		}
	}

	//casting without check error code
	value_b := interface_map["B"];
	if value_b != nil {
		b := value_b.(*B);
		if b != nil {
			fmt.Printf("%v\n", b.Name());
		}
	}

	//use .(type) to check which class instance is
	switch interface_map["C"].(type) {
		case *C:
			c := interface_map["C"].(*C);
			fmt.Printf("%v\n", c.Name());
			break;
	}
}

Functional Programming

  • 閉包 (Closure)
package main
import "fmt"
 
func main() {
	n := 1;
	f := func() int {
		n += 1;
		return n;
	};
	fmt.Printf("%v\n", f());
}
  • Callback function: 類似 C 的 function pointer
package main
import "fmt"

func DoAlsaAudioCapture(pcm_callback func([]float64) error) {
	pcm_data := make([]float64, 64);
	pcm_callback(pcm_data);
	
	pcm_data2 := make([]float64, 128);
	pcm_callback(pcm_data2);
	
	pcm_data3 := make([]float64, 256);
	pcm_callback(pcm_data3);
}
 
func main() {
	pcm_callback := func(pcm_data []float64) error {
		fmt.Printf("Receving number %v of audio samples\n", len(pcm_data));
		return nil;
	};
	DoAlsaAudioCapture(pcm_callback);
}

Json Marshal/Unmarshal

  • 當成員有了 json tag 那麼第一個字元必須大寫代表 export
package main
import (
	"fmt"
	"encoding/json"
)

type ServerConfig struct {
	ListenPort int		`json:"listen_port"`
	EnableSsl bool		`json:"enable_ssl"`
}

func main() {
	json_bytes := []byte(`
{
	"listen_port": 443,
	"enable_ssl": false
}
`);
	server_config := &ServerConfig { };
	json.Unmarshal(json_bytes, &server_config);
	fmt.Printf("%v\n", server_config);
	
	json_bytes, _ = json.Marshal(server_config);
	fmt.Printf("%v\n", string(json_bytes));
	
	json_bytes, _ = json.MarshalIndent(server_config, "", "\t")
	fmt.Printf("%v\n", string(json_bytes));
}

  • 客製化 json marshal/unmarshal 的輸入/輸出欄位
package main
import (
	"fmt"
	"encoding/json"
)

type ServerConfig struct {
	ListenPort int		`json:"listen_port"`
	EnableSsl bool		`json:"enable_ssl"`
}

func (p *ServerConfig) MarshalJSON() ([]byte, error)  {
	return json.Marshal(*p);
}

func (p *ServerConfig) UnmarshalJson(data []byte) error {
	return json.Unmarshal(data, p);
}
 
func main() {
	json_bytes := []byte(`
{
	"listen_port": 443,
	"enable_ssl": false
}
`);
	server_config := &ServerConfig { };
	server_config.UnmarshalJson(json_bytes);
	fmt.Printf("%v\n", server_config);
	
	json_bytes, _ = server_config.MarshalJSON();
	fmt.Printf("%v\n", string(json_bytes));
}


  • Language Guide (proto3)
  • Protocol Buffers 主要應用在不同程式語言的 RPC 上(當然 PB 也是可以拿來取代 JSON),並透過描述檔 *.proto 描述傳送方與接收方的資料結構,再透過 protoc code generator 去產生對映的 *.pb.go*.pb.h*.pb.cc,如此就不需再人工
    • 定義 golang 的資料結構
    • 撰寫 golang 的 unmarshal/marshal
    • 定義 C++ 的資料結構
    • 撰寫 C++ 的 unmarshal/marshal
  • Golang 需安裝 protoc 與 protoc-gen-go
  • C++ 則需安裝 protoc 與 libprotobuf(libprotobuf.so)
  • 上述工具在 source source.sh && make 後都會自動安裝




gRPC tutorial for C++

  • Quick Start
  • Basic tutorial
  • gRPC C++ 1.34.0 doxygen
  • grpc build 完自動也會將 protoc binarys 也一起 build 完,而不論要產生哪種語言的 protobuf code gen 或 grpc code gen,則都需要編譯 host 版的 c++ grpc binarys。他們都是以 grpc_cpp_plugin、grpc_python_plugin、protoc-gen-go、protoc-gen-go-grpc ... 外掛型式去丟給 protoc。
  • visual c++ 則可以從 vcpkg 去直接撈,而 mingw-w64 與 GCC 則可參考我的腳本 libex/grpc/grpc_v1.34.0,需注意下,grpc 會先編譯 host 的 c++ grpc binarys 才會再編譯 runtime 的 grpc library,而 cmake 版本則需要 cmake-3.16.1 含以上,建議在 container 或 chroot 或 systemd-nspawn 的容器環境編譯。

gRPC tutorial for Go


gRPCHelloWorld C++/Go Example


gRPCFtp C++/Go Example

  • grpcftp.proto
  • grpcftpclient_main.cc
  • grpcftpserver_main.cc
  • grpcftpclient_main.go
  • grpcftpserver_main.go
  • grpcftp.proto 實作了 grpc 的四種交互中的其中三種。第四種 request 與 response 都為 stream 目前尚未實現。
    1. Hello: Request 與 Response 都非 stream 模式
    2. List 與 Pull: Request 非 stream 模式 而 Reponse 為 stream 模式
      1. List: 列出 client 所指定的 server 資料夾的檔案
      2. Pull: 從 server 拉一個檔案,client 會下載檔案到 pull/
    3. Push: Request 為 stream 模式而 Reponse 為非 stream 模式
      1. Push: 推一個檔案到 server,server 會儲存檔案到 push/

gin web backend framework

HTTP 後端快速開發框架。可用閉包快速開發業務邏輯外,另還提供 router、middleware、group(可依 path 前綴字來分版本,如/v1/api、/v2/api)、form binding、query binding、server side rendering。

Example


resty http client library

透過閉包來快速開發 http 客戶端(原作者提及由 Ruby rest-client 啟發)

Example


Swagger Codegen

透過 OpenAPI 的定義檔(.json, .yaml) 去產生 API Docs與 Server Stub Code, Client SDK Code, HTML API Docs。

References

swagger-codegen cli install

# Download current stable 3.x.x branch (OpenAPI version 3)
wget https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.24/swagger-codegen-cli-3.0.24.jar -O swagger-codegen-cli.jar

java -jar swagger-codegen-cli.jar --help

Gen code for golang server

${SWAGGERCODEGENCLI_BIN} generate -l go-server -i openapi.yaml -o out_dir

Gen code for golang client

${SWAGGERCODEGENCLI_BIN} generate -l go -i openapi.yaml -o out_dir

Gen code for HTML2 docs

${SWAGGERCODEGENCLI_BIN} generate -l html2 -i openapi.yaml -o out_dir

Examples

下列 server, client, HTML 都由 openapi_3.0_test.yaml 定義檔並透過上面三串指令去產生。

心得:

  • OpenAPI 定義檔可透過 Swagger Editor 在線編輯後後再儲存到 openapi_3.0_test.yaml
  • 如果 openapi_3.0_test.yaml 更新後並重新執行指令,它會選擇覆蓋源碼而不是聰明的保留修改的部份
  • Server
    • 從 webserver, route, services routine 從上到下都串好了, 僅剩 services routine留下空函式
    • Code 產生的還可以接受
    • 沒有 go.mod,需手動適度修改
    • 原生框架與 gorilla/mux router,如果已使用其它 原生框架習慣了,則會需要適應原生框架

      Most Go web "frameworks" are nonidiomatic and should probably be avoided for codegen purposes, including Gin. I would recommend plain net/http handlers + selected pieces from https://github.com/gorilla as necessary; probably just https://github.com/gorilla/mux for routing is enough. from peterbourgon commented on 26 May 2016

  • Client
    • 產生的是 SDK
    • 沒有 main.go,需手動適度修改
    • 沒有 go.mod,需手動適度修改
    • 原生框架
    • Code 產生的很大一包

Go Swagger

Go swagger 可完成下列三個意圖,與 swagger-codegen 相比多了一個從已有的 code 產生 swagger spec 2.0 yaml。但由於 Generate webserver code 所產生的 Code 太過纍贅,所以稍微閱讀後就沒有再深入理解。

  • Generate webserver code from swagger spec 2.0: 通過 swagger spec 2.0 生成 webserver code
  • Generate client code from swagger spec 2.0: 通過 swagger spec 2.0 生成 web client code
  • Generate spec from source code's comments: 通過源碼生成 swagger spec 2.0

Installation

wget https://github.com/go-swagger/go-swagger/releases/download/v0.26.1/swagger_linux_amd64 -O /usr/local/swagger && chmod +x /usr/local/swagger

Generate webserver code from swagger spec 2.0

Cons

  • Server Usage 框架複雜,需深入理解框架後才有辦法開發業務邏輯
  • 業務邏輯的實現需在 configure_xxx.go,但 configure_xxx.go 裡還包含其他不相關的設定,不符合開放封閉原則不利於長期維護
  • A swagger golang hello world 則太舊
  • 目前僅支援 swagger spec 2.0 ,swagger-codegen-cli 則支援 swagger spec 2.0OpenAPI 3.0

Pros

  • 從 Code 註解產生 swagger spec 2.0 則不用遷就於 go-swagger 的 generate code 架構

References


Go Kit

gokit 的 http transport 實作是透過 kit.http.NewServer 實作 http.handler 並加入客製化的:

kit.http.NewServer 初始化完後,再丟進原生 http.Handler,與 Gorilla Mux 一樣都是再包一層自己的 http.Handler ,如此就可讓實作 http.Handler 套件互相串在一起,受限於框架的層度較少。

Example: StringSvc1

以 /count 計算字數服務來說明

  1. 初始化 /count 的 httptransport.NewServer (http.Handler). 並帶入 makeCountEndpoint(svc)decodeCountRequestencodeResponse
  2. 編譯 main.go 並執行 ./main
  3. 透過 curl -X POST -H "Content-Type: application/json" -d '{"S":"[email protected]"}' http://localhost:8080/count 驗證
  4. 執行流程如下
    1. decodeCountRequest
    2. makeCountEndpoint 裡的閉包 (Closure)
    3. 最後是 encodeResponse 輸出
  svc := stringService{}

  countHandler := httptransport.NewServer(
    makeCountEndpoint(svc),
    decodeCountRequest,
    encodeResponse,
  )
 
  http.Handle("/count", countHandler)
  log.Fatal(http.ListenAndServe(":8080", nil))
func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
  var request countRequest
  fmt.Printf("1. decodeCountRequest\n")
  if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
    return nil, err
  }
  return request, nil
}
// Endpoints are a primary abstraction in go-kit. An endpoint represents a single RPC (method in our service interface)
func makeCountEndpoint(svc StringService) endpoint.Endpoint {
  return func(_ context.Context, request interface{}) (interface{}, error) {
    fmt.Printf("2. makeCountEndpoint\n")
    req := request.(countRequest)
    v := svc.Count(req.S)
    return &countResponse{v}, nil
  }
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
  v1, ok1 := response.(*uppercaseResponse)
  v2, ok2 := response.(*countResponse)
  fmt.Printf("3. encodeResponse v1:%+v ok1=%v v2:%+v ok2=%v\n", v1, ok1, v2, ok2)
  return json.NewEncoder(w).Encode(response)
}

gokit code generator

GrantZheng/kit

目前僅剩 GrantZheng/kit . 在 2020 年底尚有更新。可用下列命令去產生 code。

kit n s hello               # Create new service 
kit g s hello              
kit g s hello -t grpc
kit g d                     # Create Docker file

gokit issues

  1. kitgen 目前大多已沒更新
  2. kitgen 產生的 Code 龐大與 swagger-codegen-cli 相比並不好理解
  3. 需要同時支持 HTTP, GRPC ... 不同 Transport 的情況會是哪些?
  4. kitgen 透過定義 service.go 檔來產生 server stubs code 是不錯,但沒有 OpenAPI 3.0 來的標準. eg: 譬如沒有 GET/PUT/POST/DELETE 方法

gin-swagger

透過在源碼定義註解而產生 Swagger 2.0 Spec。

About

learning golang by examples

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published