├── .github
├── actions
│ └── setup-db
│ │ ├── Dockerfile
│ │ ├── action.yml
│ │ └── entrypoint.sh
└── workflows
│ ├── pr-receive.yml
│ ├── pr-test.yml
│ └── unit-test.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── api
├── api.go
├── api_test.go
├── handler.go
├── http.go
├── process.go
├── process_test.go
└── types.go
├── application
├── application.go
├── application_test.go
├── disk
│ ├── disk.go
│ ├── disk_test.go
│ └── fs.go
├── ignore
│ ├── ignore.go
│ ├── ignore_ported_test.go
│ └── ignore_test.go
├── types.go
└── yaz
│ ├── build.go
│ ├── build_test.go
│ ├── ciphers
│ ├── aes.go
│ └── ase_test.go
│ ├── fs.go
│ ├── types.go
│ ├── yaz.go
│ └── yaz_test.go
├── cast
├── http.go
└── http_test.go
├── connector
├── connector.go
├── connector_test.go
├── database
│ └── xun.go
├── moapi
│ └── moapi.go
├── mongo
│ └── mongo.go
├── openai
│ └── openai.go
├── redis
│ └── redis.go
└── types.go
├── diff
├── diff.go
├── diff_test.go
└── process.go
├── dns
├── dns.go
├── dsn_test.go
└── types.go
├── encoding
├── base64
│ └── base64.go
├── base64_test.go
├── encoding.go
├── hex
│ └── hex.go
├── hex_test.go
├── json
│ └── json.go
├── json_test.go
├── xml
│ ├── decoder.go
│ └── xml.go
├── yaml
│ └── yaml.go
└── yaml_test.go
├── flow
├── exec.go
├── exec_test.go
├── flow.go
├── flow_test.go
├── process.go
├── process_test.go
└── types.go
├── fs
├── binary
│ └── README.md
├── dsl
│ ├── dsl.go
│ └── dsl_test.go
├── fs.go
├── fs_test.go
├── mongo
│ └── README.md
├── process.go
├── process_test.go
├── redis
│ └── README.md
├── s3
│ └── README.md
├── system
│ ├── README.md
│ └── system.go
├── types.go
└── xun
│ └── README.md
├── go.mod
├── go.sum
├── helper
├── bind.go
├── colorjson.go
├── helper.go
├── utils.go
└── utils_test.go
├── http
├── http.go
├── http_test.go
├── process.go
├── process_test.go
└── types.go
├── lang
├── lang.go
├── lang_test.go
└── types.go
├── model
├── README.md
├── atomic.go
├── atomic_test.go
├── column.go
├── crypt.go
├── filter.go
├── index.go
├── migrate.go
├── model.go
├── model_test.go
├── process.go
├── process_test.go
├── query.go
├── query_test.go
├── snap.go
├── snap_test.go
├── stack.go
├── types.go
├── url.go
├── url_test.go
├── validation.go
└── validation_test.go
├── plan
├── README.md
├── plan.go
├── plan_test.go
├── shared.go
└── types.go
├── plugin
├── plugin.go
├── plugin.types.go
├── plugin_test.go
├── process.go
└── process_test.go
├── process
├── args.go
├── args_test.go
├── process.go
├── process_test.go
└── types.go
├── query
├── assets
│ ├── conditions
│ │ ├── base.json
│ │ ├── error.json
│ │ └── query.json
│ ├── expressions
│ │ ├── alias.json
│ │ ├── base.json
│ │ ├── fields.json
│ │ └── type.json
│ ├── full.json
│ ├── groups
│ │ ├── error.json
│ │ ├── json.json
│ │ ├── mix.json
│ │ ├── strict.json
│ │ └── sugar.json
│ ├── orders
│ │ ├── error.json
│ │ ├── mix.json
│ │ ├── strict.json
│ │ └── sugar.json
│ ├── queries
│ │ ├── from.json
│ │ ├── groups.array.json
│ │ ├── groups.json
│ │ ├── havings.json
│ │ ├── joins.json
│ │ ├── limit.json
│ │ ├── orders.json
│ │ ├── select.json
│ │ ├── sql.json
│ │ ├── subquery.json
│ │ ├── subquery.name.json
│ │ ├── unions.json
│ │ └── wheres.json
│ └── tables
│ │ ├── base.json
│ │ └── error.json
├── engine.go
├── gou.go
├── gou
│ ├── build.go
│ ├── build_test.go
│ ├── condition.go
│ ├── condition_test.go
│ ├── expression.go
│ ├── expression_test.go
│ ├── groups.go
│ ├── groups_test.go
│ ├── having.go
│ ├── init_test.go
│ ├── orders.go
│ ├── orders_test.go
│ ├── query.go
│ ├── query_test.go
│ ├── sql.go
│ ├── table.go
│ ├── table_test.go
│ ├── types.go
│ ├── validate.go
│ └── where.go
├── gou_test.go
├── init_test.go
├── share
│ ├── dsl.go
│ └── flow.fliter.go
├── tai.go
└── tai
│ └── types.go
├── rag
├── README.md
├── driver
│ ├── openai
│ │ ├── vectorizer.go
│ │ └── vectorizer_test.go
│ ├── qdrant
│ │ ├── README.md
│ │ ├── engine.go
│ │ ├── engine_test.go
│ │ ├── file.go
│ │ └── file_test.go
│ └── types.go
├── rag.go
└── rag_test.go
├── runtime
├── transform
│ ├── transform.go
│ └── transform_test.go
└── v8
│ ├── bridge
│ ├── bridge.go
│ ├── bridge_test.go
│ ├── function.go
│ ├── go_test.go
│ ├── js_test.go
│ └── promise.go
│ ├── context.go
│ ├── dispatcher.go
│ ├── functions
│ ├── atob
│ │ ├── atob.go
│ │ └── atob_test.go
│ ├── btoa
│ │ ├── btoa.go
│ │ └── btoa_test.go
│ ├── eval
│ │ ├── READEME.md
│ │ ├── eval.go
│ │ └── eval_test.go
│ ├── lang
│ │ ├── lang.go
│ │ └── lang_test.go
│ ├── process
│ │ ├── process.go
│ │ └── process_test.go
│ └── studio
│ │ └── studio.go
│ ├── isolate.go
│ ├── isolate_test.go
│ ├── objects
│ ├── console
│ │ ├── console.go
│ │ └── console_test.go
│ ├── exception
│ │ ├── exception.go
│ │ └── exception_test.go
│ ├── fs
│ │ ├── fs.go
│ │ └── fs_test.go
│ ├── http
│ │ ├── http.go
│ │ └── http_test.go
│ ├── job
│ │ ├── job.go
│ │ └── job_test.go
│ ├── log
│ │ ├── log.go
│ │ └── log_test.go
│ ├── plan
│ │ ├── plan.go
│ │ └── plan_test.go
│ ├── query
│ │ ├── query.go
│ │ └── query_test.go
│ ├── store
│ │ ├── store.go
│ │ └── store_test.go
│ ├── time
│ │ ├── time.go
│ │ └── time_test.go
│ └── websocket
│ │ ├── websocket.go
│ │ └── websocket_test.go
│ ├── option.go
│ ├── process.go
│ ├── process_test.go
│ ├── require.go
│ ├── require_test.go
│ ├── runner.go
│ ├── script.go
│ ├── script_test.go
│ ├── sourcemap.go
│ ├── sourcemap_test.go
│ ├── store
│ ├── cache.go
│ ├── context.go
│ ├── isolate.go
│ ├── store.go
│ └── types.go
│ ├── studio_test.go
│ ├── tests
│ └── plan_test.go
│ ├── tsconfig.go
│ ├── tsconfig_test.go
│ ├── types.go
│ ├── v8.go
│ └── v8_test.go
├── schedule
├── process.go
└── process_test.go
├── schema
├── README.md
├── process.go
├── process_test.go
├── schema.go
├── types
│ ├── blueprint.go
│ ├── diff.go
│ └── types.go
├── xun
│ ├── blueprint.go
│ ├── column.go
│ ├── index.go
│ └── xun.go
└── xun_test.go
├── server
├── http
│ ├── http.go
│ ├── http_test.go
│ └── types.go
└── websocket
│ ├── README.md
│ ├── api.ws.go
│ ├── api.ws_test.go
│ ├── websocket.go
│ ├── websocket.type.go
│ └── websocket_test.go
├── session
├── buntdb.go
├── buntdb_test.go
├── process.go
├── process_test.go
├── redis.go
├── redis_test.go
├── session.go
└── types.go
├── socket
├── README.md
├── client.go
├── client_test.go
├── server.go
├── server_test.go
├── socket.go
├── socket.type.go
├── socket_test.go
└── types.go
├── ssl
├── certificate.go
├── certificate_test.go
├── process.go
├── process_test.go
├── ssl.go
├── ssl_test.go
└── types.go
├── store
├── file
│ └── README.md
├── lru
│ ├── README.md
│ ├── lru.go
│ └── types.go
├── mongo
│ ├── README.md
│ ├── mongo.go
│ └── types.go
├── process.go
├── process_test.go
├── redis
│ ├── README.md
│ ├── redis.go
│ └── types.go
├── store.go
├── store.types.go
├── store_test.go
└── types.go
├── task
├── process.go
├── process_test.go
├── task.go
├── task_test.go
└── types.go
├── types
├── fs.go
├── query.types.go
└── query.url.go
└── websocket
├── client.go
├── client_test.go
├── hub.go
├── types.go
├── upgrader.go
└── upgrader_test.go
/.github/actions/setup-db/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker:stable
2 |
3 |
4 | COPY entrypoint.sh /entrypoint.sh
5 | RUN chmod +x /entrypoint.sh
6 | ENTRYPOINT ["/entrypoint.sh"]
--------------------------------------------------------------------------------
/.github/actions/setup-db/action.yml:
--------------------------------------------------------------------------------
1 | inputs:
2 | kind:
3 | description: "Chose the kind of database (MySQL8.0, MySQL5.7, Postgres9.6, Postgres14, SQLite3)"
4 | required: true
5 | db:
6 | description: "The name of database"
7 | required: false
8 | default: "github"
9 | port:
10 | description: "The port of database"
11 | required: false
12 | user:
13 | description: "The user of database"
14 | required: false
15 | default: "github"
16 | password:
17 | description: "The passowrd of database"
18 | required: false
19 | default: "123456"
20 | runs:
21 | using: "docker"
22 | image: "Dockerfile"
23 |
--------------------------------------------------------------------------------
/.github/actions/setup-db/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | docker_run="docker run"
4 |
5 | startMySQL() {
6 | VERSION=$1
7 | PORT="3306"
8 | if [ ! -z "$INPUT_PORT" ]; then
9 | PORT=$INPUT_PORT
10 | fi
11 |
12 | echo "Start MySQL $VERSION"
13 | docker_run="$docker_run -e MYSQL_ROOT_PASSWORD=$INPUT_PASSWORD -e MYSQL_USER=$INPUT_USER -e MYSQL_PASSWORD=$INPUT_PASSWORD"
14 | docker_run="$docker_run -e MYSQL_DATABASE=$INPUT_DB"
15 | docker_run="$docker_run -d -p $PORT:3306 mysql:$VERSION --port=3306"
16 | docker_run="$docker_run --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci"
17 | sh -c "$docker_run"
18 |
19 | DB_DSN="root:$INPUT_PASSWORD@tcp(127.0.0.1:$PORT)/$INPUT_DB?charset=utf8mb4&parseTime=True&loc=Local"
20 | echo "DSN=$DB_DSN" >> $GITHUB_ENV
21 | echo "DB_DRIVER=mysql" >> $GITHUB_ENV
22 | echo $DB_DSN
23 | }
24 |
25 | startPostgres() {
26 | VERSION=$1
27 | PORT="5432"
28 | if [ ! -z "$INPUT_PORT" ]; then
29 | PORT=$INPUT_PORT
30 | fi
31 |
32 | echo "Start Postgres $VERSION"
33 | docker_run="docker run"
34 | docker_run="$docker_run -e POSTGRES_DB=$INPUT_DB"
35 | docker_run="$docker_run -e POSTGRES_USER=$INPUT_USER"
36 | docker_run="$docker_run -e POSTGRES_PASSWORD=$INPUT_PASSWORD"
37 | docker_run="$docker_run -d -p $PORT:5432 postgres:$VERSION"
38 |
39 | DB_DSN="postgres://$INPUT_USER:$INPUT_PASSWORD@127.0.0.1:$PORT/$INPUT_DB?sslmode=disable"
40 | echo "DSN=$DB_DSN" >> $GITHUB_ENV
41 | echo "DB_DRIVER=postgres" >> $GITHUB_ENV
42 | echo $DB_DSN
43 | }
44 |
45 | startSQLite3() {
46 | echo "Start SQLite3"
47 | echo "DSN=file:$INPUT_DB.db?cache=shared" >> $GITHUB_ENV
48 | echo "DB_DRIVER=sqlite3" >> $GITHUB_ENV
49 | }
50 |
51 | # MySQL8.0, MySQL5.7, Postgres9.6, Postgres14, SQLite3
52 | case $INPUT_KIND in
53 | MySQL8.0)
54 | startMySQL 8.0
55 | ;;
56 | MySQL5.7)
57 | startMySQL 5.7
58 | ;;
59 | Postgres9.6)
60 | startPostgres 9.6
61 | ;;
62 | Postgres14)
63 | startPostgres 14
64 | ;;
65 | SQLite3)
66 | startSQLite3
67 | ;;
68 | esac
69 |
--------------------------------------------------------------------------------
/.github/workflows/pr-receive.yml:
--------------------------------------------------------------------------------
1 | name: Receive PR
2 |
3 | # read-only repo token
4 | # no access to secrets
5 | on:
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Save PR number
17 | run: |
18 | mkdir -p ./pr
19 | echo ${{ github.event.number }} > ./pr/NR
20 | echo ${{ github.event.pull_request.head.sha }} > ./pr/SHA
21 | - uses: actions/upload-artifact@v4
22 | with:
23 | name: pr
24 | path: pr/
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | .DS_Store
17 | *.dev.sh
18 | app/plugins/dist
19 | app/plugins/logs
20 | app/db/*.db
21 | env.*.sh
22 | service/db/yao.db
23 | tests/yao/workshop/cache
24 | tests/yao/workshop/*
25 | .idea
26 | tests/app/db
27 | tests/app/data
28 | test.log
29 | *.prompt.md
30 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GO ?= go
2 | GOFMT ?= gofmt "-s"
3 | PACKAGES ?= $(shell $(GO) list ./...)
4 | VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
5 | GOFILES := $(shell find . -name "*.go")
6 |
7 | # ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
8 | TESTFOLDER := $(shell $(GO) list ./... | grep -E 'api|server/http|runtime|process|widget|rag|model|plan|schema|lang|query|task|schedule|flow|session|store|fs|http|encoding|ssl|plugin|connector|wasm|websocket$|v8|application|diff' | grep -v -E 'wamr|socket')
9 | TESTTAGS ?= ""
10 |
11 | .PHONY: test
12 | test:
13 | echo "mode: count" > coverage.out
14 | for d in $(TESTFOLDER); do \
15 | $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out -coverpkg=$$(echo $$d | sed "s/\/test$$//g") $$d > tmp.out; \
16 | cat tmp.out; \
17 | if grep -q "^--- FAIL" tmp.out; then \
18 | rm tmp.out; \
19 | exit 1; \
20 | elif grep -q "build failed" tmp.out; then \
21 | rm tmp.out; \
22 | exit 1; \
23 | elif grep -q "setup failed" tmp.out; then \
24 | rm tmp.out; \
25 | exit 1; \
26 | elif grep -q "runtime error" tmp.out; then \
27 | rm tmp.out; \
28 | exit 1; \
29 | fi; \
30 | if [ -f profile.out ]; then \
31 | cat profile.out | grep -v "mode:" >> coverage.out; \
32 | rm profile.out; \
33 | fi; \
34 | done
35 |
36 | .PHONY: bench
37 | bench:
38 | for d in $(TESTFOLDER); do \
39 | $(GO) test -run Benchmark -v -bench=. -benchtime=5s -benchmem $$d; \
40 | done
41 |
42 | .PHONY: fmt
43 | fmt:
44 | $(GOFMT) -w $(GOFILES)
45 |
46 | .PHONY: fmt-check
47 | fmt-check:
48 | @diff=$$($(GOFMT) -d $(GOFILES)); \
49 | if [ -n "$$diff" ]; then \
50 | echo "Please run 'make fmt' and commit the result:"; \
51 | echo "$${diff}"; \
52 | exit 1; \
53 | fi;
54 |
55 | vet:
56 | $(GO) vet $(VETPACKAGES)
57 |
58 | .PHONY: lint
59 | lint:
60 | @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
61 | $(GO) get -u golang.org/x/lint/golint; \
62 | fi
63 | for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
64 |
65 | .PHONY: misspell-check
66 | misspell-check:
67 | @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
68 | $(GO) get -u github.com/client9/misspell/cmd/misspell; \
69 | fi
70 | misspell -error $(GOFILES)
71 |
72 | .PHONY: misspell
73 | misspell:
74 | @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
75 | $(GO) get -u github.com/client9/misspell/cmd/misspell; \
76 | fi
77 | misspell -w $(GOFILES)
78 |
79 | .PHONY: tools
80 | tools:
81 | go install golang.org/x/lint/golint@latest; \
82 | go install github.com/client9/misspell/cmd/misspell@latest;
83 |
84 | .PHONY: migrate
85 | migrate:
86 | $(GO) test -tags $(TESTTAGS) -run TestModelMigrate$
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gou Framework
2 |
3 | [](https://github.com/YaoApp/gou/actions/workflows/unit-test.yml)
4 | [](https://codecov.io/gh/YaoApp/gou)
5 | [](https://goreportcard.com/report/github.com/yaoapp/gou)
6 | [](https://pkg.go.dev/github.com/yaoapp/gou)
7 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FYaoApp%2Fgou?ref=badge_shield)
8 |
9 | App engine framework
10 |
11 | Gou 来自易经姤卦。《象》曰: 天下有风,姤。后以施命诰四方。
12 |
13 | **Discord:** https://discord.gg/MJMQCJ2Q
14 |
15 | **Documentation:** https://yaoapps.com/en-US/doc
16 |
--------------------------------------------------------------------------------
/api/process.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/yaoapp/gou/process"
4 |
5 | func init() {
6 | process.RegisterGroup("api", map[string]process.Handler{
7 | "list": processList,
8 | })
9 | }
10 |
11 | func processList(process *process.Process) interface{} {
12 |
13 | ids := []string{}
14 | if process.NumOfArgs() > 0 {
15 | ids = process.ArgsStrings(0)
16 | }
17 |
18 | // List all
19 | apis := map[string]*API{}
20 | if len(ids) == 0 {
21 | for id, api := range APIs {
22 | apis[id] = api
23 | }
24 | return apis
25 | }
26 |
27 | // List by ids
28 | for _, id := range ids {
29 | if api, has := APIs[id]; has {
30 | apis[id] = api
31 | }
32 | }
33 | return apis
34 | }
35 |
--------------------------------------------------------------------------------
/api/process_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/process"
8 | )
9 |
10 | func TestProcesList(t *testing.T) {
11 |
12 | prepare(t)
13 | defer clean()
14 |
15 | var apis = process.New("api.list").Run()
16 | assert.Equal(t, 2, len(apis.(map[string]*API)))
17 | apis = process.New("api.list", []string{"user"}).Run()
18 | assert.Equal(t, 1, len(apis.(map[string]*API)))
19 | assert.Equal(t, "user", apis.(map[string]*API)["user"].ID)
20 | }
21 |
--------------------------------------------------------------------------------
/api/types.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | const allowHeaders = "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, Yao-Gateway-Billing, Yao-Request-Uid, Yao-Builder-Uid"
6 | const allowMethods = "POST, GET, OPTIONS, PUT, DELETE, HEAD, PATCH"
7 |
8 | // API 数据接口
9 | type API struct {
10 | ID string `jsong:"id"`
11 | Name string
12 | File string
13 | Type string
14 | HTTP HTTP
15 | }
16 |
17 | // HTTP http 协议服务
18 | type HTTP struct {
19 | Name string `json:"name"`
20 | Version string `json:"version"`
21 | Description string `json:"description,omitempty"`
22 | Group string `json:"group,omitempty"`
23 | Guard string `json:"guard,omitempty"`
24 | Paths []Path `json:"paths,omitempty"`
25 | }
26 |
27 | // Path HTTP Path
28 | type Path struct {
29 | Label string `json:"label,omitempty"`
30 | Description string `json:"description,omitempty"`
31 | Path string `json:"path"`
32 | Method string `json:"method"`
33 | Process string `json:"process"`
34 | Guard string `json:"guard,omitempty"`
35 | In []interface{} `json:"in,omitempty"`
36 | Out Out `json:"out,omitempty"`
37 | ProcessHandler bool `json:"processHandler,omitempty"`
38 | }
39 |
40 | // Out http 输出
41 | type Out struct {
42 | Status int `json:"status"`
43 | Type string `json:"type,omitempty"`
44 | Body interface{} `json:"body,omitempty"`
45 | Headers map[string]string `json:"headers,omitempty"`
46 | Redirect *Redirect `json:"redirect,omitempty"`
47 | }
48 |
49 | // Redirect out redirect
50 | type Redirect struct {
51 | Code int `json:"code,omitempty"`
52 | Location string `json:"location,omitempty"`
53 | }
54 |
55 | type ssEventData struct {
56 | Name string
57 | Message interface{}
58 | }
59 |
60 | type argsHandler func(c *gin.Context) []interface{}
61 |
--------------------------------------------------------------------------------
/application/application_test.go:
--------------------------------------------------------------------------------
1 | package application
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/application/yaz"
9 | "github.com/yaoapp/gou/application/yaz/ciphers"
10 | "github.com/yaoapp/kun/maps"
11 | )
12 |
13 | func TestLoad(t *testing.T) {
14 | root := os.Getenv("GOU_TEST_APPLICATION")
15 | app, err := OpenFromDisk(root)
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | Load(app)
20 | assert.NotNil(t, App)
21 | }
22 |
23 | func TestOpenFromDisk(t *testing.T) {
24 | root := os.Getenv("GOU_TEST_APPLICATION")
25 | app, err := OpenFromDisk(root)
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | testParse(t, app)
30 |
31 | _, err = OpenFromDisk("/path/not-exists")
32 | assert.NotNil(t, err)
33 | }
34 |
35 | func TestOpenFromYazFile(t *testing.T) {
36 | root := os.Getenv("GOU_TEST_APPLICATION")
37 | aesCipher := ciphers.NewAES([]byte("0123456789123456"))
38 |
39 | file, err := yaz.Pack(root, aesCipher)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | defer os.Remove(file)
44 |
45 | app, err := OpenFromYazFile(file, aesCipher)
46 | if err != nil {
47 | t.Fatal(err)
48 | }
49 |
50 | testParse(t, app)
51 |
52 | _, err = OpenFromYazFile("/path/not-exists", nil)
53 | assert.NotNil(t, err)
54 | }
55 |
56 | func TestOpenFromYaz(t *testing.T) {
57 | root := os.Getenv("GOU_TEST_APPLICATION")
58 | aesCipher := ciphers.NewAES([]byte("0123456789123456"))
59 |
60 | file, err := yaz.Pack(root, aesCipher)
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 | defer os.Remove(file)
65 |
66 | reader, err := os.Open(file)
67 | if err != nil {
68 | t.Fatal(err)
69 | }
70 | defer reader.Close()
71 |
72 | app, err := OpenFromYaz(reader, file, aesCipher)
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | testParse(t, app)
78 |
79 | app, err = OpenFromYazCache(file, aesCipher)
80 | if err != nil {
81 | t.Fatal(err)
82 | }
83 | testParse(t, app)
84 | }
85 |
86 | func testParse(t *testing.T, app Application) {
87 |
88 | data, err := app.Read("app.yao")
89 | if err != nil {
90 | t.Fatal(err)
91 | }
92 |
93 | v := maps.MapStr{}
94 | err = Parse("app.yao", data, &v)
95 | if err != nil {
96 | t.Fatal(err)
97 | }
98 | assert.Equal(t, "::Demo Application", v.Get("name"))
99 |
100 | // Error
101 | err = Parse("app.yao", []byte(`{"nade":"s}`), &v)
102 | assert.NotNil(t, err)
103 |
104 | // Yaml
105 | v = maps.MapStr{}
106 | err = Parse("test.yml", []byte(`name: "::Demo Application"`), &v)
107 | if err != nil {
108 | t.Fatal(err)
109 | }
110 | assert.Equal(t, "::Demo Application", v.Get("name"))
111 |
112 | // Error ext does not support
113 | err = Parse("app.xls", []byte(`name: "::Demo Application"`), &v)
114 | assert.NotNil(t, err)
115 | }
116 |
--------------------------------------------------------------------------------
/application/disk/fs.go:
--------------------------------------------------------------------------------
1 | package disk
2 |
3 | import (
4 | "net/http"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | // Dir is the public path
10 | type Dir string
11 |
12 | // Root return the root path
13 | func (disk *Disk) Root() string {
14 | return disk.root
15 | }
16 |
17 | // FS returns a http.FileSystem that serves files from the public directory
18 | func (disk *Disk) FS(root string) http.FileSystem {
19 | return Dir(filepath.Join(disk.root, root))
20 | }
21 |
22 | // Open implements FileSystem using os.Open, opening files for reading rooted
23 | func (dir Dir) Open(name string) (http.File, error) {
24 | return os.Open(filepath.Join(string(dir), name))
25 | }
26 |
--------------------------------------------------------------------------------
/application/types.go:
--------------------------------------------------------------------------------
1 | package application
2 |
3 | import "net/http"
4 |
5 | // Application the application interface
6 | type Application interface {
7 | Walk(path string, handler func(root, filename string, isdir bool) error, patterns ...string) error
8 | Read(name string) ([]byte, error)
9 | Write(name string, content []byte) error
10 | Remove(name string) error
11 | Exists(name string) (bool, error)
12 | Watch(handler func(event string, name string), interrupt chan uint8) error
13 | Glob(pattern string) (matches []string, err error)
14 |
15 | Root() string
16 | FS(root string) http.FileSystem
17 | }
18 |
19 | // Pack the application pack
20 | type Pack struct {
21 | Name string `json:"name,omitempty"`
22 | Environments map[string]string `json:"environments,omitempty"`
23 | }
24 |
--------------------------------------------------------------------------------
/application/yaz/ciphers/aes.go:
--------------------------------------------------------------------------------
1 | package ciphers
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "io"
8 | )
9 |
10 | // AES is a type that represents the AES cipher.
11 | type AES struct {
12 | key []byte
13 | }
14 |
15 | // NewAES creates a new AES cipher.
16 | func NewAES(key []byte) AES {
17 | return AES{key: key}
18 | }
19 |
20 | // Encrypt encrypts a byte slice.
21 | func (aesCipher AES) Encrypt(reader io.Reader, writer io.Writer) error {
22 | const blockSize = aes.BlockSize
23 | key := make([]byte, blockSize)
24 | copy(key, aesCipher.key)
25 |
26 | var iv [blockSize]byte
27 | block, err := aes.NewCipher(key)
28 | if err != nil {
29 | return err
30 | }
31 |
32 | stream := cipher.NewCFBEncrypter(block, iv[:])
33 | buf := make([]byte, 4096)
34 | for {
35 | n, err := reader.Read(buf)
36 | if err != nil && err != io.EOF {
37 | return err
38 | }
39 | if n == 0 {
40 | break
41 | }
42 | stream.XORKeyStream(buf[:n], buf[:n])
43 | if _, err := writer.Write(buf[:n]); err != nil {
44 | return err
45 | }
46 | }
47 |
48 | return nil
49 | }
50 |
51 | // Decrypt decrypts a byte slice.
52 | func (aesCipher AES) Decrypt(reader io.Reader, writer io.Writer) error {
53 |
54 | const blockSize = aes.BlockSize
55 | key := make([]byte, blockSize)
56 | copy(key, aesCipher.key)
57 |
58 | var iv [blockSize]byte
59 | block, err := aes.NewCipher(key)
60 | if err != nil {
61 | return err
62 | }
63 |
64 | stream := cipher.NewCFBDecrypter(block, iv[:])
65 | buf := make([]byte, 4096)
66 |
67 | for {
68 | n, err := reader.Read(buf)
69 | if err != nil && err != io.EOF {
70 | return err
71 | }
72 | if n == 0 {
73 | break
74 | }
75 | stream.XORKeyStream(buf[:n], buf[:n])
76 | if _, err := writer.Write(buf[:n]); err != nil {
77 | return err
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | // padding
84 | func (aesCipher AES) pkcs5Padding(ciphertext []byte, blockSize int) []byte {
85 | padding := blockSize - len(ciphertext)%blockSize
86 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
87 | return append(ciphertext, padtext...)
88 | }
89 |
90 | // unpading
91 | func (aesCipher AES) pkcs5UnPadding(origData []byte) []byte {
92 | length := len(origData)
93 | unpadding := int(origData[length-1])
94 | return origData[:(length - unpadding)]
95 | }
96 |
--------------------------------------------------------------------------------
/application/yaz/ciphers/ase_test.go:
--------------------------------------------------------------------------------
1 | package ciphers
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestAES(t *testing.T) {
11 | data := []byte("hello world")
12 | aes := NewAES([]byte("0123456789123456"))
13 | reader := bytes.NewReader(data)
14 | buffer := &bytes.Buffer{}
15 | err := aes.Encrypt(reader, buffer)
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | encrypted := buffer.Bytes()
20 |
21 | buffer = &bytes.Buffer{}
22 | err = aes.Decrypt(bytes.NewReader(encrypted), buffer)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | decrypted := buffer.Bytes()
27 | assert.Equal(t, data, decrypted)
28 | }
29 |
--------------------------------------------------------------------------------
/application/yaz/fs.go:
--------------------------------------------------------------------------------
1 | package yaz
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | // Dir is the public path
12 | type Dir struct {
13 | path string
14 | cipher Cipher
15 | }
16 |
17 | // File is the file
18 | type File struct {
19 | *os.File
20 | cipher Cipher
21 | buff []byte
22 | }
23 |
24 | // Root return the root path
25 | func (yaz *Yaz) Root() string {
26 | return yaz.root
27 | }
28 |
29 | // FS returns a http.FileSystem that serves files from the public directory
30 | func (yaz *Yaz) FS(root string) http.FileSystem {
31 | return Dir{path: filepath.Join(yaz.root, root), cipher: yaz.cipher}
32 | }
33 |
34 | // Open implements FileSystem using os.Open, opening files for reading rooted
35 | func (dir Dir) Open(name string) (http.File, error) {
36 |
37 | f, err := os.Open(filepath.Join(dir.path, name))
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | // decrypt file
43 | buff := &bytes.Buffer{}
44 | ext := strings.TrimPrefix(filepath.Ext(name), ".")
45 | if dir.cipher != nil && encryptFiles[ext] {
46 | dir.cipher.Decrypt(f, buff)
47 | }
48 |
49 | return &File{File: f, buff: buff.Bytes(), cipher: dir.cipher}, nil
50 | }
51 |
52 | // Read reads up to len(p) bytes into p.
53 | func (file *File) Read(p []byte) (n int, err error) {
54 |
55 | ext := strings.TrimPrefix(filepath.Ext(file.Name()), ".")
56 | if file.cipher != nil && encryptFiles[ext] {
57 | if len(file.buff) == 0 {
58 | return 0, nil
59 | }
60 | n = copy(p, file.buff)
61 | file.buff = file.buff[n:]
62 | return n, nil
63 | }
64 |
65 | return file.File.Read(p)
66 | }
67 |
68 | // Close closes the File, rendering it unusable for I/O.
69 | func (file *File) Close() error {
70 | file.buff = nil
71 | return file.File.Close()
72 | }
73 |
--------------------------------------------------------------------------------
/application/yaz/types.go:
--------------------------------------------------------------------------------
1 | package yaz
2 |
3 | import "io"
4 |
5 | // Yaz is a type that represents a package.
6 | type Yaz struct {
7 | root string // the app root (temp dir)
8 | cipher Cipher // the cipher interface
9 | }
10 |
11 | // Cipher is a type that represents a cipher.
12 | type Cipher interface {
13 | Encrypt(reader io.Reader, writer io.Writer) error
14 | Decrypt(reader io.Reader, writer io.Writer) error
15 | }
16 |
--------------------------------------------------------------------------------
/cast/http.go:
--------------------------------------------------------------------------------
1 | package cast
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/url"
7 | "strings"
8 | )
9 |
10 | // AnyToHeaders cast to http.Header
11 | func AnyToHeaders(v interface{}) (http.Header, error) {
12 | values, err := AnyToURLValues(v)
13 | if err != nil {
14 | return nil, err
15 | }
16 | return http.Header(values), nil
17 | }
18 |
19 | // AnyToURLValues cast to url.Values
20 | func AnyToURLValues(v interface{}) (url.Values, error) {
21 |
22 | values := url.Values{}
23 | if v == nil {
24 | return values, nil
25 | }
26 |
27 | switch input := v.(type) {
28 |
29 | // k1=v1&k2=v2&k3=v3, ?k1=v1&k2=v2&k3=v3
30 | case string:
31 | input = strings.TrimPrefix(input, "?")
32 | query, err := url.ParseQuery(input)
33 | if err != nil {
34 | return nil, err
35 | }
36 | return query, nil
37 |
38 | // {"k1":"v1", "k2":1, "k3":true, "k4":0.618}
39 | case map[string]interface{}:
40 | for key, val := range input {
41 | values.Add(key, fmt.Sprintf("%v", val))
42 | }
43 | return values, nil
44 |
45 | // {"k1": "v1", "k2": "v2"},
46 | case map[string]string:
47 | for key, val := range input {
48 | values.Add(key, val)
49 | }
50 | return values, nil
51 |
52 | // [{"k1": "v1"}, {"k1": "v11"}, {"k2": 1}, {"k3": true}, {"k4": 0.618}]
53 | case []map[string]interface{}:
54 | err := ArrayToURLValues(values, input)
55 | if err != nil {
56 | return nil, err
57 | }
58 | return values, nil
59 |
60 | // [{"k1": "v1"}, {"k1": "v11"}, {"k2": "v2"}]
61 | case []map[string]string:
62 | err := ArrayToURLValues(values, input)
63 | if err != nil {
64 | return nil, err
65 | }
66 | return values, nil
67 |
68 | // ["k1=v1","k1"="v11","k2"="v2"]
69 | case []string:
70 | err := ArrayToURLValues(values, input)
71 | if err != nil {
72 | return nil, err
73 | }
74 | return values, nil
75 |
76 | // ["k1=v1", "k1=v11", {"k2": 1}, {"k3": true}, "k2=v2"]
77 | case []interface{}:
78 | err := ArrayToURLValues(values, input)
79 | if err != nil {
80 | return nil, err
81 | }
82 | return values, nil
83 | }
84 |
85 | return nil, fmt.Errorf("Unknown type %#v", v)
86 | }
87 |
88 | // ArrayToURLValues cast array to url values
89 | func ArrayToURLValues[T string | interface{} | map[string]interface{} | map[string]string](values url.Values, input []T) error {
90 | for _, item := range input {
91 | vals, err := AnyToURLValues(item)
92 | if err != nil {
93 | return err
94 | }
95 | MergeURLValues(values, vals)
96 | }
97 | return nil
98 | }
99 |
100 | // MergeURLValues merge URL Values
101 | func MergeURLValues(values, new url.Values) {
102 | for k, val := range new {
103 | for _, v := range val {
104 | values.Add(k, v)
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/connector/moapi/moapi.go:
--------------------------------------------------------------------------------
1 | package moapi
2 |
3 | import (
4 | "github.com/yaoapp/gou/application"
5 | "github.com/yaoapp/gou/helper"
6 | "github.com/yaoapp/xun/dbal/query"
7 | "github.com/yaoapp/xun/dbal/schema"
8 | )
9 |
10 | // Connector connector
11 | type Connector struct {
12 | id string
13 | file string
14 | Name string `json:"name"`
15 | Options Options `json:"options"`
16 | }
17 |
18 | // Options the redis connector option
19 | type Options struct {
20 | Proxy string `json:"proxy,omitempty"`
21 | Model string `json:"model,omitempty"`
22 | Key string `json:"key"`
23 | }
24 |
25 | // Register the connections from dsl
26 | func (o *Connector) Register(file string, id string, dsl []byte) error {
27 | o.id = id
28 | o.file = file
29 | err := application.Parse(file, dsl, o)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | o.Options.Proxy = helper.EnvString(o.Options.Proxy)
35 | o.Options.Model = helper.EnvString(o.Options.Model)
36 | o.Options.Key = helper.EnvString(o.Options.Key)
37 | return nil
38 | }
39 |
40 | // Is the connections from dsl
41 | func (o *Connector) Is(typ int) bool {
42 | return 6 == typ || 8 == typ
43 | }
44 |
45 | // ID get connector id
46 | func (o *Connector) ID() string {
47 | return o.id
48 | }
49 |
50 | // Query get connector query interface
51 | func (o *Connector) Query() (query.Query, error) {
52 | return nil, nil
53 | }
54 |
55 | // Schema get connector schema interface
56 | func (o *Connector) Schema() (schema.Schema, error) {
57 | return nil, nil
58 | }
59 |
60 | // Close connections
61 | func (o *Connector) Close() error {
62 | return nil
63 | }
64 |
65 | // Setting get the connection setting
66 | func (o *Connector) Setting() map[string]interface{} {
67 |
68 | host := "https://api.moapi.ai"
69 | if o.Options.Proxy != "" {
70 | host = o.Options.Proxy
71 | }
72 |
73 | return map[string]interface{}{
74 | "host": host,
75 | "key": o.Options.Key,
76 | "model": o.Options.Model,
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/connector/openai/openai.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "github.com/yaoapp/gou/application"
5 | "github.com/yaoapp/gou/helper"
6 | "github.com/yaoapp/xun/dbal/query"
7 | "github.com/yaoapp/xun/dbal/schema"
8 | )
9 |
10 | // Connector connector
11 | type Connector struct {
12 | id string
13 | file string
14 | Name string `json:"name"`
15 | Options Options `json:"options"`
16 | }
17 |
18 | // Options the redis connector option
19 | type Options struct {
20 | Proxy string `json:"proxy,omitempty"`
21 | Model string `json:"model,omitempty"`
22 | Key string `json:"key"`
23 | Azure string `json:"azure,omitempty"` // "true" or "false"
24 | }
25 |
26 | // Register the connections from dsl
27 | func (o *Connector) Register(file string, id string, dsl []byte) error {
28 | o.id = id
29 | o.file = file
30 | err := application.Parse(file, dsl, o)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | o.Options.Proxy = helper.EnvString(o.Options.Proxy)
36 | o.Options.Model = helper.EnvString(o.Options.Model)
37 | o.Options.Key = helper.EnvString(o.Options.Key)
38 | o.Options.Azure = helper.EnvString(o.Options.Azure)
39 | return nil
40 | }
41 |
42 | // Is the connections from dsl
43 | func (o *Connector) Is(typ int) bool {
44 | return 6 == typ
45 | }
46 |
47 | // ID get connector id
48 | func (o *Connector) ID() string {
49 | return o.id
50 | }
51 |
52 | // Query get connector query interface
53 | func (o *Connector) Query() (query.Query, error) {
54 | return nil, nil
55 | }
56 |
57 | // Schema get connector schema interface
58 | func (o *Connector) Schema() (schema.Schema, error) {
59 | return nil, nil
60 | }
61 |
62 | // Close connections
63 | func (o *Connector) Close() error {
64 | return nil
65 | }
66 |
67 | // Setting get the connection setting
68 | func (o *Connector) Setting() map[string]interface{} {
69 |
70 | host := "https://api.openai.com"
71 | if o.Options.Proxy != "" {
72 | host = o.Options.Proxy
73 | }
74 |
75 | return map[string]interface{}{
76 | "host": host,
77 | "key": o.Options.Key,
78 | "model": o.Options.Model,
79 | "azure": o.Options.Azure,
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/connector/types.go:
--------------------------------------------------------------------------------
1 | package connector
2 |
3 | import (
4 | "github.com/yaoapp/xun/dbal/query"
5 | "github.com/yaoapp/xun/dbal/schema"
6 | )
7 |
8 | const (
9 | // DATABASE the database connector (mysql, pgsql, oracle, sqlite ... )
10 | DATABASE = iota + 1
11 |
12 | // REDIS the redis connector
13 | REDIS
14 |
15 | // MONGO the mongodb connector
16 | MONGO
17 |
18 | // ELASTICSEARCH the elasticsearch connector
19 | ELASTICSEARCH
20 |
21 | // KAFKA the kafka connector
22 | KAFKA
23 |
24 | // OPENAI the openai connector
25 | OPENAI
26 |
27 | // WEAVIATE the weaviate connector
28 | WEAVIATE
29 |
30 | // MOAPI the moapi connector
31 | MOAPI
32 |
33 | // SCRIPT ? the script connector ( difference with widget ?)
34 | SCRIPT
35 | )
36 |
37 | var types = map[string]int{
38 | "mysql": DATABASE,
39 | "sqlite": DATABASE,
40 | "sqlite3": DATABASE,
41 | "postgres": DATABASE,
42 | "oracle": DATABASE,
43 | "redis": REDIS,
44 | "mongo": MONGO,
45 | "elasticsearch": ELASTICSEARCH,
46 | "es": ELASTICSEARCH,
47 | "kafka": KAFKA,
48 | "openai": OPENAI,
49 | "weaviate": WEAVIATE,
50 | "script": SCRIPT, // ?
51 | "moapi": MOAPI,
52 | }
53 |
54 | // Connector the connector interface
55 | type Connector interface {
56 | Register(file string, id string, dsl []byte) error
57 | Query() (query.Query, error)
58 | Schema() (schema.Schema, error)
59 | Close() error
60 | ID() string
61 | Is(int) bool
62 | Setting() map[string]interface{}
63 | }
64 |
65 | // Option the option interface
66 | type Option struct {
67 | Label string `json:"label,omitempty"`
68 | Value string `json:"value,omitempty"`
69 | }
70 |
71 | // DSL the connector DSL
72 | type DSL struct {
73 | ID string `json:"-"`
74 | Type string `json:"type"`
75 | Name string `json:"name,omitempty"`
76 | Label string `json:"label,omitempty"`
77 | Version string `json:"version,omitempty"`
78 | Options map[string]interface{} `json:"options,omitempty"`
79 | }
80 |
--------------------------------------------------------------------------------
/diff/process.go:
--------------------------------------------------------------------------------
1 | package diff
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | )
7 |
8 | // ProcessPatch Compare two strings and return the patch string
9 | func ProcessPatch(p *process.Process) interface{} {
10 | p.ValidateArgNums(3)
11 | text1 := p.ArgsString(0)
12 | text2 := p.ArgsString(1)
13 | checklines := p.ArgsBool(2)
14 | return PatchString(text1, text2, checklines)
15 | }
16 |
17 | // ProcessPatchApply Apply a patch string to text
18 | func ProcessPatchApply(p *process.Process) interface{} {
19 | p.ValidateArgNums(2)
20 | text := p.ArgsString(0)
21 | patch := p.ArgsString(1)
22 | applied, _, err := PatchApplyString(text, patch)
23 | if err != nil {
24 | exception.New("Patch apply error: %s", 500, err).Throw()
25 | }
26 | return applied
27 | }
28 |
29 | // ProcessReplace Replace text using a patch string
30 | func ProcessReplace(p *process.Process) interface{} {
31 | p.ValidateArgNums(2)
32 | text := p.ArgsString(0)
33 | patch := p.ArgsString(1)
34 | applied, err := Replace(text, patch)
35 | if err != nil {
36 | exception.New("Replace error: %s", 500, err).Throw()
37 | }
38 | return applied
39 | }
40 |
41 | func init() {
42 | process.Register("diff.Patch", ProcessPatch)
43 | process.Register("diff.Apply", ProcessPatchApply)
44 | process.Register("diff.Replace", ProcessReplace)
45 | }
46 |
--------------------------------------------------------------------------------
/dns/dsn_test.go:
--------------------------------------------------------------------------------
1 | package dns
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "io/ioutil"
7 | "net/http"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/yaoapp/kun/utils"
13 | )
14 |
15 | func TestLookupIP(t *testing.T) {
16 | _, err := LookupIP("github.com", true)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 |
21 | res, err := LookupIP("api.mch.weixin.qq.com", true)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | assert.Contains(t, strings.Join(res, ","), ":")
27 | res, err = LookupIP("api.mch.weixin.qq.com", false)
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 | assert.NotContains(t, strings.Join(res, ","), ":")
32 | }
33 |
34 | func TestLookupIPCaches(t *testing.T) {
35 | ips, err := LookupIP("github.com", true)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 |
40 | assert.Equal(t, ips, caches["github.com_true"])
41 | utils.Dump(ips)
42 |
43 | _, has := caches["github.com_false"]
44 | assert.Equal(t, false, has)
45 |
46 | ips, err = LookupIP("github.com", false)
47 | if err != nil {
48 | t.Fatal(err)
49 | }
50 | utils.Dump(ips)
51 | assert.Equal(t, ips, caches["github.com_false"])
52 |
53 | }
54 |
55 | func TestLookupIPHostIsIP(t *testing.T) {
56 | ips, err := LookupIP("127.0.0.1", true)
57 | if err != nil {
58 | t.Fatal(err)
59 | }
60 | assert.Equal(t, ips, []string{"127.0.0.1"})
61 | }
62 |
63 | func TestDialContext(t *testing.T) {
64 | var body []byte
65 | req, err := http.NewRequest("GET", "https://api.github.com/users/yaoapp", bytes.NewBuffer(body))
66 | tr := &http.Transport{
67 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
68 | DialContext: DialContext(),
69 | }
70 | client := &http.Client{Transport: tr}
71 | resp, err := client.Do(req)
72 | if err != nil {
73 | t.Fatal(err)
74 | }
75 | defer resp.Body.Close()
76 | body, err = ioutil.ReadAll(resp.Body) // response body is []byte
77 | if err != nil {
78 | t.Fatal(err)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/dns/types.go:
--------------------------------------------------------------------------------
1 | package dns
2 |
--------------------------------------------------------------------------------
/encoding/base64/base64.go:
--------------------------------------------------------------------------------
1 | package base64
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 |
7 | "github.com/yaoapp/gou/process"
8 | "github.com/yaoapp/kun/exception"
9 | )
10 |
11 | // Package base64 implements base64 encoding as specified by RFC 4648.
12 |
13 | // ProcessEncode base64 Encode
14 | func ProcessEncode(process *process.Process) interface{} {
15 | process.ValidateArgNums(1)
16 | data := []byte(fmt.Sprintf("%v", process.Args[0]))
17 | return base64.StdEncoding.EncodeToString(data)
18 | }
19 |
20 | // ProcessDecode base64 Decode
21 | func ProcessDecode(process *process.Process) interface{} {
22 | process.ValidateArgNums(1)
23 | str := process.ArgsString(0)
24 | data, err := base64.StdEncoding.DecodeString(str)
25 | if err != nil {
26 | exception.New("Base64 decode error: %s", 500, err).Throw()
27 | }
28 | return string(data)
29 | }
30 |
31 | // processDecodeBinary
32 | func processDecodeBinary(process *process.Process) interface{} {
33 | process.ValidateArgNums(1)
34 | str := process.ArgsString(0)
35 | data, err := base64.StdEncoding.DecodeString(str)
36 | if err != nil {
37 | exception.New("Base64 decode error: %s", 500, err).Throw()
38 | }
39 | return data
40 | }
41 |
--------------------------------------------------------------------------------
/encoding/base64_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "encoding/base64"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | )
10 |
11 | func TestBase64Encode(t *testing.T) {
12 | data := "SomeData"
13 | res, err := process.New("encoding.base64.Encode", data).Exec()
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | v, err := base64.StdEncoding.DecodeString(res.(string))
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | assert.Equal(t, string(v), data)
22 | }
23 |
24 | func TestBase64Decode(t *testing.T) {
25 | data := base64.StdEncoding.EncodeToString([]byte("SomeData"))
26 | res, err := process.New("encoding.base64.Decode", data).Exec()
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 | assert.Equal(t, "SomeData", string(res.(string)))
31 | }
32 |
--------------------------------------------------------------------------------
/encoding/encoding.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "github.com/yaoapp/gou/encoding/base64"
5 | "github.com/yaoapp/gou/encoding/hex"
6 | "github.com/yaoapp/gou/encoding/json"
7 | "github.com/yaoapp/gou/encoding/xml"
8 | "github.com/yaoapp/gou/encoding/yaml"
9 | "github.com/yaoapp/gou/process"
10 | )
11 |
12 | func init() {
13 | process.Register("encoding.base64.Encode", base64.ProcessEncode)
14 | process.Register("encoding.base64.Decode", base64.ProcessDecode)
15 | process.Register("encoding.hex.Encode", hex.ProcessEncode)
16 | process.Register("encoding.hex.Decode", hex.ProcessDecode)
17 | process.Register("encoding.json.Encode", json.ProcessEncode)
18 | process.Register("encoding.json.Decode", json.ProcessDecode)
19 | process.Register("encoding.json.Repair", json.ProcessRepair)
20 | process.Register("encoding.json.Parse", json.ProcessParse)
21 | process.Register("encoding.yaml.Encode", yaml.ProcessEncode)
22 | process.Register("encoding.yaml.Decode", yaml.ProcessDecode)
23 | process.Register("encoding.xml.Encode", xml.ProcessEncode)
24 | process.Register("encoding.xml.Decode", xml.ProcessDecode)
25 | }
26 |
--------------------------------------------------------------------------------
/encoding/hex/hex.go:
--------------------------------------------------------------------------------
1 | package hex
2 |
3 | import (
4 | "encoding/hex"
5 | "fmt"
6 |
7 | "github.com/yaoapp/gou/process"
8 | "github.com/yaoapp/kun/exception"
9 | )
10 |
11 | // ProcessEncode hex Encode
12 | func ProcessEncode(process *process.Process) interface{} {
13 | process.ValidateArgNums(1)
14 | data := []byte(fmt.Sprintf("%v", process.Args[0]))
15 | return hex.EncodeToString(data)
16 | }
17 |
18 | // ProcessDecode hex Decode
19 | func ProcessDecode(process *process.Process) interface{} {
20 | process.ValidateArgNums(1)
21 | str := process.ArgsString(0)
22 | data, err := hex.DecodeString(str)
23 | if err != nil {
24 | exception.New("Base64 decode error: %s", 500, err).Throw()
25 | }
26 | return string(data)
27 | }
28 |
--------------------------------------------------------------------------------
/encoding/hex_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | )
10 |
11 | func TestHexEncode(t *testing.T) {
12 | data := "SomeData"
13 | res, err := process.New("encoding.hex.Encode", data).Exec()
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | v, err := hex.DecodeString(res.(string))
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | assert.Equal(t, string(v), data)
22 | }
23 |
24 | func TestHexDecode(t *testing.T) {
25 | data := hex.EncodeToString([]byte("SomeData"))
26 | res, err := process.New("encoding.hex.Decode", data).Exec()
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 | assert.Equal(t, "SomeData", string(res.(string)))
31 | }
32 |
--------------------------------------------------------------------------------
/encoding/json/json.go:
--------------------------------------------------------------------------------
1 | package json
2 |
3 | import (
4 | jsoniter "github.com/json-iterator/go"
5 | "github.com/kaptinlin/jsonrepair"
6 | "github.com/yaoapp/gou/process"
7 | "github.com/yaoapp/kun/exception"
8 | )
9 |
10 | // ProcessEncode json Encode
11 | func ProcessEncode(process *process.Process) interface{} {
12 | process.ValidateArgNums(1)
13 | res, err := jsoniter.Marshal(process.Args[0])
14 | if err != nil {
15 | exception.New("JSON decode error: %s", 500, err).Throw()
16 | }
17 | return string(res)
18 | }
19 |
20 | // ProcessDecode json Decode
21 | func ProcessDecode(process *process.Process) interface{} {
22 | process.ValidateArgNums(1)
23 | data := process.ArgsString(0)
24 | var res interface{}
25 | err := jsoniter.UnmarshalFromString(data, &res)
26 | if err != nil {
27 | exception.New("JSON decode error: %s", 500, err).Throw()
28 | }
29 | return res
30 | }
31 |
32 | // ProcessRepair json Repair
33 | func ProcessRepair(process *process.Process) interface{} {
34 | process.ValidateArgNums(1)
35 | data := process.ArgsString(0)
36 | repaired, err := jsonrepair.JSONRepair(data)
37 | if err != nil {
38 | exception.New("JSON repair error: %s", 500, err).Throw()
39 | }
40 | return string(repaired)
41 | }
42 |
43 | // ProcessParse json Parse
44 | func ProcessParse(process *process.Process) interface{} {
45 | process.ValidateArgNums(1)
46 | data := process.ArgsString(0)
47 | var res interface{}
48 | err := jsoniter.UnmarshalFromString(data, &res)
49 | if err != nil {
50 | repaired, errRepair := jsonrepair.JSONRepair(data)
51 | if errRepair != nil {
52 | exception.New("JSON parse error: %s", 500, err).Throw()
53 | }
54 |
55 | // Retry
56 | errRepair = jsoniter.UnmarshalFromString(repaired, &res)
57 | if errRepair != nil {
58 | exception.New("JSON parse error: %s", 500, err).Throw()
59 | }
60 | }
61 | return res
62 | }
63 |
--------------------------------------------------------------------------------
/encoding/json_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "testing"
5 |
6 | jsoniter "github.com/json-iterator/go"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | )
10 |
11 | func TestJSONEncode(t *testing.T) {
12 | data := []string{"foo", "bar"}
13 | res, err := process.New("encoding.json.Encode", data).Exec()
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 |
18 | new := []string{}
19 | err = jsoniter.Unmarshal([]byte(res.(string)), &new)
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | assert.Equal(t, new, data)
24 | }
25 |
26 | func TestJSONDecode(t *testing.T) {
27 | data := `["foo", "bar"]`
28 | res, err := process.New("encoding.json.Decode", data).Exec()
29 | if err != nil {
30 | t.Fatal(err)
31 | }
32 | assert.Equal(t, []interface{}{"foo", "bar"}, res)
33 | }
34 |
35 | func TestJSONRepair(t *testing.T) {
36 | data := `{"name": "foo", "items": ["bar" "baz"]}` // Invalid JSON missing comma
37 | res, err := process.New("encoding.json.Repair", data).Exec()
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 |
42 | // Verify the repaired JSON is valid
43 | var obj map[string]interface{}
44 | err = jsoniter.UnmarshalFromString(res.(string), &obj)
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 |
49 | assert.Equal(t, "foo", obj["name"])
50 | assert.Equal(t, []interface{}{"bar", "baz"}, obj["items"])
51 | }
52 |
53 | func TestJSONParse(t *testing.T) {
54 | // Test case 1: Valid JSON
55 | data1 := `{"name": "foo", "items": ["bar", "baz"]}`
56 | res1, err := process.New("encoding.json.Parse", data1).Exec()
57 | if err != nil {
58 | t.Fatal(err)
59 | }
60 | assert.Equal(t, "foo", res1.(map[string]interface{})["name"])
61 | assert.Equal(t, []interface{}{"bar", "baz"}, res1.(map[string]interface{})["items"])
62 |
63 | // Test case 2: Invalid JSON that needs repair
64 | data2 := `{"name": "foo", "items": ["bar" "baz"]}`
65 | res2, err := process.New("encoding.json.Parse", data2).Exec()
66 | if err != nil {
67 | t.Fatal(err)
68 | }
69 | assert.Equal(t, "foo", res2.(map[string]interface{})["name"])
70 | assert.Equal(t, []interface{}{"bar", "baz"}, res2.(map[string]interface{})["items"])
71 | }
72 |
--------------------------------------------------------------------------------
/encoding/xml/xml.go:
--------------------------------------------------------------------------------
1 | package xml
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/yaoapp/gou/process"
9 | "github.com/yaoapp/kun/exception"
10 | )
11 |
12 | // ProcessEncode xml Encode
13 | func ProcessEncode(process *process.Process) interface{} {
14 | process.ValidateArgNums(1)
15 | var data = process.ArgsMap(0)
16 | res, err := mapToXML(data)
17 | if err != nil {
18 | exception.New("XML decode error: %s", 500, err).Throw()
19 | }
20 | return fmt.Sprintf("\n%s\n", res)
21 | }
22 |
23 | // ProcessDecode xml Decode
24 | func ProcessDecode(process *process.Process) interface{} {
25 | process.ValidateArgNums(1)
26 | res, err := xmlToMap(process.ArgsString(0))
27 | if err != nil {
28 | exception.New("XML decode error: %s", 500, err).Throw()
29 | }
30 | return res
31 | }
32 |
33 | func mapToXML(data map[string]interface{}) ([]byte, error) {
34 | type entry struct {
35 | XMLName xml.Name
36 | Value interface{} `xml:",chardata"`
37 | }
38 |
39 | entries := make([]entry, 0, len(data))
40 | for key, value := range data {
41 | entries = append(entries, entry{XMLName: xml.Name{Local: key}, Value: value})
42 | }
43 |
44 | xmlData, err := xml.MarshalIndent(entries, " ", " ")
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | return xmlData, nil
50 | }
51 |
52 | func xmlToMap(data string) (map[string]interface{}, error) {
53 | decoder := NewDecoder(strings.NewReader(data))
54 | result, err := decoder.Decode()
55 | if err != nil {
56 | return nil, err
57 | }
58 | return result, nil
59 | }
60 |
--------------------------------------------------------------------------------
/encoding/yaml/yaml.go:
--------------------------------------------------------------------------------
1 | package yaml
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | "gopkg.in/yaml.v3"
7 | )
8 |
9 | // ProcessEncode json Encode
10 | func ProcessEncode(process *process.Process) interface{} {
11 | process.ValidateArgNums(1)
12 | res, err := yaml.Marshal(process.Args[0])
13 | if err != nil {
14 | exception.New("JSON decode error: %s", 500, err).Throw()
15 | }
16 | return string(res)
17 | }
18 |
19 | // ProcessDecode json Decode
20 | func ProcessDecode(process *process.Process) interface{} {
21 | process.ValidateArgNums(1)
22 | data := []byte(process.ArgsString(0))
23 | var res interface{}
24 | err := yaml.Unmarshal(data, &res)
25 | if err != nil {
26 | exception.New("YAML decode error: %s", 500, err).Throw()
27 | }
28 | return res
29 | }
30 |
--------------------------------------------------------------------------------
/encoding/yaml_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | "gopkg.in/yaml.v3"
10 | )
11 |
12 | func TestYAMLEncode(t *testing.T) {
13 | data := []string{"foo", "bar"}
14 | res, err := process.New("encoding.yaml.Encode", data).Exec()
15 | if err != nil {
16 | t.Fatal(err)
17 | }
18 |
19 | new := []string{}
20 | err = yaml.Unmarshal([]byte(res.(string)), &new)
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 | assert.Equal(t, new, data)
25 | }
26 |
27 | func TestYAMLDecode(t *testing.T) {
28 | data := fmt.Sprintf("\n- foo\n- bar\n")
29 | res, err := process.New("encoding.yaml.Decode", data).Exec()
30 | if err != nil {
31 | t.Fatal(err)
32 | }
33 | assert.Equal(t, []interface{}{"foo", "bar"}, res)
34 | }
35 |
--------------------------------------------------------------------------------
/flow/exec_test.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/kun/any"
9 | )
10 |
11 | func TestExec(t *testing.T) {
12 | prepare(t)
13 | flow, err := Select("basic")
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 |
18 | now := time.Now()
19 | yesterday := now.AddDate(0, 0, -1).Format("2006-01-02")
20 | tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02")
21 | res, err := flow.Exec(yesterday, tomorrow)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | r := any.Of(res).MapStr().Dot()
27 | assert.Equal(t, yesterday, r.Get("params[0]"))
28 | assert.Equal(t, tomorrow, r.Get("params[1]"))
29 | assert.Equal(t, "U1", r.Get("data.query[0].name"))
30 | assert.Equal(t, "Duck", r.Get("data.categories[2].name"))
31 | assert.Equal(t, "U3", r.Get("data.users[1].name"))
32 | }
33 |
34 | // func TestExecQuery(t *testing.T) {
35 | // flow, _ := Select("stat")
36 | // res := maps.Of(flow.Exec("2000-01-02", "2050-12-31", 1, 2).(map[string]interface{}))
37 | // // utils.Dump(res)
38 | // assert.Equal(t, res.Dot().Get("data.manus.0.id"), int64(1))
39 | // assert.Equal(t, res.Dot().Get("data.manus.0.short_name"), "云道天成")
40 | // assert.Equal(t, res.Dot().Get("data.manus.0.type"), "服务商")
41 | // assert.Equal(t, res.Dot().Get("data.manus.1.id"), int64(8))
42 | // assert.Equal(t, res.Dot().Get("data.users.total"), 3)
43 | // assert.Equal(t, res.Dot().Get("data.address.city"), "丰台区")
44 | // assert.Equal(t, res.Dot().Get("params.0"), "2000-01-02")
45 | // }
46 |
47 | // func TestExecArraySet(t *testing.T) {
48 | // args := []map[string]interface{}{
49 | // {"name": "hello", "value": "world"},
50 | // {"name": "foo", "value": "bar"},
51 | // }
52 | // flow, _ := Select("arrayset")
53 | // res := flow.Exec(args)
54 | // utils.Dump(res)
55 | // }
56 |
57 | // func TestExecGlobalSession(t *testing.T) {
58 | // sid := session.ID()
59 | // session.Global().ID(sid).Set("id", 1)
60 | // flow, _ := Select("user.info")
61 | // flow.WithSID(sid).WithGlobal(map[string]interface{}{"foo": "bar"})
62 | // res := maps.Of(flow.Exec().(map[string]interface{})).Dot()
63 | // assert.Equal(t, float64(1), res.Get("ID"))
64 | // assert.Equal(t, float64(1), res.Get("会话信息.id"))
65 | // assert.Equal(t, "admin", res.Get("会话信息.type"))
66 | // assert.Equal(t, "bar", res.Get("全局信息.foo"))
67 | // assert.Equal(t, "bar", res.Get("全局信息.foo"))
68 | // assert.Equal(t, int64(1), res.Get("用户数据.id"))
69 | // assert.Equal(t, "管理员", res.Get("用户数据.name"))
70 | // assert.Equal(t, "admin", res.Get("用户数据.type"))
71 | // assert.Equal(t, "bar", res.Get("脚本数据.global.foo"))
72 | // assert.Equal(t, float64(1), res.Get("脚本数据.session.id"))
73 | // assert.Equal(t, "admin", res.Get("脚本数据.session.type"))
74 | // }
75 |
--------------------------------------------------------------------------------
/flow/flow.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/yaoapp/kun/log"
7 |
8 | "github.com/yaoapp/gou/application"
9 | "github.com/yaoapp/gou/query"
10 | )
11 |
12 | // Flows 已加载工作流列表
13 | var Flows = map[string]*Flow{}
14 |
15 | // Load the flow
16 | func Load(file string, id string) (*Flow, error) {
17 |
18 | data, err := application.App.Read(file)
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | flow := Flow{ID: id, File: file}
24 | err = application.Parse(file, data, &flow)
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | flow.prepare()
30 | Flows[id] = &flow
31 | return Flows[id], nil
32 | }
33 |
34 | // Prepare 预加载 Query DSL
35 | func (flow *Flow) prepare() {
36 |
37 | for i, node := range flow.Nodes {
38 | if node.Query == nil {
39 | continue
40 | }
41 |
42 | if node.Engine == "" {
43 | log.Error("Node %s: 未指定数据查询分析引擎", node.Name)
44 | continue
45 | }
46 |
47 | if engine, has := query.Engines[node.Engine]; has {
48 | var err error
49 | flow.Nodes[i].DSL, err = engine.Load(node.Query)
50 | if err != nil {
51 | log.With(log.F{"query": node.Query}).Error("Node %s: %s 数据分析查询解析错误", node.Name, node.Engine)
52 | }
53 | continue
54 | }
55 | log.Error("Node %s: %s 数据分析引擎尚未注册", node.Name, node.Engine)
56 | }
57 | }
58 |
59 | // Reload 重新载入API
60 | func (flow *Flow) Reload() (*Flow, error) {
61 | new, err := Load(flow.File, flow.Name)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | flow = new
67 | Flows[flow.Name] = new
68 | return flow, nil
69 | }
70 |
71 | // WithSID 设定会话ID
72 | func (flow *Flow) WithSID(sid string) *Flow {
73 | flow.Sid = sid
74 | return flow
75 | }
76 |
77 | // WithGlobal 设定全局变量
78 | func (flow *Flow) WithGlobal(global map[string]interface{}) *Flow {
79 | flow.Global = global
80 | return flow
81 | }
82 |
83 | // Select 读取已加载Flow
84 | func Select(name string) (*Flow, error) {
85 | flow, has := Flows[name]
86 | if !has {
87 | return nil, fmt.Errorf("flows.%s not loaded", name)
88 | }
89 | return flow, nil
90 | }
91 |
--------------------------------------------------------------------------------
/flow/process.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | )
7 |
8 | func init() {
9 | process.Register("flows", processFlows)
10 | }
11 |
12 | // processScripts
13 | func processFlows(process *process.Process) interface{} {
14 |
15 | flow, err := Select(process.ID)
16 | if err != nil {
17 | exception.New("flows.%s not loaded", 404, process.ID).Throw()
18 | return nil
19 | }
20 |
21 | flow.WithGlobal(process.Global).WithSID(process.Sid)
22 |
23 | res, err := flow.Exec(process.Args...)
24 | if err != nil {
25 | exception.New(err.Error(), 500).Throw()
26 | }
27 |
28 | return res
29 | }
30 |
--------------------------------------------------------------------------------
/flow/process_test.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | "github.com/yaoapp/kun/any"
10 | )
11 |
12 | func TestProcess(t *testing.T) {
13 | prepare(t)
14 |
15 | now := time.Now()
16 | yesterday := now.AddDate(0, 0, -1).Format("2006-01-02")
17 | tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02")
18 | p, err := process.Of("flows.basic", yesterday, tomorrow)
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 |
23 | res, err := p.Exec()
24 | r := any.Of(res).MapStr().Dot()
25 | assert.Equal(t, yesterday, r.Get("params[0]"))
26 | assert.Equal(t, tomorrow, r.Get("params[1]"))
27 | assert.Equal(t, "U1", r.Get("data.query[0].name"))
28 | assert.Equal(t, "Duck", r.Get("data.categories[2].name"))
29 | assert.Equal(t, "U3", r.Get("data.users[1].name"))
30 | }
31 |
--------------------------------------------------------------------------------
/flow/types.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/yaoapp/gou/query/share"
7 | )
8 |
9 | // Flow 工作流
10 | type Flow struct {
11 | ID string `json:"-"`
12 | File string `json:"-"`
13 | Name string `json:"name"`
14 | Version string `json:"version"`
15 | Description string `json:"description,omitempty"`
16 | Nodes []Node `json:"nodes,omitempty"`
17 | Output interface{} `json:"output,omitempty"`
18 | Global map[string]interface{} // 全局变量
19 | Sid string // 会话ID
20 | }
21 |
22 | // Node 工作流节点
23 | type Node struct {
24 | Name string `json:"name,omitempty"`
25 | Process string `json:"process,omitempty"`
26 | Engine string `json:"engine,omitempty"` // 数据分析引擎名称
27 | Query interface{} `json:"query,omitempty"` // 数据分析语言 Query Source
28 | DSL share.DSL `json:"-"` // 数据分析语言 Query DSL
29 | Args []interface{} `json:"args,omitempty"`
30 | Outs []interface{} `json:"outs,omitempty"`
31 | }
32 |
33 | // Context 工作流上下文
34 | type Context struct {
35 | In []interface{}
36 | Res map[string]interface{}
37 | Context *context.Context
38 | Cancel context.CancelFunc
39 | }
40 |
--------------------------------------------------------------------------------
/fs/binary/README.md:
--------------------------------------------------------------------------------
1 | Bindata
2 |
--------------------------------------------------------------------------------
/fs/dsl/dsl.go:
--------------------------------------------------------------------------------
1 | package dsl
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "path/filepath"
7 |
8 | "github.com/yaoapp/gou/fs/system"
9 | )
10 |
11 | // File DSL File
12 | type File struct{ *system.File }
13 |
14 | // New create a new File struct
15 | func New(root ...string) *File {
16 | systemFile := system.New(root...)
17 | return &File{File: systemFile}
18 | }
19 |
20 | // WriteFile writes data to the named file, creating it if necessary.
21 | //
22 | // If the file does not exist, WriteFile creates it with permissions perm (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
23 | func (f *File) WriteFile(file string, data []byte, perm uint32) (int, error) {
24 |
25 | var err error
26 | ext := filepath.Ext(file)
27 | if ext == ".json" || ext == ".yao" || ext == ".jsonc" {
28 | data, err = f.Fmt(data)
29 | if err != nil {
30 | return 0, err
31 | }
32 | }
33 | perm = 0644
34 | return f.File.WriteFile(file, data, perm)
35 | }
36 |
37 | // Allow allow rel path
38 | func (f *File) Allow(patterns ...string) *File {
39 | f.File.Allow(patterns...)
40 | return f
41 | }
42 |
43 | // Deny deny rel path
44 | func (f *File) Deny(patterns ...string) *File {
45 | f.File.Deny(patterns...)
46 | return f
47 | }
48 |
49 | // AllowAbs allow abs path
50 | func (f *File) AllowAbs(patterns ...string) *File {
51 | f.File.AllowAbs(patterns...)
52 | return f
53 | }
54 |
55 | // DenyAbs deny abs path
56 | func (f *File) DenyAbs(patterns ...string) *File {
57 | f.File.DenyAbs(patterns...)
58 | return f
59 | }
60 |
61 | // Fmt data
62 | func (f *File) Fmt(data []byte) ([]byte, error) {
63 | var out bytes.Buffer
64 | err := json.Indent(&out, data, "", " ")
65 | if err != nil {
66 | return nil, err
67 | }
68 | return out.Bytes(), nil
69 | }
70 |
--------------------------------------------------------------------------------
/fs/dsl/dsl_test.go:
--------------------------------------------------------------------------------
1 | package dsl
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestDSLWriteFile(t *testing.T) {
12 | data := []byte(`{"foo": "bar", "hello":{ "int": 1, "float": 0.618}}`)
13 | shoud := []byte(`{
14 | "foo": "bar",
15 | "hello": {
16 | "int": 1,
17 | "float": 0.618
18 | }
19 | }`)
20 |
21 | file := "test.json"
22 | fs := New(filepath.Join(os.Getenv("GOU_TEST_APP_ROOT"), "data"))
23 | size, err := fs.WriteFile(file, data, 0644)
24 | assert.Nil(t, err)
25 | assert.Equal(t, 69, size)
26 |
27 | new, err := fs.ReadFile(file)
28 | assert.Nil(t, err)
29 | assert.Equal(t, shoud, new)
30 |
31 | data = []byte(`{"foo": "bar", "hello":{ "int": 1, "float": 0.618`)
32 | size, err = fs.WriteFile(file, data, 0644)
33 | assert.NotNil(t, err)
34 | assert.Equal(t, 0, size)
35 | }
36 |
--------------------------------------------------------------------------------
/fs/mongo/README.md:
--------------------------------------------------------------------------------
1 | Mongo db
2 |
--------------------------------------------------------------------------------
/fs/redis/README.md:
--------------------------------------------------------------------------------
1 | Redis
2 |
--------------------------------------------------------------------------------
/fs/s3/README.md:
--------------------------------------------------------------------------------
1 | S3
2 |
--------------------------------------------------------------------------------
/fs/system/README.md:
--------------------------------------------------------------------------------
1 | System I/O
2 |
--------------------------------------------------------------------------------
/fs/types.go:
--------------------------------------------------------------------------------
1 | package fs
2 |
3 | import (
4 | "io"
5 | "time"
6 | )
7 |
8 | // FileSystem the filesystem io interface
9 | type FileSystem interface {
10 | ReadFile(file string) ([]byte, error)
11 | WriteFile(file string, data []byte, perm uint32) (int, error)
12 | Write(file string, reader io.Reader, perm uint32) (int, error)
13 |
14 | AppendFile(file string, data []byte, perm uint32) (int, error)
15 | Append(file string, reader io.Reader, perm uint32) (int, error)
16 |
17 | InsertFile(file string, offset int64, data []byte, perm uint32) (int, error)
18 | Insert(file string, offset int64, reader io.Reader, perm uint32) (int, error)
19 |
20 | ReadDir(dir string, recursive bool) ([]string, error)
21 | Mkdir(dir string, perm uint32) error
22 | MkdirAll(dir string, perm uint32) error
23 | MkdirTemp(dir string, pattern string) (string, error)
24 | Glob(pattern string) ([]string, error)
25 |
26 | ReadCloser(file string) (io.ReadCloser, error)
27 | WriteCloser(file string, perm uint32) (io.WriteCloser, error)
28 |
29 | Remove(name string) error
30 | RemoveAll(name string) error
31 |
32 | Exists(name string) (bool, error)
33 | Size(name string) (int, error)
34 | Mode(name string) (uint32, error)
35 | ModTime(name string) (time.Time, error)
36 |
37 | Chmod(name string, mode uint32) error
38 | IsDir(name string) bool
39 | IsFile(name string) bool
40 | IsLink(name string) bool
41 |
42 | Move(oldpath string, newpath string) error
43 | Copy(src string, dest string) error
44 |
45 | MimeType(name string) (string, error)
46 |
47 | Root() string
48 |
49 | Walk(path string, handler func(root, filename string, isdir bool) error, patterns ...string) error
50 |
51 | List(path string, types []string, page, pageSize int, filter func(string) bool) ([]string, int, int, error)
52 |
53 | Resize(inputPath, outputPath string, width, height uint) error
54 |
55 | CleanCache()
56 | }
57 |
--------------------------------------------------------------------------------
/fs/xun/README.md:
--------------------------------------------------------------------------------
1 | Save to database
2 |
--------------------------------------------------------------------------------
/helper/helper.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "os"
7 | "strings"
8 |
9 | jsoniter "github.com/json-iterator/go"
10 | "github.com/yaoapp/kun/any"
11 | )
12 |
13 | // 常用函数
14 |
15 | // UnmarshalFile JSON Unmarshal
16 | func UnmarshalFile(file io.Reader, v interface{}) error {
17 | content, err := ReadFile(file)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | return jsoniter.Unmarshal(content, v)
23 | }
24 |
25 | // ReadFile 读取文件内容
26 | func ReadFile(file io.Reader) ([]byte, error) {
27 | buf := new(bytes.Buffer)
28 | _, err := buf.ReadFrom(file)
29 | if err != nil {
30 | return nil, err
31 | }
32 | return buf.Bytes(), nil
33 | }
34 |
35 | // ReadFileString 读取文件内容, 返回String
36 | func ReadFileString(file io.Reader) (string, error) {
37 | buf := new(bytes.Buffer)
38 | _, err := buf.ReadFrom(file)
39 | if err != nil {
40 | return "", err
41 | }
42 | return buf.String(), nil
43 | }
44 |
45 | // EnvString replace $ENV.xxx with the env
46 | func EnvString(key interface{}, defaults ...string) string {
47 | k, ok := key.(string)
48 | if !ok {
49 | if len(defaults) > 0 {
50 | return defaults[0]
51 | }
52 | return ""
53 | }
54 |
55 | if ok && strings.HasPrefix(k, "$ENV.") {
56 | k = strings.TrimPrefix(k, "$ENV.")
57 | v := os.Getenv(k)
58 | if v == "" && len(defaults) > 0 {
59 | return defaults[0]
60 | }
61 | return v
62 | }
63 | return key.(string)
64 | }
65 |
66 | // EnvInt replace $ENV.xxx with the env and cast to the integer
67 | func EnvInt(key interface{}, defaults ...int) int {
68 | if k, ok := key.(string); ok && strings.HasPrefix(k, "$ENV.") {
69 | k = strings.TrimPrefix(k, "$ENV.")
70 | v := os.Getenv(k)
71 | if v == "" {
72 | if len(defaults) > 0 {
73 | return defaults[0]
74 | }
75 | return 0
76 | }
77 | return any.Of(v).CInt()
78 | }
79 |
80 | v, ok := key.(int)
81 | if !ok {
82 | if len(defaults) > 0 {
83 | return defaults[0]
84 | }
85 | return any.Of(key).CInt()
86 | }
87 | return v
88 | }
89 |
--------------------------------------------------------------------------------
/helper/utils_test.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestToString(t *testing.T) {
12 | t.Run("Basic types", func(t *testing.T) {
13 | // Test integers
14 | result := ToString(42)
15 | assert.Equal(t, "42", result)
16 |
17 | // Test float
18 | result = ToString(3.14)
19 | assert.Equal(t, "3.14", result)
20 |
21 | // Test boolean
22 | result = ToString(true)
23 | assert.Equal(t, "true", result)
24 | })
25 |
26 | t.Run("String and bytes", func(t *testing.T) {
27 | // Test string
28 | result := ToString("hello")
29 | assert.Equal(t, "hello", result)
30 |
31 | // Test []byte
32 | result = ToString([]byte("world"))
33 | assert.Equal(t, "world", result)
34 | })
35 |
36 | t.Run("Error type", func(t *testing.T) {
37 | // Test error
38 | err := errors.New("test error")
39 | result := ToString(err)
40 | assert.Equal(t, "test error", result)
41 | })
42 |
43 | t.Run("JSON object", func(t *testing.T) {
44 | // Test JSON object
45 | obj := map[string]interface{}{
46 | "foo": "bar",
47 | "num": 123,
48 | "nested": map[string]interface{}{
49 | "key": "value",
50 | },
51 | }
52 | result := ToString(obj)
53 | assert.Contains(t, result, `"foo": "bar"`)
54 | assert.Contains(t, result, `"num": 123`)
55 | assert.Contains(t, result, `"nested": {`)
56 | assert.Contains(t, result, `"key": "value"`)
57 | })
58 |
59 | t.Run("Multiple values", func(t *testing.T) {
60 | // Test multiple values
61 | result := ToString("first", 123, true)
62 | lines := strings.Split(result, "\n")
63 | assert.Equal(t, 3, len(lines))
64 | assert.Equal(t, "first", lines[0])
65 | assert.Equal(t, "123", lines[1])
66 | assert.Equal(t, "true", lines[2])
67 | })
68 |
69 | t.Run("Array", func(t *testing.T) {
70 | // Test array
71 | arr := []interface{}{"one", 2, true}
72 | result := ToString(arr)
73 | assert.Contains(t, result, `"one"`)
74 | assert.Contains(t, result, `2`)
75 | assert.Contains(t, result, `true`)
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/http/types.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | )
7 |
8 | const (
9 |
10 | // HandlerReturnOk handler return ok
11 | HandlerReturnOk = 1
12 |
13 | // HandlerReturnBreak handler return break
14 | HandlerReturnBreak = 0
15 |
16 | // HandlerReturnError handler return error
17 | HandlerReturnError = -1
18 | )
19 |
20 | // Request HTTP Request
21 | type Request struct {
22 | url string
23 | query url.Values
24 | headers http.Header
25 | files []File
26 | fileBytes []File
27 | data interface{}
28 | }
29 |
30 | // Response HTTP Response
31 | type Response struct {
32 | Status int `json:"status"`
33 | Data interface{} `json:"data"`
34 | Headers http.Header `json:"headers"`
35 | Code int `json:"code"`
36 | Message string `json:"message"`
37 | }
38 |
39 | // File file
40 | type File struct {
41 | Name string `json:"name"`
42 | Path string `json:"path,omitempty"`
43 | Data string `json:"data,omitempty"`
44 | data []byte
45 | }
46 |
--------------------------------------------------------------------------------
/lang/types.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | // Dict the language dictionary
4 | type Dict struct {
5 | Name string
6 | Global Words
7 | Widgets map[string]Words
8 | }
9 |
10 | // Words the language words
11 | type Words map[string]string
12 |
13 | // Lang the language interface
14 | type Lang interface {
15 | Lang(trans func(widgetName string, inst string, value *string) bool)
16 | }
17 |
--------------------------------------------------------------------------------
/model/atomic_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/kun/any"
8 | "github.com/yaoapp/kun/maps"
9 | )
10 |
11 | func TestSave(t *testing.T) {
12 | prepare(t)
13 | defer clean()
14 | pet := Select("pet")
15 | id, err := pet.Save(maps.Map{"name": "Cookie"})
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | assert.Equal(t, 1, any.Of(id).CInt())
20 | }
21 |
--------------------------------------------------------------------------------
/model/index.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/yaoapp/xun/dbal/schema"
5 | )
6 |
7 | // SetIndex 设置索引
8 | func (index Index) SetIndex(table schema.Blueprint) {
9 | switch index.Type {
10 | case "unique":
11 | table.AddUnique(index.Name, index.Columns...)
12 | break
13 | case "index":
14 | table.AddIndex(index.Name, index.Columns...)
15 | break
16 | case "primary":
17 | table.AddPrimary(index.Columns...)
18 | break
19 | case "fulltext":
20 | table.AddFulltext(index.Name, index.Columns...)
21 | break
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/model/url_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "net/url"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestQueryUrlValues(t *testing.T) {
11 | params := url.Values{}
12 | params.Add("select", "name,secret,status,type")
13 | params.Add("with", "mother,addresses")
14 | params.Add("mother.select", "name,mobile,type,status")
15 | params.Add("addresses.select", "province,city,location,status")
16 | params.Add("where.status.eq", "enabled")
17 | params.Add("where.secret.notnull", "")
18 | params.Add("where.resume.null", "")
19 | params.Add("where.mobile.eq", "13900002222")
20 | params.Add("orwhere.mobile.eq", "13900001111")
21 | params.Add("where.mother.friends.status.eq", "enabled")
22 | params.Add("group.types.where.type.eq", "admin")
23 | params.Add("group.types.orwhere.type.eq", "staff")
24 | params.Add("order", "id.desc,name")
25 | param := URLToQueryParam(params)
26 | assert.Equal(t, param.Select, []interface{}{"name", "secret", "status", "type"})
27 | assert.Equal(t, len(param.Wheres), 7)
28 | assert.Equal(t, len(param.Withs), 2)
29 | assert.Equal(t, len(param.Orders), 2)
30 | }
31 |
--------------------------------------------------------------------------------
/plan/types.go:
--------------------------------------------------------------------------------
1 | package plan
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | // Status represents the status of a plan or task
9 | type Status int
10 |
11 | // Status constants for tracking plan and task states
12 | const (
13 | StatusCreated Status = iota
14 | StatusRunning
15 | StatusPaused
16 | StatusCompleted
17 | StatusFailed
18 | StatusDestroyed
19 | StatusUnknown
20 | )
21 |
22 | // Signal represents control signals for tasks
23 | type Signal int
24 |
25 | // Signal constants for task control
26 | const (
27 | SignalPause Signal = iota
28 | SignalResume
29 | SignalStop
30 | )
31 |
32 | // Config represents plan configuration
33 | type Config struct {
34 | // StatusCheckInterval is the interval between status checks
35 | StatusCheckInterval time.Duration
36 | }
37 |
38 | // DefaultConfig returns the default configuration
39 | func DefaultConfig() *Config {
40 | return &Config{
41 | StatusCheckInterval: 10 * time.Millisecond,
42 | }
43 | }
44 |
45 | // Option represents a function that modifies the plan configuration
46 | type Option func(*Config)
47 |
48 | // WithStatusCheckInterval sets the status check interval
49 | func WithStatusCheckInterval(d time.Duration) Option {
50 | return func(c *Config) {
51 | c.StatusCheckInterval = d
52 | }
53 | }
54 |
55 | // Task represents a unit of work in a plan
56 | type Task struct {
57 | ID string
58 | Order int
59 | Fn TaskFunc
60 | Status Status
61 | Data interface{}
62 | Context context.Context
63 | Cancel context.CancelFunc
64 | SignalChan chan Signal
65 | }
66 |
67 | // TaskFunc is the function type that represents a task's execution unit
68 | type TaskFunc func(ctx context.Context, shared SharedSpace, signals <-chan Signal) error
69 |
70 | // Plan represents a collection of tasks with shared space
71 | type Plan struct {
72 | ID string
73 | Tasks map[string]*Task
74 | SharedSpace SharedSpace
75 | Status Status
76 | Context context.Context
77 | Cancel context.CancelFunc
78 | Config *Config
79 | }
80 |
81 | // SharedSpace represents the interface for shared storage space
82 | type SharedSpace interface {
83 | // Set stores a value in the shared space
84 | Set(key string, value interface{}) error
85 |
86 | // Get retrieves a value from the shared space
87 | Get(key string) (interface{}, error)
88 |
89 | // Delete removes a value from the shared space
90 | Delete(key string) error
91 |
92 | // Clear removes all values from the shared space
93 | Clear() error
94 |
95 | // ClearNotify removes all values from the shared space and notifies subscribers
96 | ClearNotify() error
97 |
98 | // Subscribe subscribes to changes in the shared space
99 | Subscribe(key string, callback func(key string, value interface{})) error
100 |
101 | // Unsubscribe removes a subscription
102 | Unsubscribe(key string) error
103 | }
104 |
--------------------------------------------------------------------------------
/plugin/plugin.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "os/exec"
8 |
9 | "github.com/hashicorp/go-hclog"
10 | "github.com/hashicorp/go-plugin"
11 | "github.com/yaoapp/kun/grpc"
12 | )
13 |
14 | // Plugins 已加载插件
15 | var Plugins = map[string]*Plugin{}
16 |
17 | // Create an hclog.Logger
18 | var pluginLogger = hclog.New(&hclog.LoggerOptions{
19 | Name: "plugin",
20 | Output: os.Stdout,
21 | Level: hclog.Error,
22 | })
23 |
24 | // Load a plugin
25 | func Load(file string, id string) (*Plugin, error) {
26 |
27 | // 已载入,如果进程存在杀掉重载
28 | plug, has := Plugins[id]
29 | if has {
30 | if !plug.Client.Exited() {
31 | plug.Client.Kill()
32 | }
33 | }
34 |
35 | // We're a host. Start by launching the plugin process.
36 | client := plugin.NewClient(&plugin.ClientConfig{
37 | HandshakeConfig: grpc.Handshake,
38 | Plugins: grpc.PluginMap,
39 | Cmd: exec.Command(file),
40 | AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
41 | Logger: pluginLogger,
42 | })
43 |
44 | // Connect via RPC
45 | rpcClient, err := client.Client()
46 | if err != nil {
47 | return nil, fmt.Errorf("%s(%s) %s", id, file, err.Error())
48 | }
49 |
50 | // Request the plugin
51 | raw, err := rpcClient.Dispense("model")
52 | if err != nil {
53 | return nil, fmt.Errorf("%s(%s) %s", id, file, err.Error())
54 | }
55 |
56 | mod := raw.(grpc.Model)
57 | p := &Plugin{
58 | Client: client,
59 | Model: mod,
60 | ID: id,
61 | File: file,
62 | }
63 |
64 | Plugins[id] = p
65 | return p, nil
66 | }
67 |
68 | // KillAll kill all loaded plugins
69 | func KillAll() {
70 | for _, plug := range Plugins {
71 | if !plug.Client.Exited() {
72 | plug.Client.Kill()
73 | }
74 | }
75 | }
76 |
77 | // SetPluginLogger 设置日志
78 | func SetPluginLogger(name string, output io.Writer, level hclog.Level) {
79 | pluginLogger = hclog.New(&hclog.LoggerOptions{
80 | Name: name,
81 | Output: output,
82 | Level: level,
83 | })
84 | }
85 |
86 | // Select a plugin
87 | func Select(id string) (grpc.Model, error) {
88 | var err error
89 | plug, has := Plugins[id]
90 | if !has {
91 | return nil, fmt.Errorf("plugin %s not loaded", id)
92 |
93 | }
94 |
95 | // 如果进程已退出,重载
96 | if plug.Client.Exited() {
97 | plug, err = Load(plug.File, plug.ID)
98 | if err != nil {
99 | return nil, fmt.Errorf("%s %s", id, err)
100 | }
101 | }
102 | return plug.Model, nil
103 | }
104 |
105 | // Kill a plugin process
106 | func (plugin *Plugin) Kill() {
107 | plugin.Client.Kill()
108 | }
109 |
--------------------------------------------------------------------------------
/plugin/plugin.types.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "github.com/hashicorp/go-plugin"
5 | "github.com/yaoapp/kun/grpc"
6 | )
7 |
8 | // Plugin 插件
9 | type Plugin struct {
10 | Client *plugin.Client
11 | Model grpc.Model
12 | ID string
13 | File string
14 | }
15 |
--------------------------------------------------------------------------------
/plugin/plugin_test.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestLoadPlugin(t *testing.T) {
12 |
13 | root := os.Getenv("GOU_TEST_PLUGIN")
14 | file := path.Join(root, "user.so")
15 | p, err := Load(file, "user")
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | defer p.Kill()
20 |
21 | user, err := Select("user")
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | res, err := user.Exec("login", "13111021983", "#991832")
27 | assert.Equal(t, res.MustMap().Dot().Get("name"), "login")
28 | assert.Equal(t, res.MustMap().Dot().Get("args.0"), "13111021983")
29 | assert.Equal(t, res.MustMap().Dot().Get("args.1"), "#991832")
30 | assert.Nil(t, err)
31 | }
32 |
--------------------------------------------------------------------------------
/plugin/process.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | )
7 |
8 | func init() {
9 | process.Register("plugins", processPlugins)
10 | }
11 |
12 | // processPlugins
13 | func processPlugins(process *process.Process) interface{} {
14 |
15 | plugin, err := Select(process.ID)
16 | if err != nil {
17 | exception.New("plugins.%s not loaded", 404, process.ID).Throw()
18 | return nil
19 | }
20 | res, err := plugin.Exec(process.Method, process.Args...)
21 | if err != nil {
22 | exception.Err(err, 500).Throw()
23 | }
24 |
25 | return res.MustValue()
26 | }
27 |
--------------------------------------------------------------------------------
/plugin/process_test.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/yaoapp/gou/process"
10 | "github.com/yaoapp/kun/maps"
11 | )
12 |
13 | func TestProcessPlugins(t *testing.T) {
14 | prepare(t)
15 | defer KillAll()
16 |
17 | p, err := process.Of("plugins.user.login", "13111021983", "#991832")
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | data, err := p.Exec()
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 |
27 | res, ok := data.(maps.MapStr)
28 | if !ok {
29 | t.Fatal("return data type error")
30 | }
31 |
32 | assert.Equal(t, res.Dot().Get("name"), "login")
33 | assert.Equal(t, res.Dot().Get("args.0"), "13111021983")
34 | assert.Equal(t, res.Dot().Get("args.1"), "#991832")
35 | assert.Nil(t, err)
36 | }
37 |
38 | func prepare(t *testing.T) {
39 | KillAll()
40 | root := os.Getenv("GOU_TEST_PLUGIN")
41 | file := path.Join(root, "user.so")
42 | _, err := Load(file, "user")
43 | if err != nil {
44 | t.Fatal(err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/process/args_test.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import "testing"
4 |
5 | func TestValidateArgNums(t *testing.T) {
6 | }
7 |
8 | func TestNumOfArgs(t *testing.T) {
9 | }
10 |
11 | func TestNumOfArgsIs(t *testing.T) {
12 | }
13 |
14 | func TestArgsNotNull(t *testing.T) {
15 | }
16 |
17 | func TestArgsURLValue(t *testing.T) {
18 | }
19 |
20 | func TestArgsQueryParams(t *testing.T) {
21 | }
22 |
23 | func TestArgsInt(t *testing.T) {
24 | }
25 |
26 | func TestArgsUint32(t *testing.T) {
27 | }
28 |
29 | func TestArgsString(t *testing.T) {
30 | }
31 |
32 | func TestArgsMap(t *testing.T) {
33 | }
34 |
35 | func TestArgsBool(t *testing.T) {
36 | }
37 |
38 | func TestArgsRecords(t *testing.T) {
39 | }
40 |
41 | func TestArgsStrings(t *testing.T) {
42 | }
43 |
44 | func TestArgsArray(t *testing.T) {
45 | }
46 |
47 | func TestLang(t *testing.T) {
48 | }
49 |
--------------------------------------------------------------------------------
/process/types.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // Process the process sturct
8 | type Process struct {
9 | Name string
10 | Group string
11 | Method string
12 | Handler string
13 | ID string
14 | Args []interface{}
15 | Global map[string]interface{} // Global vars
16 | Sid string // Session ID
17 | Context context.Context // Context
18 | Runtime Runtime // Runtime
19 | _val *interface{} // Value // The result of the process
20 | }
21 |
22 | // Runtime interface
23 | type Runtime interface {
24 | Dispose()
25 | }
26 |
27 | // Handler the process handler
28 | type Handler func(process *Process) interface{}
29 |
--------------------------------------------------------------------------------
/query/assets/conditions/base.json:
--------------------------------------------------------------------------------
1 | [
2 | { "field": "score", "value": 20, "op": "=", "comment": "分数" },
3 | { "field": "score", "=": 20 },
4 | { ":score": "分数", "=": 10 },
5 |
6 | { "field": "score", "value": 20, "op": "<", "comment": "分数" },
7 | { "field": "score", "<": 20 },
8 | { ":score": "分数", "<": 20 },
9 |
10 | { "field": "score", "value": 20, "op": "<=", "comment": "分数" },
11 | { "field": "score", "<=": 20 },
12 | { ":score": "分数", "<=": 20 },
13 |
14 | { "field": "score", "value": 20, "op": ">", "comment": "分数" },
15 | { "field": "score", ">": 20 },
16 | { ":score": "分数", ">": 20 },
17 |
18 | { "field": "score", "value": 20, "op": ">=", "comment": "分数" },
19 | { "field": "score", ">=": 20 },
20 | { ":score": "分数", ">=": 20 },
21 |
22 | { "field": "name", "value": "李", "op": "match", "comment": "姓名" },
23 | { "field": "name", "match": "李" },
24 | { ":name": "姓名", "match": "李" },
25 |
26 | { "field": "name", "value": "%明", "op": "like", "comment": "姓名" },
27 | { "field": "name", "like": "%明" },
28 | { ":name": "姓名", "like": "%明" },
29 |
30 | { "field": "score", "value": [10, 20], "op": "in", "comment": "分数" },
31 | { "field": "score", "in": [10, 20] },
32 | { ":score": "分数", "in": [10, 20] },
33 |
34 | { "field": "name", "value": ["张三", "李四"], "op": "in", "comment": "姓名" },
35 | { "field": "name", "in": ["张三", "李四"] },
36 | { ":name": "姓名", "in": ["张三", "李四"] },
37 |
38 | { "field": "name", "op": "is", "value": "null", "comment": "姓名" },
39 | { "field": "name", "is": "null" },
40 | { ":name": "姓名", "is": "null" },
41 |
42 | { "field": "name", "op": "is", "value": "not null", "comment": "姓名" },
43 | { "field": "name", "is": "not null" },
44 | { ":name": "姓名", "is": "not null" },
45 |
46 | { "or": true, "field": "name", "op": "match", "value": "李" },
47 | { "or": true, "field": "name", "match": "李" },
48 | { "or :name": "或姓名", "match": "李" },
49 |
50 | { "or": true, "field": "name", "op": "is", "value": "not null" },
51 | { "or": true, "field": "name", "is": "not null" },
52 | { "or :name": "或姓名", "is": "not null" },
53 |
54 | { "or": false, "field": "name", "op": "is", "value": "not null" },
55 | { "or": false, "field": "name", "is": "not null" },
56 | { ":name": "姓名", "is": "not null" },
57 |
58 | { "field": "id", "value": 20, "op": "=" },
59 | { "field": "name", "value": "张三", "op": "=" },
60 | { "field": "id", "value": "{20}", "op": "=" },
61 | { "field": "name", "value": "{'张三'}", "op": "=" },
62 | { "field": "name", "value": "{short_name}", "op": "=" },
63 | { "or": true, "field": "name", "value": "{short_name}", "op": "=" }
64 | ]
65 |
--------------------------------------------------------------------------------
/query/assets/conditions/error.json:
--------------------------------------------------------------------------------
1 | [
2 | { ":score": "分数" },
3 | { "=": 10 },
4 | { ":score": "分数", "op": "gt", "value": 20 },
5 | { ":score": "分数", "op": "=", "value": "{hello world}" },
6 | { "field": "score hello", "op": "=", "value": "hello" },
7 | { "field": "score hello", "op": "=", "value": "{hello world}" },
8 | { "field": "score", "op": "=" },
9 | { "field": "score", "op": "is", "value": "error" },
10 | { "field": "score", "op": "is" },
11 | { "field": "score", "op": "is", "value": "null" }
12 | ]
13 |
--------------------------------------------------------------------------------
/query/assets/conditions/query.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "comment": "北京地区的厂商: 超过平均值的成绩单",
4 | "field": "score",
5 | "op": ">",
6 | "query": {
7 | "select": [":avg(score)"],
8 | "from": "user",
9 | "wheres": [{ "field": "area", "value": "北京" }]
10 | }
11 | },
12 | {
13 | "comment": "北京地区厂商的成绩单",
14 | "field": "score_id",
15 | "op": "in",
16 | "query": {
17 | "select": ["id"],
18 | "from": "manu",
19 | "wheres": [{ "field": "area", "value": "北京" }]
20 | }
21 | }
22 | ]
23 |
--------------------------------------------------------------------------------
/query/assets/expressions/alias.json:
--------------------------------------------------------------------------------
1 | ["name", "name1", "name_2", "姓名", "姓名1", "姓名_2"]
2 |
--------------------------------------------------------------------------------
/query/assets/expressions/base.json:
--------------------------------------------------------------------------------
1 | [
2 | "*",
3 | "name as n",
4 | "name as 名称",
5 |
6 | "0.618 as 黄金分割",
7 | "1 as 数字",
8 |
9 | "'0.618' as 黄金分割字符",
10 | "'1' as 字符串",
11 |
12 | "table.name as n",
13 | "table.name as 名称",
14 |
15 | "table.object$.foo as foo",
16 |
17 | "table.object$.arr[0] as arr",
18 | "table.object$.arr[*] as arr",
19 |
20 | "table.object$.arr[0].foo as arr",
21 | "table.object$.arr[*].bar as arr",
22 |
23 | "table.array[0].foo as foo",
24 | "table.array[*].foo as foo",
25 |
26 | "$model.name as n",
27 | "$model.name as 中文",
28 |
29 | "$model.object$.foo as foo",
30 |
31 | "$model.object$.arr[0] as arr",
32 | "$model.object$.arr[*] as arr",
33 |
34 | "$model.object$.arr[0].foo as arr",
35 | "$model.object$.arr[*].bar as arr",
36 |
37 | "$model.array@[0].foo as foo",
38 | "$model.array@[*].foo as foo",
39 |
40 | ":MAX(table.id)",
41 | ":MAX(table.object$.arr[*])",
42 | ":MAX($model.object$.arr[*])",
43 | ":MAX(中文)",
44 |
45 | "?:name as name",
46 | "?:姓名 as 姓名",
47 | "?:extra.score[*] as 数组",
48 | "?:extra.group[0].name as name"
49 | ]
50 |
--------------------------------------------------------------------------------
/query/assets/expressions/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | "name",
3 | "name01",
4 | "name_02",
5 |
6 | "中文",
7 | "中文01",
8 | "中文_02",
9 |
10 | "object$",
11 | "object01$",
12 | "object_02$",
13 | "对象$",
14 | "对象01$",
15 | "对象_02$",
16 |
17 | "mobile*",
18 | "手机号*",
19 |
20 | "object$.foo",
21 | "object$.foo.bar",
22 | "object$.arr[0]",
23 | "object$.arr[*]",
24 | "object$.arr[0].foo",
25 | "object$.arr[*].bar",
26 | "object$.属性1.属性2",
27 | "object$.数组[0]",
28 | "object$.数组[*]",
29 | "object$.数组[0].属性",
30 | "object$.数组[*].属性",
31 |
32 | "对象$.foo",
33 | "对象$.foo.bar",
34 | "对象$.arr[0]",
35 | "对象$.arr[*]",
36 | "对象$.arr[0].foo",
37 | "对象$.arr[*].bar",
38 | "对象$.属性1.属性2",
39 | "对象$.数组[0]",
40 | "对象$.数组[*]",
41 | "对象$.数组[0].属性",
42 | "对象$.数组[*].属性",
43 |
44 | "array@",
45 | "array01@",
46 | "array_02@",
47 | "数组@",
48 | "数组01@",
49 | "数组_02@",
50 |
51 | "array[0]",
52 | "array[*]",
53 | "数组[0]",
54 | "数组[*]",
55 |
56 | "array@[0]",
57 | "array@[*]",
58 | "数组@[0]",
59 | "数组@[*]",
60 |
61 | "array[0].foo",
62 | "array[*].bar",
63 | "array[0].foo.bar",
64 | "array[*].foo.bar",
65 |
66 | "array@[0].foo",
67 | "array@[*].bar",
68 | "array@[0].foo.bar",
69 | "array@[*].foo.bar",
70 |
71 | "array[0].arr[0]",
72 | "array[*].arr[*]",
73 |
74 | "array@[0].arr[0]",
75 | "array@[*].arr[*]",
76 |
77 | "array[0].arr[0].foo",
78 | "array[*].arr[*].bar",
79 | "array[0].arr[0].foo.bar",
80 | "array[*].arr[*].foo.bar",
81 |
82 | "array@[0].arr[0].foo",
83 | "array@[*].arr[*].bar",
84 | "array@[0].arr[0].foo.bar",
85 | "array@[*].arr[*].foo.bar",
86 |
87 | "数组[0].属性",
88 | "数组[*].属性",
89 | "数组@[0].属性",
90 | "数组@[*].属性",
91 |
92 | "数组[0].属性1.属性2",
93 | "数组[*].属性1.属性2",
94 |
95 | "数组@[0].属性1.属性2",
96 | "数组@[*].属性1.属性2",
97 |
98 | "数组[0].数组[0]",
99 | "数组[0].数组[*]",
100 |
101 | "数组@[0].数组[0]",
102 | "数组@[0].数组[*]",
103 |
104 | "数组[0].数组[0].foo",
105 | "数组[*].数组[*].bar",
106 | "数组[0].数组[0].foo.bar",
107 | "数组[*].数组[*].foo.bar",
108 |
109 | "数组@[0].数组[0].foo",
110 | "数组@[*].数组[*].bar",
111 | "数组@[0].数组[0].foo.bar",
112 | "数组@[*].数组[*].foo.bar",
113 |
114 | ":MAX(分数)",
115 | ":MAX(数组@)",
116 | ":MAX(对象$)",
117 | ":MAX(对象$.分数)",
118 | ":MAX(对象$.分数[*])",
119 | ":CONCAT(对象$.分数, 'b', 1)",
120 |
121 | "?:name",
122 | "?:绑定字段",
123 | "?:name.foo",
124 | "?:name.arr[*]",
125 | "?:name.arr[0].foo",
126 |
127 | "0",
128 | "0.618",
129 | "'123456'"
130 | ]
131 |
--------------------------------------------------------------------------------
/query/assets/expressions/type.json:
--------------------------------------------------------------------------------
1 | [
2 | "area.city@(string 20)",
3 | "area.city@(string 20)",
4 | "area.city@(string)",
5 | "area.city@.code(integer)",
6 | "price[*](float 10,2)",
7 | "price[*](decimal 10,2)",
8 | "result$.kind[*]"
9 | ]
10 |
--------------------------------------------------------------------------------
/query/assets/full.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["name", "value"],
3 | "from": "user",
4 | "wheres": [
5 | { "field": "manu_id", "=": 1 },
6 | { ":short_name": "简称", "match": "腾讯" }
7 | ],
8 | "orders": [{ "field": "id", "sort": "desc" }]
9 | }
10 |
--------------------------------------------------------------------------------
/query/assets/groups/error.json:
--------------------------------------------------------------------------------
1 | [[{ "fields": "id" }], "id rolslup", [{ "field": "id" }, "type rolslup"]]
2 |
--------------------------------------------------------------------------------
/query/assets/groups/json.json:
--------------------------------------------------------------------------------
1 | [
2 | { "field": "area.city@(string 20)", "rollup": "全部城市" },
3 | "area.city@(string 20) rollup 全部城市",
4 | { "field": "area.city@.code(integer)", "rollup": "全部代码" },
5 | "area.city@.code(integer) rollup 全部代码",
6 | { "field": "price[*](float 10,2)", "rollup": "全部定价" },
7 | "price[*](float 10,2) rollup 全部定价",
8 | { "field": "result$.kind[*](string)", "rollup": "全部类型" },
9 | "result$.kind[*] rollup 全部类型"
10 | ]
11 |
--------------------------------------------------------------------------------
/query/assets/groups/mix.json:
--------------------------------------------------------------------------------
1 | [
2 | "kind",
3 | "city rollup 所有城市",
4 | "@industries",
5 | "kind,city",
6 | "kind rollup 所有类型, city",
7 | "kind ROLlup 所有类型, city rollup 所有城市",
8 | ["city"],
9 | ["kind", "city"],
10 | ["kind", { "field": "city", "rollup": "所有类型", "comment": "按类型统计" }],
11 | [{ "field": "@industries", "rollup": "所有行业", "comment": "按行业统计" }]
12 | ]
13 |
--------------------------------------------------------------------------------
/query/assets/groups/strict.json:
--------------------------------------------------------------------------------
1 | [
2 | [{ "field": "kind" }],
3 | [{ "field": "city", "rollup": "所有城市" }],
4 | [{ "field": "@industries" }],
5 | [{ "field": "kind" }, { "field": "city" }],
6 | [
7 | { "field": "kind", "rollup": "所有类型", "comment": "按类型统计" },
8 | { "field": "city", "comment": "按城市统计" }
9 | ],
10 | [
11 | { "field": "kind", "rollup": "所有类型", "comment": "按类型统计" },
12 | { "field": "city", "rollup": "所有城市", "comment": "按城市统计" }
13 | ]
14 | ]
15 |
--------------------------------------------------------------------------------
/query/assets/groups/sugar.json:
--------------------------------------------------------------------------------
1 | [
2 | "kind",
3 | "city rollup 所有城市",
4 | "@industries",
5 | "kind,city",
6 | "kind rollup 所有类型, city",
7 | "kind rollup 所有类型, city rollup 所有城市"
8 | ]
9 |
--------------------------------------------------------------------------------
/query/assets/orders/error.json:
--------------------------------------------------------------------------------
1 | [
2 | [{ "sort": "asc" }],
3 | [{ "fields": "type", "sorts": "desc" }, { "sort": "asc" }],
4 | "id error",
5 | "id desc, name error "
6 | ]
7 |
--------------------------------------------------------------------------------
/query/assets/orders/mix.json:
--------------------------------------------------------------------------------
1 | [
2 | "id",
3 | "id desc",
4 | "type asc",
5 | "id , type desc",
6 | "id desc, type",
7 | "id desc , type desc",
8 | [{ "field": "type", "sort": "desc" }, { "field": "created_at" }],
9 | ["type desc", { "field": "created_at" }]
10 | ]
11 |
--------------------------------------------------------------------------------
/query/assets/orders/strict.json:
--------------------------------------------------------------------------------
1 | [
2 | [{ "field": "id" }],
3 | [{ "field": "id", "sort": "desc" }],
4 | [{ "field": "type" }, { "field": "created_at", "sort": "asc" }],
5 | [
6 | { "field": "type", "sort": "desc" },
7 | { "field": "created_at", "sort": "asc", "comment": "unit-test" }
8 | ],
9 | [{ "field": "type", "sort": "desc" }, { "field": "created_at" }]
10 | ]
11 |
--------------------------------------------------------------------------------
/query/assets/orders/sugar.json:
--------------------------------------------------------------------------------
1 | [
2 | "id",
3 | "id desc",
4 | "type asc",
5 | "id , type desc",
6 | "id desc, type",
7 | "id desc , type desc"
8 | ]
9 |
--------------------------------------------------------------------------------
/query/assets/queries/from.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["*"],
3 | "from": "table as name"
4 | }
5 |
--------------------------------------------------------------------------------
/query/assets/queries/groups.array.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": [
3 | ":max(score) as 最高分",
4 | "citys@(string 50) as 城市",
5 | "industries@(string 100) as 行业",
6 | "towns[*](string 100)",
7 | "goods.sku[*].price(decimal 11,2)",
8 | "goods.sku[*].gid(integer)",
9 | "option$.ids[*](integer) as ID"
10 | ],
11 | "from": "table as name",
12 | "groups": [
13 | "行业",
14 | "ID",
15 | "citys@ rollup 所有城市",
16 | "towns@ rollup 所有行政区",
17 | "goods.sku[*].price rollup 合计",
18 | "goods.sku[*].gid"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/query/assets/queries/groups.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": [":max(score) as 最高分", "city as 城市", "id", "kind"],
3 | "from": "table as name",
4 | "groups": "kind, city rollup 所有城市, id rollup ID"
5 | }
6 |
--------------------------------------------------------------------------------
/query/assets/queries/havings.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": [":max(score) as 最高分", "city as 城市", "id", "kind"],
3 | "from": "table as name",
4 | "groups": "kind, city rollup 所有城市, id rollup ID",
5 | "havings": [
6 | { "field": "城市", "=": "北京" },
7 | { "or :kind": "类型", "=": "云存储" }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/query/assets/queries/joins.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["id", "name", "t2.name2", "t3.name3", "t4.name4"],
3 | "from": "t1",
4 | "joins": [
5 | {
6 | "select": ["name2"],
7 | "from": "table2 as t2",
8 | "key": "t2.id",
9 | "foreign": "t1.t2_id",
10 | "left": true
11 | },
12 | {
13 | "select": ["name3"],
14 | "from": "table3 as t3",
15 | "key": "t3.id",
16 | "foreign": "t2.t3_id",
17 | "right": true
18 | },
19 | {
20 | "select": ["name4"],
21 | "from": "table4 as t4",
22 | "key": "t4.id",
23 | "foreign": "t2.t4_id"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/query/assets/queries/limit.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["*"],
3 | "from": "table as name",
4 | "limit": 20,
5 | "offset": 10
6 | }
7 |
--------------------------------------------------------------------------------
/query/assets/queries/orders.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["*"],
3 | "from": "table as name",
4 | "orders": [
5 | "id desc",
6 | ":MAX(id) desc",
7 | "$table.pin",
8 | "array@.id",
9 | "object$.arr[0].id"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/query/assets/queries/select.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": [
3 | "name as n",
4 | "name as 名称",
5 |
6 | "0.618 as 黄金分割",
7 | "1 as 数字",
8 |
9 | "'0.618' as 黄金分割字符",
10 | "'1' as 字符串",
11 |
12 | "table.name as n",
13 | "table.name as 名称",
14 |
15 | "table.mobile* as mobile",
16 | "table.secret*",
17 |
18 | "table.object$.foo as foo",
19 |
20 | "table.object$.arr[0] as arr",
21 | "table.object$.arr[*] as arr",
22 |
23 | "table.object$.arr[0].foo as arr",
24 | "table.object$.arr[*].bar as arr",
25 |
26 | "table.array[*] as arr",
27 |
28 | "table.array[0].foo as foo",
29 | "table.array[*].foo as foo",
30 |
31 | "$model.name as n",
32 | "$model.name as 中文",
33 |
34 | "$model.object$.foo as foo",
35 |
36 | "$model.object$.arr[0] as arr",
37 | "$model.object$.arr[*] as arr",
38 |
39 | "$model.object$.arr[0].foo as arr",
40 | "$model.object$.arr[*].bar as arr",
41 |
42 | "$model.array@[0].foo as foo",
43 | "$model.array@[*].foo as foo",
44 |
45 | ":MAX(table.id)",
46 | ":MAX(table.object$.arr[*])",
47 | ":MAX($model.object$.arr[*])",
48 | ":MAX(中文)",
49 |
50 | "?:name as name",
51 | "?:姓名 as 姓名",
52 | "?:extra.score[*] as 数组",
53 | "?:extra.group[0].name as name"
54 | ],
55 | "from": "user as u"
56 | }
57 |
--------------------------------------------------------------------------------
/query/assets/queries/sql.json:
--------------------------------------------------------------------------------
1 | {
2 | "sql": {
3 | "comment": "查询张三",
4 | "stmt": "SELECT * FROM user WHERE name = ? AND type = ?",
5 | "args": ["张三", 20]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/query/assets/queries/subquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["id", "_SUB_.name"],
3 | "query": {
4 | "select": ["id", "name"],
5 | "from": "manu"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/query/assets/queries/subquery.name.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["id", "厂商.name"],
3 | "query": {
4 | "name": "厂商",
5 | "select": ["id", "name"],
6 | "from": "manu"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/query/assets/queries/unions.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["id", "name"],
3 | "from": "t1",
4 | "unions": [
5 | {
6 | "select": ["id", "name"],
7 | "from": "t2"
8 | },
9 | {
10 | "select": ["id", "name"],
11 | "from": "t3"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/query/assets/queries/wheres.json:
--------------------------------------------------------------------------------
1 | {
2 | "select": ["*"],
3 | "from": "user as u",
4 | "wheres": [
5 | { "field": "score", "<": 100 },
6 | { "field": "score", ">": 0 },
7 | { "field": "id", "in": [0, 100] },
8 | {
9 | "wheres": [
10 | { ":name": "姓名", "match": "李" },
11 | { "or :name": "或姓名", "match": "李" }
12 | ]
13 | },
14 | {
15 | "field": "manu_id",
16 | "op": "in",
17 | "query": {
18 | "select": ["manu_id as id"],
19 | "from": "manu",
20 | "wheres": [{ "field": "status", "=": "enabled" }],
21 | "limit": 10
22 | }
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/query/assets/tables/base.json:
--------------------------------------------------------------------------------
1 | [
2 | "name",
3 | "$user",
4 | "name as n",
5 | "$user as u",
6 | "name as bar",
7 | "$user as foo",
8 | "$xiang.user as 用户_表"
9 | ]
10 |
--------------------------------------------------------------------------------
/query/assets/tables/error.json:
--------------------------------------------------------------------------------
1 | [
2 | "name.d",
3 | "$name中文",
4 | "name as ,",
5 | "$model.user as |",
6 | "$model.user as foo bar"
7 | ]
8 |
--------------------------------------------------------------------------------
/query/engine.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/yaoapp/gou/query/share"
8 | )
9 |
10 | // Engines registered engines
11 | var Engines = map[string]share.DSL{}
12 |
13 | // Register Register a Query Engine
14 | func Register(name string, engine share.DSL) {
15 | name = strings.ToLower(name)
16 | Engines[name] = engine
17 | }
18 |
19 | // Unregister Unregister a Query Engine
20 | func Unregister(name string) {
21 | name = strings.ToLower(name)
22 | delete(Engines, name)
23 | }
24 |
25 | // Alias set the Engine alias
26 | func Alias(name, alias string) {
27 | name = strings.ToLower(name)
28 | alias = strings.ToLower(alias)
29 | if _, has := Engines[name]; has {
30 | Engines[alias] = Engines[name]
31 | }
32 | }
33 |
34 | // Select choose one engine
35 | func Select(name string) (share.DSL, error) {
36 | engine, has := Engines[name]
37 | if !has {
38 | return nil, fmt.Errorf("%s not found", name)
39 | }
40 | return engine, nil
41 | }
42 |
--------------------------------------------------------------------------------
/query/gou.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/yaoapp/gou/query/gou"
7 | )
8 |
9 | // Gou 创建 Gou Query share.DSL
10 | func Gou(input []byte) *gou.Query {
11 | return gou.Make(input)
12 | }
13 |
14 | // GouRead 创建 Gou Query share.DSL (输入接口)
15 | func GouRead(reader io.Reader) *gou.Query {
16 | return gou.Read(reader)
17 | }
18 |
19 | // GouOpen 创建 Gou Query share.DSL (文件)
20 | func GouOpen(filename string) *gou.Query {
21 | return gou.Open(filename)
22 | }
23 |
--------------------------------------------------------------------------------
/query/gou/expression_test.go:
--------------------------------------------------------------------------------
1 | package gou
2 |
3 | import (
4 | "testing"
5 |
6 | jsoniter "github.com/json-iterator/go"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewExpressionBase(t *testing.T) {
11 | var exps []Expression
12 | bytes := ReadFile("expressions/base.json")
13 | err := jsoniter.Unmarshal(bytes, &exps)
14 | assert.Nil(t, err)
15 | // utils.Dump(exps)
16 | // 需要验证数据
17 | for _, exp := range exps {
18 | // utils.Dump(exp.ToString())
19 | assert.True(t, len(exp.ToString()) > 0)
20 | }
21 | }
22 |
23 | func TestNewExpressionField(t *testing.T) {
24 | var exps []Expression
25 | bytes := ReadFile("expressions/fields.json")
26 | err := jsoniter.Unmarshal(bytes, &exps)
27 | assert.Nil(t, err)
28 | // 需要验证数据
29 | for _, exp := range exps {
30 | // utils.Dump(exp.ToString())
31 | assert.True(t, len(exp.ToString()) > 0)
32 | }
33 | }
34 |
35 | func TestNewExpressionType(t *testing.T) {
36 | var exps []Expression
37 | bytes := ReadFile("expressions/type.json")
38 | err := jsoniter.Unmarshal(bytes, &exps)
39 | assert.Nil(t, err)
40 | // 需要验证数据
41 | for _, exp := range exps {
42 | // utils.Dump(exp.ToString())
43 | assert.True(t, len(exp.ToString()) > 0)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/query/gou/init_test.go:
--------------------------------------------------------------------------------
1 | package gou
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "os"
7 | "path"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/yaoapp/kun/exception"
13 | "github.com/yaoapp/xun/capsule"
14 | "github.com/yaoapp/xun/dbal/query"
15 | )
16 |
17 | // TestAPIRoot
18 | var TestQueryRoot = "/data/querys"
19 | var TestDriver = "mysql"
20 | var TestDSN = "root:123456@tcp(127.0.0.1:3306)/gou?charset=utf8mb4&parseTime=True&loc=Local"
21 | var TestAESKey = "123456"
22 |
23 | func should(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
24 | return assert.Equal(t, expected, actual, msgAndArgs...)
25 | }
26 |
27 | var qb query.Query
28 |
29 | // GetFileName
30 | func GetFileName(name string) string {
31 | return path.Join(TestQueryRoot, name)
32 | }
33 |
34 | // TableName
35 | func TableName(name string) string {
36 | return strings.TrimPrefix(name, "$")
37 | }
38 |
39 | // ReadFile
40 | func ReadFile(name string) []byte {
41 | fullname := path.Join(TestQueryRoot, name)
42 |
43 | file, err := os.Open(fullname)
44 | if err != nil {
45 | exception.New("读取文件失败 %s", 500, err.Error()).Throw()
46 | }
47 | defer file.Close()
48 |
49 | buf := bytes.NewBuffer(nil)
50 | _, err = io.Copy(buf, file)
51 | if err != nil {
52 | exception.New("读取数据失败 %s", 500, err.Error()).Throw()
53 | }
54 | return buf.Bytes()
55 | }
56 |
57 | func TestMain(m *testing.M) {
58 |
59 | TestQueryRoot = os.Getenv("GOU_TEST_QUERY_ROOT")
60 | TestDriver = os.Getenv("GOU_TEST_DB_DRIVER")
61 | TestDSN = os.Getenv("GOU_TEST_DSN")
62 | TestAESKey = os.Getenv("GOU_TEST_AES_KEY")
63 |
64 | // 数据库连接
65 | switch TestDriver {
66 | case "sqlite3":
67 | capsule.AddConn("primary", "sqlite3", TestDSN).SetAsGlobal()
68 | break
69 | default:
70 | capsule.AddConn("primary", "mysql", TestDSN).SetAsGlobal()
71 | break
72 | }
73 |
74 | qb = capsule.Query()
75 |
76 | // Run test suites
77 | exitVal := m.Run()
78 |
79 | // we can do clean up code here
80 | os.Exit(exitVal)
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/query/gou/query_test.go:
--------------------------------------------------------------------------------
1 | package gou
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestGet(t *testing.T) {
8 | // rows := Gou([]byte{}).With(qb).Get()
9 | // utils.Dump(rows)
10 | }
11 |
12 | func TestFirst(t *testing.T) {
13 | // Gou([]byte{}).With(qb).First()
14 | }
15 |
16 | func TestPaginate(t *testing.T) {
17 | // Gou([]byte{}).With(qb).Paginate()
18 | }
19 |
20 | func TestRun(t *testing.T) {
21 | // Gou([]byte{}).With(qb).Run()
22 | }
23 |
--------------------------------------------------------------------------------
/query/gou/table.go:
--------------------------------------------------------------------------------
1 | package gou
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "strings"
7 |
8 | "github.com/go-errors/errors"
9 | jsoniter "github.com/json-iterator/go"
10 | )
11 |
12 | // UnmarshalJSON for json marshalJSON
13 | func (tab *Table) UnmarshalJSON(data []byte) error {
14 | var input string
15 | err := jsoniter.Unmarshal(data, &input)
16 | if err != nil {
17 | return err
18 | }
19 |
20 | array := RegAlias.Split(input, -1)
21 |
22 | if len(array) == 1 {
23 | tab.Name = strings.TrimSpace(array[0])
24 | } else if len(array) == 2 {
25 | tab.Name = strings.TrimSpace(array[0])
26 | tab.Alias = strings.TrimSpace(array[1])
27 | } else {
28 | return errors.Errorf("%s 格式错误", input)
29 | }
30 |
31 | // 数据模型
32 | if strings.HasPrefix(tab.Name, "$") {
33 | tab.Name = strings.TrimPrefix(tab.Name, "$")
34 | tab.IsModel = true
35 | }
36 |
37 | // 过滤 "`"
38 | tab.Name = strings.ReplaceAll(tab.Name, "`", "")
39 | tab.Alias = strings.ReplaceAll(tab.Alias, "`", "")
40 |
41 | return nil
42 | }
43 |
44 | // MarshalJSON for json marshalJSON
45 | func (tab Table) MarshalJSON() ([]byte, error) {
46 | return []byte(fmt.Sprintf("\"%s\"", tab.ToString())), nil
47 | }
48 |
49 | // ToString for json marshalJSON
50 | func (tab Table) ToString() string {
51 | name := tab.Name
52 |
53 | // 数据模型
54 | if tab.IsModel {
55 | name = "$" + name
56 | }
57 |
58 | if tab.Alias != "" {
59 | name = name + " as " + tab.Alias
60 | }
61 |
62 | return name
63 | }
64 |
65 | // Validate 校验表达式格式
66 | func (tab Table) Validate() error {
67 |
68 | reg := regexp.MustCompile("^[A-Za-z0-9_\u4e00-\u9fa5]+$")
69 | mreg := regexp.MustCompile("^[a-zA-Z\\.]+$")
70 | if !tab.IsModel && !reg.MatchString(tab.Name) {
71 | return errors.Errorf("数据表名称格式不正确(%s)", tab.Name)
72 | }
73 |
74 | if tab.IsModel && !mreg.MatchString(tab.Name) {
75 | return errors.Errorf("模型名称格式不正确(%s)", tab.Name)
76 | }
77 |
78 | if tab.Alias != "" && !reg.MatchString(tab.Alias) {
79 | return errors.Errorf("别名格式不正确(%s)", tab.Name)
80 | }
81 |
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/query/gou_test.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "path"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestGouOpen(t *testing.T) {
11 | assert.Panics(t, func() {
12 | GouOpen(path.Join(TestQueryRoot, "not-exists.json"))
13 | })
14 | gou := GouOpen(path.Join(TestQueryRoot, "full.json"))
15 |
16 | assert.Equal(t, "user", gou.From.Name)
17 | assert.Len(t, gou.Orders, 1)
18 | assert.Len(t, gou.Select, 2)
19 | assert.Len(t, gou.Wheres, 2)
20 | }
21 |
--------------------------------------------------------------------------------
/query/init_test.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/yaoapp/xun/capsule"
8 | "github.com/yaoapp/xun/dbal/query"
9 | )
10 |
11 | // TestAPIRoot
12 | var TestQueryRoot = "/data/querys"
13 | var TestDriver = "mysql"
14 | var TestDSN = "root:123456@tcp(127.0.0.1:3306)/gou?charset=utf8mb4&parseTime=True&loc=Local"
15 | var TestAESKey = "123456"
16 |
17 | var qb query.Query
18 |
19 | func TestMain(m *testing.M) {
20 |
21 | TestQueryRoot = os.Getenv("GOU_TEST_QUERY_ROOT")
22 | TestDriver = os.Getenv("GOU_TEST_DB_DRIVER")
23 | TestDSN = os.Getenv("GOU_TEST_DSN")
24 | TestAESKey = os.Getenv("GOU_TEST_AES_KEY")
25 |
26 | // 数据库连接
27 | switch TestDriver {
28 | case "sqlite3":
29 | capsule.AddConn("primary", "sqlite3", TestDSN).SetAsGlobal()
30 | break
31 | default:
32 | capsule.AddConn("primary", "mysql", TestDSN).SetAsGlobal()
33 | break
34 | }
35 |
36 | qb = capsule.Query()
37 |
38 | // Run test suites
39 | exitVal := m.Run()
40 |
41 | // we can do clean up code here
42 | os.Exit(exitVal)
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/query/share/dsl.go:
--------------------------------------------------------------------------------
1 | package share
2 |
3 | import (
4 | "github.com/yaoapp/kun/maps"
5 | )
6 |
7 | // DSL QueryDSL Interface
8 | type DSL interface {
9 | Load(interface{}) (DSL, error) // 查询条件预载入
10 | Run(data maps.Map) interface{} // 执行查询根据查询条件返回结果
11 | Get(data maps.Map) []Record // 执行查询并返回数据记录集合
12 | Paginate(data maps.Map) Paginate // 执行查询并返回带分页信息的数据记录数组
13 | First(data maps.Map) Record // 执行查询并返回一条数据记录
14 | }
15 |
16 | // Record 数据记录
17 | type Record maps.MapStrAny
18 |
19 | // Paginate 带分页信息的数据记录数组
20 | type Paginate struct {
21 | Items []Record `json:"items"` // 数据记录集合
22 | Total int `json:"total"` // 总记录数
23 | Next int `json:"next"` // 下一页,如没有下一页返回 -1
24 | Prev int `json:"prev"` // 上一页,如没有上一页返回 -1
25 | Page int `json:"page"` // 当前页码
26 | PageSize int `json:"pagesize"` // 每页记录数量
27 | PageCount int `json:"pagecnt"` // 总页数
28 | }
29 |
--------------------------------------------------------------------------------
/query/share/flow.fliter.go:
--------------------------------------------------------------------------------
1 | package share
2 |
3 | // Helper 转换器
4 | type Helper func(...interface{}) interface{}
5 |
6 | // Filters 处理函数
7 | var Filters map[string]Helper = map[string]Helper{
8 | "pluck": func(args ...interface{}) interface{} { return args },
9 | }
10 |
11 | // RegisterHelper 注册 helper
12 | func RegisterHelper(name string, helper Helper) {
13 | Filters[name] = helper
14 | }
15 |
--------------------------------------------------------------------------------
/query/tai.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "github.com/yaoapp/gou/query/share"
5 | "github.com/yaoapp/gou/query/tai"
6 | )
7 |
8 | // Tai Query DSL
9 | type Tai tai.QueryDSL
10 |
11 | // Run 执行查询根据查询条件返回结果
12 | func (tai Tai) Run() interface{} {
13 | return []share.Record{}
14 | }
15 |
16 | // Get 执行查询并返回数据记录集合
17 | func (tai Tai) Get() []share.Record {
18 | return []share.Record{}
19 | }
20 |
21 | // Paginate 执行查询并返回带分页信息的数据记录数组
22 | func (tai Tai) Paginate() share.Paginate {
23 | return share.Paginate{}
24 | }
25 |
26 | // First 执行查询并返回一条数据记录
27 | func (tai Tai) First() share.Record {
28 | return share.Record{}
29 | }
30 |
--------------------------------------------------------------------------------
/query/tai/types.go:
--------------------------------------------------------------------------------
1 | package tai
2 |
3 | // QueryDSL Tai Query Domain Specific Language
4 | type QueryDSL struct{}
5 |
--------------------------------------------------------------------------------
/rag/rag.go:
--------------------------------------------------------------------------------
1 | package rag
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 |
7 | "github.com/yaoapp/gou/rag/driver"
8 | "github.com/yaoapp/gou/rag/driver/openai"
9 | "github.com/yaoapp/gou/rag/driver/qdrant"
10 | )
11 |
12 | const (
13 | // DriverQdrant is the Qdrant vector store driver
14 | DriverQdrant = "qdrant"
15 | // DriverOpenAI is the OpenAI embeddings driver
16 | DriverOpenAI = "openai"
17 | )
18 |
19 | // NewEngine creates a new RAG engine instance
20 | func NewEngine(driverName string, config driver.IndexConfig, vectorizer driver.Vectorizer) (driver.Engine, error) {
21 | switch driverName {
22 | case DriverQdrant:
23 | // Convert IndexConfig to qdrant.Config
24 | qConfig := qdrant.Config{
25 | Host: config.Options["host"],
26 | APIKey: config.Options["api_key"],
27 | Vectorizer: vectorizer,
28 | }
29 | if portStr, ok := config.Options["port"]; ok {
30 | if port, err := strconv.ParseUint(portStr, 10, 32); err == nil {
31 | qConfig.Port = uint32(port)
32 | }
33 | }
34 | return qdrant.NewEngine(qConfig)
35 | default:
36 | return nil, fmt.Errorf("unsupported engine driver: %s", driverName)
37 | }
38 | }
39 |
40 | // NewVectorizer creates a new vectorizer instance
41 | func NewVectorizer(driverName string, config driver.VectorizeConfig) (driver.Vectorizer, error) {
42 | switch driverName {
43 | case DriverOpenAI:
44 | return openai.New(openai.Config{
45 | APIKey: config.Options["api_key"],
46 | Model: config.Model,
47 | })
48 | default:
49 | return nil, fmt.Errorf("unsupported vectorizer driver: %s", driverName)
50 | }
51 | }
52 |
53 | // NewFileUpload creates a new file upload instance
54 | func NewFileUpload(driverName string, engine driver.Engine, vectorizer driver.Vectorizer) (driver.FileUpload, error) {
55 | switch driverName {
56 | case DriverQdrant:
57 | if qEngine, ok := engine.(*qdrant.Engine); ok {
58 | return qdrant.NewFileUpload(qEngine, vectorizer)
59 | }
60 | return nil, fmt.Errorf("engine type mismatch: expected *qdrant.Engine")
61 | default:
62 | return nil, fmt.Errorf("unsupported file upload driver: %s", driverName)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/runtime/v8/bridge/bridge_test.go:
--------------------------------------------------------------------------------
1 | package bridge
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/yaoapp/gou/application"
9 | "rogchap.com/v8go"
10 | )
11 |
12 | func call(ctx *v8go.Context, method string, args ...interface{}) (interface{}, error) {
13 |
14 | global := ctx.Global()
15 | jsArgs, err := JsValues(ctx, args)
16 | if err != nil {
17 | return nil, err
18 | }
19 | defer FreeJsValues(jsArgs)
20 |
21 | jsRes, err := global.MethodCall(method, Valuers(jsArgs)...)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | goRes, err := GoValue(jsRes, ctx)
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | return goRes, nil
32 | }
33 |
34 | func prepare(t *testing.T) *v8go.Context {
35 |
36 | root := os.Getenv("GOU_TEST_APPLICATION")
37 |
38 | // Load app
39 | app, err := application.OpenFromDisk(root)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | application.Load(app)
44 |
45 | file := filepath.Join("scripts", "runtime", "bridge.js")
46 | source, err := app.Read(file)
47 | if err != nil {
48 | t.Fatal(err)
49 | }
50 |
51 | iso := v8go.NewIsolate()
52 | ctx := v8go.NewContext(iso)
53 | _, err = ctx.RunScript(string(source), file)
54 | if err != nil {
55 | t.Fatal(err)
56 | }
57 |
58 | return ctx
59 | }
60 |
61 | func close(ctx *v8go.Context) {
62 | ctx.Close()
63 | ctx.Isolate().Dispose()
64 | }
65 |
--------------------------------------------------------------------------------
/runtime/v8/bridge/function.go:
--------------------------------------------------------------------------------
1 | package bridge
2 |
3 | import "fmt"
4 |
5 | // Call jsValue Function
6 | func (f FunctionT) Call(args ...interface{}) (interface{}, error) {
7 |
8 | if f.ctx == nil {
9 | return nil, fmt.Errorf("invalid context")
10 | }
11 |
12 | cb, err := f.value.AsFunction()
13 | if err != nil {
14 | return nil, err
15 | }
16 |
17 | jsArgs, err := JsValues(f.ctx, args)
18 | if err != nil {
19 | return nil, err
20 | }
21 | defer FreeJsValues(jsArgs)
22 |
23 | value, err := cb.Call(f.ctx.Global(), Valuers(jsArgs)...)
24 | if err != nil {
25 | return nil, err
26 | }
27 | defer func() {
28 | if value != nil {
29 | value.Release()
30 | }
31 | }()
32 |
33 | goValue, err := GoValue(value, f.ctx)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | return goValue, nil
39 | }
40 |
--------------------------------------------------------------------------------
/runtime/v8/bridge/promise.go:
--------------------------------------------------------------------------------
1 | package bridge
2 |
3 | // Result returns the result of the promise.
4 | func (p PromiseT) Result() (interface{}, error) {
5 | promise, err := p.value.AsPromise()
6 | if err != nil {
7 | return nil, err
8 | }
9 |
10 | value := promise.Result()
11 | defer func() {
12 | if value != nil {
13 | value.Release()
14 | }
15 | }()
16 |
17 | goValue, err := GoValue(value, p.ctx)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | return goValue, nil
23 | }
24 |
--------------------------------------------------------------------------------
/runtime/v8/functions/atob/atob.go:
--------------------------------------------------------------------------------
1 | package atob
2 |
3 | import (
4 | "encoding/base64"
5 |
6 | "github.com/yaoapp/gou/runtime/v8/bridge"
7 | "rogchap.com/v8go"
8 | )
9 |
10 | // ExportFunction function template
11 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
12 | return v8go.NewFunctionTemplate(iso, exec)
13 | }
14 |
15 | // exec
16 | func exec(info *v8go.FunctionCallbackInfo) *v8go.Value {
17 |
18 | jsArgs := info.Args()
19 | if len(jsArgs) < 1 {
20 | return bridge.JsException(info.Context(), "missing parameters")
21 | }
22 |
23 | if !jsArgs[0].IsString() {
24 | return bridge.JsException(info.Context(), "the first parameter should be a string")
25 | }
26 |
27 | goRes, err := base64.StdEncoding.DecodeString(jsArgs[0].String())
28 | if err != nil {
29 | return bridge.JsException(info.Context(), err.Error())
30 | }
31 |
32 | jsRes, err := bridge.JsValue(info.Context(), string(goRes))
33 | if err != nil {
34 | return bridge.JsException(info.Context(), err)
35 | }
36 |
37 | return jsRes
38 | }
39 |
--------------------------------------------------------------------------------
/runtime/v8/functions/atob/atob_test.go:
--------------------------------------------------------------------------------
1 | package atob
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/runtime/v8/bridge"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | func TestAtob(t *testing.T) {
12 |
13 | ctx := prepare(t, false, "", nil)
14 | defer close(ctx)
15 |
16 | jsRes, err := ctx.RunScript(`
17 | const test = () => {
18 | const result = atob("ZGVtbzoxMjM0NTY=");
19 | return {
20 | result: result,
21 | __yao_global:__yao_global,
22 | __yao_sid: __yao_sid,
23 | __YAO_SU_ROOT: __YAO_SU_ROOT,
24 | }
25 | }
26 | test()
27 | `, "")
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 |
32 | goRes, err := bridge.GoValue(jsRes, ctx)
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | res, ok := goRes.(map[string]interface{})
38 | if !ok {
39 | t.Fatal("result error")
40 | }
41 |
42 | assert.Equal(t, "demo:123456", res["result"])
43 | assert.Equal(t, nil, res["__yao_global"])
44 | assert.Equal(t, false, res["__YAO_SU_ROOT"])
45 | }
46 |
47 | func close(ctx *v8go.Context) {
48 | ctx.Isolate().Dispose()
49 | }
50 |
51 | func prepare(t *testing.T, root bool, sid string, global map[string]interface{}) *v8go.Context {
52 |
53 | iso := v8go.NewIsolate()
54 |
55 | template := v8go.NewObjectTemplate(iso)
56 | template.Set("atob", ExportFunction(iso))
57 |
58 | ctx := v8go.NewContext(iso, template)
59 |
60 | var err error
61 | jsGlobal := v8go.Undefined(ctx.Isolate())
62 | jsGlobal, err = bridge.JsValue(ctx, global)
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | if err = ctx.Global().Set("__YAO_SU_ROOT", root); err != nil {
68 | t.Fatal(err)
69 | }
70 |
71 | if err = ctx.Global().Set("__yao_global", jsGlobal); err != nil {
72 | t.Fatal(err)
73 | }
74 | if err = ctx.Global().Set("__yao_sid", sid); err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | return ctx
79 | }
80 |
--------------------------------------------------------------------------------
/runtime/v8/functions/btoa/btoa.go:
--------------------------------------------------------------------------------
1 | package btoa
2 |
3 | import (
4 | "encoding/base64"
5 |
6 | "github.com/yaoapp/gou/runtime/v8/bridge"
7 | "rogchap.com/v8go"
8 | )
9 |
10 | // ExportFunction function template
11 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
12 | return v8go.NewFunctionTemplate(iso, exec)
13 | }
14 |
15 | // exec
16 | func exec(info *v8go.FunctionCallbackInfo) *v8go.Value {
17 |
18 | jsArgs := info.Args()
19 | if len(jsArgs) < 1 {
20 | return bridge.JsException(info.Context(), "missing parameters")
21 | }
22 |
23 | if !jsArgs[0].IsString() {
24 | return bridge.JsException(info.Context(), "the first parameter should be a string")
25 | }
26 |
27 | goRes := base64.StdEncoding.EncodeToString([]byte(jsArgs[0].String()))
28 | jsRes, err := bridge.JsValue(info.Context(), goRes)
29 | if err != nil {
30 | return bridge.JsException(info.Context(), err)
31 | }
32 |
33 | return jsRes
34 | }
35 |
--------------------------------------------------------------------------------
/runtime/v8/functions/btoa/btoa_test.go:
--------------------------------------------------------------------------------
1 | package btoa
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/runtime/v8/bridge"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | func TestBtoa(t *testing.T) {
12 |
13 | ctx := prepare(t, false, "", nil)
14 | defer close(ctx)
15 |
16 | jsRes, err := ctx.RunScript(`
17 | const test = () => {
18 | const result = btoa("demo:123456");
19 | return {
20 | result: result,
21 | __yao_global:__yao_global,
22 | __yao_sid: __yao_sid,
23 | __YAO_SU_ROOT: __YAO_SU_ROOT,
24 | }
25 | }
26 | test()
27 | `, "")
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 |
32 | goRes, err := bridge.GoValue(jsRes, ctx)
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | res, ok := goRes.(map[string]interface{})
38 | if !ok {
39 | t.Fatal("result error")
40 | }
41 |
42 | assert.Equal(t, "ZGVtbzoxMjM0NTY=", res["result"])
43 | assert.Equal(t, nil, res["__yao_global"])
44 | assert.Equal(t, false, res["__YAO_SU_ROOT"])
45 | }
46 |
47 | func close(ctx *v8go.Context) {
48 | ctx.Isolate().Dispose()
49 | }
50 |
51 | func prepare(t *testing.T, root bool, sid string, global map[string]interface{}) *v8go.Context {
52 |
53 | iso := v8go.NewIsolate()
54 |
55 | template := v8go.NewObjectTemplate(iso)
56 | template.Set("btoa", ExportFunction(iso))
57 |
58 | ctx := v8go.NewContext(iso, template)
59 |
60 | var err error
61 | jsGlobal := v8go.Undefined(ctx.Isolate())
62 | jsGlobal, err = bridge.JsValue(ctx, global)
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | if err = ctx.Global().Set("__YAO_SU_ROOT", root); err != nil {
68 | t.Fatal(err)
69 | }
70 |
71 | if err = ctx.Global().Set("__yao_global", jsGlobal); err != nil {
72 | t.Fatal(err)
73 | }
74 | if err = ctx.Global().Set("__yao_sid", sid); err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | return ctx
79 | }
80 |
--------------------------------------------------------------------------------
/runtime/v8/functions/eval/READEME.md:
--------------------------------------------------------------------------------
1 | # Eval Function
2 |
3 | The `Eval` function allows JavaScript code execution from strings within the V8 runtime environment. This is useful for dynamically generating and executing code at runtime.
4 |
5 | ## Usage
6 |
7 | ```javascript
8 | // Syntax
9 | Eval(codeString, ...args);
10 | ```
11 |
12 | ### Parameters
13 |
14 | - `codeString` (string): A JavaScript code string to evaluate. It should contain a function declaration.
15 | - `...args`: Optional parameters to pass to the evaluated function.
16 |
17 | ### Return Value
18 |
19 | Returns the result of executing the evaluated function.
20 |
21 | ## Examples
22 |
23 | ### Basic Example
24 |
25 | ```javascript
26 | // Evaluate a simple addition function
27 | const sum = Eval("function add(a, b) { return a + b; }", 5, 3);
28 | console.log(sum); // Output: 8
29 | ```
30 |
31 | ### Accessing Context Data
32 |
33 | When running in the Yao runtime environment, the evaluated code can access the context data:
34 |
35 | ```javascript
36 | // Assume __yao_data.DATA.user = "World"
37 | const greeting = Eval(
38 | "function greet(prefix) { return prefix + ', ' + __yao_data.DATA.user + '!'; }",
39 | "Hello"
40 | );
41 | console.log(greeting); // Output: "Hello, World!"
42 | ```
43 |
44 | ## Implementation Details
45 |
46 | The Eval function:
47 |
48 | 1. Takes a JavaScript code string, typically containing a function definition
49 | 2. Converts the function definition to an arrow function format for execution
50 | 3. Compiles and runs the script in the V8 context
51 | 4. Passes any additional arguments to the evaluated function
52 | 5. Returns the result of the function execution
53 |
54 | ## Notes
55 |
56 | - The evaluated code has access to the current V8 context, including any global variables
57 | - For security reasons, be careful when evaluating user-provided code
58 | - Performance may be impacted when frequently evaluating code as compilation occurs each time
59 |
--------------------------------------------------------------------------------
/runtime/v8/functions/eval/eval.go:
--------------------------------------------------------------------------------
1 | package eval
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 |
7 | "github.com/google/uuid"
8 | "github.com/yaoapp/gou/runtime/v8/bridge"
9 | "rogchap.com/v8go"
10 | )
11 |
12 | // Eval execute an anonymous function
13 |
14 | var reFuncHead = regexp.MustCompile(`\s*function\s+(\w+)\s*\(([^)]*)\)\s*\{`)
15 |
16 | // ExportFunction function template
17 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
18 | return v8go.NewFunctionTemplate(iso, exec)
19 | }
20 |
21 | // exec
22 | func exec(info *v8go.FunctionCallbackInfo) *v8go.Value {
23 |
24 | jsArgs := info.Args()
25 | if len(jsArgs) < 1 {
26 | return bridge.JsException(info.Context(), "missing parameters")
27 | }
28 |
29 | if !jsArgs[0].IsString() {
30 | return bridge.JsException(info.Context(), "the first parameter should be a string")
31 | }
32 |
33 | args := []v8go.Valuer{}
34 | if len(jsArgs) > 1 {
35 | for _, arg := range jsArgs[1:] {
36 | args = append(args, arg)
37 | }
38 | }
39 |
40 | source := jsArgs[0].String()
41 | source = reFuncHead.ReplaceAllString(source, "($2) => {")
42 | name := fmt.Sprintf("__anonymous_%s", uuid.New().String())
43 |
44 | iso := info.Context().Isolate()
45 |
46 | script, err := iso.CompileUnboundScript(source, name, v8go.CompileOptions{})
47 | if err != nil {
48 | return bridge.JsException(info.Context(), err)
49 | }
50 |
51 | fn, err := script.Run(info.Context())
52 | if err != nil {
53 | return bridge.JsException(info.Context(), err)
54 | }
55 | defer fn.Release()
56 |
57 | global := info.Context().Global()
58 | global.Set(name, fn)
59 | defer global.Delete(name)
60 |
61 | jsRes, err := global.MethodCall(name, args...)
62 | if err != nil {
63 | return bridge.JsException(info.Context(), err)
64 | }
65 | return jsRes
66 | }
67 |
--------------------------------------------------------------------------------
/runtime/v8/functions/lang/lang.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/yaoapp/gou/lang"
7 | "github.com/yaoapp/gou/runtime/v8/bridge"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | // ExportFunction function template
12 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
13 | return v8go.NewFunctionTemplate(iso, replace)
14 | }
15 |
16 | // replace
17 | func replace(info *v8go.FunctionCallbackInfo) *v8go.Value {
18 | args := info.Args()
19 | if len(args) == 0 {
20 | return v8go.Undefined(info.Context().Isolate())
21 | }
22 |
23 | if !args[0].IsString() {
24 | return args[0]
25 | }
26 |
27 | value := strings.TrimPrefix(args[0].String(), "::")
28 | lang.Replace(&value)
29 |
30 | jsValue, err := bridge.JsValue(info.Context(), value)
31 | if err != nil {
32 | return bridge.JsException(info.Context(), err)
33 | }
34 |
35 | return jsValue
36 | }
37 |
--------------------------------------------------------------------------------
/runtime/v8/functions/lang/lang_test.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/runtime/v8/bridge"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | func TestReplace(t *testing.T) {
12 |
13 | ctx := prepare(t, false, "", nil)
14 | defer close(ctx)
15 |
16 | jsRes, err := ctx.RunScript(`
17 | const test = () => {
18 | const result = $L("::Pet");
19 | return {
20 | result: result,
21 | __yao_global:__yao_global,
22 | __yao_sid: __yao_sid,
23 | __YAO_SU_ROOT: __YAO_SU_ROOT,
24 | }
25 | }
26 | test()
27 | `, "")
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 |
32 | goRes, err := bridge.GoValue(jsRes, ctx)
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | res, ok := goRes.(map[string]interface{})
38 | if !ok {
39 | t.Fatal("result error")
40 | }
41 |
42 | assert.Equal(t, "Pet", res["result"])
43 | assert.Equal(t, nil, res["__yao_global"])
44 | assert.Equal(t, false, res["__YAO_SU_ROOT"])
45 | }
46 |
47 | func close(ctx *v8go.Context) {
48 | ctx.Isolate().Dispose()
49 | }
50 |
51 | func prepare(t *testing.T, root bool, sid string, global map[string]interface{}) *v8go.Context {
52 |
53 | iso := v8go.NewIsolate()
54 |
55 | template := v8go.NewObjectTemplate(iso)
56 | template.Set("$L", ExportFunction(iso))
57 |
58 | ctx := v8go.NewContext(iso, template)
59 |
60 | var err error
61 | jsGlobal := v8go.Undefined(ctx.Isolate())
62 | jsGlobal, err = bridge.JsValue(ctx, global)
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | if err = ctx.Global().Set("__YAO_SU_ROOT", root); err != nil {
68 | t.Fatal(err)
69 | }
70 |
71 | if err = ctx.Global().Set("__yao_global", jsGlobal); err != nil {
72 | t.Fatal(err)
73 | }
74 | if err = ctx.Global().Set("__yao_sid", sid); err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | return ctx
79 | }
80 |
--------------------------------------------------------------------------------
/runtime/v8/functions/process/process.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/gou/runtime/v8/bridge"
6 | "rogchap.com/v8go"
7 | )
8 |
9 | // ExportFunction function template
10 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
11 | return v8go.NewFunctionTemplate(iso, exec)
12 | }
13 |
14 | // exec
15 | func exec(info *v8go.FunctionCallbackInfo) *v8go.Value {
16 |
17 | jsArgs := info.Args()
18 | if len(jsArgs) < 1 {
19 | return bridge.JsException(info.Context(), "missing parameters")
20 | }
21 |
22 | if !jsArgs[0].IsString() {
23 | return bridge.JsException(info.Context(), "the first parameter should be a string")
24 | }
25 |
26 | share, err := bridge.ShareData(info.Context())
27 | if err != nil {
28 | return bridge.JsException(info.Context(), err)
29 | }
30 |
31 | goArgs := []interface{}{}
32 | if len(jsArgs) > 1 {
33 | for _, arg := range jsArgs[1:] {
34 | v, err := bridge.GoValue(arg, info.Context())
35 | if err != nil {
36 | return bridge.JsException(info.Context(), err)
37 | }
38 | goArgs = append(goArgs, v)
39 | }
40 | }
41 |
42 | goRes, err := process.New(jsArgs[0].String(), goArgs...).
43 | WithGlobal(share.Global).
44 | WithSID(share.Sid).
45 | Exec()
46 |
47 | if err != nil {
48 | return bridge.JsException(info.Context(), err)
49 | }
50 |
51 | jsRes, err := bridge.JsValue(info.Context(), goRes)
52 | if err != nil {
53 | return bridge.JsException(info.Context(), err)
54 | }
55 |
56 | return jsRes
57 | }
58 |
--------------------------------------------------------------------------------
/runtime/v8/functions/studio/studio.go:
--------------------------------------------------------------------------------
1 | package studio
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/yaoapp/gou/process"
8 | "github.com/yaoapp/gou/runtime/v8/bridge"
9 | "rogchap.com/v8go"
10 | )
11 |
12 | // ExportFunction function template
13 | func ExportFunction(iso *v8go.Isolate) *v8go.FunctionTemplate {
14 | return v8go.NewFunctionTemplate(iso, exec)
15 | }
16 |
17 | // exec
18 | func exec(info *v8go.FunctionCallbackInfo) *v8go.Value {
19 |
20 | share, err := bridge.ShareData(info.Context())
21 | if err != nil {
22 | return bridge.JsException(info.Context(), err)
23 | }
24 |
25 | if !share.Root {
26 | return bridge.JsException(info.Context(), "function is not allowed")
27 | }
28 |
29 | jsArgs := info.Args()
30 | if len(jsArgs) < 1 {
31 | return bridge.JsException(info.Context(), "missing parameters")
32 | }
33 |
34 | if !jsArgs[0].IsString() {
35 | return bridge.JsException(info.Context(), "the first parameter should be a string")
36 | }
37 |
38 | goArgs := []interface{}{}
39 | if len(jsArgs) > 1 {
40 | goArgs, err = bridge.GoValues(jsArgs[1:], info.Context())
41 | if err != nil {
42 | return bridge.JsException(info.Context(), err)
43 | }
44 | }
45 |
46 | name := fmt.Sprintf("studio.%s", strings.TrimPrefix(jsArgs[0].String(), "studio."))
47 | goRes, err := process.New(name, goArgs...).
48 | WithGlobal(share.Global).
49 | WithSID(share.Sid).
50 | Exec()
51 |
52 | if err != nil {
53 | return bridge.JsException(info.Context(), err)
54 | }
55 |
56 | jsRes, err := bridge.JsValue(info.Context(), goRes)
57 | if err != nil {
58 | return bridge.JsException(info.Context(), err)
59 | }
60 |
61 | return jsRes
62 | }
63 |
--------------------------------------------------------------------------------
/runtime/v8/isolate_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/yaoapp/kun/log"
8 | )
9 |
10 | func TestSelectIsoStandard(t *testing.T) {
11 | option := option()
12 | option.Mode = "standard"
13 | option.HeapSizeLimit = 4294967296
14 |
15 | prepare(t, option)
16 | defer Stop()
17 |
18 | iso, err := SelectIsoStandard(time.Millisecond * 100)
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 | defer iso.Dispose()
23 | }
24 |
25 | // go test -bench=BenchmarkSelectIsoStandard
26 | // go test -bench=BenchmarkSelectIsoStandard -benchmem -benchtime=5s
27 | // go test -bench=BenchmarkSelectIsoStandard -benchtime=5s
28 | func BenchmarkSelectIsoStandard(b *testing.B) {
29 | option := option()
30 | option.Mode = "standard"
31 | option.HeapSizeLimit = 4294967296
32 |
33 | b.ResetTimer()
34 | var t *testing.T
35 | prepare(t, option)
36 | defer Stop()
37 | log.SetLevel(log.FatalLevel)
38 |
39 | // run the Call function b.N times
40 | for n := 0; n < b.N; n++ {
41 | iso, err := SelectIsoStandard(500 * time.Millisecond)
42 | if err != nil {
43 | b.Fatal(err)
44 | }
45 | iso.Dispose()
46 | }
47 | b.StopTimer()
48 | }
49 |
50 | func BenchmarkSelectIsoStandardPB(b *testing.B) {
51 | option := option()
52 | option.Mode = "standard"
53 | option.HeapSizeLimit = 4294967296
54 |
55 | b.ResetTimer()
56 | var t *testing.T
57 | prepare(t, option)
58 | defer Stop()
59 | log.SetLevel(log.FatalLevel)
60 |
61 | // run the Call function b.N times
62 | b.RunParallel(func(pb *testing.PB) {
63 | for pb.Next() {
64 | iso, err := SelectIsoStandard(500 * time.Millisecond)
65 | if err != nil {
66 | b.Fatal(err)
67 | }
68 | iso.Dispose()
69 | }
70 | })
71 | b.StopTimer()
72 | }
73 |
--------------------------------------------------------------------------------
/runtime/v8/objects/job/job_test.go:
--------------------------------------------------------------------------------
1 | package job
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | "github.com/yaoapp/gou/runtime/v8/bridge"
10 | "rogchap.com/v8go"
11 | )
12 |
13 | func TestJob(t *testing.T) {
14 |
15 | ctx := prepare(t, false, "", nil)
16 | defer close(ctx)
17 |
18 | jsRes, err := ctx.RunScript(`
19 | const test = () => {
20 | let job = new Job("test.job.run", "http://test.com", "foo=bar");
21 | let progress = 0
22 | job.Pending(() => {
23 | progress ++
24 | });
25 | let data = job.Data()
26 | return {progress:progress, ...data};
27 | }
28 | test()
29 | `, "")
30 | if err != nil {
31 | t.Fatal(err)
32 | }
33 |
34 | goRes, err := bridge.GoValue(jsRes, ctx)
35 | if err != nil {
36 | t.Fatal(err)
37 | }
38 |
39 | res, ok := goRes.(map[string]interface{})
40 | if !ok {
41 | t.Fatal("result error")
42 | }
43 |
44 | assert.Equal(t, "hello", res["message"])
45 | assert.Equal(t, "http://test.com", res["url"])
46 | assert.Equal(t, "foo=bar", res["payload"])
47 | assert.Greater(t, res["progress"], float64(0))
48 | }
49 |
50 | func close(ctx *v8go.Context) {
51 | ctx.Isolate().Dispose()
52 | }
53 |
54 | func prepare(t *testing.T, root bool, sid string, global map[string]interface{}) *v8go.Context {
55 |
56 | iso := v8go.NewIsolate()
57 |
58 | template := v8go.NewObjectTemplate(iso)
59 | template.Set("Job", New().ExportFunction(iso))
60 |
61 | ctx := v8go.NewContext(iso, template)
62 |
63 | process.Register("test.job.run", func(process *process.Process) interface{} {
64 | time.Sleep(200 * time.Millisecond)
65 | url := process.ArgsString(0)
66 | payload := process.ArgsString(1)
67 | return map[string]interface{}{"message": "hello", "url": url, "payload": payload}
68 | })
69 |
70 | return ctx
71 | }
72 |
--------------------------------------------------------------------------------
/runtime/v8/objects/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/yaoapp/gou/runtime/v8/bridge"
7 | "github.com/yaoapp/kun/log"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | // Object Javascript API
12 | type Object struct{}
13 |
14 | // New create a new Log Object
15 | func New() *Object {
16 | return &Object{}
17 | }
18 |
19 | // ExportObject Export as a Log Object
20 | // log.Trace("%s %v", "name", {"foo":"bar"} )
21 | // log.Error("%s %v", "name", {"foo":"bar"} )
22 | func (obj *Object) ExportObject(iso *v8go.Isolate) *v8go.ObjectTemplate {
23 | tmpl := v8go.NewObjectTemplate(iso)
24 | tmpl.Set("Trace", obj.run(iso, log.TraceLevel))
25 | tmpl.Set("Debug", obj.run(iso, log.DebugLevel))
26 | tmpl.Set("Info", obj.run(iso, log.InfoLevel))
27 | tmpl.Set("Warn", obj.run(iso, log.WarnLevel))
28 | tmpl.Set("Error", obj.run(iso, log.ErrorLevel))
29 | tmpl.Set("Fatal", obj.run(iso, log.FatalLevel))
30 | tmpl.Set("Panic", obj.run(iso, log.PanicLevel))
31 | return tmpl
32 | }
33 |
34 | func (obj *Object) run(iso *v8go.Isolate, level log.Level) *v8go.FunctionTemplate {
35 |
36 | return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
37 |
38 | args := info.Args()
39 | if len(args) < 1 {
40 | msg := fmt.Sprintf("Log: %s", "Missing parameters")
41 | log.Error(msg)
42 | return bridge.JsException(info.Context(), msg)
43 | }
44 |
45 | message := args[0].String()
46 | values := []interface{}{}
47 | var err error
48 | if len(args) > 1 {
49 | values, err = bridge.GoValues(args[1:], info.Context())
50 | if err != nil {
51 | msg := fmt.Sprintf("Log: %s", err.Error())
52 | log.Error(msg)
53 | return bridge.JsException(info.Context(), msg)
54 | }
55 | }
56 |
57 | switch level {
58 | case log.TraceLevel:
59 | log.Trace(message, values...)
60 | break
61 | case log.DebugLevel:
62 | log.Debug(message, values...)
63 | break
64 | case log.InfoLevel:
65 | log.Info(message, values...)
66 | break
67 | case log.WarnLevel:
68 | log.Warn(message, values...)
69 | break
70 | case log.ErrorLevel:
71 | log.Error(message, values...)
72 | break
73 | case log.FatalLevel:
74 | log.Fatal(message, values...)
75 | break
76 | case log.PanicLevel:
77 | log.Panic(message, values...)
78 | break
79 | default:
80 | log.Error(message, values...)
81 | }
82 |
83 | return v8go.Null(iso)
84 | })
85 | }
86 |
--------------------------------------------------------------------------------
/runtime/v8/objects/log/log_test.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/yaoapp/kun/log"
7 | "rogchap.com/v8go"
8 | )
9 |
10 | func TestLogObject(t *testing.T) {
11 |
12 | iso := v8go.NewIsolate()
13 | defer iso.Dispose()
14 |
15 | obj := &Object{}
16 | global := v8go.NewObjectTemplate(iso)
17 | global.Set("log", obj.ExportObject(iso))
18 |
19 | ctx := v8go.NewContext(iso, global)
20 | defer ctx.Close()
21 |
22 | log.SetLevel(log.TraceLevel)
23 |
24 | // ===== Trace
25 | _, err := ctx.RunScript(`log.Trace("Trace: %s %v %#v", "hello world", ["foo", "bar"], {"foo":"bar"})`, "")
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 |
30 | // ===== Debug
31 | _, err = ctx.RunScript(`log.Debug("Debug: %s %v %#v", "hello world", ["foo", "bar"], {"foo":"bar"})`, "")
32 | if err != nil {
33 | t.Fatal(err)
34 | }
35 |
36 | // ===== Info
37 | _, err = ctx.RunScript(`log.Info("Info: %s %v %#v", "hello world", ["foo", "bar"], {"foo":"bar"})`, "")
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 |
42 | // ===== Error
43 | _, err = ctx.RunScript(`log.Error("Error: %s %v %#v", "hello world", ["foo", "bar"], {"foo":"bar"})`, "")
44 | if err != nil {
45 | t.Fatal(err)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/runtime/v8/objects/time/time_test.go:
--------------------------------------------------------------------------------
1 | package time
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | "github.com/yaoapp/kun/exception"
10 | "rogchap.com/v8go"
11 | )
12 |
13 | func TestSleep(t *testing.T) {
14 |
15 | iso := v8go.NewIsolate()
16 | defer iso.Dispose()
17 |
18 | obj := &Object{}
19 | ctx := v8go.NewContext(iso)
20 | defer ctx.Close()
21 | obj.Set("time", ctx)
22 |
23 | _, err := ctx.RunScript(`
24 | const test = () => time.Sleep(200)
25 | test()
26 | `, "")
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 |
31 | }
32 |
33 | func TestAfter(t *testing.T) {
34 | iso := v8go.NewIsolate()
35 | defer iso.Dispose()
36 |
37 | obj := &Object{}
38 | ctx := v8go.NewContext(iso)
39 | defer ctx.Close()
40 | obj.Set("time", ctx)
41 |
42 | testArgs := []interface{}{}
43 |
44 | process.Register("unit.test.time", func(process *process.Process) interface{} {
45 | testArgs = process.Args
46 | return nil
47 | })
48 |
49 | _, err := ctx.RunScript(`
50 | time.After(200, "unit.test.time", "foo", "bar")
51 | `, "")
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 |
56 | time.Sleep(300 * time.Millisecond)
57 | assert.Equal(t, []interface{}{"foo", "bar"}, testArgs)
58 |
59 | }
60 |
61 | func TestAfterError(t *testing.T) {
62 | iso := v8go.NewIsolate()
63 | defer iso.Dispose()
64 |
65 | obj := &Object{}
66 | ctx := v8go.NewContext(iso)
67 | defer ctx.Close()
68 | obj.Set("time", ctx)
69 |
70 | testArgs := []interface{}{}
71 |
72 | process.Register("unit.test.time", func(process *process.Process) interface{} {
73 | testArgs = process.Args
74 | exception.New("unit.test.time", 200).Throw()
75 | return nil
76 | })
77 |
78 | _, err := ctx.RunScript(`
79 | time.After(200, "unit.test.time", "foo", "bar")
80 | `, "")
81 | if err != nil {
82 | t.Fatal(err)
83 | }
84 |
85 | time.Sleep(300 * time.Millisecond)
86 | assert.Equal(t, []interface{}{"foo", "bar"}, testArgs)
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/runtime/v8/objects/websocket/websocket_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/yaoapp/gou/websocket"
10 | "rogchap.com/v8go"
11 | )
12 |
13 | func TestWebSocketPush(t *testing.T) {
14 |
15 | serve := serve(t)
16 | defer serve.Stop()
17 |
18 | iso := v8go.NewIsolate()
19 | defer iso.Dispose()
20 |
21 | ws := &WebSocket{}
22 | global := v8go.NewObjectTemplate(iso)
23 | global.Set("WebSocket", ws.ExportFunction(iso))
24 |
25 | ctx := v8go.NewContext(iso, global)
26 | defer ctx.Close()
27 |
28 | v, err := ctx.RunScript(`
29 | var ws = new WebSocket("ws://127.0.0.1:5056/websocket/test", "po")
30 | ws.push("Hello World!")
31 | `, "")
32 |
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | assert.True(t, v.IsUndefined())
38 | }
39 |
40 | func serve(t *testing.T) *websocket.Upgrader {
41 |
42 | ws, err := websocket.NewUpgrader("test")
43 | if err != nil {
44 | t.Fatalf("%s", err)
45 | }
46 |
47 | router := gin.Default()
48 | ws.SetHandler(func(message []byte, client int) ([]byte, error) { return message, nil })
49 | ws.SetRouter(router)
50 |
51 | go ws.Start()
52 | go router.Run(":5056")
53 | time.Sleep(200 * time.Millisecond)
54 | return ws
55 | }
56 |
--------------------------------------------------------------------------------
/runtime/v8/option.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import "github.com/yaoapp/kun/log"
4 |
5 | // SetHeapAvailableSize set runtime Available
6 | func SetHeapAvailableSize(size uint) {
7 | runtimeOption.HeapAvailableSize = uint64(size)
8 | }
9 |
10 | // DisablePrecompile disable the precompile
11 | func DisablePrecompile() {
12 | runtimeOption.Precompile = false
13 | }
14 |
15 | // EnablePrecompile enable the precompile feature
16 | func EnablePrecompile() {
17 | runtimeOption.Precompile = true
18 | }
19 |
20 | // EnableDebug enable the debug mode
21 | func EnableDebug() {
22 | runtimeOption.Debug = true
23 | }
24 |
25 | // Validate the option
26 | func (option *Option) Validate() {
27 |
28 | if option.MinSize == 0 {
29 | option.MinSize = 50
30 | }
31 |
32 | if option.MaxSize == 0 {
33 | option.MaxSize = 100
34 | }
35 |
36 | if option.DefaultTimeout == 0 {
37 | option.DefaultTimeout = 200
38 | }
39 |
40 | if option.ContextTimeout == 0 {
41 | option.ContextTimeout = 200
42 | }
43 |
44 | if option.ContetxQueueSize == 0 {
45 | option.ContetxQueueSize = 10
46 | }
47 |
48 | if option.Mode == "" {
49 | option.Mode = "standard"
50 | }
51 |
52 | if option.MinSize > 500 {
53 | log.Warn("[V8] the maximum value of initSize is 500")
54 | option.MinSize = 500
55 | }
56 |
57 | if option.MaxSize > 500 {
58 | log.Warn("[V8] the maximum value of maxSize is 500")
59 | option.MaxSize = 500
60 | }
61 |
62 | if option.MinSize > option.MaxSize {
63 | log.Warn("[V8] the initSize value should smaller than maxSize")
64 | option.MaxSize = option.MinSize
65 | }
66 |
67 | if option.HeapSizeLimit == 0 {
68 | option.HeapSizeLimit = 1518338048 // 1.5G
69 | }
70 |
71 | if option.HeapSizeLimit > 4294967296 {
72 | log.Warn("[V8] the maximum value of HeapSizeLimit is 4294967296(4G)")
73 | option.HeapSizeLimit = 1518338048 // 1.5G
74 | }
75 |
76 | if option.HeapSizeRelease == 0 {
77 | option.HeapSizeRelease = 524288 // 50M
78 | }
79 |
80 | if option.HeapSizeRelease > 524288000 {
81 | log.Warn("[V8] the maximum value of heapSizeRelease is 524288000(500M)")
82 | option.HeapSizeRelease = 524288000 // 500M
83 | }
84 |
85 | if option.HeapAvailableSize == 0 {
86 | option.HeapAvailableSize = 524288000 // 500M
87 | }
88 |
89 | if option.HeapAvailableSize < 524288000 || option.HeapAvailableSize > option.HeapSizeLimit {
90 | log.Warn("[V8] the heapAvailableSize value is 524288000(500M) or heapSizeLimit * 0.30 to reduce the risk of program crashes")
91 | // option.HeapSizeRelease = 524288000 // 500M
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/runtime/v8/process.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | )
7 |
8 | func init() {
9 | process.Register("scripts", processScripts)
10 | process.Register("studio", processStudio)
11 | }
12 |
13 | // processScripts
14 | func processScripts(process *process.Process) interface{} {
15 |
16 | script, err := Select(process.ID)
17 | if err != nil {
18 | exception.New("scripts.%s not loaded", 404, process.ID).Throw()
19 | return nil
20 | }
21 |
22 | return script.Exec(process)
23 | }
24 |
25 | // processScripts scripts.ID.Method
26 | func processStudio(process *process.Process) interface{} {
27 |
28 | script, err := SelectRoot(process.ID)
29 | if err != nil {
30 | exception.New("studio.%s not loaded", 404, process.ID).Throw()
31 | return nil
32 | }
33 | return script.Exec(process)
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/runtime/v8/process_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | // func TestProcessScripts(t *testing.T) {
4 | // prepare(t)
5 | // time.Sleep(20 * time.Millisecond)
6 | // assert.Equal(t, 2, isolates.Len)
7 | // assert.Equal(t, 2, len(chIsoReady))
8 |
9 | // p, err := process.Of("scripts.runtime.basic.Hello", "world")
10 | // if err != nil {
11 | // t.Fatal(err)
12 | // }
13 |
14 | // value, err := p.Exec()
15 | // if err != nil {
16 | // t.Fatal(err)
17 | // }
18 |
19 | // assert.Equal(t, "world", value)
20 |
21 | // p, err = process.Of("scripts.runtime.basic.Error", "world")
22 | // if err != nil {
23 | // t.Fatal(err)
24 | // }
25 |
26 | // _, err = p.Exec()
27 | // assert.Contains(t, err.Error(), "at callStackTest")
28 | // assert.Contains(t, err.Error(), "at Error")
29 |
30 | // }
31 |
32 | // func TestProcessScriptsRoot(t *testing.T) {
33 | // prepare(t)
34 | // time.Sleep(20 * time.Millisecond)
35 | // assert.Equal(t, 2, isolates.Len)
36 | // assert.Equal(t, 2, len(chIsoReady))
37 |
38 | // p, err := process.Of("studio.runtime.basic.Hello", "world")
39 | // if err != nil {
40 | // t.Fatal(err)
41 | // }
42 |
43 | // value, err := p.Exec()
44 | // if err != nil {
45 | // t.Fatal(err)
46 | // }
47 |
48 | // assert.Equal(t, "world", value)
49 | // }
50 |
--------------------------------------------------------------------------------
/runtime/v8/require.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "github.com/yaoapp/gou/runtime/v8/bridge"
5 | "rogchap.com/v8go"
6 | )
7 |
8 | // Require function template
9 | func Require(iso *v8go.Isolate) *v8go.FunctionTemplate {
10 | return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
11 |
12 | share, err := bridge.ShareData(info.Context())
13 | if err != nil {
14 | return bridge.JsException(info.Context(), err)
15 | }
16 |
17 | jsArgs := info.Args()
18 | if len(jsArgs) < 1 {
19 | return bridge.JsException(info.Context(), "missing parameters")
20 | }
21 |
22 | if !jsArgs[0].IsString() {
23 | return bridge.JsException(info.Context(), "the first parameter should be a string")
24 | }
25 |
26 | id := jsArgs[0].String()
27 | script := Scripts[id]
28 | if share.Root {
29 | if _, has := RootScripts[id]; has {
30 | script = RootScripts[id]
31 | }
32 | }
33 |
34 | globalName := "require"
35 | info.Context().RunScript(Transform(script.Source, globalName), script.File)
36 | global, _ := info.Context().Global().Get(globalName)
37 | return global
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/runtime/v8/require_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/runtime/v8/bridge"
8 | "rogchap.com/v8go"
9 | )
10 |
11 | func TestRequre(t *testing.T) {
12 |
13 | option := option()
14 | option.Mode = "standard"
15 | option.HeapSizeLimit = 4294967296
16 |
17 | prepare(t, option)
18 |
19 | ctx := requrePrepare(t, false, "", nil)
20 | defer requireClose(ctx)
21 |
22 | jsRes, err := ctx.RunScript(`
23 | const lib = Require("runtime.lib");
24 | const { Foo } = Require("runtime.lib");
25 | function Hello() {
26 | return {
27 | "lib.Foo": lib.Foo(),
28 | "Foo": Foo()
29 | };
30 | }
31 | Hello()
32 | `, "")
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | goRes, err := bridge.GoValue(jsRes, ctx)
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 |
42 | res, ok := goRes.(map[string]interface{})
43 | if !ok {
44 | t.Fatal("result error")
45 | }
46 |
47 | assert.Equal(t, "bar", res["lib.Foo"])
48 | assert.Equal(t, "bar", res["Foo"])
49 | }
50 |
51 | func requireClose(ctx *v8go.Context) {
52 | ctx.Isolate().Dispose()
53 | }
54 |
55 | func requrePrepare(t *testing.T, root bool, sid string, global map[string]interface{}) *v8go.Context {
56 |
57 | iso := v8go.NewIsolate()
58 |
59 | template := v8go.NewObjectTemplate(iso)
60 | template.Set("Require", Require(iso))
61 |
62 | ctx := v8go.NewContext(iso, template)
63 |
64 | var err error
65 | goData := map[string]interface{}{
66 | "SID": sid,
67 | "ROOT": root,
68 | "DATA": global,
69 | }
70 |
71 | jsData, err := bridge.JsValue(ctx, goData)
72 | if err != nil {
73 | t.Fatal(err)
74 | }
75 |
76 | if err = ctx.Global().Set("__yao_data", jsData); err != nil {
77 | t.Fatal(err)
78 | }
79 |
80 | return ctx
81 | }
82 |
--------------------------------------------------------------------------------
/runtime/v8/sourcemap_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "path/filepath"
5 | "strings"
6 | "testing"
7 | "time"
8 |
9 | "github.com/google/uuid"
10 | "github.com/stretchr/testify/assert"
11 | "github.com/yaoapp/gou/application"
12 | "rogchap.com/v8go"
13 | )
14 |
15 | func TestStackTrace(t *testing.T) {
16 | option := option()
17 | option.Mode = "standard"
18 | option.Import = true
19 | option.HeapSizeLimit = 4294967296
20 | option.Debug = true
21 |
22 | // add tsconfig
23 | tsconfig := &TSConfig{
24 | CompilerOptions: &TSConfigCompilerOptions{
25 | Paths: map[string][]string{
26 | "@yao/*": {"./scripts/.types/*"},
27 | "@lib/*": {"./scripts/runtime/ts/lib/*"},
28 | },
29 | },
30 | }
31 | option.TSConfig = tsconfig
32 |
33 | prepare(t, option)
34 | defer Stop()
35 |
36 | files := map[string]string{
37 | "page.ts": filepath.Join("scripts", "runtime", "ts", "page.ts"),
38 | "lib.hello.ts": filepath.Join("scripts", "runtime", "ts", "lib", "hello.ts"),
39 | }
40 |
41 | source, err := application.App.Read(files["page.ts"])
42 | if err != nil {
43 | t.Fatal(err)
44 | }
45 |
46 | script, err := MakeScript(source, files["page.ts"], 5*time.Second)
47 |
48 | ctx, err := script.NewContext(uuid.New().String(), map[string]interface{}{})
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 | defer ctx.Close()
53 |
54 | global := ctx.Global()
55 | _, err = global.MethodCall("SomethingError")
56 | if err == nil {
57 | t.Fatal("error expected but got nil")
58 | }
59 |
60 | e, ok := err.(*v8go.JSError)
61 | if !ok {
62 | t.Fatal("error is not a JSError")
63 | }
64 |
65 | trace := StackTrace(e, nil)
66 | assert.NotEmpty(t, trace)
67 | assert.Contains(t, trace, "400 Error occurred")
68 | assert.Contains(t, trace, "/scripts/runtime/ts/page.ts:12:2")
69 | assert.Contains(t, trace, "/scripts/runtime/ts/lib/bar.ts:7:2")
70 | assert.Contains(t, trace, "/scripts/runtime/ts/lib/err.ts:8:10")
71 |
72 | // with source root
73 | trace = StackTrace(e, map[string]string{"/scripts": "/iscripts"})
74 | assert.NotEmpty(t, trace)
75 | assert.Contains(t, trace, "400 Error occurred")
76 | assert.Contains(t, trace, "/iscripts/runtime/ts/page.ts:12:2")
77 | assert.Contains(t, trace, "/iscripts/runtime/ts/lib/bar.ts:7:2")
78 | assert.Contains(t, trace, "/iscripts/runtime/ts/lib/err.ts:8:10")
79 |
80 | // with source root function
81 | replace := func(file string) string {
82 | if strings.HasPrefix(file, "/scripts") {
83 | return strings.Replace(file, "/scripts", "/fscripts", 1)
84 | }
85 | return file
86 | }
87 |
88 | trace = StackTrace(e, replace)
89 | assert.NotEmpty(t, trace)
90 | assert.Contains(t, trace, "400 Error occurred")
91 | assert.Contains(t, trace, "/fscripts/runtime/ts/page.ts:12:2")
92 | assert.Contains(t, trace, "/fscripts/runtime/ts/lib/bar.ts:7:2")
93 | assert.Contains(t, trace, "/fscripts/runtime/ts/lib/err.ts:8:10")
94 | }
95 |
--------------------------------------------------------------------------------
/runtime/v8/store/cache.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | // Key the cache
4 | func (cache *Cache) Key() string {
5 | return cache.key
6 | }
7 |
8 | // Dispose the cache
9 | func (cache *Cache) Dispose() {
10 | for _, ctx := range cache.contexts {
11 | ctx.Context.Close()
12 | ctx = nil
13 | }
14 |
15 | // if iso, has := Isolates.Get(cache.key); has {
16 | // iso.Dispose()
17 | // iso = nil
18 | // }
19 |
20 | cache.contexts = nil
21 | cache = nil
22 | }
23 |
--------------------------------------------------------------------------------
/runtime/v8/store/context.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import "rogchap.com/v8go"
4 |
5 | var caches = New()
6 |
7 | // NewContext create a new context
8 | func NewContext(isolate, script string, ctx *v8go.Context) *Context {
9 | return &Context{
10 | isolate: isolate,
11 | script: script,
12 | Context: ctx,
13 | }
14 | }
15 |
16 | // Release release the context
17 | func (ctx *Context) Release() error {
18 | // Remove the context from cache and release the context
19 | RemoveContextCache(ctx.isolate, ctx.script)
20 | return nil
21 | }
22 |
23 | // GetContextFromCache get the context from cache
24 | func GetContextFromCache(isolate, script string) (*Context, bool) {
25 |
26 | cache, has := caches.Get(isolate)
27 | if !has {
28 | return nil, false
29 | }
30 |
31 | ctx, has := cache.(*Cache).contexts[script]
32 | if !has {
33 | return nil, false
34 | }
35 |
36 | return ctx, true
37 | }
38 |
39 | // SetContextCache set the context to cache
40 | func SetContextCache(isolate, script string, ctx *Context) {
41 |
42 | cache, has := caches.Get(isolate)
43 | if !has {
44 | cache = &Cache{
45 | key: isolate,
46 | contexts: map[string]*Context{},
47 | }
48 | }
49 | cache.(*Cache).contexts[script] = ctx
50 | caches.Add(cache)
51 | }
52 |
53 | // RemoveContextCache remove the context cache
54 | func RemoveContextCache(isolate, script string) {
55 | cache, has := caches.Get(isolate)
56 | if !has {
57 | return
58 | }
59 |
60 | ctx, has := cache.(*Cache).contexts[script]
61 | if !has {
62 | return
63 | }
64 |
65 | ctx.Context.Close()
66 | ctx.Context = nil
67 | ctx = nil
68 | delete(cache.(*Cache).contexts, script)
69 | return
70 | }
71 |
72 | // MakeIsolateCache make the isolate cache
73 | func MakeIsolateCache(isolate string) {
74 | caches.Add(&Cache{
75 | key: isolate,
76 | contexts: map[string]*Context{},
77 | })
78 | }
79 |
80 | // CleanIsolateCache clean the isolate cache
81 | func CleanIsolateCache(isolate string) {
82 | cache, has := caches.Get(isolate)
83 | if !has {
84 | return
85 | }
86 | cache.Dispose()
87 | caches.Remove(isolate)
88 | }
89 |
--------------------------------------------------------------------------------
/runtime/v8/store/isolate.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | const (
8 |
9 | // IsoReady isolate is ready
10 | IsoReady uint8 = 0
11 |
12 | // IsoBusy isolate is in used
13 | IsoBusy uint8 = 1
14 | )
15 |
16 | // Dispose the isolate
17 | func (iso *Isolate) Dispose() {
18 | // fmt.Printf("dispose isolate: %s\n", iso.Key())
19 | Isolates.Remove(iso.Key()) // remove from normal isolates
20 | iso.Isolate.Dispose()
21 | iso.Isolate = nil
22 | iso.Template = nil
23 | iso = nil
24 | }
25 |
26 | // Key return the key of the isolate
27 | func (iso *Isolate) Key() string {
28 | return fmt.Sprintf("%p", iso)
29 | }
30 |
31 | // Lock the isolate
32 | func (iso *Isolate) Lock() {
33 | iso.Status = IsoBusy
34 | }
35 |
36 | // Unlock the isolate
37 | func (iso *Isolate) Unlock() {
38 | iso.Status = IsoReady
39 | }
40 |
41 | // Locked check if the isolate is locked
42 | func (iso *Isolate) Locked() bool {
43 | return iso.Status == IsoBusy
44 | }
45 |
46 | // Health check the isolate health
47 | func (iso *Isolate) Health(HeapSizeRelease uint64, HeapAvailableSize uint64) bool {
48 |
49 | // {
50 | // "ExternalMemory": 0,
51 | // "HeapSizeLimit": 1518338048,
52 | // "MallocedMemory": 16484,
53 | // "NumberOfDetachedContexts": 0,
54 | // "NumberOfNativeContexts": 3,
55 | // "PeakMallocedMemory": 24576,
56 | // "TotalAvailableSize": 1518051356,
57 | // "TotalHeapSize": 1261568,
58 | // "TotalHeapSizeExecutable": 262144,
59 | // "TotalPhysicalSize": 499164,
60 | // "UsedHeapSize": 713616
61 | // }
62 |
63 | if iso.Isolate == nil {
64 | return false
65 | }
66 |
67 | stat := iso.Isolate.GetHeapStatistics()
68 | if stat.TotalAvailableSize < HeapAvailableSize { // 500M
69 | return false
70 | }
71 |
72 | return true
73 | }
74 |
--------------------------------------------------------------------------------
/runtime/v8/store/store.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Isolates the new isolate store
8 | var Isolates = New()
9 |
10 | // New create a new store
11 | func New() *Store {
12 | return &Store{data: map[string]IStore{}, mutex: &sync.Mutex{}}
13 | }
14 |
15 | // Get get a isolate
16 | func (store *Store) Get(key string) (IStore, bool) {
17 | store.mutex.Lock()
18 | defer store.mutex.Unlock()
19 | v, ok := store.data[key]
20 | if !ok {
21 | return nil, false
22 | }
23 | return v.(IStore), true
24 | }
25 |
26 | // Add a isolate
27 | func (store *Store) Add(data IStore) {
28 | store.mutex.Lock()
29 | defer store.mutex.Unlock()
30 | store.data[data.Key()] = data
31 |
32 | }
33 |
34 | // Remove a isolate
35 | func (store *Store) Remove(key string) {
36 | store.mutex.Lock()
37 | defer store.mutex.Unlock()
38 | delete(store.data, key)
39 |
40 | }
41 |
42 | // Len the length of store
43 | func (store *Store) Len() int {
44 | return len(store.data)
45 | }
46 |
47 | // Range traverse isolates
48 | func (store *Store) Range(callback func(data IStore) bool) {
49 | for _, v := range store.data {
50 | if !callback(v.(IStore)) {
51 | break
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/runtime/v8/store/types.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "sync"
5 |
6 | "rogchap.com/v8go"
7 | )
8 |
9 | // Store the sync map
10 | type Store struct {
11 | data map[string]IStore
12 | mutex *sync.Mutex
13 | }
14 |
15 | // IStore the interface of store
16 | type IStore interface {
17 | Key() string
18 | Dispose()
19 | }
20 |
21 | // Isolate v8 Isolate
22 | type Isolate struct {
23 | *v8go.Isolate
24 | Status uint8
25 | Template *v8go.ObjectTemplate
26 | }
27 |
28 | // Context runtime context
29 | type Context struct {
30 | script string // Script ID
31 | isolate string // Isolate ID
32 | *v8go.Context
33 | }
34 |
35 | // Cache the cache
36 | type Cache struct {
37 | key string
38 | contexts map[string]*Context
39 | }
40 |
--------------------------------------------------------------------------------
/runtime/v8/tests/plan_test.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/yaoapp/gou/application"
9 | "github.com/yaoapp/gou/process"
10 | v8 "github.com/yaoapp/gou/runtime/v8"
11 | "github.com/yaoapp/kun/utils"
12 | )
13 |
14 | func TestPlan(t *testing.T) {
15 | option := v8.Option{}
16 | prepare(t, &option)
17 | defer v8.Stop()
18 |
19 | script, err := v8.Select("runtime.api.plan")
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 |
24 | p := process.New("scripts.runtime.api.plan.Test")
25 | res := script.Exec(p)
26 | utils.Dump(res)
27 | }
28 |
29 | func prepare(t *testing.T, option *v8.Option) {
30 | root := os.Getenv("GOU_TEST_APPLICATION")
31 |
32 | // Load app
33 | app, err := application.OpenFromDisk(root)
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 | application.Load(app)
38 |
39 | // application scripts
40 | scripts := map[string]string{
41 | "runtime.api.plan": filepath.Join("scripts", "runtime", "api", "plan.ts"),
42 | }
43 |
44 | for id, file := range scripts {
45 | _, err := v8.Load(file, id)
46 | if err != nil {
47 | t.Fatal(err)
48 | }
49 | }
50 |
51 | prepareSetup(t, option)
52 | }
53 |
54 | func prepareSetup(t *testing.T, option *v8.Option) {
55 | v8.EnablePrecompile()
56 | v8.Start(option)
57 | }
58 |
--------------------------------------------------------------------------------
/runtime/v8/tsconfig.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 |
8 | "github.com/yaoapp/gou/application"
9 | )
10 |
11 | // GetFileName get the file name from the tsconfig
12 | func (tsconfg *TSConfig) GetFileName(path string) (string, bool, error) {
13 | if tsconfg == nil {
14 | return path, false, nil
15 | }
16 |
17 | if tsconfg.CompilerOptions == nil || tsconfg.CompilerOptions.Paths == nil {
18 | return path, false, nil
19 | }
20 |
21 | for pattern, paths := range tsconfg.CompilerOptions.Paths {
22 | if tsconfg.Match(pattern, path) {
23 | f := tsconfg.ReplacePattern(path, pattern)
24 | for _, p := range paths {
25 | matched := false
26 | dir := filepath.Clean(filepath.Dir(p))
27 | f = filepath.Join(dir, f)
28 | err := application.App.Walk(dir, func(root, filename string, isdir bool) error {
29 | if isdir {
30 | return nil
31 | }
32 | if filename == f {
33 | matched = true
34 | return filepath.SkipAll
35 | }
36 | return nil
37 | }, "*.ts")
38 |
39 | if matched {
40 | return f, true, nil
41 | }
42 |
43 | if err == nil {
44 | return path, false, nil
45 | }
46 | }
47 | }
48 | }
49 | return path, false, nil
50 | }
51 |
52 | // Match match the pattern
53 | func (tsconfg *TSConfig) Match(pattern, path string) bool {
54 | prefix := strings.Split(pattern, "/*")[0] + string(os.PathSeparator)
55 | return strings.HasPrefix(path, prefix)
56 | }
57 |
58 | // ReplacePattern replace the pattern
59 | func (tsconfg *TSConfig) ReplacePattern(path, pattern string) string {
60 | prefix := strings.Split(pattern, "/*")[0]
61 | file := strings.TrimPrefix(path, prefix)
62 | if strings.HasSuffix(file, ".ts") {
63 | return file
64 | }
65 | return file + ".ts"
66 | }
67 |
--------------------------------------------------------------------------------
/runtime/v8/tsconfig_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestTSConfigGetFileName(t *testing.T) {
10 | option := option()
11 | option.Mode = "standard"
12 | option.Import = true
13 | option.HeapSizeLimit = 4294967296
14 |
15 | // add tsconfig
16 | tsconfig := &TSConfig{
17 | CompilerOptions: &TSConfigCompilerOptions{
18 | Paths: map[string][]string{
19 | "@yao/*": {"./scripts/.types/*"},
20 | "@lib/*": {"./scripts/runtime/ts/lib/*"},
21 | },
22 | },
23 | }
24 | option.TSConfig = tsconfig
25 | prepare(t, option)
26 | defer Stop()
27 |
28 | file, match, err := tsconfig.GetFileName("@lib/foo")
29 | if err != nil {
30 | t.Fatal(err)
31 | }
32 |
33 | if !match {
34 | t.Fatal("not match")
35 | }
36 |
37 | assert.Equal(t, "scripts/runtime/ts/lib/foo.ts", file)
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/runtime/v8/v8.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "github.com/yaoapp/gou/runtime/v8/store"
5 | )
6 |
7 | var runtimeOption = &Option{}
8 |
9 | // Start v8 runtime
10 | func Start(option *Option) error {
11 | option.Validate()
12 | runtimeOption = option
13 | initialize()
14 | return nil
15 | }
16 |
17 | // Stop v8 runtime
18 | func Stop() {
19 | if isoReady != nil {
20 | close(isoReady)
21 | }
22 | isoReady = nil
23 | store.Isolates.Range(func(iso store.IStore) bool {
24 | key := iso.Key()
25 | store.CleanIsolateCache(key)
26 | store.Isolates.Remove(key)
27 | return true
28 | })
29 | release()
30 | }
31 |
--------------------------------------------------------------------------------
/runtime/v8/v8_test.go:
--------------------------------------------------------------------------------
1 | package v8
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/yaoapp/gou/application"
9 | )
10 |
11 | func option() *Option {
12 | option := &Option{}
13 | option.Validate()
14 | return option
15 | }
16 |
17 | func prepare(t *testing.T, option *Option) {
18 | root := os.Getenv("GOU_TEST_APPLICATION")
19 |
20 | // Load app
21 | app, err := application.OpenFromDisk(root)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 | application.Load(app)
26 |
27 | // application scripts
28 | scripts := map[string]string{
29 | "runtime.basic": filepath.Join("scripts", "runtime", "basic.js"),
30 | "runtime.lib": filepath.Join("scripts", "runtime", "lib.js"),
31 | "runtime.typescript": filepath.Join("scripts", "runtime", "typescript.ts"),
32 | }
33 |
34 | for id, file := range scripts {
35 | _, err := Load(file, id)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 | }
40 |
41 | // root scripts
42 | rootScripts := map[string]string{
43 | "runtime.basic": filepath.Join("studio", "runtime", "basic.js"),
44 | }
45 |
46 | for id, file := range rootScripts {
47 | _, err := LoadRoot(file, id)
48 | if err != nil {
49 | t.Fatal(err)
50 | }
51 | }
52 |
53 | prepareSetup(t, option)
54 | }
55 |
56 | func prepareSetup(t *testing.T, option *Option) {
57 | EnablePrecompile()
58 | Start(option)
59 | }
60 |
--------------------------------------------------------------------------------
/schema/schema.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import (
4 | "github.com/yaoapp/gou/schema/types"
5 | "github.com/yaoapp/gou/schema/xun"
6 | "github.com/yaoapp/xun/capsule"
7 | )
8 |
9 | /**
10 | * Schema helpers and processes
11 | */
12 |
13 | // Use pick a schema driver
14 | func Use(name string) types.Schema {
15 | switch name {
16 | case "tests":
17 | return &xun.Xun{}
18 | default:
19 | return &xun.Xun{
20 | Option: xun.Option{Manager: capsule.Global},
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/schema/xun/index.go:
--------------------------------------------------------------------------------
1 | package xun
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/yaoapp/gou/schema/types"
7 | "github.com/yaoapp/xun/dbal/schema"
8 | )
9 |
10 | // setIndex
11 | func setIndex(table schema.Blueprint, index types.Index) error {
12 | if index.Name == "" {
13 | return fmt.Errorf("missing name %v", index)
14 | }
15 |
16 | if len(index.Columns) == 0 {
17 | return fmt.Errorf("index %s missing columns", index.Name)
18 | }
19 |
20 | switch index.Type {
21 | case "unique":
22 | table.AddUnique(index.Name, index.Columns...)
23 | return nil
24 | case "index":
25 | table.AddIndex(index.Name, index.Columns...)
26 | return nil
27 | case "primary":
28 | table.AddPrimary(index.Columns...)
29 | return nil
30 | case "fulltext":
31 | table.AddFulltext(index.Name, index.Columns...)
32 | return nil
33 | }
34 | return fmt.Errorf("Index %s, Type %s does not support", index.Name, index.Type)
35 | }
36 |
--------------------------------------------------------------------------------
/server/http/types.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "net"
5 | "time"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/yaoapp/gou/websocket"
9 | )
10 |
11 | const (
12 | // CREATED the server instance was created
13 | CREATED = uint8(iota)
14 | // STARTING the server instance is starting
15 | STARTING
16 | // READY the server instance is ready
17 | READY
18 | // RESTARTING the server instance is restarting
19 | RESTARTING
20 | // CLOSED the server instance was stopped
21 | CLOSED
22 | )
23 |
24 | const (
25 | // CLOSE close signal
26 | CLOSE = uint8(iota) + 5
27 | // RESTART restart signal
28 | RESTART
29 | // ERROR error signal
30 | ERROR
31 | )
32 |
33 | // Option the http server opiton
34 | type Option struct {
35 | Port int `json:"port,omitempty"`
36 | Host string `json:"host,omitempty"`
37 | Timeout time.Duration `json:"timeout,omitempty"`
38 | Root string `json:"root,omitempty"` // API Root
39 | Allows []string `json:"allows,omitempty"` // CORS Domains
40 | }
41 |
42 | // Server the http server opiton
43 | type Server struct {
44 | router *gin.Engine
45 | addr net.Addr
46 | signal chan uint8
47 | event chan uint8
48 | status uint8
49 | option *Option
50 | upgraders map[string]*websocket.Upgrader
51 | }
52 |
--------------------------------------------------------------------------------
/server/websocket/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 |
3 | # Refactor after v0.10.3 release
4 |
--------------------------------------------------------------------------------
/server/websocket/websocket.type.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/yaoapp/gou/websocket"
4 |
5 | // WebSocket the websocket struct
6 | type WebSocket struct {
7 | websocket.WSClientOption
8 | Event WebSocketEvent `json:"event,omitempty"`
9 | Client *websocket.WSClient
10 | }
11 |
12 | // WebSocketEvent the websocket struct
13 | type WebSocketEvent struct {
14 | Data string `json:"data,omitempty"`
15 | Error string `json:"error,omitempty"`
16 | Closed string `json:"closed,omitempty"`
17 | Connected string `json:"connected,omitempty"`
18 | }
19 |
--------------------------------------------------------------------------------
/server/websocket/websocket_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // func TestLoadWebSocket(t *testing.T) {
4 | // ws, err := LoadWebSocket(path.Join("websockets", "message.ws.json"), "message")
5 | // assert.Nil(t, err)
6 | // assert.Equal(t, ws.Name, "message")
7 | // assert.Equal(t, ws.URL, "ws://127.0.0.1:5011/websocket/message")
8 | // assert.Equal(t, ws.Protocols, []string{"yao-message-01"})
9 | // assert.Equal(t, ws.Event.Data, "scripts.websocket.onData")
10 | // assert.Equal(t, ws.Event.Closed, "scripts.websocket.onClosed")
11 | // assert.Equal(t, ws.Event.Error, "scripts.websocket.onError")
12 | // assert.Equal(t, ws.Event.Connected, "scripts.websocket.onConnected")
13 | // }
14 |
15 | // func TestWebSocketOpen(t *testing.T) {
16 | // // err := Yao.Load(path.Join(TestScriptRoot, "websocket.js"), "websocket")
17 | // // if err != nil {
18 | // // t.Fatal(err)
19 | // // }
20 |
21 | // srv, url := serve(t)
22 | // defer srv.Stop()
23 |
24 | // LoadWebSocket(path.Join("websockets", "message.ws.json"), "message")
25 | // ws := SelectWebSocket("message")
26 | // err := ws.Open(url, "messageV2", "chatV3")
27 | // if err != nil {
28 | // t.Fatal(err)
29 | // }
30 |
31 | // }
32 |
33 | // func serve(t *testing.T) (*websocket.Upgrader, string) {
34 |
35 | // ws, err := websocket.NewUpgrader("test")
36 | // if err != nil {
37 | // t.Fatalf("%s", err)
38 | // }
39 |
40 | // gin.SetMode(gin.ReleaseMode)
41 | // router := gin.Default()
42 | // ws.SetHandler(func(message []byte, id int) ([]byte, error) { return message, nil })
43 | // ws.SetRouter(router)
44 |
45 | // listener, err := net.Listen("tcp", "127.0.0.1:0")
46 | // if err != nil {
47 | // t.Fatal(err)
48 | // }
49 |
50 | // go ws.Start()
51 | // go func() {
52 | // http.Serve(listener, router)
53 | // }()
54 | // time.Sleep(200 * time.Millisecond)
55 |
56 | // return ws, fmt.Sprintf("ws://127.0.0.1:%d/websocket/test", listener.Addr().(*net.TCPAddr).Port)
57 | // }
58 |
--------------------------------------------------------------------------------
/session/buntdb_test.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func init() {
11 | bunt, err := NewBuntDB("")
12 | if err != nil {
13 | panic(err)
14 | }
15 | Register("buntdb", bunt)
16 | }
17 |
18 | func TestBuntDBMake(t *testing.T) {
19 | s := Use("buntdb").Make().Expire(3600 * time.Second).AsGlobal()
20 | assert.NotNil(t, s.GetID())
21 | }
22 |
23 | func TestBuntDBID(t *testing.T) {
24 | id := ID()
25 | s := Use("buntdb").ID(id)
26 | assert.Equal(t, id, s.GetID())
27 | }
28 |
29 | func TestBuntDBMustSetGetDel(t *testing.T) {
30 | id := ID()
31 | s := Use("buntdb").ID(id).Expire(200 * time.Millisecond)
32 | s.MustSet("foo", "bar")
33 | v := s.MustGet("foo")
34 | assert.Equal(t, "bar", v)
35 |
36 | s.MustSetMany(map[string]interface{}{"hello": "world", "hi": "gou"})
37 | assert.Equal(t, "world", s.MustGet("hello"))
38 | assert.Equal(t, "gou", s.MustGet("hi"))
39 |
40 | s.MustDel("hi")
41 | assert.Nil(t, s.MustGet("hi"))
42 |
43 | time.Sleep(201 * time.Millisecond)
44 | assert.Nil(t, s.MustGet("foo"))
45 | assert.Nil(t, s.MustGet("hello"))
46 | assert.Nil(t, s.MustGet("hi"))
47 | }
48 |
49 | func TestBuntDBMustSetWithEx(t *testing.T) {
50 | id := ID()
51 | ss := Use("buntdb").ID(id)
52 | ss.MustSetWithEx("foo", "bar", 200*time.Millisecond)
53 | assert.Equal(t, "bar", ss.MustGet("foo"))
54 |
55 | ss.MustSetManyWithEx(map[string]interface{}{"hello": "world", "hi": "gou"}, 200*time.Millisecond)
56 | assert.Equal(t, "world", ss.MustGet("hello"))
57 | assert.Equal(t, "gou", ss.MustGet("hi"))
58 |
59 | time.Sleep(201 * time.Millisecond)
60 | assert.Nil(t, ss.MustGet("foo"))
61 | assert.Nil(t, ss.MustGet("hello"))
62 | assert.Nil(t, ss.MustGet("hi"))
63 | }
64 |
65 | func TestBuntDBMustDump(t *testing.T) {
66 | id := ID()
67 | ss := Use("buntdb").ID(id).Expire(200 * time.Millisecond)
68 | ss.MustSet("foo", "bar")
69 | ss.MustSet("hello", "world")
70 |
71 | data := ss.MustDump()
72 | assert.Equal(t, "bar", data["foo"])
73 | assert.Equal(t, "world", data["hello"])
74 |
75 | time.Sleep(1000 * time.Millisecond)
76 | data = ss.MustDump()
77 | assert.Equal(t, map[string]interface{}{}, data)
78 | }
79 |
--------------------------------------------------------------------------------
/session/redis_test.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func init() {
12 |
13 | host := os.Getenv("GOU_TEST_REDIS_HOST")
14 | port := os.Getenv("GOU_TEST_REDIS_PORT")
15 | db := os.Getenv("GOU_TEST_REDIS_DB")
16 | pass := os.Getenv("GOU_TEST_REDIS_PASSWORD")
17 |
18 | args := []string{}
19 | if port != "" {
20 | args = append(args, port)
21 | }
22 |
23 | if db != "" {
24 | args = append(args, db)
25 | }
26 |
27 | if pass != "" {
28 | args = append(args, pass)
29 | }
30 |
31 | rdb, err := NewRedis(host, args...)
32 | if err != nil {
33 | panic(err)
34 | }
35 | Register("redis", rdb)
36 | }
37 |
38 | func TestRedisMake(t *testing.T) {
39 | s := Use("redis").Make().Expire(3600 * time.Second).AsGlobal()
40 | assert.NotNil(t, s.GetID())
41 | }
42 |
43 | func TestRedisID(t *testing.T) {
44 | id := ID()
45 | s := Use("redis").ID(id)
46 | assert.Equal(t, id, s.GetID())
47 | }
48 |
49 | func TestRedisMustSetGetDel(t *testing.T) {
50 | id := ID()
51 | s := Use("redis").ID(id).Expire(200 * time.Millisecond)
52 | s.MustSet("foo", "bar")
53 | v := s.MustGet("foo")
54 | assert.Equal(t, "bar", v)
55 |
56 | s.MustSetMany(map[string]interface{}{"hello": "world", "hi": "gou"})
57 | assert.Equal(t, "world", s.MustGet("hello"))
58 | assert.Equal(t, "gou", s.MustGet("hi"))
59 |
60 | s.MustDel("hi")
61 | assert.Nil(t, s.MustGet("hi"))
62 |
63 | time.Sleep(201 * time.Millisecond)
64 | assert.Nil(t, s.MustGet("foo"))
65 | assert.Nil(t, s.MustGet("hello"))
66 | assert.Nil(t, s.MustGet("hi"))
67 | }
68 |
69 | func TestRedisMustSetWithEx(t *testing.T) {
70 | id := ID()
71 | ss := Use("redis").ID(id)
72 | ss.MustSetWithEx("foo", "bar", 200*time.Millisecond)
73 | assert.Equal(t, "bar", ss.MustGet("foo"))
74 |
75 | ss.MustSetManyWithEx(map[string]interface{}{"hello": "world", "hi": "gou"}, 200*time.Millisecond)
76 | assert.Equal(t, "world", ss.MustGet("hello"))
77 | assert.Equal(t, "gou", ss.MustGet("hi"))
78 |
79 | time.Sleep(210 * time.Millisecond)
80 | assert.Nil(t, ss.MustGet("foo"))
81 | assert.Nil(t, ss.MustGet("hello"))
82 | assert.Nil(t, ss.MustGet("hi"))
83 | }
84 |
85 | func TestRedisMustDump(t *testing.T) {
86 | id := ID()
87 | ss := Use("redis").ID(id).Expire(200 * time.Millisecond)
88 | ss.MustSet("foo", "bar")
89 | ss.MustSet("hello", "world")
90 |
91 | data := ss.MustDump()
92 | assert.Equal(t, "bar", data["foo"])
93 | assert.Equal(t, "world", data["hello"])
94 |
95 | time.Sleep(201 * time.Millisecond)
96 | data = ss.MustDump()
97 | assert.Equal(t, map[string]interface{}{}, data)
98 | }
99 |
--------------------------------------------------------------------------------
/session/types.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import "time"
4 |
5 | // Manager Session 管理器
6 | type Manager interface {
7 | Init()
8 | Set(id string, key string, value interface{}, expired time.Duration) error
9 | Get(id string, key string) (interface{}, error)
10 | Del(id string, key string) error
11 | Dump(id string) (map[string]interface{}, error)
12 | }
13 |
14 | // Session 数据结构
15 | type Session struct {
16 | id string
17 | name string
18 | timeout time.Duration
19 | Manager Manager
20 | }
21 |
--------------------------------------------------------------------------------
/socket/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 |
3 | # Refactor after v0.10.3 release
4 |
--------------------------------------------------------------------------------
/socket/server.go:
--------------------------------------------------------------------------------
1 | package socket
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net"
7 |
8 | "github.com/yaoapp/kun/log"
9 | )
10 |
11 | // Socket server (alpha -> will be refactored at a beta version...)
12 |
13 | // Start start socket server
14 | func Start(proto string, host string, port string, bufferSize int, KeepAlive int, handler func([]byte, int, error) ([]byte, error)) {
15 | if proto == "tcp" {
16 | tcpStart(host, port, bufferSize, KeepAlive, handler)
17 | }
18 | }
19 |
20 | // tcpStart start socket server with TCP/IP using TCP/IP protocol
21 | func tcpStart(host string, port string, bufferSize int, KeepAlive int, handler func([]byte, int, error) ([]byte, error)) error {
22 | listen, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
23 | if err != nil {
24 | log.Error("Start error: %s", err)
25 | return err
26 | }
27 | defer listen.Close()
28 | log.With(log.F{"bufferSize": bufferSize, "KeepAlive": KeepAlive}).Info("Listening ON: %s:%s", host, port)
29 |
30 | for {
31 | conn, err := listen.Accept()
32 | if err != nil {
33 | log.Error("Received error: %s", err)
34 | continue
35 | }
36 | log.With(log.F{"Remote": conn.RemoteAddr()}).Info("Connected")
37 | go handleRequest(conn, bufferSize, KeepAlive, handler)
38 | }
39 | }
40 |
41 | // handleRequest handel the client request
42 | func handleRequest(conn net.Conn, bufferSize int, KeepAlive int, handler func([]byte, int, error) ([]byte, error)) {
43 | clientAddr := conn.RemoteAddr().String()
44 | defer conn.Close()
45 |
46 | log.Trace("Connection success. Client address: %s", clientAddr)
47 | for {
48 | buffer := make([]byte, bufferSize)
49 | recvLen, err := conn.Read(buffer)
50 | if err != nil && err != io.EOF {
51 | log.Error("Read error: %s %s", err.Error(), clientAddr)
52 | break
53 | }
54 |
55 | if err == io.EOF {
56 | log.With(log.F{"Remote": conn.RemoteAddr()}).Info("Connection closed")
57 | break
58 | }
59 |
60 | log.Trace("Message received from Client %s %x", clientAddr, buffer)
61 |
62 | res, err := handler(buffer[:recvLen], recvLen, err)
63 | if err != nil {
64 | log.Error("Handler error: %s %s", err, clientAddr)
65 | break
66 | }
67 |
68 | log.Trace("Handler response %s %x", clientAddr, res)
69 |
70 | // Send Response to Server
71 | if res != nil {
72 | if _, err := conn.Write(res); err != nil {
73 | log.Error("Send error: %s %s", err, clientAddr)
74 | break
75 | }
76 | }
77 |
78 | if KeepAlive == -1 {
79 | log.With(log.F{"Remote": conn.RemoteAddr()}).Info("Close connection")
80 | break
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/socket/server_test.go:
--------------------------------------------------------------------------------
1 | package socket
2 |
3 | // func TestStart(t *testing.T) {
4 | // // Start("tcp", "0.0.0.0", "3019", 512, -1, func(data []byte, recvLen int, err error) ([]byte, error) {
5 | // // fmt.Printf("%x %d %v\n", data, recvLen, err)
6 | // // return []byte("pong"), nil
7 | // // })
8 | // }
9 |
10 | // func TestStartKeepAlive(t *testing.T) {
11 | // // Start("tcp", "0.0.0.0", "3019", 512, 0, func(data []byte, recvLen int, err error) ([]byte, error) {
12 | // // fmt.Printf("%x %d %v\n", data, recvLen, err)
13 | // // return []byte("pong"), nil
14 | // // })
15 | // }
16 |
--------------------------------------------------------------------------------
/socket/socket.type.go:
--------------------------------------------------------------------------------
1 | package socket
2 |
3 | // Socket struct
4 | type Socket struct {
5 | Name string `json:"name"`
6 | Version string `json:"version"`
7 | Mode string `json:"mode,omitempty"` // Server | client
8 | Description string `json:"description,omitempty"`
9 | Protocol string `json:"protocol,omitempty"`
10 | Host string `json:"host,omitempty"`
11 | Port string `json:"port,omitempty"`
12 | Event Event `json:"event,omitempty"`
13 | Timeout int `json:"timeout,omitempty"` // timeout (seconds)
14 | BufferSize int `json:"buffer,omitempty"` // bufferSize
15 | KeepAlive int `json:"keep,omitempty"` // -1 not keep alive, 0 keep alive always, keep alive n seconds.
16 | Process string `json:"process,omitempty"`
17 | AttemptAfter int `json:"attempt_after,omitempty"` // Attempt attempt_after
18 | Attempts int `json:"attempts,omitempty"` // max times try to reconnect server when connection break (client mode only)
19 | client *Client
20 | }
21 |
22 | // Event struct
23 | type Event struct {
24 | Data string `json:"data,omitempty"`
25 | Error string `json:"error,omitempty"`
26 | Closed string `json:"closed,omitempty"`
27 | Connected string `json:"connected,omitempty"`
28 | }
29 |
--------------------------------------------------------------------------------
/socket/socket_test.go:
--------------------------------------------------------------------------------
1 | package socket
2 |
3 | // func TestLoadSocket(t *testing.T) {
4 | // sock, err := Load(path.Join("sockets", "rfid.sock.json"), "rfid")
5 | // assert.Nil(t, err)
6 | // assert.Equal(t, sock.Name, "RFID Receiver (Server Mode)")
7 | // assert.Equal(t, sock.Mode, "server")
8 | // assert.Equal(t, sock.Event.Data, "scripts.socket.onData")
9 | // assert.Equal(t, sock.Event.Closed, "scripts.socket.onClosed")
10 | // assert.Equal(t, sock.Event.Error, "scripts.socket.onError")
11 | // assert.Equal(t, sock.Event.Connected, "scripts.socket.onConnected")
12 | // assert.Equal(t, sock.Port, "3019")
13 | // assert.Equal(t, sock.Host, "0.0.0.0")
14 | // }
15 |
16 | // func TestSocketStart(t *testing.T) {
17 | // Load(path.Join("sockets", "rfid.sock.json"), "rfid")
18 | // sock := Select("rfid")
19 | // assert.Equal(t, sock.Name, "RFID Receiver (Server Mode)")
20 | // assert.Equal(t, sock.Mode, "server")
21 | // assert.Equal(t, sock.Event.Data, "scripts.socket.onData")
22 | // assert.Equal(t, sock.Event.Closed, "scripts.socket.onClosed")
23 | // assert.Equal(t, sock.Event.Error, "scripts.socket.onError")
24 | // assert.Equal(t, sock.Event.Connected, "scripts.socket.onConnected")
25 | // assert.Equal(t, sock.Port, "3019")
26 | // assert.Equal(t, sock.Host, "0.0.0.0")
27 |
28 | // // sock.Start()
29 | // }
30 |
31 | // func TestSocketConnect(t *testing.T) {
32 | // Load(path.Join("sockets", "rfid_client.sock.json"), "rfid_client")
33 | // sock := Select("rfid_client")
34 | // assert.Equal(t, sock.Name, "RFID Receiver (Client Mode)")
35 | // assert.Equal(t, sock.Mode, "client")
36 | // assert.Equal(t, sock.Event.Data, "scripts.socket.onData")
37 | // assert.Equal(t, sock.Event.Error, "scripts.socket.onError")
38 | // assert.Equal(t, sock.Event.Closed, "scripts.socket.onClosed")
39 | // assert.Equal(t, sock.Event.Connected, "scripts.socket.onConnected")
40 | // assert.Equal(t, sock.Port, "3019")
41 | // assert.Equal(t, sock.Host, "192.168.31.33")
42 |
43 | // // sock.Connect()
44 | // }
45 |
46 | // func TestSocketOpen(t *testing.T) {
47 | // // err := Yao.Load(path.Join(TestScriptRoot, "socket.js"), "socket")
48 | // // if err != nil {
49 | // // t.Fatal(err)
50 | // // }
51 | // // LoadSocket(path.Join("sockets", "rfid_client.sock.json"), "rfid_client")
52 | // // sock := SelectSocket("rfid_client")
53 | // // sock.Open()
54 | // }
55 |
--------------------------------------------------------------------------------
/socket/types.go:
--------------------------------------------------------------------------------
1 | package socket
2 |
3 | import (
4 | "net"
5 | "time"
6 | )
7 |
8 | const (
9 | // WAITING waiting for connecting
10 | WAITING uint = iota
11 |
12 | // CONNECTING connecting the host
13 | CONNECTING
14 |
15 | // CONNECTED the socket is connected
16 | CONNECTED
17 |
18 | // CLOSED the socket is closed
19 | CLOSED
20 | )
21 |
22 | const (
23 |
24 | // MREAD socket read error ( the local peer closed )
25 | MREAD uint = iota
26 |
27 | // MBREAK the remote peer closed
28 | MBREAK
29 |
30 | // MCLOSE user send the CLOSE signal
31 | MCLOSE
32 | )
33 |
34 | // Client the socket client
35 | type Client struct {
36 | Status uint
37 | Conn net.Conn
38 | Option Option
39 | Handlers Handlers
40 | Attempts int
41 | AttemptAfter time.Duration
42 | AttemptTimes int
43 | }
44 |
45 | // Option the socket option
46 | type Option struct {
47 | Protocol string `json:"protocol,omitempty"` // TCP/UDP
48 | Host string `json:"host,omitempty"`
49 | Port string `json:"port,omitempty"`
50 | Timeout time.Duration `json:"timeout,omitempty"` // timeout (seconds)
51 | BufferSize int `json:"buffer,omitempty"` // bufferSize
52 | KeepAlive time.Duration `json:"keep,omitempty"` // -1 not keep alive, 0 keep alive always, keep alive n seconds.
53 | AttemptAfter time.Duration `json:"attempt_after,omitempty"` // Attempt attempt_after
54 | Attempts int `json:"attempts,omitempty"` // max times try to reconnect server when connection break (client mode only)
55 | }
56 |
57 | // Handlers the socket handlers
58 | type Handlers struct {
59 | Data DataHandler
60 | Error ErrorHandler
61 | Closed ClosedHandler
62 | Connected ConnectedHandler
63 | }
64 |
65 | // DataHandler Handler
66 | type DataHandler func([]byte, int) ([]byte, error)
67 |
68 | // ErrorHandler Handler
69 | type ErrorHandler func(error)
70 |
71 | // ClosedHandler Handler
72 | type ClosedHandler func([]byte, error) []byte
73 |
74 | // ConnectedHandler Handler
75 | type ConnectedHandler func(option Option) error
76 |
--------------------------------------------------------------------------------
/ssl/certificate.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import (
4 | "crypto/x509"
5 | "encoding/pem"
6 | "fmt"
7 |
8 | "github.com/yaoapp/gou/application"
9 | )
10 |
11 | // Certificates loaded
12 | var Certificates = map[string]*Certificate{}
13 |
14 | // Load Load the certificate from the given file
15 | func Load(file string, name string) (*Certificate, error) {
16 | data, err := application.App.Read(file)
17 | if err != nil {
18 | return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
19 | }
20 | return LoadCertificate(data, name)
21 | }
22 |
23 | // LoadCertificate load the certificate from the given file
24 | func LoadCertificate(data []byte, name string) (*Certificate, error) {
25 | cert, err := NewCertificate(data)
26 | if err != nil {
27 | return nil, err
28 | }
29 | Certificates[name] = cert
30 | return cert, nil
31 | }
32 |
33 | // NewCertificate creat a new certificate from file
34 | func NewCertificate(data []byte) (*Certificate, error) {
35 | block, _ := pem.Decode(data)
36 | if block == nil {
37 | return nil, fmt.Errorf("decode certificate error")
38 | }
39 |
40 | switch block.Type {
41 | case "CERTIFICATE":
42 | cert, err := x509.ParseCertificate(block.Bytes)
43 | if err != nil {
44 | return nil, fmt.Errorf("parse certificate err:%s", err.Error())
45 | }
46 | return &Certificate{cert: cert, pub: cert.PublicKey, data: data, Type: block.Type, Format: "PEM"}, nil
47 |
48 | case "PRIVATE KEY":
49 | key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
50 | if err != nil {
51 | return nil, fmt.Errorf("parse certificate err:%s", err.Error())
52 | }
53 | return &Certificate{pri: key, data: data, Type: block.Type, Format: "PEM"}, nil
54 |
55 | case "PUBLIC KEY":
56 | key, err := x509.ParsePKIXPublicKey(block.Bytes)
57 | if err != nil {
58 | return nil, fmt.Errorf("parse certificate err:%s", err.Error())
59 | }
60 | return &Certificate{pub: key, data: data, Type: block.Type, Format: "PEM"}, nil
61 | }
62 |
63 | return nil, fmt.Errorf("the kind of PEM should be CERTIFICATE")
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/ssl/certificate_test.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/yaoapp/gou/application"
10 | )
11 |
12 | func TestLoadCertificateFrom(t *testing.T) {
13 | prepare(t)
14 | cert, err := Load(certFile("cert.pem"), "cert")
15 | if err != nil {
16 | t.Fatal(err)
17 | }
18 | assert.Equal(t, "CERTIFICATE", cert.Type)
19 | assert.NotNil(t, cert.cert)
20 |
21 | _, has := Certificates["cert"]
22 | assert.True(t, has)
23 | }
24 |
25 | func TestLoadCertificateFromPrivate(t *testing.T) {
26 | prepare(t)
27 | cert, err := Load(certFile("private.pem"), "private")
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 | assert.Equal(t, "PRIVATE KEY", cert.Type)
32 | assert.NotNil(t, cert.pri)
33 |
34 | _, has := Certificates["private"]
35 | assert.True(t, has)
36 | }
37 |
38 | func TestLoadCertificateFromPublic(t *testing.T) {
39 | prepare(t)
40 | cert, err := Load(certFile("public.pem"), "public")
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 | assert.Equal(t, "PUBLIC KEY", cert.Type)
45 | assert.NotNil(t, cert.pub)
46 |
47 | _, has := Certificates["public"]
48 | assert.True(t, has)
49 | }
50 |
51 | func prepare(t *testing.T) {
52 | root := os.Getenv("GOU_TEST_APPLICATION")
53 | app, err := application.OpenFromDisk(root) // Load app
54 | if err != nil {
55 | t.Fatal(err)
56 | }
57 | application.Load(app)
58 | }
59 |
60 | func certFile(name string) string {
61 | return path.Join("certs", name)
62 | }
63 |
--------------------------------------------------------------------------------
/ssl/process.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import (
4 | "github.com/yaoapp/gou/process"
5 | "github.com/yaoapp/kun/exception"
6 | )
7 |
8 | func init() {
9 | process.Register("ssl.sign", ProcessSign)
10 | process.Register("ssl.verify", ProcessVerify)
11 | }
12 |
13 | // ProcessSign computes a signature for the specified data by generating a cryptographic digital signature
14 | func ProcessSign(process *process.Process) interface{} {
15 | process.ValidateArgNums(3)
16 | data := process.ArgsString(0)
17 | certName := process.ArgsString(1)
18 | algorithm := process.ArgsString(2)
19 |
20 | cert, has := Certificates[certName]
21 | if !has {
22 | exception.New("cert %s does not load ", 400, certName).Throw()
23 | }
24 |
25 | sign, err := SignStrBase64(data, cert, algorithm)
26 | if err != nil {
27 | exception.New("%s", 500, err).Throw()
28 | }
29 |
30 | return sign
31 | }
32 |
33 | // ProcessVerify verifies that the signature is correct for the specified data
34 | func ProcessVerify(process *process.Process) interface{} {
35 | process.ValidateArgNums(4)
36 | data := process.ArgsString(0)
37 | sign := process.ArgsString(1)
38 | certName := process.ArgsString(2)
39 | algorithm := process.ArgsString(3)
40 |
41 | cert, has := Certificates[certName]
42 | if !has {
43 | exception.New("cert %s does not load", 400, certName).Throw()
44 | }
45 |
46 | res, err := VerifyStrBase64(data, sign, cert, algorithm)
47 | if err != nil {
48 | exception.New("%s", 500, err).Throw()
49 | }
50 |
51 | return res
52 | }
53 |
--------------------------------------------------------------------------------
/ssl/process_test.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/yaoapp/gou/process"
8 | )
9 |
10 | func TestProcessSign(t *testing.T) {
11 | prepare(t)
12 | _, err := Load(certFile("private.pem"), "private")
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 |
17 | args := []interface{}{"hello world", "private", "SHA256"}
18 | signature, err := process.New("ssl.Sign", args...).Exec()
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 | assert.Equal(t, "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==", signature)
23 | }
24 |
25 | func TestProcessVerify(t *testing.T) {
26 | _, err := Load(certFile("cert.pem"), "cert")
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 |
31 | signature := "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w=="
32 | args := []interface{}{"hello world", signature, "cert", "SHA256"}
33 | res, err := process.New("ssl.Verify", args...).Exec()
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | assert.True(t, res.(bool))
39 | }
40 |
--------------------------------------------------------------------------------
/ssl/ssl_test.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestSignStrBase64(t *testing.T) {
11 | prepare(t)
12 | cert, err := Load(certFile("private.pem"), "private")
13 | if err != nil {
14 | t.Fatal(err)
15 | }
16 |
17 | signature, err := SignStrBase64("hello world", cert, "SHA256")
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | assert.Equal(t, "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==", signature)
22 | }
23 |
24 | func TestSignHexBase64(t *testing.T) {
25 | prepare(t)
26 | cert, err := Load(certFile("private.pem"), "private")
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 | hexstr := hex.EncodeToString([]byte("hello world"))
31 | signature, err := SignHexBase64(hexstr, cert, "SHA256")
32 | if err != nil {
33 | t.Fatal(err)
34 | }
35 | assert.Equal(t, "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==", signature)
36 | }
37 |
38 | func TestCertVerifyStrBase64(t *testing.T) {
39 | prepare(t)
40 | cert, err := Load(certFile("cert.pem"), "cert")
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 |
45 | signature := "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w=="
46 | res, err := VerifyStrBase64("hello world", signature, cert, "SHA256")
47 | if err != nil {
48 | t.Fatal(err)
49 | }
50 |
51 | assert.True(t, res)
52 | }
53 |
54 | func TestCertVerifyStrBase64Public(t *testing.T) {
55 | prepare(t)
56 | cert, err := Load(certFile("public.pem"), "public")
57 | if err != nil {
58 | t.Fatal(err)
59 | }
60 |
61 | signature := "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w=="
62 | res, err := VerifyStrBase64("hello world", signature, cert, "SHA256")
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | assert.True(t, res)
68 | }
69 |
--------------------------------------------------------------------------------
/ssl/types.go:
--------------------------------------------------------------------------------
1 | package ssl
2 |
3 | import "crypto/x509"
4 |
5 | // Certificate the ssl Certificate
6 | type Certificate struct {
7 | cert *x509.Certificate
8 | pub any
9 | pri any
10 | Type string
11 | Format string // PEM
12 | data []byte
13 | }
14 |
--------------------------------------------------------------------------------
/store/file/README.md:
--------------------------------------------------------------------------------
1 | # File KV-Store
2 |
--------------------------------------------------------------------------------
/store/lru/README.md:
--------------------------------------------------------------------------------
1 | # LRU Cache
2 |
--------------------------------------------------------------------------------
/store/lru/types.go:
--------------------------------------------------------------------------------
1 | package lru
2 |
3 | import lru "github.com/hashicorp/golang-lru"
4 |
5 | // Cache lru cache
6 | type Cache struct {
7 | size int
8 | lru *lru.ARCCache
9 | }
10 |
--------------------------------------------------------------------------------
/store/mongo/README.md:
--------------------------------------------------------------------------------
1 | # MongoDB KV-Store
2 |
--------------------------------------------------------------------------------
/store/mongo/types.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "time"
5 |
6 | "go.mongodb.org/mongo-driver/mongo"
7 | )
8 |
9 | // Store redis store
10 | type Store struct {
11 | Database *mongo.Database
12 | Collection *mongo.Collection
13 | Option Option
14 | }
15 |
16 | // Option redis option
17 | type Option struct {
18 | Timeout time.Duration
19 | }
20 |
--------------------------------------------------------------------------------
/store/process.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/yaoapp/gou/process"
7 | "github.com/yaoapp/kun/log"
8 | )
9 |
10 | // StoreHandlers store process handlers
11 | var StoreHandlers = map[string]process.Handler{
12 | "get": processStoreGet,
13 | "set": processStoreSet,
14 | "has": processStoreHas,
15 | "del": processStoreDel,
16 | "getdel": processStoreGetDel,
17 | "len": processStoreLen,
18 | "keys": processStoreKeys,
19 | "clear": processStoreClear,
20 | }
21 |
22 | func init() {
23 | process.RegisterGroup("stores", StoreHandlers)
24 | }
25 |
26 | // processStoreGet stores..Get
27 | func processStoreGet(process *process.Process) interface{} {
28 | process.ValidateArgNums(1)
29 | store := Select(process.ID)
30 | value, ok := store.Get(process.ArgsString(0))
31 | if !ok {
32 | log.Error("store %s Get return false", process.ID)
33 | return nil
34 | }
35 | return value
36 | }
37 |
38 | // processStoreSet stores..Set
39 | func processStoreSet(process *process.Process) interface{} {
40 | process.ValidateArgNums(2)
41 | store := Select(process.ID)
42 | duration := process.ArgsInt(2, 0)
43 | store.Set(process.ArgsString(0), process.Args[1], time.Duration(duration)*time.Second)
44 | return nil
45 | }
46 |
47 | // processStoreHas stores..Has
48 | func processStoreHas(process *process.Process) interface{} {
49 | process.ValidateArgNums(1)
50 | store := Select(process.ID)
51 | return store.Has(process.ArgsString(0))
52 | }
53 |
54 | // processStoreDel stores..Del
55 | func processStoreDel(process *process.Process) interface{} {
56 | process.ValidateArgNums(1)
57 | store := Select(process.ID)
58 | store.Del(process.ArgsString(0))
59 | return nil
60 | }
61 |
62 | // processStoreGetDel stores..GetDel
63 | func processStoreGetDel(process *process.Process) interface{} {
64 | process.ValidateArgNums(1)
65 | store := Select(process.ID)
66 | value, ok := store.GetDel(process.ArgsString(0))
67 | if !ok {
68 | log.Error("store %s GetDel return false", process.ID)
69 | return nil
70 | }
71 | return value
72 | }
73 |
74 | // processStoreLen stores..Len
75 | func processStoreLen(process *process.Process) interface{} {
76 | store := Select(process.ID)
77 | return store.Len()
78 | }
79 |
80 | // processStoreKeys stores..Keys
81 | func processStoreKeys(process *process.Process) interface{} {
82 | store := Select(process.ID)
83 | return store.Keys()
84 | }
85 |
86 | // processStoreClear stores..Keys
87 | func processStoreClear(process *process.Process) interface{} {
88 | store := Select(process.ID)
89 | store.Clear()
90 | return nil
91 | }
92 |
--------------------------------------------------------------------------------
/store/process_test.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/yaoapp/gou/process"
9 | )
10 |
11 | func TestStoreProcess(t *testing.T) {
12 | prepare(t)
13 | prepareStores(t)
14 | testStoreProcess(t, "cache")
15 | testStoreProcess(t, "share")
16 | testStoreProcess(t, "data")
17 | }
18 |
19 | func testStoreProcess(t *testing.T, name string) {
20 |
21 | process.New(fmt.Sprintf("stores.%s.Clear", name)).Run()
22 | value := process.New(fmt.Sprintf("stores.%s.Len", name)).Run()
23 | assert.Equal(t, 0, value)
24 |
25 | assert.NotPanics(t, func() {
26 | process.New(fmt.Sprintf("stores.%s.Set", name), "key1", "foo").Run()
27 | process.New(fmt.Sprintf("stores.%s.Set", name), "key2", "bar").Run()
28 | process.New(fmt.Sprintf("stores.%s.Set", name), "key3", 1024).Run()
29 | process.New(fmt.Sprintf("stores.%s.Set", name), "key4", 0.618).Run()
30 | })
31 |
32 | value = process.New(fmt.Sprintf("stores.%s.Get", name), "key1").Run()
33 | assert.Equal(t, "foo", value)
34 |
35 | value = process.New(fmt.Sprintf("stores.%s.GetDel", name), "key2").Run()
36 | assert.Equal(t, "bar", value)
37 |
38 | value = process.New(fmt.Sprintf("stores.%s.Has", name), "key2").Run()
39 | assert.False(t, value.(bool))
40 |
41 | value = process.New(fmt.Sprintf("stores.%s.Len", name)).Run()
42 | assert.Equal(t, 3, value)
43 |
44 | value = process.New(fmt.Sprintf("stores.%s.Keys", name)).Run()
45 |
46 | assert.Contains(t, value, "key3")
47 | assert.Contains(t, value, "key4")
48 | assert.Contains(t, value, "key1")
49 | assert.NotContains(t, value, "key2")
50 | }
51 |
--------------------------------------------------------------------------------
/store/redis/README.md:
--------------------------------------------------------------------------------
1 | # Redis Cache
2 |
--------------------------------------------------------------------------------
/store/redis/types.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-redis/redis/v8"
7 | )
8 |
9 | // Store redis store
10 | type Store struct {
11 | rdb *redis.Client
12 | Option Option
13 | }
14 |
15 | // Option redis option
16 | type Option struct {
17 | Timeout time.Duration
18 | Prefix string
19 | }
20 |
--------------------------------------------------------------------------------
/store/store.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/yaoapp/gou/application"
8 | "github.com/yaoapp/gou/connector"
9 | "github.com/yaoapp/gou/helper"
10 | "github.com/yaoapp/gou/store/lru"
11 | "github.com/yaoapp/gou/store/mongo"
12 | "github.com/yaoapp/gou/store/redis"
13 | "github.com/yaoapp/kun/exception"
14 | )
15 |
16 | // Pools LRU pools
17 | var Pools = map[string]Store{}
18 |
19 | // Load load kv store
20 | func Load(file string, name string) (Store, error) {
21 |
22 | data, err := application.App.Read(file)
23 | if err != nil {
24 | return nil, err
25 | }
26 |
27 | inst := Instance{}
28 | err = application.Parse(file, data, &inst)
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | typ := strings.ToLower(inst.Type)
34 | if typ == "lru" {
35 | stor, err := New(nil, inst.Option)
36 | if err != nil {
37 | return nil, err
38 | }
39 | Pools[name] = stor
40 | return Pools[name], nil
41 | }
42 |
43 | connector, has := connector.Connectors[inst.Connector]
44 | if !has {
45 | return nil, fmt.Errorf("Store %s Connector:%s was not loaded", name, inst.Connector)
46 | }
47 |
48 | stor, err := New(connector, inst.Option)
49 | if err != nil {
50 | return nil, err
51 | }
52 |
53 | Pools[name] = stor
54 | return Pools[name], nil
55 | }
56 |
57 | // Select Select loaded kv store
58 | func Select(name string) Store {
59 | store, has := Pools[name]
60 | if !has {
61 | exception.New("Store:%s does not load", 500, name).Throw()
62 | }
63 | return store
64 | }
65 |
66 | // New create a store via connector
67 | func New(c connector.Connector, option Option) (Store, error) {
68 |
69 | if c == nil {
70 | size := 10240
71 | if option != nil {
72 | if v, has := option["size"]; has {
73 | size = helper.EnvInt(v, 10240)
74 | }
75 | }
76 | return lru.New(size)
77 | }
78 |
79 | if c.Is(connector.REDIS) {
80 | return redis.New(c)
81 | } else if c.Is(connector.MONGO) {
82 | return mongo.New(c)
83 | }
84 |
85 | return nil, fmt.Errorf("the connector does not support")
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/store/store.types.go:
--------------------------------------------------------------------------------
1 | package store
2 |
--------------------------------------------------------------------------------
/store/types.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import "time"
4 |
5 | // Store The interface of a key-value store
6 | type Store interface {
7 | Get(key string) (value interface{}, ok bool)
8 | Set(key string, value interface{}, ttl time.Duration) error
9 | Del(key string) error
10 | Has(key string) bool
11 | Len() int
12 | Keys() []string
13 | Clear()
14 | GetSet(key string, ttl time.Duration, getValue func(key string) (interface{}, error)) (interface{}, error)
15 | GetDel(key string) (value interface{}, ok bool)
16 | GetMulti(keys []string) map[string]interface{}
17 | SetMulti(values map[string]interface{}, ttl time.Duration)
18 | DelMulti(keys []string)
19 | GetSetMulti(keys []string, ttl time.Duration, getValue func(key string) (interface{}, error)) map[string]interface{}
20 | }
21 |
22 | // Instance the kv-store setting
23 | type Instance struct {
24 | Name string `json:"name"`
25 | Description string `json:"description,omitempty"`
26 | Connector string `json:"connector,omitempty"`
27 | Type string `json:"type,omitempty"` // warning: type is deprecated in the future new version
28 | Option map[string]interface{} `json:"option,omitempty"`
29 | }
30 |
31 | // Option the store option
32 | type Option map[string]interface{}
33 |
--------------------------------------------------------------------------------
/task/process_test.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "os"
5 | "path"
6 | "path/filepath"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/yaoapp/gou/application"
12 | "github.com/yaoapp/gou/process"
13 | v8 "github.com/yaoapp/gou/runtime/v8"
14 | "github.com/yaoapp/kun/utils"
15 | )
16 |
17 | func TestLoadTask(t *testing.T) {
18 | taskLoad(t)
19 | assert.Equal(t, 1, len(Tasks))
20 | assert.NotPanics(t, func() { Select("mail") })
21 | }
22 |
23 | func TestTaskProcess(t *testing.T) {
24 | mail := taskLoad(t)
25 | go mail.Start()
26 | defer mail.Stop()
27 | id, err := process.New("tasks.mail.Add", "max@iqka.com").Exec()
28 | if err != nil {
29 | t.Fatal(err)
30 | }
31 |
32 | id2, err := process.New("tasks.mail.Add", "max@iqka.com", 1).Exec()
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 |
37 | // time.Sleep(200 * time.Millisecond)
38 | s1, err := process.New("tasks.mail.Get", id).Exec()
39 | // if err != nil {
40 | // t.Fatal(err)
41 | // }
42 |
43 | time.Sleep(100 * time.Millisecond)
44 | s2, err := process.New("tasks.mail.Get", id).Exec()
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 |
49 | // time.Sleep(100 * time.Millisecond)
50 |
51 | utils.Dump(s1, s2, id, id2)
52 | // assert.Equal(t, 1025, id)
53 | // assert.Equal(t, 1026, id2)
54 | // assert.Equal(t, "RUNNING", s1.(map[string]interface{})["status"])
55 | // assert.Equal(t, "RUNNING", s2.(map[string]interface{})["status"])
56 | // assert.Equal(t, 3, s2.(map[string]interface{})["total"])
57 | // assert.Equal(t, "unit-test", s2.(map[string]interface{})["message"])
58 |
59 | // waitting
60 | time.Sleep(3000 * time.Millisecond)
61 | }
62 |
63 | func taskLoad(t *testing.T) *Task {
64 | loadApp(t)
65 | loadScripts(t)
66 |
67 | process.Register("xiang.system.Sleep", func(process *process.Process) interface{} {
68 | process.ValidateArgNums(1)
69 | ms := process.ArgsInt(0)
70 | time.Sleep(time.Duration(ms) * time.Millisecond)
71 | return nil
72 | })
73 |
74 | _, err := Load(path.Join("tasks", "mail.task.yao"), "mail")
75 | if err != nil {
76 | t.Fatal(err)
77 | }
78 |
79 | return Select("mail")
80 | }
81 |
82 | func loadApp(t *testing.T) {
83 |
84 | root := os.Getenv("GOU_TEST_APPLICATION")
85 | app, err := application.OpenFromDisk(root) // Load app
86 | if err != nil {
87 | t.Fatal(err)
88 | }
89 | application.Load(app)
90 |
91 | }
92 |
93 | func loadScripts(t *testing.T) {
94 |
95 | scripts := map[string]string{
96 | "tests.task.mail": filepath.Join("scripts", "tests", "task", "mail.js"),
97 | }
98 |
99 | for id, file := range scripts {
100 | _, err := v8.Load(file, id)
101 | if err != nil {
102 | t.Fatal(err)
103 | }
104 | }
105 |
106 | err := v8.Start(&v8.Option{})
107 | if err != nil {
108 | t.Fatal(err)
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/task/types.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 | )
8 |
9 | const (
10 | // WAITING the job is waiting
11 | WAITING = iota + 1
12 |
13 | // RUNNING the job is running
14 | RUNNING
15 |
16 | // SUCCESS the job is success
17 | SUCCESS
18 |
19 | // FAILURE the job is failure
20 | FAILURE
21 | )
22 |
23 | var status = map[int]string{
24 | WAITING: "WAITING",
25 | RUNNING: "RUNNING",
26 | SUCCESS: "SUCCESS",
27 | FAILURE: "SUCCESS",
28 | }
29 |
30 | // Task the task struct
31 | type Task struct {
32 | name string
33 | timeout int
34 | handlers *Handlers
35 | pool *Pool
36 | jobs map[int]*Job
37 | mutex sync.Mutex
38 | ctx context.Context
39 | cancel context.CancelFunc
40 | Option Option
41 | }
42 |
43 | // Option the task option
44 | type Option struct {
45 | Name string
46 | JobQueueLength int
47 | WorkerNums int
48 | AttemptAfter int
49 | Attempts int
50 | Timeout int
51 | }
52 |
53 | // Pool the worker pool
54 | type Pool struct {
55 | size int
56 | max int
57 | jobque chan *Job
58 | workerque chan *Worker
59 | }
60 |
61 | // Worker the work struct
62 | type Worker struct {
63 | job chan *Job
64 | }
65 |
66 | // Job the job
67 | type Job struct {
68 | id int
69 | ctx context.Context
70 | cancel context.CancelFunc
71 | timeout time.Duration
72 | curr int
73 | total int
74 | status int
75 | message string
76 | response interface{}
77 | args []interface{}
78 | }
79 |
80 | // Handlers the event handlers
81 | type Handlers struct {
82 | Exec func(int, ...interface{}) (interface{}, error)
83 | Progress func(int, int, int, string)
84 | NextID func() (int, error)
85 | Add func(int)
86 | Success func(int, interface{})
87 | Error func(int, error)
88 | }
89 |
--------------------------------------------------------------------------------
/types/fs.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "hash/fnv"
6 | "net/textproto"
7 | "strings"
8 |
9 | "github.com/google/uuid"
10 | )
11 |
12 | // UploadFile upload file
13 | type UploadFile struct {
14 | UID string `json:"uid,omitempty"` // Content-Uid The unique identifier of the file ( for chunk upload )
15 | Range string `json:"range,omitempty"` // Content-Range bytes start-end/total (for chunk upload)
16 | Sync bool `json:"sync,omitempty"` // Content-Sync sync upload or not. the default is false
17 | Name string `json:"name"`
18 | TempFile string `json:"tempFile"`
19 | Size int64 `json:"size"`
20 | Header textproto.MIMEHeader `json:"header"`
21 | Error string `json:"error"`
22 | }
23 |
24 | // UploadProgress upload progress
25 | type UploadProgress struct {
26 | Total int64 `json:"total"`
27 | Uploaded int64 `json:"uploaded"`
28 | Completed bool `json:"completed"`
29 | }
30 |
31 | // IsChunk is chunk upload or not
32 | func (upload UploadFile) IsChunk() bool {
33 | return upload.Range != ""
34 | }
35 |
36 | // IsError is error or not
37 | func (upload UploadFile) IsError() bool {
38 | return upload.Error != ""
39 | }
40 |
41 | // Hash hash
42 | func (upload UploadFile) Hash() string {
43 |
44 | h := fnv.New64a()
45 | // UUID Hash
46 | if upload.UID != "" {
47 | h.Write([]byte(fmt.Sprintf("%v", upload.UID)))
48 | return fmt.Sprintf("%x", h.Sum64())
49 | }
50 |
51 | // TempFile Hash (for chunk upload)
52 | if upload.TempFile != "" {
53 | h.Write([]byte(fmt.Sprintf("%v", upload.TempFile)))
54 | return fmt.Sprintf("%x", h.Sum64())
55 | }
56 |
57 | // General a uuid and hash
58 | uuid := uuid.NewString()
59 | h.Write([]byte(fmt.Sprintf("%v", uuid)))
60 | return fmt.Sprintf("%x", h.Sum64())
61 | }
62 |
63 | // ChunkFileName get chunk file name
64 | func (upload UploadFile) ChunkFileName() string {
65 | name := strings.ReplaceAll(upload.Range, "bytes ", "")
66 | name = strings.ReplaceAll(name, "/", "_")
67 | return fmt.Sprintf("%s.chunk", name)
68 | }
69 |
70 | // TotalSize total size
71 | func (upload UploadFile) TotalSize() int64 {
72 | var total int64
73 | nameInfo := strings.Split(upload.Range, "/")
74 | fmt.Sscanf(nameInfo[len(nameInfo)-1], "%d", &total)
75 | return total
76 | }
77 |
--------------------------------------------------------------------------------
/types/query.types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // QueryParam 数据查询器参数
4 | type QueryParam struct {
5 | Model string `json:"model,omitempty"`
6 | Table string `json:"table,omitempty"`
7 | Alias string `json:"alias,omitempty"`
8 | Export string `json:"export,omitempty"` // 导出前缀
9 | Select []interface{} `json:"select,omitempty"` // string | dbal.Raw
10 | Wheres []QueryWhere `json:"wheres,omitempty"`
11 | Orders []QueryOrder `json:"orders,omitempty"`
12 | Limit int `json:"limit,omitempty"`
13 | Page int `json:"page,omitempty"`
14 | PageSize int `json:"pagesize,omitempty"`
15 | Withs map[string]With `json:"withs,omitempty"`
16 | }
17 |
18 | // With relations 关联查询
19 | type With struct {
20 | Name string `json:"name"`
21 | Query QueryParam `json:"query,omitempty"`
22 | }
23 |
24 | // QueryWhere Where 查询条件
25 | type QueryWhere struct {
26 | Rel string `json:"rel,omitempty"` // Relation Name
27 | Column interface{} `json:"column,omitempty"`
28 | Value interface{} `json:"value,omitempty"`
29 | Method string `json:"method,omitempty"` // where,orwhere, wherein, orwherein...
30 | OP string `json:"op,omitempty"` // 操作 eq/gt/lt/ge/le/like...
31 | Wheres []QueryWhere `json:"wheres,omitempty"` // 分组查询
32 | }
33 |
34 | // QueryOrder Order 查询排序
35 | type QueryOrder struct {
36 | Rel string `json:"rel,omitempty"` // Relation Name
37 | Column string `json:"column"`
38 | Option string `json:"option,omitempty"` // desc, asc
39 | }
40 |
--------------------------------------------------------------------------------
/websocket/client_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "net/http"
7 | "testing"
8 | "time"
9 |
10 | "github.com/gin-gonic/gin"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestPush(t *testing.T) {
15 | srv, url := serve(t)
16 | defer srv.Stop()
17 |
18 | conn, err := NewWebSocket(url, []string{"po"})
19 | if err != nil {
20 | t.Fatalf("%s", err)
21 | }
22 |
23 | err = Push(conn, "Hello World!")
24 | if err != nil {
25 | t.Fatalf("%s", err)
26 | }
27 | assert.Nil(t, err)
28 | }
29 |
30 | func TestOpen(t *testing.T) {
31 | srv, url := serve(t)
32 | defer srv.Stop()
33 |
34 | var ws *WSClient = nil
35 | var handlers = Handlers{
36 | Connected: func(option WSClientOption) error {
37 | fmt.Println("onConnected", option)
38 | fmt.Println("Online", srv.Online())
39 | fmt.Println("Clients", srv.Clients())
40 | srv.Broadcast([]byte("Hello world"))
41 | time.Sleep(500 * time.Millisecond)
42 | ws.Write([]byte("1|I'm Here, The connection will be closed"))
43 | return nil
44 | },
45 | Closed: func(data []byte, err error) []byte {
46 | fmt.Printf("onClosed: %s %v\n", data, err)
47 | return nil
48 | },
49 | Data: func(data []byte, length int) ([]byte, error) {
50 | fmt.Printf("onData: %s %d\n", data, length)
51 | if data[0] == 0x31 {
52 | err := ws.Close()
53 | fmt.Println("Close connection", err)
54 | }
55 | return nil, nil
56 | },
57 | Error: func(err error) {
58 | fmt.Printf("Error: %s\n", err)
59 | },
60 | }
61 | ws = NewWSClient(WSClientOption{URL: url, Protocols: []string{"po"}}, handlers)
62 | err := ws.Open()
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 | }
67 |
68 | func serve(t *testing.T) (*Upgrader, string) {
69 |
70 | ws, err := NewUpgrader("test")
71 | if err != nil {
72 | t.Fatalf("%s", err)
73 | }
74 |
75 | gin.SetMode(gin.ReleaseMode)
76 | router := gin.Default()
77 | ws.SetHandler(func(message []byte, client int) ([]byte, error) { return message, nil })
78 | ws.SetRouter(router)
79 |
80 | listener, err := net.Listen("tcp", "127.0.0.1:0")
81 | if err != nil {
82 | t.Fatal(err)
83 | }
84 |
85 | go ws.Start()
86 | go func() {
87 | http.Serve(listener, router)
88 | }()
89 | time.Sleep(200 * time.Millisecond)
90 |
91 | return ws, fmt.Sprintf("ws://127.0.0.1:%d/websocket/test", listener.Addr().(*net.TCPAddr).Port)
92 | }
93 |
--------------------------------------------------------------------------------
/websocket/hub.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "encoding/binary"
5 | "sync"
6 |
7 | "github.com/gorilla/websocket"
8 | "github.com/yaoapp/kun/log"
9 | )
10 |
11 | func newHub() *Hub {
12 | return &Hub{
13 | broadcast: make(chan []byte),
14 | direct: make(chan []byte),
15 | register: make(chan *Client),
16 | unregister: make(chan *Client),
17 | clients: make(map[*Client]bool),
18 | interrupt: make(chan int),
19 | indexes: map[uint32]*Client{},
20 | }
21 | }
22 |
23 | // NextID return the next client ID
24 | func (h *Hub) NextID() uint32 {
25 | var mutex sync.Mutex
26 | mutex.Lock()
27 | id := len(h.clients)
28 | mutex.Unlock()
29 | return uint32(id) + 1
30 | }
31 |
32 | // Clients return the online clients
33 | func (h *Hub) Clients() []uint32 {
34 | ids := []uint32{}
35 | for client := range h.clients {
36 | ids = append(ids, client.id)
37 | }
38 | return ids
39 | }
40 |
41 | // Nums count the online client's nums
42 | func (h *Hub) Nums() int {
43 | return len(h.clients)
44 | }
45 |
46 | // AddID add a id to the message
47 | // 0-4 id, 4~N message eg: [0 0 0 1 49 124...]
48 | func (h *Hub) AddID(id uint32, message []byte) []byte {
49 | head := make([]byte, 4)
50 | binary.BigEndian.PutUint32(head, id)
51 | res := append([]byte{}, head...)
52 | res = append(res, message...)
53 | return res
54 | }
55 |
56 | func (h *Hub) run() {
57 | LOOP:
58 | for {
59 | select {
60 |
61 | case client := <-h.register:
62 | h.clients[client] = true
63 | h.indexes[client.id] = client
64 |
65 | case client := <-h.unregister:
66 | if _, ok := h.clients[client]; ok {
67 | delete(h.indexes, client.id)
68 | delete(h.clients, client)
69 | close(client.send)
70 | }
71 |
72 | case message := <-h.direct:
73 | if len(message) > 4 {
74 | // 0-4 id, 4~N message eg: [0 0 0 1 49 124...]
75 | id := binary.BigEndian.Uint32(message[0:4])
76 | msg := message[4:]
77 | if client, ok := h.indexes[id]; ok {
78 | select {
79 | case client.send <- msg:
80 | default:
81 | close(client.send)
82 | delete(h.indexes, client.id)
83 | delete(h.clients, client)
84 | }
85 | }
86 | }
87 |
88 | case message := <-h.broadcast:
89 | for client := range h.clients {
90 | select {
91 | case client.send <- message:
92 | default:
93 | close(client.send)
94 | delete(h.indexes, client.id)
95 | delete(h.clients, client)
96 | }
97 | }
98 |
99 | case exit := <-h.interrupt:
100 | if exit == 1 {
101 | for client := range h.clients {
102 | err := client.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseServiceRestart, "Repair"))
103 | log.Trace("Close Client Connection, %v", err)
104 | // close(client.send)
105 | // delete(h.clients, client)
106 | }
107 | break LOOP
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/websocket/upgrader_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | // var cstDialer = websocket.Dialer{
4 | // Subprotocols: []string{"p1", "p2"},
5 | // ReadBufferSize: 1024,
6 | // WriteBufferSize: 1024,
7 | // HandshakeTimeout: 30 * time.Second,
8 | // }
9 |
10 | // func TestStart(t *testing.T) {
11 | // log.SetOutput(os.Stdout)
12 | // log.SetLevel(log.TraceLevel)
13 | // ws := &WebSocket{}
14 |
15 | // http.HandleFunc("/echo", func(rw http.ResponseWriter, r *http.Request) {
16 | // ws.Start(rw, r, nil)
17 | // })
18 |
19 | // go func() { http.ListenAndServe("127.0.0.1:5081", nil) }()
20 |
21 | // for i := 0; i < 3; i++ {
22 | // time.Sleep(200 * time.Microsecond)
23 | // conn, err := Dial()
24 | // if err != nil || conn == nil {
25 | // continue
26 | // }
27 | // echo(t, conn)
28 | // break
29 | // }
30 | // }
31 |
32 | // func Dial() (*websocket.Conn, error) {
33 | // ws, _, err := cstDialer.Dial("ws://127.0.0.1:5081/echo", nil)
34 | // if err != nil {
35 | // log.Error("Dial: %v", err)
36 | // }
37 | // return ws, nil
38 | // }
39 |
40 | // func echo(t *testing.T, ws *websocket.Conn) {
41 |
42 | // const message = "Hello World!"
43 | // if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
44 | // t.Fatalf("SetWriteDeadline: %v", err)
45 | // }
46 | // if err := ws.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
47 | // t.Fatalf("WriteMessage: %v", err)
48 | // }
49 | // if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
50 | // t.Fatalf("SetReadDeadline: %v", err)
51 | // }
52 | // _, p, err := ws.ReadMessage()
53 | // if err != nil {
54 | // t.Fatalf("ReadMessage: %v", err)
55 | // }
56 | // if string(p) != message {
57 | // t.Fatalf("message=%s, want %s", p, message)
58 | // }
59 |
60 | // log.Trace("Message:%s", message)
61 | // }
62 |
--------------------------------------------------------------------------------