#833 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)
}

#832 Golang 源码结构

2022-09-16
# tree -L 1 -d .
.
├── api
├── doc
├── lib
├── misc
├── src
└── test

6 directories
# tree -L 1 -d src/
src/
├── archive
├── bufio
├── builtin
├── bytes
├── cmd
├── compress
├── container
├── context
├── crypto
├── database
├── debug
├── embed
├── encoding
├── errors
├── expvar
├── flag
├── fmt
├── go
├── hash
├── html
├── image
├── index
├── internal
├── io
├── log
├── math
├── mime
├── net
├── os
├── path
├── plugin
├── reflect
├── regexp
├── runtime
├── sort
├── strconv
├── strings
├── sync
├── syscall
├── testdata
├── testing
├── text
├── time
├── unicode
├── unsafe
└── vendor

46 directories
tree -L 2 -d src/net/
src/net/
├── http
│   ├── cgi
│   ├── cookiejar
│   ├── fcgi
│   ├── httptest
│   ├── httptrace
│   ├── httputil
│   ├── internal
│   ├── pprof
│   └── testdata
├── internal
│   └── socktest
├── mail
├── rpc
│   └── jsonrpc
├── smtp
├── testdata
├── textproto
└── url

19 directories

#831 Golang UUID

2022-09-13

awesome-go 上面列出来的项目:

  • jakehl/goid  - Generate and Parse RFC4122 compliant V4 UUIDs.
  • twharmon/gouid  - Generate cryptographically secure random string IDs with just one allocation.
  • aidarkhanov/nanoid  - A tiny and efficient Go unique string ID generator.
  • muyo/sno  - Compact, sortable and fast unique IDs with embedded metadata.
  • oklog/ulid  - Go implementation of ULID (Universally Unique Lexicographically Sortable Identifier).
  • uniq - No hassle safe, fast unique identifiers with commands.
  • agext/uuid  - Generate, encode, and decode UUIDs v1 with fast or cryptographic-quality random node identifier.
  • gofrs/uuid  - Implementation of Universally Unique Identifier (UUID). Supports both creation and parsing of UUIDs. Actively maintained fork of satori uuid.
  • google/uuid  - Go package for UUIDs based on RFC 4122 and DCE 1.1: Authentication and Security Services.
  • edwingeng/wuid  - An extremely fast globally unique number generator.
  • rs/xid  - Xid is a globally unique id generator library, ready to be safely used directly in your server code.

我自己又在 GitHub 上搜罗了几个:

package main

import (
    "os/exec"

    guuid "github.com/google/uuid"
    suuid "github.com/satori/go.uuid"
)

func UseGoogle() string {
    id := guuid.New()
    return id.String()
}

func UseSatori() string {
    id := suuid.NewV4()
    return id.String()
}

func UseUuidgen() string {
    id, _ := exec.Command("uuidgen").Output()
    return string(id)
}

func main() {
    fmt.Println("Google UUID:", UseGoogle())
    fmt.Println("Satori UUID:", UseSatori())
    fmt.Println("Uuidgen UUID:", UseUuidgen())
}

#830 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

#829 Protobuf 枚举类型的实现

2022-09-12
enum Gender {
    UNKNOWN = 0;
    MALE = 1;
    FEMALE = 2;
}

转换成 Golang 之后是这个样子:

type Person_Gender int32

const (
    Person_UNKNOWN Person_Gender = 0
    Person_MALE    Person_Gender = 1
    Person_FEMALE  Person_Gender = 2
)

// Enum value maps for Person_Gender.
var (
    Person_Gender_name = map[int32]string{
        0: "UNKNOWN",
        1: "MALE",
        2: "FEMALE",
    }
    Person_Gender_value = map[string]int32{
        "UNKNOWN": 0,
        "MALE":    1,
        "FEMALE":  2,
    }
)

func (x Person_Gender) Enum() *Person_Gender {
    p := new(Person_Gender)
    *p = x
    return p
}

func (x Person_Gender) String() string {
    return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}

func (Person_Gender) Descriptor() protoreflect.EnumDescriptor {
    return file_example_proto_enumTypes[0].Descriptor()
}

func (Person_Gender) Type() protoreflect.EnumType {
    return &file_example_proto_enumTypes[0]
}

func (x Person_Gender) Number() protoreflect.EnumNumber {
    return protoreflect.EnumNumber(x)
}

// Deprecated: Use Person_Gender.Descriptor instead.
func (Person_Gender) EnumDescriptor() ([]byte, []int) {
    return file_example_proto_rawDescGZIP(), []int{0, 0}
}

#828 Protobuf 二进制编码

2022-09-12

相较于文本编码的 JSON

  • 速度快(序列化和反序列化)
  • 数据小(二进制数据流更加紧凑)
  • 数据类型丰富(enum,map...)
  • 可读性差

数据类型

Protobuf 类型 描述 Go 对应类型
int32 有符号 32 位整数,采用可变长度编码,适合较小的整数 int32
int64 有符号 64 位整数,采用可变长度编码 int64
uint32 无符号 32 位整数,采用可变长度编码 uint32
uint64 无符号 64 位整数,采用可变长度编码 uint64
sint32 有符号 32 位整数,采用 ZigZag 编码,适合负数较多的情况 int32
sint64 有符号 64 位整数,采用 ZigZag 编码 int64
fixed32 无符号 32 位整数,固定 4 字节编码,对于大整数比 uint32 更高效 uint32
fixed64 无符号 64 位整数,固定 8 字节编码 uint64
sfixed32 有符号 32 位整数,固定 4 字节编码 int32
sfixed64 有符号 64 位整数,固定 8 字节编码 int64
float 单精度 32 位浮点数 float32
double 双精度 64 位浮点数 float64
bool 布尔值,取值为 truefalse bool
string 包含 UTF - 8 编码或 7 位 ASCII 文本的字符串,长度不能超过 2^32 string
bytes 任意的字节序列,长度不能超过 2^32 []byte
message 自定义的数据结构,由多个字段组成,可以嵌套定义 struct
enum 用于定义一组命名的常量值 Go 枚举实现
repeated 在字段前加上 repeated 关键字表示该字段是一个数组 Go 数组
map<KeyType, ValueType> 从 proto3 开始支持的映射类型,用于表示键值对 map
  1. 枚举类型,有另一篇文章可供参考:Protobuf 枚举类型的实现

  2. 数组(Repeated)示例:

    message Person {
        repeated string hobbies = 5;
    }
    
  3. 映射(Map)示例:

    message Person {
        map<string, string> contacts = 6;
    }
    

    这里 contacts 是一个键为字符串、值也为字符串的映射。

基本流程

  1. 定义数据结构(.proto 文件)
  2. 使用编译器生成目标编程语言代码
  3. 在程序中引入生成的代码

  4. Protobuf 编译器: protoc,作用是生成目标语言的数据定义代码
    生成的代码
    需要到 GitHub 下载。

  5. Protobuf 运行时,作用是序列化和反序列化的基础库

    Lang Repo
    C++ https://github.com/protocolbuffers/protobuf/blob/main/src
    Java https://github.com/protocolbuffers/protobuf/blob/main/java
    Python https://github.com/protocolbuffers/protobuf/blob/main/python
    Golang https://github.com/protocolbuffers/protobuf-go

示例

  1. proto 文件

    syntax = "proto3";
    
    package example;
    
    message Person {
        enum Gender {
            UNKNOWN = 0;
            MALE = 1;
            FEMALE = 2;
        }
        string name = 1;
        int32 age = 2;
        Gender gender = 3;
    }
    
  2. 编译:

    $ protoc --go_out=. --go_opt=Mexample.proto=./generated example.proto
    $ find .
    .
    ./example.proto
    ./generated
    ./generated/example.pb.go
    
  3. 调用 proto 生成的 Go 代码

    package main
    
    import (
        "fmt"
        "log"
    
        pb "example/generated" // 导入生成的包
    
        "google.golang.org/protobuf/proto"
    )
    
    func main() {
        // 创建一个 Person 实例
        person := &pb.Person{
            Name:   "Alice",
            Age:    30,
            Gender: pb.Person_FEMALE,
        }
    
        // 序列化
        data, err := proto.Marshal(person)
        if err != nil {
            log.Fatalf("Failed to marshal: %v", err)
        }
    
        // 反序列化
        newPerson := &pb.Person{}
        err = proto.Unmarshal(data, newPerson)
        if err != nil {
            log.Fatalf("Failed to unmarshal: %v", err)
        }
    
        fmt.Printf("Deserialized person: %+v\n", newPerson)
    }
    
    $ go mod init example
    $ go mod tidy
    
    $ go build -o main.exe .
    $ ./main.exe
    Deserialized person: name:"Alice" age:30 gender:FEMALE
    

#827 HSTS: HTTP Strict Transport Security

2022-09-04

HSTS 全称 HTTP Strict Transport Security,中文就是 HTTP 严格传输安全

HSTS 的作用是通过特定的 HTTP 响应头告知浏览器,未来访问该网站时必须使用 HTTPS 连接,确保通信的安全性。这可以避免用户通过 HTTP 访问时的自动重定向,从而减少延迟。

对于同时支持 HTTP 和 HTTPS 的站点,HSTS 强制客户端始终使用 HTTPS,防止潜在的中间人攻击。主流浏览器都已全面支持 HSTS。

HSTS 于 2012 年成为互联网标准建议(RFC 6797),并逐渐被广泛采用。

背景

SSL 剥离攻击(SSLStrip),HTTP 降级攻击

受益于一整套相对可靠的公钥基础设施(PKI),直接的 HTTPS 通信是无法中间人攻击的。
中间人的伪造证书会被校验出来。

但是部分网站都是配置 HTTP 和 HTTPS 地址共存。

工作原理

通过 Strict-Transport-Security 响应头(下面称之为 HSTS 头),告诉客户端应该采用 HTTPS 连接。
客户端以后再访问这个网站就会自动重定向到 HTTPS(即使指定了 HTTP 协议)。

语法

Strict-Transport-Security: max-age=<expire-time>
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains; preload
  • max-age 作用时长(秒),表示在这么长的时间之内都应该直接请求 HTTPS 协议。
  • includeSubDomains 对所有子域名生效
  • preload 添加到 HSTS 预加载列表需要的
    • 不是标准的一部分,但主流浏览器都支持

例如:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

表示两年之内,当前域名以及所有子域名,都通过 HTTPS 访问。

预加载列表

要求原文:

  1. Serve a valid certificate.
  2. Redirect from HTTP to HTTPS on the same host, if you are listening on port 80.
  3. Serve all subdomains over HTTPS.
    1. In particular, you must support HTTPS for the www subdomain if a DNS record for that subdomain exists.
  4. Serve an HSTS header on the base domain for HTTPS requests:
    1. The max-age must be at least 31536000 seconds (1 year).
    2. The includeSubDomains directive must be specified.
    3. The preload directive must be specified.
    4. If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to).

问题

  1. 第一次访问不受 HSTS 保护
  2. 如果网站想回退到 HTTP,可能会受阻

Nginx HSTS 配置

  1. 80 rewrite 到 https

    server {
        listen 80;
        server_name example.com;
        # rewrite ^(.*)$ https://$host$1 permanent;
        return 301 https://$server_name$request_uri;
    }
    
  2. HSTS 头

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    

PKP:Public Key Pinning

和 HSTS 类似,服务器告诉浏览器网站的证书指纹,浏览器缓存起来。
后面的每次访问都会计算证书指纹,如果和之前缓存的哈希不匹配,则向用户发出警告,存在中间人攻击风险。

计算证书指纹的方法:

  1. SPKI
  2. 哈希,对整个证书计算哈希值,更加安全

和 HSTS 类似的是工作方式,目的(提升安全性),不同的是手段,HSTS 是为了确保采用安全的协议,PKP 是确保证书不被伪造。

参考资料与拓展阅读

#826 转载:USB 的前世今生

2022-08-30

在人类的历史长河中,很少有一种技术或者传输标准能像USB那样跟我们的生活息息相关,甚至到了没有不行的地步。
USB对于今天的人们来说,就好像是空气,是水,是我们每天必需但是又熟视无睹的东西,没有多少人知道它从哪来,也没多少人关心它要往哪去,对于大多数人来说,它平凡得不能再平凡了。
但是,在我们“电子攻城狮”的眼里,它太有趣了,它是目前使用率最高的接口,它是我们身边林林总总电子设备之间的高速公路。
因此我们必须关注它,如果有必要,我们还不得不去了解如何才能实现它。作为一个USB开发者(电子爱好者),接下来我会为大家揭开USB神秘的面纱,带大家去了解USB是怎么出现并且逐渐演化的,以及在它出现后给我们带来了什么。

#825 关于 Linux 发行版

2022-08-28

发行版

  • Minix
  • Unix 阵营
  • 闭源
    • HP-UX
    • Solaris
    • AIX
    • macOS
  • 开源
    • FreeBSD
    • NetBSD
    • OpenBSD
    • DragonFly BSD
  • Linux 阵营
  • Android (AOSP)
    • LineageOS
  • Debian
    • Ubuntu
  • RedHat
    • CentOS
  • OpenSUSE
  • Gentoo
  • Arch Linux
    • Manjaro

Debian / Ubuntu 和 RHEL / CentOS 衍生版本太多,就不列了。
我们常见的 Linux 发行版应该差不多都能找到自己的位置。

桌面环境

图形库主要就是两种:GTK,Qt

  • 基于 GTK
  • GNOME
  • Xfce
  • Cinnamon
  • MATE
  • LXDE
  • Unity:ubuntu
  • Budgie
  • 基于 Qt
  • KDE Plasma
  • LXQt
  • Deepin Desktop Environment (DDE)
  • UKUI:ubunty kylin

个人看法

  1. 在国际关系越来越趋于对抗的这个局面下,Linux 对于中俄等国都有战略安全的重要作用。
  2. 国产 Linux 系统厂商大有搞头。
  3. 关于国产系统:
    1. 国产 Linux 桌面只有 Deepin 可以一战,而且做了太多工作,应该得到尊敬。
    2. 国产服务器系统我一个都没有用过,不知道如何。
  4. Unix 系统的生态和 Linux 完全不是同一个级别。
  5. 除非有特别的原因,否则应该直接选择 Linux。
  6. Deepin 等国产 Linux 系统提供商想搞什么国产根系统,想切断和社区的关联,在我看来是非常愚蠢的。
  7. 反而应该加大对社区的投入。
  8. 如果不知道该选哪个 Linux 发行版,我的建议是 Ubuntu。
  9. 无论是服务器还是个人使用,都可以直接选择上一个版本 Ubuntu LTS
  10. 如果比较喜欢尝鲜,个人使用可以选最新版本 Ubuntu
  11. 如果对 Ubuntu snap 不满意,就选择 Debian。
  12. 如果问选哪个桌面环境,就选 GNOME
  13. KDE 我没有用过,不知道
  14. 选 GNOME 作为默认桌面的多,应该是有道理的
  15. 桌面并没有那么重要,方便快捷的命令行操作才是 Linux 精髓
  16. 如果对发行版不满,就在基准系统上通过增删组件达成自己的目的,不要觉得换一个系统就能完美解决问题。
  17. 如果有时间折腾不同的发行版,还不如去玩一会儿游戏。

#824 HTTP/2 的几个特性

2022-08-20
  • Server Push 服务器主动推送资源,客户端请求 A 的时候,服务器把 A 相关的资源 B, C, D 都一起推送给客户端。
  • Early Hints HTTP 103 状态码。客户端请求 A 的时候,

    1. 如果是普通情况,服务器返回 200 状态码,带上资源信息
    2. 如果应用上 Early Hints,服务器返回 103 状态码,带上需要资源信息(Link 头),然后是资源信息

    好处就是节省了浏览器解析 HTML 获取子资源信息的延迟。刚解析 103 头,就可以开始请求子资源了。
    就最近的几个月,主流浏览器开始提供支持。

  • Preload Critical Assets 就是 HTML link 头中的 rel="preload",提前加载文件,避免按照文件解析生成的调用链顺序来加载,而提升资源加载速度。

Chrome 将禁用 HTTP/2 服务器推送(Server Push)支持

谷歌博客显示,在 Chrome 106 和其他基于 Chromium 的浏览器的下个版本中,默认情况下将禁用对 HTTP/2 服务器推送(HTTP/2 Server Push)的支持。

HTTP/2 允许服务器在实际请求之前 “推送” 服务端可能需要的资源, HTTP/2 的 Server Push 特性解决了 HTTP/1.x 的无脑按顺序加载资源的问题,本意是提高网页的响应性能。

然而这功能逻辑本身就有问题,比如资源存放在单个业务服务器上,并行推送多个静态资源只会降低响应速度,性能不升反降。而对于前后端分离的业务来说,HTTP/2 本身就支持多路复用,server push 只能稍微降低浏览器解析 html 的时间,对现代浏览器来说性能提升可以忽略不计。

HTTP/2 时代也只有 1.25% 的 HTTP/2 站点使用了这个特性。在 HTTP/3 出来之后,该功能更是彻底被遗忘了,最新的分析中,网站对 HTTP/2 的支持率从 1.25% 下降到 0.7%。

替代方案

103 Early Hints  是 Server Push 的首选替代方案,它具有 Push 的许多优点,而缺点则少得多。与服务器推送资源不同,103 Early Hints 只向浏览器发送可能受益于请求的资源提示。浏览器可以控制它是否需要这些资源,比如浏览器已经在 HTTP 缓存中拥有这些资源,则无需额外加载。

预加载关键资源是另一种选择,它允许页面和浏览器一起工作,在页面加载的早期抢先加载关键资源。它不如 Server Push  或 Early Hints   快 —— 但它不会延迟关键页面资源,而另外两种解决方案都可能发生这种情况。

参考资料与拓展阅读