mirror of
https://github.com/fumiama/yamaih.git
synced 2026-06-05 00:10:28 +08:00
init: lib and cmd
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,3 +23,6 @@ go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
.DS_Store
|
||||
*.db
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
# yamaih
|
||||
यमैः (ins. pl.), using-geminī, from PIE *yemHós
|
||||
|
||||
## Quick Start
|
||||
```bash
|
||||
go run cmd/main.go
|
||||
```
|
||||
|
||||
17
cmd/main.go
Normal file
17
cmd/main.go
Normal file
@@ -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())
|
||||
}
|
||||
76
data.go
Normal file
76
data.go
Normal file
@@ -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)
|
||||
}
|
||||
37
gemini.go
Normal file
37
gemini.go
Normal file
@@ -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)
|
||||
}
|
||||
23
go.mod
Normal file
23
go.mod
Normal file
@@ -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
|
||||
53
go.sum
Normal file
53
go.sum
Normal file
@@ -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=
|
||||
111
procuratio.go
Normal file
111
procuratio.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user