#92 gotip 命令

2023-05-30

gotip 是官方推出的,从最新的开发分支下拉代码,编译生成 go 运行时的工具。
如果希望体验最新特性,可以在编译成功之后,可以直接用 gotip 取代 go 命令用来执行 go 程序。
gotip 就可以理解为开发版的 go。

go install golang.org/dl/gotip@latest

gotip
gotip: not downloaded. Run 'gotip download' to install to C:\Users\nosch\sdk\gotip

gotip download
Cloning into 'C:\Users\nosch\sdk\gotip'...
...
Building Go cmd/dist using C:\Program Files\Go. (go1.20.5 windows/amd64)
Building Go toolchain1 using C:\Program Files\Go.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for windows/amd64.

成功的时候:
---
Installed Go for windows/amd64 in C:\Users\nosch\sdk\gotip
Installed commands in C:\Users\nosch\sdk\gotip
Success. You may now run 'gotip'!

失败的时候:
# runtime/cgo
gcc_libinit_windows.c: In function '_cgo_beginthread':
gcc_libinit_windows.c:143:27: error: implicit declaration of function '_beginthread'; did you mean '_cgo_beginthread'? [-Werror=implicit-function-declaration]
  143 |                 thandle = _beginthread(func, 0, arg);
      |                           ^~~~~~~~~~~~
      |                           _cgo_beginthread
cc1: all warnings being treated as errors
go tool dist: FAILED: C:\Users\nosch\sdk\gotip\pkg\tool\windows_amd64\go_bootstrap install std: exit status 1
Success. You may now run 'gotip'!

#90 Golang 实现简单反向代理

2023-04-27

看到微信公众号 Go语言教程 的文章《golang 实现简单网关》才知道还有 httputil.ReverseProxy 这么个东西。
PS:这玩意儿有什么必要放在标准库?

还挺有趣,在作者示例的基础之上完善了一下,实现多服务,多后端节点的一个负载均衡(还可以再补上权重)。

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "math/rand"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
    "time"
)

func main() {
    addr := "127.0.0.1:2002"
    backends := map[string][]string{
        "service1": {"http://127.0.0.1:2003", "http://127.0.0.1:2004"},
    }
    reversePorxy := NewReverseProxy(backends)
    log.Println("Starting httpserver at " + addr)
    log.Fatal(http.ListenAndServe(addr, reversePorxy))
}

func reqConvert(req *http.Request, target *url.URL) {
    req.URL.Scheme = target.Scheme
    req.URL.Host = target.Host
    req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
    if target.RawQuery == "" || req.URL.RawQuery == "" {
        req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
    } else {
        req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
    }
    if _, ok := req.Header["User-Agent"]; !ok {
        req.Header.Set("User-Agent", "")
    }
    req.Header.Set("X-Real-Ip", strings.Split(req.RemoteAddr, ":")[0])
}

func NewReverseProxy(backends map[string][]string) *httputil.ReverseProxy {
    var targets = make(map[string][]*url.URL)
    for srv, nodes := range backends {
        for _, nodeUrl := range nodes {
            target, _ := url.Parse(nodeUrl)
            targets[srv] = append(targets[srv], target)
        }
    }

    director := func(req *http.Request) {
        segments := strings.SplitN(req.URL.Path, "/", 3)
        if len(segments) != 3 {
            return
        }
        srv := segments[1]
        req.URL.Path = segments[2]
        if _, ok := targets[srv]; !ok {
            log.Printf("unknown path: %s", req.URL.Path)
            return
        }
        rand.Seed(time.Now().UnixNano())
        randomIndex := rand.Intn(len(targets[srv]))
        target := targets[srv][randomIndex]
        reqConvert(req, target)
    }

    modifyFunc := func(res *http.Response) error {
        if res.StatusCode != http.StatusOK {
            oldPayLoad, err := ioutil.ReadAll(res.Body)
            if err != nil {
                return err
            }
            newPayLoad := []byte("hello " + string(oldPayLoad))
            res.Body = ioutil.NopCloser(bytes.NewBuffer(newPayLoad))
            res.ContentLength = int64(len(newPayLoad))
            res.Header.Set("Content-Length", fmt.Sprint(len(newPayLoad)))
        }
        return nil
    }
    return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc}
}

func singleJoiningSlash(a, b string) string {
    aslash := strings.HasSuffix(a, "/")
    bslash := strings.HasPrefix(b, "/")
    switch {
    case aslash && bslash:
        return a + b[1:]
    case !aslash && !bslash:
        return a + "/" + b
    }
    return a + b
}

#84 gob:Golang 二进制编码格式(标准库)

2022-10-01
package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    // 编码
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)
    err := enc.Encode(Person{"John", 30})
    if err != nil {
        panic(err)
    }

    // 解码
    dec := gob.NewDecoder(&buf)
    var p Person
    err = dec.Decode(&p)
    if err != nil {
        panic(err)
    }

    fmt.Println(p)
}

#81 Go 谚语

2022-09-13

得知了 Go 谚语这么个东西,搜索一番,找到:

  • https://go-proverbs.github.io/
  • https://github.com/jboursiquot/go-proverbs
  • https://www.youtube.com/watch?v=PAAkCSZUG1c

原来是类似 Python 之禅(import this)一样的东西。

  1. Don't communicate by sharing memory, share memory by communicating. 别通过共享内存通信,要通过通信共享内存。
  2. Concurrency is not parallelism. 并发不是并行。
  3. Channels orchestrate; mutexes serialize.
  4. The bigger the interface, the weaker the abstraction. 接口越大, 抽象越弱。
  5. Make the zero value useful.
  6. interface{} says nothing.
  7. Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 别争论风格问题,不管喜不喜欢,用 gofmt 就是了。
  8. A little copying is better than a little dependency. 少量的复制好过引入新的依赖。
  9. Syscall must always be guarded with build tags.
  10. Cgo must always be guarded with build tags.
  11. Cgo is not Go. 别用 cgo。
  12. With the unsafe package there are no guarantees. 慎用 unsafe 包(安全无保障)
  13. Clear is better than clever. 清晰的代码比聪明的代码好。
  14. Reflection is never clear. 慎用反射(代码不清晰)。
  15. Errors are values.
  16. Don't just check errors, handle them gracefully.
  17. Design the architecture, name the components, document the details.
  18. Documentation is for users. 文档是给用户的(注意代码本身的可读性)。
  19. Don't panic. 不要滥用 panic

#80 Viper: Go 项目配置管理

2022-07-22
  • main.go

    package main
    
    import (
        "fmt"
        "os"
    
        "github.com/spf13/pflag"
        "github.com/spf13/viper"
    )
    
    func main() {
        // 1. 设置 Viper 配置
        viper.SetConfigName("config") // 配置文件名(不带后缀)
        viper.AddConfigPath(".")      // 配置文件路径
        viper.SetConfigType("yaml")   // 配置文件类型
        viper.AutomaticEnv()          // 自动读取环境变量
    
        // 2. 设置命令行参数
        pflag.String("name", "", "project name")
        pflag.String("host", "", "host address")
        pflag.String("port", "", "port number")
        pflag.String("config", "./config.yaml", "config file") // 配置文件参数
        pflag.Parse()
        viper.BindPFlags(pflag.CommandLine) // 将命令行参数绑定到 Viper
    
        // 3. 读取配置文件
        if configFile := viper.GetString("config"); configFile != "" {
            fmt.Println(configFile)
            if err := viper.ReadInConfig(); err != nil {
                fmt.Fprintf(os.Stderr, "读取配置文件失败:%v\n", err)
                os.Exit(1)
            }
        }
    
        // 4. 读取配置项
        projectName := viper.GetString("name")
        port := viper.GetInt("port")
        fmt.Printf("ProjectName: %s, Port: %d\n", projectName, port)
    }
    
  • config.yaml

    name: hello
    host: 10.10.0.172
    port: 9090
    
  • 支持环境变量、命令行参数、配置文件。

  • 支持多种配置文件,包括 JSON,YAML,TOML,INI 等。
  • 支持监控配置文件的变化,会自动加载新的配置。
  • 支持从远程加载配置,比如 etcd、zk、consul、redis 等,
    也可以通过 RemoteProvider 接口自定义远程数据源:

    type RemoteProvider interface {
        Set(key string, value []byte) error
        Watch(key string) (chan *RemoteResponse, chan error)
        Get(key string) ([]byte, error)
    }
    
  • 支持默认值。