service

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2024 License: MIT Imports: 24 Imported by: 0

README

低代码的服务器端应用框架 基于 ssgo/s

快速创建一个web服务,提供 http、https、http2、h2c、websocket 服务
支持作为静态文件服务器
支持 rewrite 和 proxy反向代理(可以代理到discover应用或其他http服务器)
支持服务发现 ssgo/discover 快速构建一个服务网络

快速开始

import s from "apigo.cc/gojs/service"

func main() {
    s.config({
        listen: '8080',
    })
    s.register({ path: '/echo' }, ({ args }) => {
        return args.name
    })
    s.start()
}

受Javascript虚拟机机制限制简单方式是单线程,大量耗时请求压力下可能难以胜任

使用对象池实现高并发

api/echo.js
import s from "apigo.cc/gojs/service"

function main() {
    service.register({ path: '/echo' }, ({ args }) => {
        return args.name
    })
}
main.js
import s from "apigo.cc/gojs/service"

function main() {
    s.load('api/echo.js', { min: 20, max: 1000, idle: 100 })
    s.start()
}

这种模式下服务代码会脱离主线程,使用对象池实现高并发
如果不配置对象池参数则不约束虚拟机数量上限,可以达到最佳性能但是对CPU和内存有一定挑战
设置较小的max可以有效保护CPU和内存资源但是设置过小将无法发挥服务器的性能

注册到服务发现

在 user 服务中配置注册中心地址和应用名称,并配置访问令牌
s.config({
    app: 'user',
    registry: redis://:password@127.0.0.1:6379/15
    accessTokens: {
        aaaaaaaa: 1,
    },
})
s.register({ path: '/getUserInfo', authLevel: 1 }, ({ session }) => {
    return session.get('id', 'name')
})
在 调用的服务中配置访问user服务使用的令牌
s.config({
    registry: redis://:password@127.0.0.1:6379/15
    calls: {
        user: 'aaaaaaaa',
    },
})
s.register({ path: '/getUserInfo' }, ({ caller }) => {
    return caller.get('user/getUserInfo').object()
})

authLevel 不设置则不进行校验,如果获得 authLevel 2 则允许访问所有 2及以下级别的接口
如果 user 服务不配置 listen 默认使用 h2c 协议随机端口
如果不使用 h2c 协议,调用方配置 calls 时需指定 'http:aaaaaaaa'

Session

服务默认启用 session 和 device 功能,如果不希望使用可以在配置中设置 sessionKey 或 deviceKey 为空
如果配置了 sessionProvider,session 将会存储在 redis 中,否则存储在内存中
sessionID和deviceID 同时支持HTTP头和Cookie两种传输方式,HTTP头优先,如果客户端没有传递则服务器会自动分配
如需使用 session 只需要在接口中直接获取即可

下面是一个使用 session 并且使用参数有效性验证和限流器的例子
import service from "apigo.cc/gojs/service"

let verifies = {
    id: v => { return /^\d+$/.test(v) },
    name: /^[a-zA-Z0-9_-\u4e00-\u9fa5\u3400-\u4db5\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u1100-\u11FF\u3130-\u318F\uAC00-\uD7AF\uD82F\uD835\uD83C\uD83D\uD83E\uD83F\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872]+$/u,
}

function main() {
    service.config({
        userIdKey: 'id',
        limitedMessage: { code: 429, message: "访问过于频繁" },
        authFieldMessage: { code: 403, message: "身份验证失败 [{{USER_AUTHLEVEL}}/{{TARGET_AUTHLEVEL}}]" },
        verifyFieldMessage: { code: 400, message: "参数 [{{FAILED_FIELDS}} 验证失败" },
        limiters: {
            ip1s: {
                from: 'ip',
                time: 1000,
                times: 10
            }
        },
    })
    service.register({ method: 'POST', path: '/login', , limiters: ['ip1s'], verifies }, ({ args, session }) => {
        session.set('id', args.id)
        session.set('name', args.name)
        session.setAuthLevel(1)
        session.save()
        return { code: 1 }
    })
    service.register({ method: 'GET', path: '/userInfo', authLevel: 1, limiters: ['ip1s'] }, ({ session }) => {
        return { code: 1, data: session.get('id', 'name') }
    })
}

session对象自动注入,无需任何其他操作。修改session后需要使用 session.save 来保存
调用 session.setAuthLevel 可以设置用户权限,当接口注册的 authLevel 大于0时可以基于 session 中的设置进行访问控制
配置了 userIdKey 后,会自动将 session 的用户ID记录在访问日志中,方便对用户的访问进行分析和统计
示例中创建了一个每个IP每秒允许10次请求的限流器并且在接口中使用了这个限流器
login接口配置了 id 和 name 两个参数的有效性验证规则
参数有效性验证配置可以支持以下类型:

  • value为string或RegExp对象时进行正则表达式校验
  • value为number时表示 字符串长度
  • value为boolean时表示 必须存在
  • value为数组时表示 必须是数组中的值
  • value为函数时调用函数进行校验

配置文件

除了在代码中使用 s.config 外可以有三种配置方式

1、在当前目录下创建 service.yml 或 service.json 文件

listen: 80|443
ssl:
  yourdomain.com:
    certfile: /path/yourdomain.pem
    keyfile: /path/yourdomain.pem

2、在环境配置文件 env.yml 或 env.json 中配置

service:
  listen: 80|443

3、在环境变量中配置(以docker为例)

docker run -e SERVICE_LISTEN=8080:8443
所有配置方式的优先级为 s.config > 环境变量 > env.yml > service.yml

静态文件

service:
  static:
    yourdomain.com: /path/www
    yourdomain.com:8080: /path/www8080
    yourdomain.com:80/abc: /path/abc
    /def: /path/def

可以根据域名和路径配置静态文件
可以使用 file 模块将文件加载到内存中加速访问

import file from "apigo.cc/gojs/file"

file.cache('/path/www', true)

反向代理和Rewrite

service:
  proxy:
    yourdomain.com: serverA
    /abc: http://HOST:PORT/PATH
    yourdomain.com/def/(.*): http://127.0.0.1:8080/$1
  rewrite:
    yourdomain.com/001/(.*): /path/001/$1
    yourdomain.com/002/(.*): http://127.0.0.1:8080/$1
    http://yourdomain.com(.*): https://yourdomain.com$1

websocket

import service from "apigo.cc/gojs/service"

function main() {
    service.register({
        method: 'WS', path: '/ws',
        onMessage: ({ client, type, data }) => {
            // onMessage
            client.writeMessage(type, data)
        },
        onClose: ({ client }) => {
            // onClose
        },
    }, ({ client }) => {
        // onOpen
        client.write('Hello, World!')
    })
}

注册接口时将 method 指定为 WS 即可创建 websocket 服务,配置 onMessage 来异步处理消息

后台任务

taskA.js
import s from "apigo.cc/gojs/service"

// function onStart() {
//     // TODO 任务启动时执行
// }

function onRun() {
    // TODO 在指定间隔时间到达时被调用,根据需要对资源进行处理
    if ( s.dataCount('websocketClients') > 0 ) {
        let conns = s.dataFetch('websocketClients')
        for ( let conn of conns ) {
            conn.write('Hello, World!')
        }
    }
}

function onStop() {
    // TODO 服务结束时被调用,用来收尾或释放资源
    s.dataRemove('websocketClients')
}
main.js
import s from "apigo.cc/gojs/service"

function main() {
    s.task('task.js', 1000) // 每秒执行一次
    s.register({method: 'WS', path: '/ws'}, ({ client }) => {
        // 将连接放到资源池中供后台任务使用
        s.dataSet('websocketClients', client.id, client)
    })
    s.start()
}

task 必须在单独的js文件中定义
每个 task 都会运行在单独的vm中
定义 task 必须在服务启动(s.start)之前
服务停止(s.stop)所有任务会被停止

任务队列
s.listPush('taskA', {})
s.listPop('taskA', {})

可以使用 list 相关操作实现基于队列的后台任务处理

完整的API参考 service.ts

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClearRewritesAndProxies

func ClearRewritesAndProxies()

func DataCount

func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func DataFetch

func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func DataGet

func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func DataKeys

func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func DataRemove

func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func DataSet

func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func GetPoolStatus

func GetPoolStatus() map[string]PoolStatus

func ListCount

func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func ListPop

func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func ListPush

func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func ListRemove

func ListRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func MakeRequest

func MakeRequest(req *s.Request, args map[string]any, headers map[string]string) map[string]any

func MakeWSClient

func MakeWSClient(client *websocket.Conn, id string) gojs.Map

func MatchProxy

func MatchProxy(request *s.Request) (toApp, toPath string, ok bool)

func MatchRewrite

func MatchRewrite(request *s.Request) (toApp, toPath string, ok bool)

func NewCaller

func NewCaller(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func UpdateProxy

func UpdateProxy(in map[string]string) bool

func UpdateRewrite

func UpdateRewrite(in map[string]string) bool

func UpdateStatic

func UpdateStatic(in map[string]string) bool

Types

type Caller

type Caller struct {
	// contains filtered or unexported fields
}

func (*Caller) Delete

func (cl *Caller) Delete(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Caller) Do

func (cl *Caller) Do(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Caller) Get

func (cl *Caller) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Caller) Head

func (cl *Caller) Head(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Caller) Post

func (cl *Caller) Post(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Caller) Put

func (cl *Caller) Put(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

type Config

type Config struct {
	SessionKey         string
	DeviceKey          string
	ClientKey          string
	UserIdKey          string
	SessionProvider    string
	SessionTimeout     int64
	AuthFieldMessage   string
	VerifyFieldMessage string
	LimitedMessage     string
	Limiters           map[string]*LimiterConfig
	LimiterRedis       string

	Proxy   map[string]string
	Rewrite map[string]string
	Static  map[string]string
}

type LimiterConfig

type LimiterConfig struct {
	From  string
	Time  int
	Times int
}

type PoolStatus

type PoolStatus struct {
	Total       uint
	MaxTotal    uint
	MaxWaiting  uint
	CreateTimes uint
}

type Request

type Request struct {
	Id            string
	Proto         string
	Scheme        string
	Host          string
	Method        string
	Path          string
	RemoteAddr    string
	RealIP        string
	Referer       string
	UserAgent     string
	Url           string
	ContentLength int64
	Cookies       map[string]string
	Headers       map[string]string
	Args          map[string]any
	Files         map[string]map[string]any
	MultiFiles    map[string][]map[string]any
	// contains filtered or unexported fields
}

func (*Request) Close

func (r *Request) Close(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) Get

func (r *Request) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) GetHeader

func (r *Request) GetHeader(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) MakeURL

func (r *Request) MakeURL(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) Read

func (r *Request) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) ReadAll

func (r *Request) ReadAll(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) Set

func (r *Request) Set(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) SetHeader

func (r *Request) SetHeader(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Request) SetUserID

func (r *Request) SetUserID(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

type Response

type Response struct {
	Id string
	// contains filtered or unexported fields
}

func (*Response) AddHeader

func (r *Response) AddHeader(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) DownloadFile

func (r *Response) DownloadFile(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) End

func (r *Response) End(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) Flush

func (r *Response) Flush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) GetHeader

func (r *Response) GetHeader(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) Location

func (r *Response) Location(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) SendFile

func (r *Response) SendFile(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) SetCookie

func (r *Response) SetCookie(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) SetHeader

func (r *Response) SetHeader(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) SetStatus

func (r *Response) SetStatus(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Response) Write

func (r *Response) Write(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

type Session

type Session struct {
	// contains filtered or unexported fields
}

func NewSession

func NewSession(id string, logger *log.Logger) *Session

func (*Session) Get

func (session *Session) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Session) Remove

func (session *Session) Remove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Session) Save

func (session *Session) Save(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Session) Set

func (session *Session) Set(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

func (*Session) SetAuthLevel

func (session *Session) SetAuthLevel(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL