GinNotes Golang Gin
2021-05-06
先列一下目前最火的几个 Go 语言框架:
- gin


- beego 国人开发

- iris The fastest HTTP/2 Go Web Framework.
自称是 ExpressJS 和 Laravel 的继承者。

- echo High performance, minimalist Go web framework

- go-micro Microservice framework for Go


- fiber

- kratos A Go framework for microservices. B 站出品

- revel/revel

- go-zero 国人开发(七牛)


- martini Classy web framework for Go

- GoFrame Classy web framework for Go 国人开发

Gin
http://gin-gonic.com

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()
}
}
开发工具 计算机网络 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
参考资料与拓展阅读
Golang
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")
}
}
Golang 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)
}
}
开发者 ImageMagick 图像处理 多媒体 LinuxTools
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轴坐标>
- 如果没有指定坐标,那就是切割图片。
- 宽高可以是百分比(不能混用,只要一个百分号那就都是比例)。
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
参考资料与拓展阅读
ffmpeg 视频处理 LinuxTools
2021-04-22
想写几个常用的 ffmpeg 命令作个分享,又想起来还有 convert,pwgen 等命令,干脆搞一个 Linux 工具箱系列,总结一下为什么我被锁定在 Linux 平台上无法自拔了。
正则表达式
2021-04-14
- https://zh.wikipedia.org/wiki/正则表达式
- 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:
Python:
re.A
, re.ASCII
re.I
, re.IGNORECASE
re.M
, re.MULTILINE
re.S
, re.DOTALL
re.DEBUG
re.X
, re.VERBOSE
参考资料与拓展阅读
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"
}
}
Git 项目管理
2021-04-07

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

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

- 从 master 拉分支,提交,推送
- pull request
- 评审,测试
- merge 到 master
特点:基本不涉及项目的分支管理,主要是对多人协作方式提出建议:Pull Request。适合多人参与的开源项目,由项目管理员负责维护主干。
中间可以借助自动化工具来静态分析、部署、测试,提升开发速度。但这些工具不是 GitHub Flow 专有,或者说不是它的特色。
这套逻辑看似非常简单,但要硬套到企业项目开发流程中可能会非常复杂,水土不服。
PS: 虽然 git 新增了一个 request-pull 子命令,但可能很鸡肋,不可能脱离 Web 来参与讨论、评审代码。
GitLab Flow

分不同环境:
- master 开发分支,管理方式就是 GitHub Flow,不过就是 Pull Request 改名为了 Merge Request
- pre-production 预发布分支,只能从 master 合并
- production 发布分支,只能从 pre-production 合并
如果需要发布:
- 从 master 拉分支,比如:
20-04-stable
,创建语义化版本号。
- 如果后期有严重 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 有几个需要注意的问题:
- 合并的时候保留之前的合并记录,谨慎快进 (
--no-ff
)
- hotfix, release 分支推送到 master 发布之后,切记还要同步推送到 develop 分支
- 必须坚持 master, develop 上不随意推送(还是得靠 Pull Request 机制)
- 如果有不属于当前发布周期需要的开发,不要合并到 develop
- 临时分支必须快用快销,不要长时间保留,且合并之后理解删除
我设想中的开发流程
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,针对客户有关的信息、资源应该留给打包系统来统一处理
此外:
- 项目应有明确的 roadmap
- 代码评审
- 数据库设计统一管理
- 项目文档,需求库,用例库
- 版本号基本上遵循语义化版本规范
- 提交记录遵循 git commit message 规范 (借助工具)
- CI/CD + 自动化测试
参考资料与拓展阅读
- 阮一峰, Git分支管理策略
- 阮一峰, Git 工作流程
- git-flow 备忘清单
- GitLab, Introduction to GitLab Flow
- GitHub, Understanding the GitHub flow
- GitHub, GitHub Flow
- Git-Tower, git-flow 的工作流程
- 阮一峰, Git 使用规范流程
- 阮一峰, Commit message 和 Change log 编写指南
- 阮一峰, git cherry-pick 教程
- 阮一峰, Git远程操作详解
- 阮一峰, git bisect 命令教程
- 阮一峰, 常用 Git 命令清单
- 廖雪峰, Git教程
- https://github.com/wangdoc/git-tutorial
LinuxNetwork 网络管理 iptables
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或运算
匹配
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 呢?
提示:
- 这个规则也可以放到 INPUT 链上。
- 如果加上
-p tcp –dport 80
可能更加精确,免得别的什么包里面包含了 baidu.com 被拦截。
- 可以加多个 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
。
参考资料与拓展阅读