#479 GO111MODULE 是什么?

2021-02-04

每一个 Golang 初学者都会遇到 GOPATH 和 Go Module 的问题,至少在最近的一两年是这样的。
简单的说,就是由于 Golang 诞生于谷歌,所以早期的项目工程化受谷歌内部开发流程影响很大。谷歌内部不同项目的代码放在一起,方便相互引用。GOPATH 就是这种代码管理方式的体现,所有的包都放在一个固定的开发目录下。
但是谷歌外面的世界,可能是受开源生态的影响,我们习惯将代码拆分成不同的包,分散在不通的仓库,需要什么包就导入什么包。所以虽然有一些人一直在吹捧 GOPATH 模式,但是大多数人还是喜欢传统的包模式。
所以在 Go Module 之前,官方或者社区也都有出一些解决方案,其中最有名的是 depvender。但随着最终方案 Go Module 的确定,他们已经完成了历史使命,而我最近两个月才开始学 Go,当然就跳过他们了。

#478 Golang HTTP Server

2021-02-01

最简模式

package main

import (
    "fmt"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}

func main() {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":8080", nil)
}

重点是记住这两点:

  1. func hello(w http.ResponseWriter, req *http.Request)
  2. http.HandleFunc 注册一个函数到指定路径上

ListenAndServe 流程分析

https://github.com/golang/go/blob/master/src/net/http/server.go#L3240
https://github.com/golang/go/blob/master/src/net/http/server.go#L2976

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

主要的逻辑:

  1. net.Listen("tcp", addr) -> net.Listener
  2. for 循环:
  3. net.Listener.Accept() -> net.Conn
  4. http.Server.newConn(conn) -> http.Conn
  5. go http.Conn.serve(ctx)
  6. goroutine 中又是一个 for 循环:
  7. http.Conn.readRequest(ctx) -> http.response
  8. serverHandler{c.server}.ServeHTTP(w, w.req)
  9. 最后这个 serverHandler.ServeHTTP 就比较关键了:
    优先采用 http.Server 上的 Handler;
    如果没有,则用默认的 http.DefaultServeMux 实现简单的 URL 路由。
    这也是为什么 ListenAndServe 传了个 nil 进来,srv.Handlernil 就用 DefaultServeMux
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

func (mux *ServeMux) match(path string) (h Handler, pattern string)
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool)
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

DefaultServeMux

// ==================================================================
// 封装 handler,添加到路由表 =========================================
// ==================================================================
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    // https://github.com/golang/go/blob/master/src/net/http/server.go#L2505
    // func (mux *ServeMux) Handle(pattern string, handler Handler)
    // 把 handler 注册到 mux.m (map[string]muxEntry) 上
    // muxEntry{h: handler, pattern: pattern}
    mux.Handle(pattern, HandlerFunc(handler))
}

// 封装普通函数为 Handler
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// ==================================================================
// 路由匹配 =========================================================
// ==================================================================
ServeHTTP -> Handler -> handler -> match
          -> redirectToPathSlash -> shouldRedirectRLocked -> 跳转(http.RedirectHandler)

默认路由规则:

  1. 优先完整匹配
  2. URL 前缀匹配,最长的路由规则优先

另一种方式 (http.Handle)

不用 http.HandleFunc,而是传入一个 Handler(实现 ServeHTTP 方法的结构体)。

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.Handle("/", &HelloHandler{"hello world\n"})

    http.ListenAndServe(":8080", nil)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

http.Handlehttp.HandleFunc 都只是给默认路由(DefaultServeMux)上注册一个规则,知道这一点就可以了。

重点是 mux.HandleHandler 接口。

还可以这样

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.ListenAndServe(":8080", &HelloHandler{"hello world\n"})
}

知道背后的流程就豁然开朗了。

当然,这样也是可以的:

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}
func main() {
    http.ListenAndServe(":8080", http.HandlerFunc(hello))
}

整理总结

  • http.Conn
  • http.Server
  • 到处都是 Hander(接口),路由(mux)也是 Handler
  • func(w http.ResponseWriter, req *http.Request)

实现正则路由

GitHub 上一搜索 golang mux golang router 就有,这是几个可以参考的方案:

  1. gorilla/mux shield.io
  2. julienschmidt/httprouter shield.io
  3. go-chi/chi shield.io
  4. celrenheit/lion shield.io
  5. beego/mux shield.io

我自己实现一个简单的路由机制,参考 Django:

<name> OR <converter:name>

其中:converter 内置支持 str, int, slug, uuid, path, 可以自定义,默认是 str; name 可以是合法的 Go 变量名。


中间件

通过 Handler 套娃实现中间件:

type LogMiddleware struct {
    handler Handler
}
func (this LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    this.handler.ServeHTTP(w, r)
    fmt.Printf("%s %s %v\n", r.Method, r.URL.RequestURI(), time.Since(start))
}
mux := ...
http.ListenAndServe(":8000", LogMiddleware{mux})

系统内置 Handler:

grep -E "func.+ServeHTTP" src/net/http -R | grep -Fv "_test.go"
src/net/http/cgi/host.go:func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/fs.go:func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/httputil/reverseproxy.go:func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/pprof/pprof.go:func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
src/net/http/server.go:func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/server.go:func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (h initALPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/triv.go:func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
src/net/http/triv.go:func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {

有待分析。

优雅重启


参考资料与拓展阅读

#477 Node Socket 编程 EMFILE 错误的一种情况

2021-01-31

本月,听了同事做的一次技术分享,觉得很有意思的问题,我在这里隐去业务细节,只保留技术部分,做个总结归纳,最后用一个业务无关的脚本来模拟一下这个过程。
同事的分享就好比是先逐步实验发现一个现象,然后研究出他背后的原理是什么。我在这里作为事后的归纳总结,就直接冲着背后的原理说了。

#475 转载:Golang 自举

2021-01-30

Google最近公布了实现Go 1.5自举(Bootstrap)的计划。相关文档的作者是Go核心开发者Russ Cox,他在Go语言上已经耕耘了接近6年。据Russ介绍,Google就“如何从Go源码树中去除所有的C程序”已经酝酿了一年。

#474 Python 类型提示(Type Hints)

2021-01-28

Type Hint, 英文直译应该是输入提示。

动态类型语言有一个优点,同时也是缺点:不好做静态类型检查,IDE 或者其他开发工具很难根据代码去准确判断一个变量的类型。
Python 3.0 开始引入并逐渐完善类型注解(Type Annotation)则给 Python 静态类型检查提供了可能性。
PS: Python 运行时会忽略类型注解,不会给任何提示或警告。
PS: 之前的一些工具可以通过注释来做类型检查 (Type Comment),起到相同的作用。

PEP

SF 3107 [2006-12-02] (3.0 ) Function Annotations  开始引入函数注解
SF 3141 [2007-04-23] (    ) A Type Hierarchy for Numbers
SF  424 [2012-07-14] (3.4 ) A method for exposing a length hint
SF  451 [2013-08-08] (3.4 ) A ModuleSpec Type for the Import System
SP  484 [2014-09-29] (3.5 ) Type Hints            类型提示
IF  483 [2014-12-19] (    ) The Theory of Type Hints
IF  482 [2015-01-08] (    ) Literature Overview for Type Hints
SF  526 [2016-08-09] (3.6 ) Syntax for Variable Annotations
SA  544 [2017-03-05] (3.8 ) Protocols: Structural subtyping (static duck typing)
SA  560 [2017-09-03] (3.7 ) Core support for typing module and generic types
SA  563 [2017-09-08] (3.7 ) Postponed Evaluation of Annotations
SA  561 [2017-09-09] (3.7 ) Distributing and Packaging Type Information
SA  585 [2019-03-03] (3.9 ) Type Hinting Generics In Standard Collections
SA  586 [2019-03-14] (3.8 ) Literal Types
SA  591 [2019-03-15] (3.8 ) Adding a final qualifier to typing
SA  589 [2019-03-20] (3.8 ) TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys
SA  593 [2019-04-26] (3.9 ) Flexible function and variable annotations
SA  604 [2019-08-28] (3.10) Allow writing union types as ``X | Y``
SA  613 [2020-01-21] (    ) Explicit Type Aliases     引入类型别名
SA  647 [2020-10-07] (3.10) User-Defined Type Guards  引入 TypeGuard 类型,缩小类型检查时的范围
S   649 [2021-01-11] (    ) Deferred Evaluation Of Annotations Using Descriptors
S   655 [2021-01-30] (3.10) Marking individual TypedDict items as required or potentially-missing

基础用法

如果担心类型检查会,可以使用 @no_type_check 装饰器。

def greeting(name: str) -> str:
    return 'Hello ' + name

Vector = list[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

Union

from typing import NoReturn

Address = tuple[str, int]

def connect(Union[Address, str]) -> NoReturn:
    pass

Optional[T]Union[T, None] 的简写。

def get_argument(name:str, default:Optional[str]=None) -> Union[str, None]:
    pass

其他常用类型

  • Any
  • Callable
  • ClassVar
  • NewType

stub 文件

https://github.com/python/typeshed
Collection of library stubs for Python, with static types

类型检查工具

IDE,比如 PyCharm,可以配置类型检查。
VSCode 或者 Atom 之类的编辑器也可以通过插件支持类型检查。

  1. mypy
  2. pyright
  3. pytype
  4. pyre

参考:

mypy

参考资料与拓展阅读

#472 Python f-string

2021-01-25

以前的字符串格式换方法

1. format 方法格式化

这种方法用的不多(对应 string.Formatter)。

Format String Syntax

replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"

field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
arg_name          ::=  [identifier | digit+]
attribute_name    ::=  identifier
element_index     ::=  digit+ | index_string
index_string      ::=  <any source character except "]"> +

conversion        ::=  "r" | "s" | "a"

format_spec       ::=  [[fill]align][sign][#][0][width][grouping_option][.precision][type]
fill              ::=  <any character>
align             ::=  "<" | ">" | "=" | "^"
sign              ::=  "+" | "-" | " "
width             ::=  digit+
grouping_option   ::=  "_" | ","
precision         ::=  digit+
type              ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
print('{} {}'.format('Hello', 'World'))
print('There are three people in my family: {0}, {1}, and I, and I love my {0} a litte more.'.format('father', 'mother'))

2. 模板字符串格式化

这种方法我只在文档中看到,从没真的用过。
PS: 被废弃的 PEP 215 曾建议采用 $'a = $a, b = $b' 这种语法。

Template strings

from string import Template
Template('$who likes $what').substitute(who='tim', what='kung pao')
Template('$who likes $what').safe_substitute({'who': 'time'})

3. 百分号格式化

这应该是现在的最主流的字符串格式化方式。

printf-style String Formatting

print('Hello %s' % 'World')
print('action %s cost %.3f seconds' % ('download', 0.123456789))
print('%(language)s has %(number)03d quote types.' % {'language': "Python", "number": 2})

Python 3.6 新加入 f-string

Formatted string literals

f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::=  "{" f_expression ["="] ["!" conversion] [":" format_spec] "}"
f_expression      ::=  (conditional_expression | "*" or_expr)
                         ("," conditional_expression | "," "*" or_expr)* [","]
                       | yield_expression
conversion        ::=  "s" | "r" | "a"
format_spec       ::=  (literal_char | NULL | replacement_field)*
literal_char      ::=  <any code point except "{", "}" or NULL>

format 方法格式化语法中复用了很多 (格式化和 conversion 部分), 不过变得更强大了。
主要是里面支持条件语句,表达式 (包括 yield)。

a = 3.1415926
f'{a}'
f'{a:.2f}'

f"{1 + 1}", f"{{1 + 1}}", f"{{{1 + 1}}}"
# ('2', '{1 + 1}', '{2}')

注意:f-sting 里面不能使用反斜杠转义!

f'{John\'s}'
# SyntaxError: f-string expression part cannot include a backslash

r, s, a

  • !r -> repr()
  • !s -> str()
  • !a -> ascii()

PS: ascii 方法是 Python 3 引入,和 repr 相似,但是 ascii 方法仅使用 ASCII 字符。
例如:a = '中国'; print(f'{a!a} {ascii(a)}') 输出 '\u4e2d\u56fd' '\u4e2d\u56fd'

print(f'{a!r}')

=

有人提议加入 !d 表示输出表达式本身,然后加上等于号,加上计算值,例如 f'1 + 1!d' => 1 + 1=2
后来实现成了这样:

a = 3.14
b = 1
print(f'a + b=')   # a + b=4.140000000000001
print(f'a + b = ') # a + b = 4.140000000000001

很有趣!

参考资料与拓展阅读