#523 Gin 框架自学: 基础

2021-05-06

先列一下目前最火的几个 Go 语言框架:

  • gin
    stars num forks num License
    stars
  • beego 国人开发
    stars num forks num License
    stars
  • iris The fastest HTTP/2 Go Web Framework.
    自称是 ExpressJS 和 Laravel 的继承者。
    stars num forks num License
  • echo High performance, minimalist Go web framework
    stars num forks num License
  • go-micro Microservice framework for Go
    stars num forks num License
    stars
  • fiber
    stars num forks num License
  • kratos A Go framework for microservices. B 站出品
    stars num forks num License
  • revel/revel
    stars num forks num License
  • go-zero 国人开发(七牛)
    stars num forks num License
    stars
  • martini Classy web framework for Go
    stars num forks num License
  • GoFrame Classy web framework for Go 国人开发
    stars num forks num License

Gin

http://gin-gonic.com

Gin LOGO

Gin 相比 Beego 非常轻量级,不带 ORM,不支持 Session,正则路由都不支持,需要自行实现。

最新版本 56 个 文件, 算上注释,大概七千行代码。

$ find . -name "*.go" -not -path "*_test.go" | cat | wc -l
56
$ find . -name "*.go" -not -path "*_test.go" | xargs cat | wc -l
6910
Tag CreatedAt ReleasedAt
v1.9.0 2023-02-21 2023-02-21
v1.8.2 2022-12-22 2022-12-22
v1.8.1 2022-06-06 2022-06-06
v1.8.0 2022-05-30 2022-05-30
v1.7.7 2021-11-24 2021-11-24
v1.7.6 2021-08-03 2021-11-23
v1.7.4 2021-08-03 2021-08-15
v1.7.3 2021-08-03 2021-08-03
v1.7.2 2021-05-21 2021-05-21
v1.7.1 2021-04-08 2021-04-08
v1.7.0 2021-04-08 2021-04-08
v1.6.3 2020-05-03 2020-05-03
v1.6.2 2020-03-27 2020-03-27
v1.6.1 2020-03-23 2020-03-23
v1.6.0 2020-03-22 2020-03-22
v1.5.0 2019-11-24 2019-11-28
v1.4.0 2019-05-07 2019-05-08
v1.3.0 2018-08-14 2018-08-14
v1.2 2017-07-02 2017-07-02
v1.1.4 2016-12-04 2016-12-05
v1.1.3 2016-12-03 2016-12-04

HTTP 参数

// c: *gin.Context
// func (c *gin.Context) {
//     balabala...
// }

c.Request.URL.Query()

c.Param(key string) string // 路径参数
c.Params.ByName(key)

c.Query(key string) string // 查询参数
c.Request.URL.Query().Get(key)
c.GetQuery(key)

c.QueryMap(key string) map[string][]string
c.GetQueryMap(key)

c.QueryArray(key string) []string

c.GetQuery(key string) (string, bool)
c.GetQueryArray(key) 的第 0 个元素

c.GetQueryMap(key string) (map[string]string, bool)
c.GetQueryArray(key string) (values []string, ok bool)
c.PostForm(key string) string
c.PostFormMap(key string) map[string][]string
c.PostFormArray(key string) []string
c.GetPostForm(key string) (string, bool)
c.GetPostFormMap(key string) (map[string]string, bool)
c.GetPostFormArray(key string) (values []string, ok bool)
c.FormFile(key string) (*multipart.FileHeader, error)
c.DefaultQuery(key string, defaultValue string) string
c.DefaultPostForm(key string, defaultValue string) string

快速启动

package main

import (
    "io"
    "net/http"
    "os"
    "path/filepath"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 处理 GET 请求中的 URL 参数
    r.GET("/hello", func(c *gin.Context) {
        name := c.Query("name")
        c.String(http.StatusOK, "Hello %s!", name)
    })

    // 处理带路径参数的 GET 请求
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.String(http.StatusOK, "User ID: %s", id)
    })

    // 处理 POST 请求中的 JSON 请求体
    r.POST("/data", func(c *gin.Context) {
        var json struct {
            Data string `json:"data"`
        }
        c.BindJSON(&json)
        c.JSON(http.StatusOK, gin.H{"message": "Received data: " + json.Data})
    })

    // 处理文件上传
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.String(http.StatusBadRequest, "File upload failed: %s", err.Error())
            return
        }

        // 将上传的文件保存到服务器上
        dst := filepath.Join("./uploads", file.Filename)
        if err := c.SaveUploadedFile(file, dst); err != nil {
            c.String(http.StatusInternalServerError, "File save failed: %s", err.Error())
            return
        }

        c.String(http.StatusOK, "File uploaded successfully")
    })

    // 处理返回文件
    r.GET("/download", func(c *gin.Context) {
        filename := "example.txt"
        filepath := "./downloads/" + filename

        // 打开文件
        file, err := os.Open(filepath)
        if err != nil {
            c.String(http.StatusInternalServerError, "Failed to open file: %s", err.Error())
            return
        }
        defer file.Close()

        // 设置响应头
        c.Header("Content-Disposition", "attachment; filename="+filename)
        c.Header("Content-Type", "application/octet-stream")
        c.Header("Content-Transfer-Encoding", "binary")

        // 将文件内容写入响应主体
        _, err = io.Copy(c.Writer, file)
        if err != nil {
            c.String(http.StatusInternalServerError, "Failed to write file: %s", err.Error())
        }
    })

    r.Run(":8080")
}

路由

设置路由不存在的处理方法:

r.NoRoute(func(c *gin.Context) {
    c.JSON(404, gin.H{"message": "page not found"})
})

限制请求大小:

r.MaxMultipartMemory = 10 << 20 // 10MB
// "github.com/gin-contrib/size"
r.Use(size.Check(size.MB * 10))
r.Use(requestSizeLimitMiddleware(10 << 20))
func requestSizeLimitMiddleware(maxSize int64) gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.ContentLength > maxSize {
            c.AbortWithStatusJSON(413, gin.H{"message": "request entity too large"})
            return
        }
        c.Next()
    }
}

#522 nmap 基本用法

2021-05-01

检查端口可连接性,并支持检测操作系统信息、占用端口的常见应用程序。

  • 开放(Open)
  • 关闭(Closed)
  • 过滤(Filtered)
  • 未过滤(Unfiltered)
  • 开放或过滤(Open|Filtered)
  • 关闭或过滤(Closed|Filtered)

  • 默认扫描常用端口

常用命令

nmap 192.168.31.0/24

# Ping扫描
nmap -sn 192.168.31.0/24
# 快速端口扫描
nmap -F  192.168.31.0/24

# 操作系统
sudo nmap -O -v 192.168.31.42
sudo nmap -A -v 192.168.31.42  # 输出更详细

# 常规检查:端口(-sV)和操作系统(-O)
sudo nmap -sV -O 192.168.31.42

# 检查指定端口
nmap -p 22 192.168.31.42
nmap -p 22,80,443,3306 192.168.31.42
# 所有端口
nmap -p 1-65525 192.168.31.42
nmap -p-        192.168.31.42
# 加上使用这个端口的服务版本信息
nmap -p 22 192.168.31.42 -sV

# 对端口进行探测
nmap -PS 192.168.31.42  # SYN
nmap -PA 192.168.31.42  # ASK
nmap -PU 192.168.31.42  # UDP

sudo nmap -sS 192.168.1.1-10   # SYN
sudo nmap -sA 192.168.31.0/24  # ACK
sudo nmap -sF 192.168.31.0/24  # FIN
sudo nmap -sN 192.168.31.0/24  # TCP Null
sudo nmap -sT 192.168.31.0/24  # TCP Connect
sudo nmap -sU 192.168.31.0/24  # UDP
sudo nmap -sO 192.168.31.0/24  # IP 协议扫描

nmap -O  192.168.0.1/16
nmap -sP 192.168.0.1/16

nmap -sSU 192.168.1.1/24
# Ping Scan
# Parallel DNS resolution
# SYN Stealth Scan
# UDP Scan

参考资料与拓展阅读

#521 Golang 自动过期 Map

2021-04-26
type ExpiringMap struct {
    m      sync.Map
    expiry time.Duration
}

func (e *ExpiringMap) set(key, value interface{}) {
    e.m.Store(key, value)

    go func() {
        time.Sleep(e.expiry)
        e.m.Delete(key)
    }()
}

改进:定时清理

package main

import (
    "sync"
    "time"
)

type Item struct {
    value      interface{}
    expiration int64
}

type ExpiringMap struct {
    m       sync.Mutex
    items   map[string]Item
    timeout time.Duration
}

func NewExpiringMap(timeout time.Duration) *ExpiringMap {
    em := &ExpiringMap{
        items:   make(map[string]Item),
        timeout: timeout,
    }
    go em.cleanup()
    return em
}

func (em *ExpiringMap) Set(key string, value interface{}) {
    em.m.Lock()
    defer em.m.Unlock()
    em.items[key] = Item{
        value:      value,
        expiration: time.Now().Add(em.timeout).UnixNano(),
    }
}

func (em *ExpiringMap) Get(key string) (interface{}, bool) {
    em.m.Lock()
    defer em.m.Unlock()
    item, found := em.items[key]
    if !found {
        return nil, false
    }
    if time.Now().UnixNano() > item.expiration {
        return nil, false
    }
    return item.value, true
}

func (em *ExpiringMap) Delete(key string) {
    em.m.Lock()
    defer em.m.Unlock()
    delete(em.items, key)
}

func (em *ExpiringMap) deleteExpiredKeys() {
    em.m.Lock()
    defer em.m.Unlock()
    now := time.Now().UnixNano()
    for key, item := range em.items {
        if now < item.expiration {
            break
        }
        delete(em.items, key)
    }
}

func (em *ExpiringMap) cleanup() {
    for {
        time.Sleep(em.timeout) // 阻塞协程
        em.deleteExpiredKeys()
    }
}

func main() {
    em := NewExpiringMap(time.Second * 10)

    time.Sleep(time.Second * 15)

    _, ok := em.Get("key1")
    if !ok {
        println("key1 is deleted")
    }

    _, ok = em.Get("key2")
    if !ok {
        println("key2 is deleted")
    }
}

#520 fasthttp: 十倍速

2021-04-26

https://github.com/valyala/fasthttp

fasthttp

package main

import (
    "flag"
    "fmt"
    "log"

    "github.com/valyala/fasthttp"
)

var (
    addr     = flag.String("addr", ":8080", "TCP address to listen to")
    compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
)

func main() {
    flag.Parse()

    h := requestHandler
    if *compress {
        h = fasthttp.CompressHandler(h)
    }

    if err := fasthttp.ListenAndServe(*addr, h); err != nil {
        log.Fatalf("Error in ListenAndServe: %v", err)
    }
}

func requestHandler(ctx *fasthttp.RequestCtx) {
    fmt.Fprintf(ctx, "Hello, world!\n\n")

    fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method())
    fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI())
    fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
    fmt.Fprintf(ctx, "Host is %q\n", ctx.Host())
    fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs())
    fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent())
    fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime())
    fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time())
    fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum())
    fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP())

    fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request)

    ctx.SetContentType("text/plain; charset=utf8")

    // Set arbitrary headers
    ctx.Response.Header.Set("X-My-Header", "my-header-value")

    // Set cookies
    var c fasthttp.Cookie
    c.SetKey("cookie-name")
    c.SetValue("cookie-value")
    ctx.Response.Header.SetCookie(&c)
}
  • r.Body -> ctx.PostBody()
  • r.URL.Path -> ctx.Path()
  • r.URL -> ctx.URI()
  • r.Method -> ctx.Method()
  • r.Header -> ctx.Request.Header
  • r.Header.Get() -> ctx.Request.Header.Peek()
  • r.Host -> ctx.Host()
  • r.Form -> ctx.QueryArgs() + ctx.PostArgs()
  • r.PostForm -> ctx.PostArgs()
  • r.FormValue() -> ctx.FormValue()
  • r.FormFile() -> ctx.FormFile()
  • r.MultipartForm -> ctx.MultipartForm()
  • r.RemoteAddr -> ctx.RemoteAddr()
  • r.RequestURI -> ctx.RequestURI()
  • r.TLS -> ctx.IsTLS()
  • r.Cookie() -> ctx.Request.Header.Cookie()
  • r.Referer() -> ctx.Referer()
  • r.UserAgent() -> ctx.UserAgent()
  • w.Header() -> ctx.Response.Header
  • w.Header().Set() -> ctx.Response.Header.Set()
  • w.Header().Set("Content-Type") -> ctx.SetContentType()
  • w.Header().Set("Set-Cookie") -> ctx.Response.Header.SetCookie()
  • w.Write() -> ctx.Write(), ctx.SetBody(), ctx.SetBodyStream(), ctx.SetBodyStreamWriter()
  • w.WriteHeader() -> ctx.SetStatusCode()
  • w.(http.Hijacker).Hijack() -> ctx.Hijack()
  • http.Error() -> ctx.Error()
  • http.FileServer() -> fasthttp.FSHandler(), fasthttp.FS
  • http.ServeFile() -> fasthttp.ServeFile()
  • http.Redirect() -> ctx.Redirect()
  • http.NotFound() -> ctx.NotFound()
  • http.StripPrefix() -> fasthttp.PathRewriteFunc

fasthttp/router

  • /user/{user} named
  • /user/{user}_admin
  • /user/{user?} optional
  • /user/{name:[a-zA-Z]{5}} regex
  • /user/{name?:[a-zA-Z]{5}}
  • /user/{name:*} cache all

fasthttp/session

package main

import (
    "log"
    "time"

    "github.com/fasthttp/session/v2"
    "github.com/fasthttp/session/v2/providers/redis"
)

var serverSession *session.Session

func init() {
    var provider session.Provider
    var err error
    encoder := session.MSGPEncode
    decoder := session.MSGPDecode
    provider, err = redis.New(redis.Config{
        KeyPrefix:   "session",
        Addr:        "127.0.0.1:6379",
        PoolSize:    8,
        IdleTimeout: 30 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    cfg := session.NewDefaultConfig()
    cfg.EncodeFunc = encoder
    cfg.DecodeFunc = decoder
    serverSession = session.New(cfg)
    if err = serverSession.SetProvider(provider); err != nil {
        log.Fatal(err)
    }
}
// 获取 SessionStore
store, err := serverSession.Get(ctx)
if err != nil {
    ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    return
}
defer func() {
    if err := serverSession.Save(ctx, store); err != nil {
        ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    }
}()

// Set
store.Set("foo", "bar")

// Get
val := store.Get("foo")
if val == nil {
    // ...
}

store.Delete(key)

store.Set(bytesKey)
val := store.GetBytes(bytesKey)
store.DeleteBytes(bytesKey)

data := store.GetAll()  // Dict
data := store.Ptr()     // *Dict

store.Flush()
store.Reset()

// Session ID,默认 sessionid
id := store.GetSessionID()
store.SetSessionID(bytesID)

// Session 有效期
changed := store.HasExpirationChanged()
duration := store.GetExpiration()
err := store.SetExpiration(duration)

savsgio/atreugo

构建与 fasthttp 和 fasthttp/router 之上的 HTTP 框架,做了一些简单的封装工作。
提供了几个示例

基础用法:

package main

import (
    "github.com/savsgio/atreugo/v11"
)

func main() {
    config := atreugo.Config{
        Addr: "0.0.0.0:8000",
    }
    server := atreugo.New(config)

    server.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello World")
    })

    server.GET("/echo/{path:*}", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Echo message: " + ctx.UserValue("path").(string))
    })

    v1 := server.NewGroupPath("/v1")
    v1.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello V1 Group")
    })

    if err := server.ListenAndServe(); err != nil {
        panic(err)
    }
}

#519 Linux 工具箱 2:convert (图像处理)

2021-04-24

关于 convert 的文章,之前已经写过两篇:

安装 ImageMagick

sudo apt install imagemagick

convert -list type
convert -list font # 支持的字体

获取图片信息

identify markjour.png
identify -verbose markjour.png
identify -format "Size: %w x %h\n" markjour.png

# Exif 信息
identify -format '%[Exif:*]' ~/Pictures/Photos/2019-09-14-18-48-22.jpg

# sudo apt install exif
exif ~/Pictures/Photos/2019-09-14-18-48-22.jpg

清除所有 Exif 信息

convert -strip input.jpg out.jpg
convert +profile "*" input.jpg out.jpg

图片切割

<宽> x <高> + <X轴坐标> + <Y轴坐标>

  1. 如果没有指定坐标,那就是切割图片。
  2. 宽高可以是百分比(不能混用,只要一个百分号那就都是比例)。
convert -crop 300x400+10+10 src.jpg dest.jpg

# 指定中心位址为基准点:
convert -gravity center -crop 300x400+0+0 src.jpg dest.jpg

convert -crop 25%x100% src.jpg dest.jpg

图片合并

之前(convert 图片转换的一次示例)合并图片用的就是这个命令。

# 横向
convert +append markjour-l.jpg markjour-c.jpg markjour-r.jpg markjour.jpg
# 纵向
convert -append markjour-t.jpg markjour-c.jpg markjour-b.jpg markjour.jpg

图片旋转

convert -rotate 90 input.jpg output.jpg  # 顺时针
convert -rotate -90 input.jpg output.jpg # 逆时针

# 左右反转,镜像效果
convert -flop input.jpg output.jpg

# 上下反转,这个和旋转 270 效果还是不一样的
convert -flip input.jpg output.jpg

图片缩放

# 限宽我很常用,控制页面图片尺寸
convert -resize 108x markjour.png markjour-108.png

convert -sample 50%  markjour.png markjour-new.png
convert -sample 200% markjour.png markjour-big.png

PS: 放大时 resize 会自动采样插值,而 sample 不会

图片压缩

convert input.jpg -quality 50 output.jpg

颜色

# 灰度,就是常见的黑白照片效果
convert -colorspace gray input.jpg output.jpg

# 分离 RGB 三个通道,输出三张图片,不知道为什么都是灰色
convert -separate input.png output.png

convert -threshold 40% input.png output.png # 也是一种常见效果,不知道叫什么
convert -negate input.png output.png # 反色

# 黑白(非灰度)sRGB -> Gray 2c
convert -monochrome input.png output.png

# 重新转成 sRGB 模式(但是颜色还是留在黑白两色)
convert -define png:color-type=2 input.png output.png
convert -colorspace sRGB -type truecolor input.jpg output.jpg

# 效果很奇特,可以试试:
convert -remap pattern:gray60 input.png output.png

# 替换
convert -fuzz 15% -fill white -opaque "rgb(143,141,250)" -opaque "rgb(216,217,62)" input.png output.png

滤镜

convert -blur 70 input.png output.png
# 后面的数字对模糊程度有着决定性作用
convert -blur 70x15 input.png output.png

convert -charcoal 2  input.png output.png # 铅笔画风格
convert -noise    3  input.png output.png
convert -paint    4  input.png output.png # 油画风格
convert -spread   50 input.png output.png # 毛玻璃
convert -swirl    60 input.png output.png # 扭曲

convert -paint 4 -raise 5x5 input.png output.png

# 调整透明度
# 先确保图片有 Alpha 通道
convert -define png:format=png32 input.png output.png
convert -channel alpha -fx "0.5" output.png output2.png

边框

# 加边框
convert -mattecolor "#000" -frame 60x60 input.png output.png
convert -mattecolor "#fff" -frame 60x60 input.png output.png

# 相同效果
convert -bordercolor "#fff" -border 60x60 input.png output.png

# 再配合上 raise:
convert -bordercolor "#fff" -border 10x10 input.png output.png
convert -raise 5x5 output.png output2.png

# 去掉边框:
convert -trim -fuzz 10% input.png output.png

水印

convert -fill "#1770CC" \
-font Ubuntu-Mono -pointsize 50 -draw 'text 130,50 "©"' \
-font 楷体_GB2312 -pointsize 40 -draw 'text 50,50 "码厩"' \
-gravity southeast \
input.png output.png

# 改用 RGBA 模式
convert -fill "rgba(23,112,204,0.25)" \
-font Ubuntu-Mono -pointsize 50 -draw 'text 130,50 "©"' \
-font 楷体_GB2312 -pointsize 40 -draw 'text 50,50 "码厩"' \
-gravity southeast \
input.png output.png

# 这个不错,京东那里学来的:
convert -size 100x100 -fill "#1770CC" -gravity center \
-font Ubuntu -pointsize 30 -draw 'rotate -45 text 0,0 "markjour"' \
xc:none miff:- | composite -tile -dissolve 25 - input.png output.png

# 图片水印
convert -size 150x50 -fill "#1770CC" -gravity center \
-font Ubuntu -pointsize 30 -draw 'text 0,0 "markjour"' \
xc:none /tmp/mark.png
convert input.png -gravity southeast -compose over /tmp/mark.png -composite output.png

其他

# 查看图片
# GNOME 桌面好像都是 eog
eog markjour.png

# 或者使用 ImageMagick 自带图片查看工具:
display markjour.png

# 查看颜色信息
convert xc:"#fff" -colorspace sRGB -format "%[pixel:u.p{0,0}]\n" txt:
convert xc:"#fff" -colorspace sRGB -format "%[pixel:u.p{0,0}]\n" info:

convert xc:"#fff" -colorspace sRGB -format "rgb(%[fx:int(255*r)],%[fx:int(255*g)],%[fx:int(255*b)])\n" info:

# 获取指定像素点的颜色(RGB)
convert "input.png[1x1+100+100]" -format "rgb(%[fx:int(255*r)],%[fx:int(255*g)],%[fx:int(255*b)])\n" info:

# 创建一张新图片
convert -size 1000x1000 xc:none /tmp/image.png
convert -size 1000x1000 xc:transparent /tmp/image.png
convert -size 1000x1000 xc:white /tmp/image.png

webp

sudo apt install -y webp
  • cwebp 转成 WEBP 格式
  • dwebp 转成别的格式
cwebp -o xxx.png xxx.webp
dwebp -o xxx.webp xxx.png

参考资料与拓展阅读

#517 正则表达式

2021-04-14
  1. https://zh.wikipedia.org/wiki/正则表达式
  2. 2021/12/19,正则表达式历史

正则表达式, Regular Expression, 简写: regex, regexp, 正则。
一般用来以指定模式对字符串进行查找,替换,切割。

基本知识

Linux 下, grep, find 等命令支持几种风格:

find -regextype help
# find: 未知的正则表达式类型 ‘help’;合法的类型是 ‘findutils-default’, ‘ed’, ‘emacs’, ‘gnu-awk’, ‘grep’, ‘posix-awk’, ‘awk’, ‘posix-basic’, ‘posix-egrep’, ‘egrep’, ‘posix-extended’, ‘posix-minimal-basic’, ‘sed’。
man grep | grep -E '\-regexp$'
#        -E, --extended-regexp
#        -G, --basic-regexp
#        -P, --perl-regexp
#        -w, --word-regexp
#        -x, --line-regexp

因为历史原因,大体上可以分为:

  • 基本型正则表达式(Basic Regular Expression,BRE)
  • 扩展型正则表达式(Extended Regular Express,ERE)
  • PCRE(Perl兼容正则表达式,Perl Compatible Regular Expressions)

我见过的编程语言中都是采用最为强大的 PCRE 风格:

  • PHP 是 pgeg_xxx 系列方法。
  • Python 是 re 包。
  • Go 是 regexp 包。
  • C 直接使用 regex.h (libc)
  • C 老版本也是 regex.h,或者使用第三方库(如 boost), C 11 之后就内置正则支持了(std::regex, 默认 ECMAScript 风格)

语法

特殊字符

| Characters / constructs | Corresponding article |
| :------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | ----------------- |
| \ , . , \cX , \d , \D , \f , \n , \r , \s , \S , \t , \v ,
\w , \W , \0 , \xhh , \uhhhh , \uhhhhh , [\b] | Character classes |
| ^ , $ , x(?=y) , x(?!y) , (?<=y)x , (?<!y)x , \b , \B | Assertions |
| (x) , (?:x) , (?<Name>x) , x | y,[xyz],[^xyz],\Number | Groups and ranges |
| * , + , ? , x{n} , x{n,} , x{n,m} | Quantifiers |
| \p{UnicodeProperty} , \P{UnicodeProperty} | Unicode property escapes |

范围

  • .
  • \w , \d , \s , \W , \D , \S
  • [abc] [^abc] [a-z]

分组

  • (abc)
  • \1
  • (?:abc)
  • (?=abc)
  • (?!abc)

量词

  • a* , a+ , a?
  • a{7,9} , a{7} , a{7,}
  • a+? , a{2,}?

其他

  • ^ , $
  • ab|cd or
  • \b , \B word

修饰符

JavaScript:

  • g global
  • i case insensitive
  • m multiline
  • s single line (dotall)
  • u unicode
  • y sticky

Go:

  • i
  • m
  • s
  • U

Python:

  • re.A, re.ASCII
  • re.I, re.IGNORECASE
  • re.M, re.MULTILINE
  • re.S, re.DOTALL
  • re.DEBUG
  • re.X, re.VERBOSE

参考资料与拓展阅读

#516 PHP 版本

2021-04-12
{
  "4.0": {
    "4.0.0": "2000-05-22",
    "4.0.1": "2000-06-28",
    "4.0.2": "2000-08-29",
    "4.0.3": "2000-10-11",
    "4.0.4": "2000-12-19",
    "4.0.5": "2001-04-30",
    "4.0.6": "2001-06-23"
  },
  "4.1": {
    "4.1.0": "2001-12-10",
    "4.1.1": "2001-12-26",
    "4.1.2": "2002-02-27"
  },
  "4.2": {
    "4.2.0": "2002-04-22",
    "4.2.1": "2002-05-13",
    "4.2.2": "2002-07-22",
    "4.2.3": "2002-09-06"
  },
  "4.3": {
    "4.3.0": "2002-12-27",
    "4.3.1": "2003-02-17",
    "4.3.2": "2003-05-29",
    "4.3.3": "2003-08-25",
    "4.3.4": "2003-11-03",
    "4.3.5": "2004-03-26",
    "4.3.6": "2004-04-15",
    "4.3.7": "2004-06-03",
    "4.3.8": "2004-07-13",
    "4.3.9": "2004-09-22",
    "4.3.10": "2004-12-14",
    "4.3.11": "2005-03-31"
  },
  "4.4": {
    "4.4.0": "2005-07-11",
    "4.4.1": "2005-10-31",
    "4.4.2": "2006-01-11",
    "4.4.3": "2006-08-03",
    "4.4.4": "2006-08-17",
    "4.4.5": "2007-02-14",
    "4.4.6": "2007-03-01",
    "4.4.7": "2007-05-03",
    "4.4.8": "2008-01-03",
    "4.4.9": "2008-08-07"
  },
  "5.0": {
    "5.0.0": "2004-07-13",
    "5.0.1": "2004-08-12",
    "5.0.2": "2004-09-23",
    "5.0.3": "2004-12-15",
    "5.0.4": "2005-03-31",
    "5.0.5": "2005-09-05"
  },
  "5.1": {
    "5.1.0": "2005-11-24",
    "5.1.1": "2005-11-28",
    "5.1.2": "2006-01-12",
    "5.1.3": "2006-05-02",
    "5.1.4": "2006-05-04",
    "5.1.5": "2006-08-17",
    "5.1.6": "2006-08-24"
  },
  "5.2": {
    "5.2.0": "2006-11-02",
    "5.2.1": "2007-02-08",
    "5.2.2": "2007-05-03",
    "5.2.3": "2007-05-31",
    "5.2.4": "2007-08-30",
    "5.2.5": "2007-11-08",
    "5.2.6": "2008-05-01",
    "5.2.7": "2008-12-04",
    "5.2.8": "2008-12-08",
    "5.2.9": "2009-02-26",
    "5.2.10": "2009-06-18",
    "5.2.11": "2009-09-16",
    "5.2.12": "2009-12-17",
    "5.2.13": "2010-02-25",
    "5.2.14": "2010-07-22",
    "5.2.15": "2010-12-08",
    "5.2.16": "2010-12-16",
    "5.2.17": "2011-01-06"
  },
  "5.3": {
    "5.3.0": "2009-06-30",
    "5.3.1": "2009-11-19",
    "5.3.2": "2010-03-04",
    "5.3.3": "2010-07-22",
    "5.3.4": "2010-12-09",
    "5.3.5": "2011-01-06",
    "5.3.6": "2011-03-17",
    "5.3.7": "2011-08-18",
    "5.3.8": "2011-08-23",
    "5.3.9": "2012-01-10",
    "5.3.10": "2012-02-02",
    "5.3.11": "2012-04-26",
    "5.3.12": "2012-05-03",
    "5.3.13": "2012-05-08",
    "5.3.14": "2012-06-06",
    "5.3.15": "2012-07-19",
    "5.3.16": "2012-08-16",
    "5.3.17": "2012-09-13",
    "5.3.18": "2012-10-18",
    "5.3.19": "2012-11-22",
    "5.3.20": "2012-12-20",
    "5.3.21": "2013-01-17",
    "5.3.22": "2013-02-21",
    "5.3.23": "2013-03-14",
    "5.3.24": "2013-04-11",
    "5.3.25": "2013-05-09",
    "5.3.26": "2013-06-06",
    "5.3.27": "2013-07-11",
    "5.3.28": "2013-12-12",
    "5.3.29": "2014-08-14"
  },
  "5.4": {
    "5.4.0": "2012-03-01",
    "5.4.1": "2012-04-26",
    "5.4.2": "2012-05-03",
    "5.4.3": "2012-05-08",
    "5.4.4": "2012-06-06",
    "5.4.5": "2012-07-19",
    "5.4.6": "2012-08-16",
    "5.4.7": "2012-09-13",
    "5.4.8": "2012-10-18",
    "5.4.9": "2012-11-22",
    "5.4.10": "2012-12-20",
    "5.4.11": "2013-01-17",
    "5.4.12": "2013-02-21",
    "5.4.13": "2013-03-14",
    "5.4.14": "2013-04-11",
    "5.4.15": "2013-05-09",
    "5.4.16": "2013-06-06",
    "5.4.17": "2013-07-04",
    "5.4.18": "2013-08-15",
    "5.4.19": "2013-08-22",
    "5.4.20": "2013-09-19",
    "5.4.21": "2013-10-17",
    "5.4.22": "2013-11-14",
    "5.4.23": "2013-12-12",
    "5.4.24": "2014-01-09",
    "5.4.25": "2014-02-06",
    "5.4.26": "2014-03-06",
    "5.4.27": "2014-04-03",
    "5.4.28": "2014-05-01",
    "5.4.29": "2014-05-29",
    "5.4.30": "2014-06-26",
    "5.4.31": "2014-07-24",
    "5.4.32": "2014-08-21",
    "5.4.33": "2014-09-18",
    "5.4.34": "2014-10-16",
    "5.4.35": "2014-11-13",
    "5.4.36": "2014-12-18",
    "5.4.37": "2015-01-22",
    "5.4.38": "2015-02-19",
    "5.4.39": "2015-03-19",
    "5.4.40": "2015-04-16",
    "5.4.41": "2015-05-14",
    "5.4.42": "2015-06-11",
    "5.4.43": "2015-07-09",
    "5.4.44": "2015-08-06",
    "5.4.45": "2015-09-03"
  },
  "5.5": {
    "5.5.0": "2013-06-20",
    "5.5.1": "2013-07-18",
    "5.5.2": "2013-08-15",
    "5.5.3": "2013-08-22",
    "5.5.4": "2013-09-19",
    "5.5.5": "2013-10-17",
    "5.5.6": "2013-11-14",
    "5.5.7": "2013-12-12",
    "5.5.8": "2014-01-09",
    "5.5.9": "2014-02-06",
    "5.5.10": "2014-03-06",
    "5.5.11": "2014-04-03",
    "5.5.12": "2014-05-01",
    "5.5.13": "2014-05-29",
    "5.5.14": "2014-06-26",
    "5.5.15": "2014-07-24",
    "5.5.16": "2014-08-21",
    "5.5.17": "2014-09-18",
    "5.5.18": "2014-10-16",
    "5.5.19": "2014-11-13",
    "5.5.20": "2014-12-18",
    "5.5.21": "2015-01-22",
    "5.5.22": "2015-02-19",
    "5.5.23": "2015-03-19",
    "5.5.24": "2015-04-16",
    "5.5.25": "2015-05-14",
    "5.5.26": "2015-06-11",
    "5.5.27": "2015-07-09",
    "5.5.28": "2015-08-06",
    "5.5.29": "2015-09-03",
    "5.5.30": "2015-10-01",
    "5.5.31": "2016-01-07",
    "5.5.32": "2016-02-04",
    "5.5.33": "2016-03-03",
    "5.5.34": "2016-03-31",
    "5.5.35": "2016-04-28",
    "5.5.36": "2016-05-26",
    "5.5.37": "2016-06-23",
    "5.5.38": "2016-07-21"
  },
  "5.6": {
    "5.6.0": "2014-08-28",
    "5.6.1": "2014-10-02",
    "5.6.2": "2014-10-16",
    "5.6.3": "2014-11-13",
    "5.6.4": "2014-12-18",
    "5.6.5": "2015-01-22",
    "5.6.6": "2015-02-19",
    "5.6.7": "2015-03-19",
    "5.6.8": "2015-04-16",
    "5.6.9": "2015-05-14",
    "5.6.10": "2015-06-11",
    "5.6.11": "2015-07-10",
    "5.6.12": "2015-08-06",
    "5.6.13": "2015-09-03",
    "5.6.14": "2015-10-01",
    "5.6.15": "2015-10-29",
    "5.6.16": "2015-11-26",
    "5.6.17": "2016-01-07",
    "5.6.18": "2016-02-04",
    "5.6.19": "2016-03-03",
    "5.6.20": "2016-03-31",
    "5.6.21": "2016-04-28",
    "5.6.22": "2016-05-26",
    "5.6.23": "2016-06-23",
    "5.6.24": "2016-07-21",
    "5.6.25": "2016-08-18",
    "5.6.26": "2016-09-15",
    "5.6.27": "2016-10-13",
    "5.6.28": "2016-11-10",
    "5.6.29": "2016-12-08",
    "5.6.30": "2017-01-19",
    "5.6.31": "2017-07-06",
    "5.6.32": "2017-10-26",
    "5.6.33": "2018-01-04",
    "5.6.34": "2018-03-01",
    "5.6.35": "2018-03-29",
    "5.6.36": "2018-04-26",
    "5.6.37": "2018-07-19",
    "5.6.38": "2018-09-13",
    "5.6.39": "2018-12-06",
    "5.6.40": "2019-01-10"
  },
  "7.0": {
    "7.0.0": "2015-12-03",
    "7.0.1": "2015-12-17",
    "7.0.2": "2016-01-07",
    "7.0.3": "2016-02-04",
    "7.0.4": "2016-03-03",
    "7.0.5": "2016-03-31",
    "7.0.6": "2016-04-28",
    "7.0.7": "2016-05-26",
    "7.0.8": "2016-06-23",
    "7.0.9": "2016-07-21",
    "7.0.10": "2016-08-18",
    "7.0.11": "2016-09-15",
    "7.0.12": "2016-10-13",
    "7.0.13": "2016-11-10",
    "7.0.14": "2016-12-08",
    "7.0.15": "2017-01-19",
    "7.0.16": "2017-02-16",
    "7.0.17": "2017-03-16",
    "7.0.18": "2017-04-13",
    "7.0.19": "2017-05-11",
    "7.0.20": "2017-06-08",
    "7.0.21": "2017-07-06",
    "7.0.22": "2017-08-03",
    "7.0.23": "2017-08-31",
    "7.0.24": "2017-09-28",
    "7.0.25": "2017-10-26",
    "7.0.26": "2017-11-23",
    "7.0.27": "2018-01-04",
    "7.0.28": "2018-03-01",
    "7.0.29": "2018-03-29",
    "7.0.30": "2018-04-26",
    "7.0.31": "2018-07-19",
    "7.0.32": "2018-09-13",
    "7.0.33": "2018-12-06"
  },
  "7.1": {
    "7.1.0": "2016-12-01",
    "7.1.1": "2017-01-19",
    "7.1.2": "2017-02-16",
    "7.1.3": "2017-03-16",
    "7.1.4": "2017-04-13",
    "7.1.5": "2017-05-11",
    "7.1.6": "2017-06-07",
    "7.1.7": "2017-07-06",
    "7.1.8": "2017-08-03",
    "7.1.9": "2017-08-31",
    "7.1.10": "2017-09-28",
    "7.1.11": "2017-10-26",
    "7.1.12": "2017-11-23",
    "7.1.13": "2018-01-04",
    "7.1.14": "2018-02-01",
    "7.1.15": "2018-03-01",
    "7.1.16": "2018-03-29",
    "7.1.17": "2018-04-26",
    "7.1.18": "2018-05-24",
    "7.1.19": "2018-06-22",
    "7.1.20": "2018-07-19",
    "7.1.21": "2018-08-16",
    "7.1.22": "2018-09-13",
    "7.1.23": "2018-10-11",
    "7.1.24": "2018-11-08",
    "7.1.25": "2018-12-06",
    "7.1.26": "2019-01-10",
    "7.1.27": "2019-03-07",
    "7.1.28": "2019-04-04",
    "7.1.29": "2019-05-02",
    "7.1.30": "2019-05-30",
    "7.1.31": "2019-08-01",
    "7.1.32": "2019-08-29",
    "7.1.33": "2019-10-24"
  },
  "7.2": {
    "7.2.0": "2017-11-30",
    "7.2.1": "2018-01-04",
    "7.2.2": "2018-02-01",
    "7.2.3": "2018-03-01",
    "7.2.4": "2018-03-29",
    "7.2.5": "2018-04-26",
    "7.2.6": "2018-05-24",
    "7.2.7": "2018-06-21",
    "7.2.8": "2018-07-19",
    "7.2.9": "2018-08-16",
    "7.2.10": "2018-09-13",
    "7.2.11": "2018-10-11",
    "7.2.12": "2018-11-08",
    "7.2.13": "2018-12-06",
    "7.2.14": "2019-01-10",
    "7.2.15": "2019-02-07",
    "7.2.16": "2019-03-07",
    "7.2.17": "2019-04-04",
    "7.2.18": "2019-05-02",
    "7.2.19": "2019-05-30",
    "7.2.20": "2019-07-04",
    "7.2.21": "2019-08-01",
    "7.2.22": "2019-08-29",
    "7.2.23": "2019-09-26",
    "7.2.24": "2019-10-24",
    "7.2.25": "2019-11-21",
    "7.2.26": "2019-12-18",
    "7.2.27": "2020-01-23",
    "7.2.28": "2020-02-20",
    "7.2.29": "2020-03-19",
    "7.2.30": "2020-04-16",
    "7.2.31": "2020-05-14",
    "7.2.32": "2020-07-09",
    "7.2.33": "2020-08-06",
    "7.2.34": "2020-10-01"
  },
  "7.3": {
    "7.3.0": "2018-12-06",
    "7.3.1": "2019-01-10",
    "7.3.2": "2019-02-07",
    "7.3.3": "2019-03-07",
    "7.3.4": "2019-04-04",
    "7.3.5": "2019-05-02",
    "7.3.6": "2019-05-30",
    "7.3.7": "2019-07-04",
    "7.3.8": "2019-08-01",
    "7.3.9": "2019-08-29",
    "7.3.10": "2019-09-26",
    "7.3.11": "2019-10-24",
    "7.3.12": "2019-11-21",
    "7.3.13": "2019-12-18",
    "7.3.14": "2020-01-23",
    "7.3.15": "2020-02-20",
    "7.3.16": "2020-03-19",
    "7.3.17": "2020-04-16",
    "7.3.18": "2020-05-14",
    "7.3.19": "2020-06-11",
    "7.3.20": "2020-07-09",
    "7.3.21": "2020-08-06",
    "7.3.22": "2020-09-03",
    "7.3.23": "2020-10-01",
    "7.3.24": "2020-10-29",
    "7.3.25": "2020-11-26",
    "7.3.26": "2021-01-07",
    "7.3.27": "2021-02-04",
    "7.3.28": "2021-04-29",
    "7.3.29": "2021-07-01",
    "7.3.30": "2021-08-26",
    "7.3.31": "2021-09-23"
  },
  "7.4": {
    "7.4.0": "2019-11-28",
    "7.4.1": "2019-12-18",
    "7.4.2": "2020-01-23",
    "7.4.3": "2020-02-20",
    "7.4.4": "2020-03-19",
    "7.4.5": "2020-04-16",
    "7.4.6": "2020-05-14",
    "7.4.7": "2020-06-11",
    "7.4.8": "2020-07-09",
    "7.4.9": "2020-08-06",
    "7.4.10": "2020-09-03",
    "7.4.11": "2020-10-01",
    "7.4.12": "2020-10-29",
    "7.4.13": "2020-11-26",
    "7.4.14": "2021-01-07",
    "7.4.15": "2021-02-04",
    "7.4.16": "2021-03-04",
    "7.4.18": "2021-04-29",
    "7.4.19": "2021-05-06",
    "7.4.20": "2021-06-03",
    "7.4.21": "2021-07-01",
    "7.4.22": "2021-07-29",
    "7.4.23": "2021-08-26",
    "7.4.24": "2021-09-23"
  },
  "8.0": {
    "8.0.0": "2020-11-26",
    "8.0.1": "2021-01-07",
    "8.0.2": "2021-02-04",
    "8.0.3": "2021-03-04",
    "8.0.5": "2021-04-29",
    "8.0.6": "2021-05-06",
    "8.0.7": "2021-06-03",
    "8.0.8": "2021-07-01",
    "8.0.9": "2021-07-29",
    "8.0.10": "2021-08-26",
    "8.0.11": "2021-09-23"
  }
}

#515 Git 分支管理策略(工作流)

2021-04-07

Git Logo

Git Flow

荷兰程序员 Vincent Driessen 2010 年提出。

Git Flow
PDF 下载

  • master 主干
  • develop 开发分支
  • feature/xxx 功能开发分支(临时)
    基于 develop 创建,开发完成之后:合并到 develop 分支,删除
  • release/xxx 预发布分支(临时)
    基于 develop 创建,测试通过之后:合并到 master 分支,打 tag,合并到 develop 分支,删除
  • hotfix/xxx 问题修复分支(临时)
    基于 master 创建,问题修复之后:合并到 master 分支,打 tag,合并到 develop 分支,删除

特点:基于版本交付

PS: 作者于 10 年后,也就是 2020 年,更新了一次,表示:对于互联网应用的开发应该考虑更加简单的工作流,比如 GitHub Flow。但不管怎样,应该结合自身的实际情况,不能盲目照搬。

GitHub Flow

GitHub Flow

  1. 从 master 拉分支,提交,推送
  2. pull request
  3. 评审,测试
  4. merge 到 master

特点:基本不涉及项目的分支管理,主要是对多人协作方式提出建议:Pull Request。适合多人参与的开源项目,由项目管理员负责维护主干。

中间可以借助自动化工具来静态分析、部署、测试,提升开发速度。但这些工具不是 GitHub Flow 专有,或者说不是它的特色。

这套逻辑看似非常简单,但要硬套到企业项目开发流程中可能会非常复杂,水土不服。
PS: 虽然 git 新增了一个 request-pull 子命令,但可能很鸡肋,不可能脱离 Web 来参与讨论、评审代码。

GitLab Flow

GitLab Flow

分不同环境:

  • master 开发分支,管理方式就是 GitHub Flow,不过就是 Pull Request 改名为了 Merge Request
  • pre-production 预发布分支,只能从 master 合并
  • production 发布分支,只能从 pre-production 合并

如果需要发布:

  1. 从 master 拉分支,比如:20-04-stable,创建语义化版本号。
  2. 如果后期有严重 BUG,可以从 master cherry-pick 过来。

特点:比 GitHub Flow 更进一步,对项目的发布和部署提出了建议,并支持多个环境。

主要是其中有一点特别好的就是: Upstream First, 上游优先。所有环境以及维护分支的提交必须来自 master 分支。

我的思考

当然,还是那句老话:适合自己的就是最好的。
项目分支管理流程要和项目自身的特点,以及团队成员的技术水平相匹配。

Git Flow 的整套流程挺完备的,符合一般开发的习惯,只是对其作出规范而已。
GitHub Flow 也好,GitLab Flow 也好,都可以看做是 Git Flow 的补充。

GitHub Flow 和 GitLab Flow 都假定 master 分支是可发布的,而 Git Flow 从 master 拆分出了一个 develop 作为缓冲,我认为这个设计比较合理。

Git Flow 有几个需要注意的问题:

  1. 合并的时候保留之前的合并记录,谨慎快进 (--no-ff)
  2. hotfix, release 分支推送到 master 发布之后,切记还要同步推送到 develop 分支
  3. 必须坚持 master, develop 上不随意推送(还是得靠 Pull Request 机制)
  4. 如果有不属于当前发布周期需要的开发,不要合并到 develop
  5. 临时分支必须快用快销,不要长时间保留,且合并之后理解删除

我设想中的开发流程

1,基本参照 Git Flow (借助工具)

上游优先原则:master 分支作为所有可发布环境的上游

2,自动化测试:

  • develop, release, master: 只要有变更就触发一次自动化测试
  • 此外, master 应该保持一定频率的定时自动化测试

3,老版本需要维护就在 tag 上拉分支。

现实场景中,可能要为 abc 环境上的某个老版本 v1.0.0 修复 BUG、加功能,或对已有功能进行一些调整。

git branch maintain/abc v1.0.0 # 一旦分叉,就需要长期保留该分支
# maintain/abc/develop
# maintain/abc/feature/xxx
# maintain/abc/hotfix/xxx
# maintain/abc/release/xxx
# 视情况,可以简化处理,不用上面这些分支

PS: 最后需要重新发布的时候可以对版本号加上附加的标识 v1.0.0.1.abc

5,如果是 OEM,针对客户有关的信息、资源应该留给打包系统来统一处理

此外:

  1. 项目应有明确的 roadmap
  2. 代码评审
  3. 数据库设计统一管理
  4. 项目文档,需求库,用例库
  5. 版本号基本上遵循语义化版本规范
  6. 提交记录遵循 git commit message 规范 (借助工具)
  7. CI/CD + 自动化测试

参考资料与拓展阅读

#514 iptables (2): 基本操作

2021-04-04

命令参数

iptables -[ACD] chain rule-specification [options]
iptables -I chain [rulenum] rule-specification [options]
iptables -R chain rulenum rule-specification [options]
iptables -D chain rulenum [options]
iptables -[LS] [chain [rulenum]] [options]
iptables -[FZ] [chain] [options]
iptables -[NX] chain
iptables -E old-chain-name new-chain-name
iptables -P chain target [options]

链相关

  • --new -N [chain] 链:创建
  • --delete-chain -X [chain] 链:删除
  • --rename-chain -E old-chain new-chain 链:更名
  • --list -L [chain [rulenum]] 列出指定链或所有链中的所有规则(表格形式)
  • --list-rules -S [chain [rulenum]] 同上,不过是按照规则定义的格式列出 很好用
  • --flush -F [chain] 清空规则
  • --zero -Z [chain [rulenum]] 计数器清零(数据包计数器,流量计数器)
  • --policy -P chain target 修改策略

规则相关

  • --append -A chain rule-specification 附加规则
  • --check -C chain rule-specification 检查规则是否存在
  • --delete -D chain rule-specification 删除匹配规则
  • --delete -D chain rulenum 删除 指定序号的 规则
  • --insert -I chain [rulenum] 插入到指定位置(默认插入到第一个)
  • --replace -R chain rulenum 替换 指定序号的 规则

规则说明

常用命令

查看

sudo iptables -S
sudo iptables -t nat -S POSTROUTING

sudo iptables -nL
sudo iptables -nL INPUT

sudo iptables -nL --line-numbers

sudo iptables -Z
sudo iptables -Z INPUT
sudo iptables -Z INPUT 1

# 如果是这么定义的话:
#             -A INPUT -m conntrack --ctstate INVALID -j DROP
sudo iptables -D INPUT -m conntrack --ctstate INVALID -j DROP
sudo iptables -D INPUT 3

访问拦截

# 对指定端口放行
sudo iptables -I INPUT -p tcp --dport 1022 -j ACCEPT

# 禁止来自无线网络的流量访问某端口(突发奇想的一个小例子)
sudo iptables -A PREROUTINE -i wlp6s0 --dport 22 -j DROP

NAT

iptables -t nat -A POSTROUTING -d 192.168.0.102 -j SNAT --to 192.168.0.1
iptables -t nat -A PREROUTING -d 202.202.202.2 -j DNAT --to-destination 192.168.0.102
iptables -t nat -D PREROUTING -p tcp --dport 8080 -i eth2.2 -j REDIRECT --to 80

对指定流量打标记

内核可以给这个包加上一个标记(可能存在包的数据结构中,总之,只对本地环境有效),可以实现流量的统计、限制等其他复杂的控制。

标记值最大可以到 2^32

iptables -t mangle -A PREROUTING -s 192.168.1.3 -j MARK --set-mark 60
iptables -t mangle -A PREROUTING -p tcp --dport 22 -j MARK --set-mark 2

//打标记
iptables -t mangle -A PREROUTING -j MARK --set-mark 33
//匹配标记
iptables -t nat -A PREROUTING -m mark --mark 33  -j ACCEPT
//or-mark
iptables -t mangle -A PREROUTING -j MARK --or-mark 0x400
//掩码匹配
iptables -t nat -A PREROUTING -m mark --mark 0x400/0x400  -j ACCEPT

iptables -t mangle -A INPUT -m state --state NEW -j MARK --set-mark 1
iptables -t mangle -A INPUT -j CONNMARK --save-mark
iptables -t mangle -A INPUT -j CONNMARK --restore-mark

iptables -t mangle -A PREROUTING -mttl --ttl-eq 64 -j MARK --set-mark 10
iptables -t mangle -A PREROUTING -mttl --ttl-eq 123 -j MARK --set-mark 20
iptables -t filter -A FORWARD -m mark--mark 10 -j ACCEPT
iptables -t filter -A FORWARD -m mark--mark 20 -j DROP

iptables -t mangle -A QOS_MARK_FORWARD_eth1 -j CONNMARK --restore-mark --nfmask 0xfffff --ctmask 0xfffff
iptables -t mangle -A QOS_MARK_FORWARD_eth1 -m mark --mark 0x0/0xfffff -j QOS_RULES_FORWARD_eth1
iptables -t mangle -A QOS_RULES_FORWARD_eth1 -j CONNMARK --save-mark --nfmask 0xfffff --ctmask 0xfffff

iptables -t mangle -A POSTROUTING -m iprange --src-range 192.168.0.2-192.168.0.200 -j MARK --or-mark 0x1

-m mark
-m connmark
-j MARK
-j CONNMARK
-j CONNSECMARK
-j SECMARK

  • --set-mark value 设置nfmark值
  • --and-mark value nfmark与value与运算
  • --or-mark value nfmark与value或运算

匹配

  • [!] --mark value[/mask]
iptables -t mangle -A INPUT -m mark --mark 1

限速

思路:hashlimit 模块

屏蔽指定网站

利用 string 模块 (xt_string.ko) 做域名匹配:

sudo iptables -A OUTPUT -m string --string baidu.com --algo bm -j LOG --log-prefix "iptables-test:blocked:baidu.com: "
sudo iptables -A OUTPUT -m string --string baidu.com --algo bm -j DROP
sudo iptables -vnL
curl https://www.baidu.com/
tail -f /var/log/syslog | grep "iptables-test"
sudo iptables -F OUTPUT

sudo iptables -A FORWARD -m string --string baidu.com --algo bm -j LOG --log-prefix "iptables-test:blocked:baidu.com: "
sudo iptables -A FORWARD -m string --string baidu.com --algo bm -j DROP

HTTPS 居然也可以生效,其原理我还不清楚,到底是匹配到了包的哪一部分包含了 baidu.com 呢?

提示:

  1. 这个规则也可以放到 INPUT 链上。
  2. 如果加上 -p tcp –dport 80 可能更加精确,免得别的什么包里面包含了 baidu.com 被拦截。
  3. 可以加多个 string 参数。

如果是 HTTP 的话,在我的理解范围之内,可以做到更细致的匹配,比如匹配到路径,甚至 Cookie。

string match options:
--from                       Offset to start searching from
--to                         Offset to stop searching
--algo                       Algorithm
--icase                      Ignore case (default: 0)
[!] --string string          Match a string in a packet
[!] --hex-string string      Match a hex string in a packet

algo 的选项:bm, kmp,参考 man iptables-extensions

参考资料与拓展阅读