#534 使用 Go 语言开发 Python 拓展
Golang Python PythonExt 2021-05-15Python 有一个好搭档就是 C/C,Python 提供生产力,C/C 则负责效率。
这篇文章探讨 Python + Go 混合开发的可能性。
coding in a complicated world
Python 有一个好搭档就是 C/C,Python 提供生产力,C/C 则负责效率。
这篇文章探讨 Python + Go 混合开发的可能性。
其实我说的 wiki 完全谈不上 wiki。
wiki 应该是每个读者都可以编辑的,那需要一个很复杂的控制,我做不来这个。
就是我自己整理的很多笔记而已,不过都按章节编写。
或许叫做手册更为贴切(后面就一直叫手册吧)。
我觉得自己的这个习惯很好,留下有结构的一个知识体系,也是一种对学到知识的梳理。
但最近我觉得需要作出一点调整,就像 RH 变更 CentOS 的定位,将其作为 RHEL 的上游一样,我每次整理知识结构的时候先不急着去写我的手册,先写篇博文出来,积累沉淀一下,日后再从博客中提取内容充实手册。
有两个好处:
一举两得。
抱歉,水文一篇
我公司办公环境需要用到 OpenVPN,之前允许一个账号在两处登录,现在处于安全考虑只允许一处登录了。
然后,我就不方便了,因为我办公环境有两台电脑(台式机 Windows,笔记本 Ubuntu)都需要接入 VPN。
先列一下目前最火的几个 Go 语言框架:
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 |
// 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()
}
}
检查端口可连接性,并支持检测操作系统信息、占用端口的常见应用程序。
关闭或过滤(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
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")
}
}
https://github.com/valyala/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
/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 allpackage 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)
构建与 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)
}
}