0%

go1.16 提供了一个新功能 embed,使用 embed 可以在编译的时候将静态文件资源嵌入到程序中。

在代码中使用 //go:embed 指令可以将文件资源映射为 string[]byteembed.FS 类型。

将一个文件嵌入为 string

1
2
3
4
5
import _ "embed"

//go:embed hello.txt
var s string
print(s)

将一个文件嵌入为 []byte

1
2
3
4
5
import _ "embed"

//go:embed hello.txt
var b []byte
print(string(b))

将一个文件嵌入为 embed.FS

1
2
3
4
5
6
import "embed"

//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))

同时嵌入多个静态资源:

1
2
3
4
import "embed"

//go:embed assets/* templates/*
var f embed.FS

入门

安装

1
pip install pipe

examples

1
2
3
4
5
6
7
>>> import * from pipe
>>> sum(range(100) | select(lambda x: x ** 2) | where(lambda x: x < 100))
285
>>> sum([1, 2, 3, 4] | where(lambda x: x % 2 == 0))
6
>>> sum([1, [2, 3], 4] | traverse)
10
Read more »

encoding/json 提供了有关 json 的操作:

  • json.Marshal: 序列化 json
  • json.Unmarshal: 反序列化 json 字符串

基础类型序列化及反序列化

直接使用 json.Marshal 进行序列化,使用 json.Unmarshal 反序列化 json 字符串。 在进行反序列化时需要注意接受数据的变量类型。

map 序列化及反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import (
"encoding/json"
"fmt"
)

func main() {
article := map[string]interface{}{
"article_id": 6931630572720619534,
"user_id": 465848661970824,
"category_id": 6809637769959178254,
"tag_ids": []int{6809640445233070094},
"link_url": "",
"cover_image": "",
"title": "spring中那些让你爱不释手的代码技巧",
"brief_content": "最近越来越多的读者认可我的文章,还是件挺让人高兴的事情。有些读者私信我说希望后面多分享spring方面的文章,这样能够在实际工作中派上用场。正好我对spring源码有过一定的研究,并结合我这几年实际的工作经验,把spring中我认为不错的知识点总结一下,希望对您有所帮助。 实现…",
"is_english": 0,
"is_original": 1,
"content": "",
"ctime": 1613896165,
"mtime": 1625579546,
"rtime": 1613994419,
"view_count": 10374,
"collect_count": 533,
"digg_count": 339,
"comment_count": 22,
"status": 2,
}
str, _ := json.Marshal(article)
fmt.Println(string(str))
var article2 map[string]interface{}
_ = json.Unmarshal(str, &article2)
fmt.Println(article2)
}
Read more »

net/http 提供了 HTTP 客户端及服务器的实现。

Get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import (
"fmt"
"io/ioutil"
"net/http"
)

func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
if resp.StatusCode > 299 {
fmt.Printf("Response failed with status code: %d and\nbody: %s\n", resp.StatusCode, body)
return
}
fmt.Println(string(body))
}
Read more »

在其他语言中可以直接使用 yield 实现生成器的用法,go 中并没有直接实现生成器的用法。 但是我们可以使用 chan + goroutine 的方式实现生成器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func Generator() chan int {
ch := make(chan int)
go func() {
defer close(ch)
for i := 0; i < 100; i++ {
ch <- i
}
}()
return ch
}

func main() {
for i := range Generator() {
fmt.Println(i)
}
}

重要

在 chan 使用完之后记得关闭,否则读取完最后一条数据之后,进程将处于堵塞状态。

Install

  1. 直接使用下面 docker-compose.yaml 启动服务:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    version: '3.7'

    services:
    adguard:
    image: adguard/adguardhome
    container_name: adguard
    restart: always
    labels:
    - traefik.http.routers.adguard.rule=Host(`adguard.domain.com`)
    - traefik.http.routers.adguard.entrypoints=websecure
    - traefik.http.routers.adguard.service=adguard
    - traefik.http.services.adguard.loadbalancer.server.port=3000
    network_mode: host
    volumes:
    - ./config:/opt/adguardhome/conf
  2. 使用浏览器打开页面 https://adguard.domain.com ,根据向导初始化 AdGuard 服务

  3. 修改 config/AdGuardHome.yaml 文件,将 bind_port 修改为 3000,然后重启服务

  4. 再次打开网页 https://adguard.domain.com ,进入 Dashboard 页面

Read more »

什么是 Jamstack?

Jamstack 并非一个具体的技术,而是一个概念。

在了解 Jamstack 之前,我们先了解一下:

  • 网页动静之分
  • 动态内容静态化

网页动静之分

如果网页内容都由 html 静态资源提供,那么 web 是静态的。 否则,如果网页内容是通过查询后端服务,由浏览器实时渲染出来的,那就是动态 web。

静态网页具有以下优点:

  • 对资源占用极少
  • 可缓存,如 CDN 等
  • 易被搜索引擎收录
Read more »

魔法函数 object.missing,官方原文描述如下:

Called by dict.getitem() to implement self[key] for dict subclasses when key is not in the dictionary.

翻译过来大概意思是:当语句 self[key] 中的 key 不在字典中的时候被调用。

通常情况下,我们从 dict 中获取一个不存在的 key 时会抛出 KeyError 异常:

1
2
3
4
5
6
7
>>> d = {}
>>> d['a']
Traceback (most recent call last):
File "/***/code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
KeyError: 'a'

如果不想抛出异常,那么大概率会使用 defaultdict

1
2
3
4
>>> from collections import defaultdict
>>> d = defaultdict(lambda: 'default value')
>>> d['a']
'default value'

使用 help(dict) 可以发现,dict 描述中虽然有许多魔法函数,但是却没有 __missing__。 而使用 help(defaultdict) 查看,可以发现:

1
2
3
4
5
6
7
8
9
class defaultdict(builtins.dict)
| defaultdict(default_factory[, ...]) --> dict with default factory
| ...
| __missing__(...)
| __missing__(key) # Called by __getitem__ for missing key; pseudo-code:
| if self.default_factory is None: raise KeyError((key,))
| self[key] = value = self.default_factory()
| return value
| ...

contextvars 用于管理、存储和访问上下文相关的状态。

在了解 contextvars 之前,我们先了解一下 ThreadLocal。

ThreadLocal

顾名思义,ThreadLocal 就是本地线程变量,它可用来管理当前线程中的变量。 主要有以下几种使用场景:

  1. 跨层传输数据的时候,可以简化参数传递,打破层与层之间的屏障
  2. 隔离线程之间的数据

用法大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import threading
import time

global_data = threading.local()

def func():
if not hasattr(global_data, 'num'):
global_data.num = 0
for _ in range(100):
global_data.num += 1
print(threading.current_thread().getName(), global_data.num)

if __name__ == '__main__':
for _ in range(20):
thread = threading.Thread(target=func)
thread.start()
time.sleep(10)

ThreadLocal 不适合使用的场景:

  1. 协程:一些阻塞的操作,我们经常会使用到协程(如 gevent)进行处理;
  2. 线程池:在 WSGI 中,并不能保证每次都产生一个新的线程来处理请求, 那么上一个请求遗留下来的数据将会污染当前请求。
Read more »

PKCE

PKCE,发音为 pixy,是 Proof Key for Code Exchange 的首字母缩写。 PKCE 流程和标准授权代码流程之间的主要区别是用户不需要提供 client_secret。 PKCE 降低了本地应用程序(Native Application, OAuth客户端的一种)的安全风险, 因为源代码中不需要嵌入 secret,这限制了通过逆向工程的获取 secret 的可能性。

为了代替 client_secret,客户端应用程序创建了一个唯一的字符串值 code_verifier,并其进行哈希处理生成 code_challenge。 当客户端应用程序启动授权码流程的第一部分时,它会发送一个 code_challenge

一旦用户通过身份验证并将授权码返回给客户端应用程序,它就会用授权码请求换取一个 access_token

在此步骤中,客户端应用程序必须在 code_verifier 参数中包含原始唯一字符串值。 如果成功匹配,则身份验证完成并返回 access_token

其工作流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sequenceDiagram
autonumber
participant Webapp
participant Passport
participant Backend

Webapp ->> Webapp: generate and store code_verifier
Webapp ->> +Passport: redirect /login?...<br/>&redirect_uri=webapp<br/>&code_challenge=hash(code_verifier)
Passport ->> Passport: login
Passport -->> -Webapp: ?code=...

Webapp ->> +Passport: /TOKEN?code=...
Passport -->> -Webapp: access_token

Webapp ->> Backend: Authorization: Bearer <access_token>

攻击

攻击者无法篡改流程中的任何信息,但是在很多情况下,请求可能会被截获。 在移动应用程序中,攻击者可以为指定的 URL 注册专门的处理程序, 或者通过后端程序将请求日志记录到一个不安全的地方。 这两种方式,攻击者都能拿到授权码,进而获取到有效的授权令牌。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sequenceDiagram
autonumber
participant Webapp
participant Passport
Actor Attacker
participant Backend

Webapp ->> Webapp: generate and store code_verifier
Webapp ->> +Passport: redirect /login?...<br/>&redirect_uri=webapp<br/>&code_challenge=hash(code_verifier)
Passport ->> Passport: login
Note Over Attacker: intercept redirect
Passport -->> -Attacker: ?code=...

Attacker ->> +Passport: /TOKEN?code=...
Passport -->> -Attacker: access_token

Attacker ->> Backend: Authorization: Bearer <access_token>
Read more »