From 7007fd5def30a5a4c90664a0b8271710163b0ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:37:25 +0900 Subject: [PATCH] init: lib and cmd --- .gitignore | 3 ++ README.md | 5 +++ cmd/main.go | 17 ++++++++ data.go | 76 ++++++++++++++++++++++++++++++++++ gemini.go | 37 +++++++++++++++++ go.mod | 23 +++++++++++ go.sum | 53 ++++++++++++++++++++++++ procuratio.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 325 insertions(+) create mode 100644 cmd/main.go create mode 100644 data.go create mode 100644 gemini.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 procuratio.go diff --git a/.gitignore b/.gitignore index 6f72f89..c53ff56 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ go.work.sum # env file .env + +.DS_Store +*.db diff --git a/README.md b/README.md index fca97e7..69193a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # yamaih यमैः (ins. pl.), using-geminī, from PIE *yemHós + +## Quick Start +```bash +go run cmd/main.go +``` diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..3f942d7 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/fumiama/yamaih" +) + +func main() { + ep := flag.String("l", "127.0.0.1:6783", "listening endpoint") + df := flag.String("f", "log.db", "log database file path") + api := flag.String("api", "v1beta", "api version") + flag.Parse() + g := yamaih.NewGemini(*ep, *df, *api) + fmt.Println("Fatal err:", g.RunBlocking()) +} diff --git a/data.go b/data.go new file mode 100644 index 0000000..a848fd3 --- /dev/null +++ b/data.go @@ -0,0 +1,76 @@ +package yamaih + +import ( + "strconv" + "strings" + "time" +) + +type User struct { + Key string // Key user's api key + LastTime int64 // LastTime last visit time + LastIP string // LastIP last visit IP + Count int64 // Count total visit count +} + +type Visit struct { + ID *int + UserKey string + Time int64 + WaitMilli int64 + Code int + IP string + Method string + Path string + Query string + Request []byte + Response []byte +} + +func (v *Visit) String() string { + sb := strings.Builder{} + sb.WriteByte('[') + sb.WriteString(strconv.FormatInt(v.WaitMilli, 10)) + sb.WriteString("ms] ") + sb.WriteString(v.IP) + sb.WriteByte(' ') + sb.WriteString(v.Method) + sb.WriteByte(' ') + sb.WriteString(v.Path) + return sb.String() +} + +func (g *Gemini) initdb() error { + err := g.logdb.Open(time.Hour) + if err != nil { + return err + } + _, err = g.logdb.Exec("PRAGMA foreign_keys = ON;") + if err != nil { + return err + } + err = g.logdb.Create("user", &User{}) + if err != nil { + return err + } + return g.logdb.Create("visit", &Visit{}, + "FOREIGN KEY(UserKey) REFERENCES user(Key)", + ) +} + +func (g *Gemini) visit(v *Visit) error { + v.ID = nil + g.dbmu.Lock() + defer g.dbmu.Unlock() + u := User{} + _ = g.logdb.Find("user", &u, "WHERE Key=?", v.UserKey) + u.Key = v.UserKey + u.LastTime = v.Time + u.LastIP = v.IP + u.Count++ + err := g.logdb.Insert("user", &u) + if err != nil { + return err + } + return g.logdb.Insert("visit", v) +} diff --git a/gemini.go b/gemini.go new file mode 100644 index 0000000..d788489 --- /dev/null +++ b/gemini.go @@ -0,0 +1,37 @@ +package yamaih + +import ( + "net/http" + "sync" + + sql "github.com/FloatTech/sqlite" +) + +const api = "https://generativelanguage.googleapis.com" + +type Gemini struct { + dbmu sync.Mutex + endpoint string + apiver string // apiver usually v1beta + logdb sql.Sqlite + mux *http.ServeMux +} + +func NewGemini(endpoint, logfile, apiver string) *Gemini { + g := &Gemini{ + endpoint: endpoint, + apiver: apiver, + logdb: sql.New(logfile), + mux: http.NewServeMux(), + } + err := g.initdb() + if err != nil { + panic(err) + } + g.mux.HandleFunc("/", g.handler) + return g +} + +func (g *Gemini) RunBlocking() error { + return http.ListenAndServe(g.endpoint, g.mux) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..aa63e46 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module github.com/fumiama/yamaih + +go 1.24.1 + +require github.com/FloatTech/sqlite v1.7.2 + +require ( + github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.26.0 // indirect + modernc.org/libc v1.61.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.33.1 // indirect +) + +replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.29.10-simp + +replace modernc.org/libc => github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..423dc76 --- /dev/null +++ b/go.sum @@ -0,0 +1,53 @@ +github.com/FloatTech/sqlite v1.7.2 h1:b8COegNLSzofzOyARsVwSbz9OOzWEa8IElsTlx1TBLw= +github.com/FloatTech/sqlite v1.7.2/go.mod h1:/4tzfCGhrZnnjC1U8vcfwGQeF6eR649fhOsS3+Le0+s= +github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ/VCf80LiQo9C7jHgrunZDwiRcY= +github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA= +github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= +github.com/fumiama/sqlite3 v1.29.10-simp h1:c5y3uKyU0q9t0/SyfynzYyuslQ5zP+5CD8e0yYY554A= +github.com/fumiama/sqlite3 v1.29.10-simp/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= +modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= +modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/procuratio.go b/procuratio.go new file mode 100644 index 0000000..84582fd --- /dev/null +++ b/procuratio.go @@ -0,0 +1,111 @@ +package yamaih + +import ( + "bytes" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +func (g *Gemini) handler(w http.ResponseWriter, r *http.Request) { + if len(r.URL.Path) <= 1 { + http.Error(w, "400 Invalid Path", http.StatusBadRequest) + return + } + k := r.URL.Query().Get("key") + if k == "" { + http.Error(w, "400 Empty API Key", http.StatusBadRequest) + return + } + extractIP(r) + v := &Visit{ + UserKey: k, + Time: time.Now().UnixMilli(), + IP: r.RemoteAddr, + Method: r.Method, + Path: r.URL.Path, + Query: r.URL.RawQuery, + } + respstr := "" + defer func() { + v.WaitMilli = time.Now().UnixMilli() - v.Time + if respstr != "" { + v.Response = []byte(respstr) + } + g.visit(v) + fmt.Println(v) + }() + apiver, _, _ := strings.Cut(r.URL.Path[1:], "/") + if apiver != g.apiver { + respstr = "400 Invalid API Version" + v.Code = 400 + http.Error(w, respstr, http.StatusBadRequest) + return + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + respstr = "400 Bad Request: " + err.Error() + v.Code = 400 + http.Error(w, respstr, http.StatusBadRequest) + return + } + v.Request = data + req, err := http.NewRequest( + r.Method, api+r.URL.String(), bytes.NewReader(data), + ) + if err != nil { + respstr = "400 Bad Request: " + err.Error() + v.Code = 400 + http.Error(w, respstr, http.StatusBadRequest) + return + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + respstr = "500 Do: " + err.Error() + v.Code = 500 + http.Error(w, respstr, http.StatusInternalServerError) + return + } + defer resp.Body.Close() + v.Code = resp.StatusCode + h := w.Header() + for k, vs := range resp.Header { + if len(vs) == 0 { + continue + } + h.Set(k, vs[0]) + for _, v := range vs[1:] { + h.Add(k, v) + } + } + w.WriteHeader(resp.StatusCode) + var b []byte + if resp.ContentLength > 0 { + b = make([]byte, 0, resp.ContentLength) + } + buf := bytes.NewBuffer(b) + _, _ = io.Copy(io.MultiWriter(w, buf), resp.Body) + v.Response = buf.Bytes() +} + +// extractIP parse real IP addr to r.RemoteAddr from proxy +func extractIP(r *http.Request) { + raddr := r.RemoteAddr + if strings.Contains(raddr, "127.0.0.1") || + strings.Contains(raddr, "localhost") || + strings.Contains(raddr, "@") { + realr := r.Header.Get("X-Forwarded-For") + if len(realr) > 0 && !strings.Contains(realr, "@") { + raddr = realr + } else { + realr = r.Header.Get("X-Real-IP") + if len(realr) > 0 && !strings.Contains(realr, "@") { + raddr = realr + } + } + } + r.RemoteAddr = raddr +}