{"id":13753859,"url":"https://github.com/ssgo/s","last_synced_at":"2026-01-22T22:33:30.815Z","repository":{"id":57482135,"uuid":"114429118","full_name":"ssgo/s","owner":"ssgo","description":"a go web freamwork for micro service, very very easy to create and deploy, with auto service registry and discover, high performance and based on http/2 no ssl","archived":false,"fork":false,"pushed_at":"2024-10-21T09:55:55.000Z","size":712,"stargazers_count":66,"open_issues_count":10,"forks_count":15,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-10-21T20:19:29.598Z","etag":null,"topics":["discover","framework","go","go-micro","golang","micro","micro-service","microservice","registry","service","web"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssgo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-16T03:01:19.000Z","updated_at":"2024-10-21T09:55:46.000Z","dependencies_parsed_at":"2023-09-24T05:49:26.360Z","dependency_job_id":"58abf475-a003-4718-9a16-a85c939e7cda","html_url":"https://github.com/ssgo/s","commit_stats":null,"previous_names":[],"tags_count":154,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgo%2Fs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgo%2Fs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgo%2Fs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssgo%2Fs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssgo","download_url":"https://codeload.github.com/ssgo/s/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253329046,"owners_count":21891568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["discover","framework","go","go-micro","golang","micro","micro-service","microservice","registry","service","web"],"created_at":"2024-08-03T09:01:31.692Z","updated_at":"2026-01-22T22:33:30.755Z","avatar_url":"https://github.com/ssgo.png","language":"Go","readme":"# Go的一个服务框架\n\n[![Build Status](https://travis-ci.org/ssgo/s.svg?branch=master)](https://travis-ci.org/ssgo/s)\n[![codecov](https://codecov.io/gh/ssgo/s/branch/master/graph/badge.svg)](https://codecov.io/gh/ssgo/s)\n\nssgo能以非常简单的方式快速部署成为微服务群\n\n## 开始使用\n\n如果您的电脑go version \u003e= 1.11，使用以下命令初始化依赖自定义sshow:\n\n```shell\ngo mod init sshow\ngo mod tidy\n```\n\n1、下载并安装s\n\n```shell\ngo get -u github.com/ssgo/s\n```\n\n2、在项目代码中导入它\n\n```shell\nimport \"github.com/ssgo/s\"\n```\n\n## 快速构建一个服务\n\nstart.go:\n\n```go\npackage main\n\nimport \"github.com/ssgo/s\"\n\nfunc main() {\n\ts.Restful(0, \"GET\", \"/hello\", func() string{\n\t\treturn \"Hello ssgo\\n\"\n\t})\n\ts.Start()\n}\n```\n\n即可快速构建出一个8080端口可访问的服务:\n\n```shell\nexport service_listen=:8080\nexport service_httpversion=1\ngo run start.go\n```\n\nwindows下使用：\n\n```cmd\nset service_listen=:8080\nset service_httpversion=1\ngo run start.go\n```\n\n环境变量设置不区分大小写\n\n服务默认使用随机端口启动，若要指定端口可设置环境变量，或start.go目录下配置文件service.json\n\n```json\n{\n  \"listen\":\":8081\"\n}\n```\n开发时可以使用配置文件\n\n部署推荐使用容器技术设置环境变量\n\n\n## redis\n\n框架服务发现机制基于redis实现，所以使用discover之前，先要准备一个redis服务\n\n默认使用127.0.0.1:6379，db默认为15，密码默认为空，也可以在项目根目录自定义配置redis.json\n\n如果您的redis的密码如果不为空，需要使用AES加密后将密文放在配置文件password字段上，保障密码不泄露\n\n#### 密码使用AES加密\n使用github.com/ssgo/tool中的sskey来生成密码：\n\n```shell\ncd ssgo/tool/sskey\n# 如果以前没有编译过\ngo build sskey.go\n# 创建并保存秘钥\nsskey -c sshow\n# 输入原始密码\nsskey -e sshow\n```\n得到AES加密后的密码放入discover.json中\n\n```json\n{\n  \"registry\":\"127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg==\"\n}\n```\n也可以通过环境变量来设置：\n\n```shell\nexport DISCOVER_REGISTRY=\"127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg==\"\n```\nwindows下：\n```cmd\nset discover_registry=\"127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg==\"\n```\n\ndiscover_registry的设置代表：\n\nredis主机:端口号:数据库:res加密后的密码\n\n## 服务发现\n\n#### Service\n\n```go\npackage main\n\nimport \"github.com/ssgo/s\"\n\nfunc getFullName(in struct{ Name string }) (out struct{ FullName string }) {\n  out.FullName = in.Name + \" Lee\"\n  return\n}\n\nfunc main() {\n  s.Register(1, \"/{name}/fullName\", getFullName)\n  s.Start()\n}\n```\n\n```shell\nexport discover_app=s1\nexport service_accesstokens='{\"s1token\":1}'\ngo run service.go\n```\n\nwindows下使用：\n\n```cmd\nset discover_app=s1\nset service_accesstokens={\"s1token\":1}\ngo run service.go\n```\n\nRegister第一个参数值为1表示该服务工作在认证级别1上，派发了一个令牌 “s1token”，不带该令牌的请求将被拒绝\n\ns.Start()将会工作在 HTTP/2.0 No SSL 协议上（服务间通讯默认都使用 HTTP/2.0 No SSL 协议）\n\n并且自动连接本机默认的redis服务，并注册一个叫 s1 的服务（如需使用其他可以参考redis的配置）\n\n可运行多个实例，调用方访问时将会自动负载均衡到某一个节点\n\n#### Controller\n\n```go\npackage main\n\nimport (\n\t\"github.com/ssgo/s\"\n\t\"github.com/ssgo/discover\"\n)\n\nfunc getInfo(in struct{ Name string }, c *discover.Caller) (out struct{ FullName string }) {\n  c.Get(\"s1\", \"/\"+in.Name+\"/fullName\", nil).To(\u0026out)\n  return\n}\n\nfunc main() {\n  s.Register(0, \"/{name}\", getInfo)\n  s.Start()\n}\n```\n\n```shell\nexport discover_app=g1\nexport service_httpversion=1\nexport service_listen=:8091\nexport discover_calls='{\"s1\":\"s1token\"}'\ngo run controller.go \u0026\n```\n\n\nwindows下使用：\n\n```cmd\nset discover_app=g1\nset service_httpversion=1\nset service_listen=:8091\nset discover_calls={\"s1\":\"s1token\"}\ngo run controller.go\n```\n\n该服务工作在认证级别0上，工作在 HTTP/1.1 协议上,可以直接访问\n\ngetInfo 方法中调用 s1 时会根据 redis 中注册的节点信息负载均衡到某一个节点\n\n所有调用 s1 服务的请求都会自动带上 \"sltoken\" 这个令牌以获得相应等级的访问权限\n\n## 框架常用方法\n\n```go\n// 注册服务\nfunc Register(authLevel uint, name string, serviceFunc any) {}\n\n// 注册以正则匹配的服务\nfunc RegisterByRegex(name string, service any){}\n\n// 设置前置过滤器\nfunc SetInFilter(filter func(in *map[string]any, request *http.Request, response *http.ResponseWriter) (out any)) {}\n\n// 设置后置过滤器\nfunc SetOutFilter(filter func(in *map[string]any, request *http.Request, response *http.ResponseWriter, out any) (newOut any, isOver bool)) {}\n\n// 注册身份认证模块\nfunc SetAuthChecker(authChecker func(authLevel uint, url *string, request *map[string]any) bool) {}\n\n// 设置panic错误处理方法\nfunc SetErrorHandle(myErrorHandle func(err any, request *http.Request, response *http.ResponseWriter) any)\n\n\n// 默认启动HTTP/2.0服务（若未配置证书将工作在No SSL模式）\n// 如果设置了httpVersion=1则启动HTTP/1.1服务\nfunc Start() {}\n\n// 默认异步方式启动HTTP/2.0服务（）\n// 如果设置了httpVersion=1则启动HTTP/1.1服务\nfunc AsyncStart() *AsyncServer {}\n\n// 停止以异步方式启动的服务后等待各种子线程结束\nfunc (as *AsyncServer) Stop() {}\n\n// 调用异步方式启动的服务\nfunc (as *AsyncServer) Get(path string, headers ... string) *Result {}\nfunc (as *AsyncServer) Post(path string, data any, headers ... string) *Result {}\nfunc (as *AsyncServer) Put(path string, data any, headers ... string) *Result {}\nfunc (as *AsyncServer) Head(path string, data any, headers ... string) *Result {}\nfunc (as *AsyncServer) Delete(path string, data any, headers ... string) *Result {}\nfunc (as *AsyncServer) Do(path string, data any, headers ... string) *Result {}\n\n```\n\n## 基本使用\n\n#### Restful使用GET、POST、PUT、HEAD、DELETE和OPTIONS\n```go\n\npackage main\n\nimport (\n\t\"github.com/ssgo/s\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype actionIn struct {\n\tAaa int\n\tBbb string\n\tCcc string\n}\n\nfunc restAct(req *http.Request, in actionIn) actionIn {\n\treturn in\n}\nfunc showFullName(in struct{ Name string }) (out struct{ FullName string }) {\n\tout.FullName = in.Name + \" Lee.\"\n\treturn\n}\nfunc main() {\n\t//http://127.0.0.1:8301/api/echo?aaa=1\u0026bbb=2\u0026ccc=3\n\ts.Restful(0, \"GET\", \"/api/echo\", restAct)\n\ts.Restful(0, \"POST\", \"/api/echo\", restAct)\n\ts.Restful(0, \"PUT\", \"/api/echo\", restAct)\n\t//HEAD和GET本质一样，区别在于HEAD不含呈现数据，仅仅是HTTP头信息\n\ts.Restful(0, \"HEAD\", \"/api/echo\", restAct)\n\ts.Restful(0, \"DELETE\", \"/api/echo\", restAct)\n\ts.Restful(0, \"OPTIONS\", \"/api/echo\", restAct)\n\t//传参\n\t//http://127.0.0.1:8301/full_name/david\n\ts.Restful(0, \"GET\", \"/full_name/{name}\", showFullName)\n\t//访问设置header content-type:application/json  params:{\"name\":\"jim\"}\n\ts.Restful(0, \"PUT\", \"/full_name\", showFullName)\n\ts.Start()\n}\n```\n\n| 环境变量| 值 |\n|:------ |:------ |\n| service_listen | :8301 |\n| service_httpVersion | 1 |\n\n请求例子\n\n```\nPOST http://127.0.0.1:8301/api/echo HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\naaa=12\u0026bbb=hello\u0026ccc=world\n```\n\n```\nPUT http://127.0.0.1:8301/api/echo HTTP/1.1\nContent-Type: application/json\n\n{\n    \"aaa\": 12,\n    \"bbb\": \"hello\",\n    \"ccc\": \"world\"\n}\n```\n\n#### https\n\n配置https服务需要在原来配置基础上增加两个环境变量\n\n```shell\nexport service_certfile=\"your cert file path\"\nexport service_keyfile=\"your key file path\"\n```\n\nwindows下：\n\n```shell\nset service_certfile=D:/server/ssl/your.pem\nset service_keyfile=D:/server/ssl/your.key\n```\n\n对于上面的restful实例，如果设置为https服务：\n\n请求例子\n\n```\nPOST https://127.0.0.1:8301/api/echo HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\naaa=12\u0026bbb=hello\u0026ccc=world\n```\n\n#### 请求头和响应头\n\n```go\npackage main\n\nimport (\n\t\"github.com/ssgo/s\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc headerTest(request *http.Request, response http.ResponseWriter) (token string) {\n\ttoken = \"Get header token:\" + request.Header.Get(\"token\")\n\tresponse.Header().Set(\"resToken\", \"Hello world\")\n\treturn\n}\n\n//输入参数放在前放在后都可以\nfunc label(in struct{ Enter string }, \n request *http.Request, response http.ResponseWriter) (out struct{ Label string}) {\n\tprefix := request.Header.Get(\"prefix\")\n\tout.Label = prefix + in.Enter\n\tresponse.Header().Set(\"accept\", \"application/json\")\n\treturn\n}\n\nfunc main() {\n\t//header\n\ts.Restful(0, \"GET\", \"/header_test\", headerTest)\n\ts.Restful(0, \"POST\", \"/label\", label)\n\ts.Start() ////设置service_httpVersion=1\n}\n\n```\n#### 设置响应状态码\n\n使用go标准库自带的response\n\n```go\ns.Register(1, \"/ssdesign\", func(response http.ResponseWriter) string {\n\tresponse.WriteHeader(504)\n\treturn \"controller timeout\"\n})\n```\n\n#### 文件上传\n\n文件上传使用标准包自带功能\n\n```go\n// 处理/upload 逻辑\nfunc upload(w http.ResponseWriter, r *http.Request) {\n\tr.ParseMultipartForm(32 \u003c\u003c 20)\n\tfile, handler, err := r.FormFile(\"uploadfile\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer file.Close()\n\tfmt.Fprintf(w, \"%v\", handler.Header)\n\tf, err := os.OpenFile(\"./upload/\"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\tio.Copy(f, file)\n\n}\n```\n\n#### 过滤器与身份认证\n\n执行先后顺序为：前置过滤器、身份认证、后置过滤器\n\n```go\npackage main\n\nimport (\n\t\"github.com/ssgo/s\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype actionFilter struct {\n\tAaa     int\n\tBbb     string\n\tCcc     string\n\tFilter1 string\n\tFilter2 int\n}\n\nfunc authTest(in actionFilter) actionFilter {\n\treturn in\n}\n\nfunc main() {\n\ts.Restful(0, \"GET\", \"/auth_test\", authTest)\n\ts.Restful(1, \"POST\", \"/auth_test\", authTest)\n\ts.Restful(2, \"PUT\", \"/auth_test\", authTest)\n    //前置过滤器\n\ts.SetInFilter(func(in *map[string]any, request *http.Request, response *http.ResponseWriter) any {\n\t\t(*in)[\"Filter1\"] = \"see\"\n\t\t(*in)[\"filter2\"] = 100\n\t\t(*response).Header().Set(\"content-type\", \"application/json\")\n\t\treturn nil\n\t})\n    //身份认证\n\ts.SetAuthChecker(func(authLevel uint, url *string, in *map[string]any, request *http.Request) bool {\n\t\ttoken := request.Header.Get(\"Token\")\n\t\tswitch authLevel {\n\t\tcase 1:\n\t\t\treturn token == \"dev\" || token == \"develop\"\n\t\tcase 2:\n\t\t\treturn token == \"dev\"\n\t\t}\n\t\treturn false\n\t})\n    //后置过滤器\n\ts.SetOutFilter(func(in *map[string]any, request *http.Request, response *http.ResponseWriter, result any) (any, bool) {\n\t\tdata := result.(actionFilter)\n\t\tdata.Filter2 = data.Filter2 + 100\n\t\treturn data, false\n\t})\n\n\ts.Start() ////设置service_httpVersion=1\n}\n```\n\nSetAuthChecker方法return false时，请求会返回403状态码，禁止访问\n\n#### Rewrite\n\n实现对url的重写\n\n```go\nfunc main() {\n\ts.Register(0, \"/show\", func(in struct{ S1, S2 string }) string {\n\t\treturn in.S1 + \" \" + in.S2\n\t})\n\ts.Register(0, \"/show/{s1}\", func(in struct{ S1, S2 string }) string {\n\t\treturn in.S1 + \" \" + in.S2\n\t})\n\ts.Rewrite(\"/r1\", \"/show\")\n\t//get http://127.0.0.1:8305/r2/123?s2=456 --\u003e http://127.0.0.1:8305/r2/123?s2=456\n\ts.Rewrite(\"/r2/(\\\\w+?)\\\\?.*?\", \"/show/$1\")\n\t//post http://127.0.0.1:8305/r3?name=123  s2=456\n\ts.Rewrite(\"/r3\\\\?name=(\\\\w+)\", \"/show/$1\")\n\ts.Start() //设置service_httpVersion=1\n}\n```\n#### 异步服务\n\n启动异步服务与异步服务的调用：\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/ssgo/s\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype actIn struct {\n\tAaa int\n\tBbb string\n\tCcc string\n}\n\nfunc act(req *http.Request, in actIn) actIn {\n\treturn in\n}\n\nfunc main() {\n\ts.ResetAllSets()\n\t//http://127.0.0.1:8301/api/echo?aaa=1\u0026bbb=2\u0026ccc=3\n\ts.Restful(0, \"GET\", \"/act/echo\", act)\n\ts.Restful(1, \"POST\", \"/act/echo\", act)\n\t//s.Restful(2, \"PUT\", \"/act/echo\", act)\n\tas := s.AsyncStart()\n\tdefer as.Stop()\n\n\tasyncPost := as.Post(\"/act/echo?aaa=hello\u0026bbb=hi\", s.Map{\n\t\t\"ccc\": \"welcome\",\n\t}, \"Cid\", \"demo-post\").Map()\n\tasyncPut := as.Put(\"/act/echo\", s.Map{\n\t\t\"aaa\": \"hello\",\n\t\t\"bbb\": \"hi\",\n\t\t\"ccc\": \"welcome\",\n\t}, \"Cid\", \"demo-put\").Map()\n\tasyncGet := as.Get(\"/act/echo?aaa=11\u0026bbb=222\u0026ccc=333\").Map()\n\tfmt.Println(\"asyncPut:\", asyncPut)\n\tfmt.Println(\"asyncPost:\", asyncPost)\n\tfmt.Println(\"asyncGet\", asyncGet)\n}\n\n```\n\n#### proxy\n\n将服务代理为自定义服务，支持正则表达式\n\n```go\nfunc main() {\n\ts.Register(1, \"/serv/provide\", func() (out struct{ Name string }) {\n\t\tout.Name = \"server here\"\n\t\treturn\n\t})\n\t//调用注册的服务\n\ts.Register(2, \"/serv/gate_get\", func(c *discover.Caller) string {\n\t\tr := struct{ Name string }{}\n\t\tc.Get(\"e1\", \"/serv/provide\").To(\u0026r)\n\t\treturn \"gate get \" + r.Name\n\t})\n\ts.Proxy(\"/proxy/(.+?)\", \"e1\", \"/serv/$1\")\n\n\tos.Setenv(\"LOG_FILE\", os.DevNull)\n\tos.Setenv(\"SERVICE_ACCESSTOKENS\", `{\"e1_level1\": 1, \"e1_level2\": 2, \"e1_level3\":3}`)\n\tos.Setenv(\"DISCOVER_CALLS\", `{\"e1\":\"5000:e1_level3:1\"}`)\n\t//一定要做reset，不然手动设置的环境变量不可被加载****\n\tconfig.ResetConfigEnv()\n\tas := s.AsyncStart()\n\tfmt.Println(\"/serv/provide:\")\n\tfmt.Println(as.Get(\"/serv/provide\", \"Access-Token\", \"e1_level1\"))\n\tfmt.Println(\"/serv/gate_get:\")\n\tfmt.Println(as.Get(\"/serv/gate_get\", \"Access-Token\", \"e1_level2\"))\n\tfmt.Println(\"/proxy/provide:\")\n\tfmt.Println(as.Get(\"/proxy/provide\", \"Access-Token\", \"e1_level2\"))\n\tfmt.Println(\"/proxy/gate_get:\")\n\tfmt.Println(as.Get(\"/proxy/gate_get\", \"Access-Token\", \"e1_level2\"))\n\tdefer as.Stop()\n}\n```\n\n#### 授权等级\n\n注册服务指定authLevel为0，启动的服务被调用时不需要鉴权，可以直接访问\n\n可以为一个服务指定多个授权等级(1,2,3,4,5,6……)，具备对应授权等级的accessToken的客户端，有权限访问服务\n\ncalls客户端使用服务具备的高等级的访问token，可以访问以低授权等级启动的服务\n\n如果accessToken错误，或者具备小于服务启动授权等级的token，访问服务会被拒绝，返回403\n\n例如：\n\n客户端具备6等级accessToken，对于应用下注册启动的1,2,3,4,5,6服务默认具备访问权限\n\n客户端仅具备1等级accessToken，对于应用下注册启动的2,3,4,5,6服务的访问会被拒绝\n\n\n#### 静态资源\n\n```go\ns.Static(\"/\", \"resource/\")\ns.Start()\n```\n\n注意：resource结尾一定要有/\n\n启动服务可以访问站点resource目录下的静态资源\n\ncontroller可以通过proxy来实现多个静态服务的负载代理：\n```go\ns.Proxy(\"/proxy/(.+?)\", \"k1\", \"/$1\")\ns.Start()\n```\n\n\n#### Websocket\n\n一个以Action为处理单位的 Websocket 封装\n\n```go\n// 注册Websocket服务\nfunc RegisterWebsocket(authLevel uint, name string, updater *websocket.Upgrader,\n\tonOpen any,\n\tonClose any,\n\tdecoder func(data any) (action string, request *map[string]any, err error),\n\tencoder func(action string, data any) any) *ActionRegister {}\n\n// 注册Websocket Action\nfunc (ar *ActionRegister) RegisterAction(authLevel uint, actionName string, action any) {}\n\n// 注册针对 Action 的认证模块\nfunc SetActionAuthChecker(authChecker func(authLevel uint, url *string, action *string, request *map[string]any, sess any) bool) {}\n\n```\n\n使用websocket：\n\n```go\nws := s.RegisterWebsocket(1, \"/dc/ws\", updater, open, close, decode, encode)\nws.RegisterAction(0, \"hello\", func(in struct{ Name string }) \n    (out struct{ Name string }) {\n    out.Name = in.Name + \"!\"\n    return\n})\nc, _, err := websocket.DefaultDialer.Dial(\"ws://\"+addr2+\"/proxy/ws\", nil)\nerr = c.WriteJSON(s.Map{\"action\": \"hello\", \"name\": \"aaa\"})\nerr = c.ReadJSON(\u0026r)\nc.Close()\n```\n\n#### cookie\n\ncookie可以使用go标准包http提供的方法，cookie发送给浏览器,即添加一个cookie\n\n```go\nfunc hadler(w http.ResponseWriter) {\n\tcookieName := http.Cookie{\n        Name:     \"name\",\n        Value:    \"jim\",\n        HttpOnly: true,\n    }\n    cookieToken := http.Cookie{\n        Name:       \"token\",\n        Value:      \"asd123dsa\",\n        HttpOnly:   true,\n        MaxAge:     60,//设置有效期为60s\n    }\n    \n    w.Header().Set(\"Set-Cookie\", cookieName.String())\n    w.Header().Add(\"Set-Cookie\", cookieToken.String())\n}\n```\n\n使用http的setCookie也可以\n\n```go\nfunc handler2(w http.ResponseWriter) {\n    cookieName := http.Cookie{\n        Name:     \"name\",\n        Value:    \"jim\",\n        HttpOnly: true,\n    }\n    cookieToken := http.Cookie{\n        Name:     \"token\",\n        Value:    \"asd123dsa\",\n        HttpOnly: true,\n    }\n\n    http.SetCookie(w, \u0026cookieName)\n    http.SetCookie(w, \u0026cookieToken)\n}\n```\n\n读取Cookie\n\n```go\nfunc readCookie(w http.ResponseWriter, r *http.Request) {\n    cookies := r.Header[\"Cookie\"]\n    nameCookie, _ := r.Cookie(\"name\")\n}\n```\n\n#### SessionKey和SessionInject\n\n```go\n// 设置 SessionKey，自动在 Header 中产生，AsyncStart 的客户端支持自动传递\nfunc SetSessionKey(inSessionKey string) {}\n\n// 获取 SessionKey\nfunc GetSessionKey() string {}\n\n// 设置一个生命周期在 Request 中的对象，请求中可以使用对象类型注入参数方便调用\nfunc SetSessionInject(request *http.Request, obj any) {}\n\n// 获取本生命周期中指定类型的 Session 对象\nfunc GetSessionInject(request *http.Request, dataType reflect.Type) any {}\n```\n\n基于 Http Header 传递 SessionId（不推荐使用Cookie）\n\n```go\ns.Restful(2, \"PUT\", \"/api/echo\", action)\ns.SetSessionKey(\"name\")\ns.Start()\nfunc showFullName(in struct{ Name string },req *http.Request) (out struct{ FullName string }) {\n\tout.FullName = in.Name + \" Lee.\" + s.GetSessionId(req)\n\treturn\n}\n```\n\n使用 SetSession 设置的对象可以在服务方法中直接使用相同类型获得对象，一般是在 AuthChecker 或者 InFilter 中设置\n\nsession对象注入\n\n```go\naiValue := actionIn{2, \"so\", \"cool\"}\ns.SetSessionInject(req, aiValue)\nai := s.GetSessionInject(req, reflect.TypeOf(actionIn{})).(actionIn)\n```\n\n#### 对象注入\n\n```go\n// 设置一个注入对象，请求中可以使用对象类型注入参数方便调用\nfunc SetInject(obj any) {}\n\n// 获取一个注入对象\nfunc GetInject(dataType reflect.Type) any {}\n```\n\n注入对象可以跨请求体\n\n```go\ntype actionIn struct {\n\tAaa int\n\tBbb string\n\tCcc string\n}\nfunc showInject(in struct{ Name string }) (out struct{ FullName string }) {\n\tai := s.GetInject(reflect.TypeOf(actionIn{})).(actionIn)\n\tout.FullName = in.Name + \" Lee.\" + \" \" + ai.Ccc\n\treturn\n}\nfunc main() {\n\t//……\n\taiValue := actionIn{2, \"so\", \"cool\"}\n\ts.SetInject(aiValue)\n\t//……\n}\n```\n\n#### panic处理\n\n接受服务方法主动panic的处理，可自定义SetErrorHandle\n\n如果没有自定义errorHandle，可以走框架默认的处理方式，建议自定义SetErrorHandle，设置自身的服务方法的统一panic处理\n\n```go\nfunc panicFunc() {\n\tpanic(errors.New(\"s panic test\"))\n}\n\nfunc main() {\n    s.ResetAllSets()\n    s.Register(0, \"/panic_test\", panicFunc)\n    \n    s.SetErrorHandle(func(err any, req *http.Request, rsp *http.ResponseWriter) any {\n        return s.Map{\"msg\": \"defined\", \"code\": \"30889\", \"panic\": fmt.Sprintf(\"%s\", err)}\n    })\n    as := s.AsyncStart()\n    defer as.Stop()\n    \n    r := as.Get(\"/panic_test\")\n    panicArr := r.Map()\n    \n    fmt.Println(panicArr)\t\n}\n```\n\n## 配置\n\n#### 服务配置\n\n可在应用根目录放置一个 service.json\n\n```json\n{\n  \"listen\": \":8081\",\n  \"httpVersion\": 2,\n  \"rwTimeout\": 5000,\n  \"keepaliveTimeout\": 15000,\n  \"rewriteTimeout\": 10000,\n  \"noLogGets\": false,\n  \"noLogHeaders\": \"Accept,Accept-Encoding,Cache-Control,Pragma,Connection\",\n  \"noLogInputFields\": false,\n  \"logInputArrayNum\": 0,\n  \"logOutputFields\": \"code,message\",\n  \"logOutputArrayNum\": 2,\n  \"logWebsocketAction\": false,\n  \"compress\": true,\n  \"certFile\": \"\",\n  \"keyFile\": \"\",\n  \"accessTokens\": {\n    \"hasfjlkdlasfsa\": 1,\n    \"fdasfsadfdsa\": 2,\n    \"9ifjjabdsadsa\": 2\n  }\n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ | \n| listen | string | :8081 | 服务绑定的端口号 |\n| httpVersion | int | 2 | 服务的http版本 |\n| rwTimeout | int\u003cbr\u003e毫秒 | 10000 | 服务读写超时时间 |\n| keepaliveTimeout | int\u003cbr\u003e毫秒 | 10000 | keepalived激活时连接允许空闲的最大时间\u003cbr\u003e如果未设置，默认为15秒 |\n| rewriteTimeout | int\u003cbr\u003e毫秒 | 5000 | rewrite、proxy操作的超时时间 |\n| noLogGets | bool | false | 为true时屏蔽Get网络请求日志 |\n| noLogHeaders | string | Accept,Accept-Encoding | 日志请求头和响应头屏蔽header头指定字段输出\u003cbr /\u003e可设置为false |\n| noLogInputFields | string | accessToken | 日志过滤输入的字段，目前未启用\u003cbr\u003e为false代表所有字段都日志打印 |\n| logInputArrayNum | int | 2 | 输入字段子元素（数组）日志打印个数限制\u003cbr\u003e默认为0， s.Arr{1, 2, 3}会输出为float64 (3)|\n| logOutputFields | string | code,message | 日志输出的字段白名单\u003cbr\u003e默认为false，代表不限制 |\n| logOutputArrayNum | int | 3 | 输出字段子元素（数组）日志打印个数限制\u003cbr\u003e默认为0 |\n| logWebsocketAction | bool | false | 是否展示websocket的WSACTION请求日志 |\n| compress | bool | false | 是否开启响应gzip压缩(包含静态资源) |\n| compressMinSize | int | 1024 | 设置响应内容gzip压缩满足的最小尺寸\u003cbr /\u003e默认为1024Bytes |\n| compressMaxSize | int | 4096000 | 设置响应内容gzip压缩满足的最大尺寸\u003cbr /\u003e默认为4096000Bytes |\n| certFile | string |  | https签名证书文件路径 |\n| keyFile | string |  | https私钥证书文件路径 |\n| accessTokens | map | {\"ad2dc32cde9\" : 1} | 当前服务访问授权码，可以根据不同的授权等级设置多个 |\n| acceptXRealIpWithoutRequestId| bool | false | 在没有X-Request-ID的情况下是否忽略 X-Real-IP\u003cbr /\u003efalse代表忽略 |\n\n#### 服务发现配置\n\n可在应用根目录放置一个 discover.json\n\n```json\n{\n  \"registry\": \"127.0.0.1:6379:15\",\n  \"app\": \"\",\n  \"weight\": 1,\n  \"calls\": {\n    \"s1\": \"5000:hasfjlkdlasfsa:2:s\"\n  }\n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ | \n| registry | string | 127.0.0.1:6379:15 | 服务发现redis的host、端口、数据库、密码、超时时间配置\u003cbr\u003e用于服务注册与注销 |\n| app | string | s1 | 可被发现的服务应用名 |\n| weight | int | 2 | 负载均衡服务权重 |\n| calls | string |  | 客户端访问服务的配置\u003cbr\u003e{\"s1\":\"5000:adfad\"}\u003cbr\u003e{\"s1\":\"5000:s\"}\u003cbr\u003e{\"s1\":\"adfad:s\"}|\n| callRetryTimes | int | 10 | 客户端访问服务失败重试次数 |\n\ncalls中包含：\n\ntimeout:accessToken:httpVersion:SSL\n\n没有固定前后顺序，自动检查配置型\n\n| 配置项| 类型 | 不填默认 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ |:------ |\n| timeout |  int | 5000ms |5000| 如果未设置默认为10000（10秒）,比如：\":s1token\"   \":s1token:2\" | \n| accessToken | string | 空 | 5000 | 比如：\"5000\" | \n| httpVersion | int | http 2.0 no ssl | 2 | 调用服务使用的http协议：1代表http1.1 2代表http2 | \n| SSL | string  | 不使用ssl | s | 是否使用https ssl  这里的值可以不填或者为s|\n\n\ncalls检查顺序：\n* 1或2代表http 1.1 2.0  默认为2\n* s代表https 没有s 表示不使用ssl\n* 字符串代表token\n* int类型代表超时的ms\n* 其他类型为token\n\n\n\n#### 日志配置\n\n可在应用根目录放置一个 log.json\n```json\n{\n  \"level\": \"info\",\n  \"truncations\": [\"github.com/\", \"/ssgo/\"],\n  \"sensitive\": [\"password\", \"secure\", \"token\", \"accessToken\"],\n  \"regexSensitive\": [\"(^|[^\\\\d])(1\\\\d{10})([^\\\\d]|$)\", \"\\\\[(\\\\w+)\\\\]\"],\n  \"sensitiveRule\": [\"11:3*3\", \"7:2*2\", \"3:1*1\", \"2:1*0\"]\n  \n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ | \n| level | string | info | 指定的日志输出级别\u003cbr /\u003edebug,info,warning,error |\n| file | string | /dev/null | 日志文件\u003cbr /\u003e设置为nil,不展示日志\u003cbr\u003e可以指定日志文件路径\u003cbr\u003e不设置默认打向控制台 |\n| truncations | []string |  [\"github.com/\", \"/ssgo/\"] | 程序调用栈callStack字段忽略的目录 |\n| sensitive | []string | [\"password\",\"token\"] | 敏感字段 |\n| regexSensitive | []string | [\"\\\\[(\\\\w+)\\\\]\"] | 日志敏感信息正则匹配 |\n| sensitiveRule | []string | [\"11:3*3\", \"7:2*2\", \"3:1*1\", \"2:1*0\"] | 敏感字段展示规则 |\n\n#### redis配置\n\n可在应用根目录放置一个 redis.json\n\n```json\n{\n  \"test\": {\n    \"host\": \"127.0.0.1:6379\",\n    \"password\": \"\",\n    \"db\": 1,\n    \"maxActive\": 100,\n    \"maxIdles\": 30,\n    \"idleTimeout\": 0,\n    \"connTimeout\": 3000,\n    \"readTimeout\": 0,\n    \"writeTimeout\": 0\n  },\n  \"dev\": {\n    \"…\":\"…\"\n  }\n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ | \n| host | string | 127.0.0.1:6379 | host配置 |\n| password | string |  | AES加密后的密码 |\n| db | int | 1 | 选择的数据库 |\n| maxActive | int | 1 | 最大连接数\u003cbr\u003e默认为0,代表不限制 |\n| maxIdles | int | 10 | 最大空闲连接数，默认为0表示不限制 |\n| idleTimeout | int\u003cbr\u003e毫秒 | 10000 | keepalived激活时连接允许空闲的最大时间\u003cbr\u003e默认为0，代表不限制 |\n| connTimeout | int\u003cbr\u003e毫秒 | 10000 | 连接超时时间，默认为10s |\n| readTimeout | int\u003cbr\u003e毫秒 | 10000 | 读超时时间，默认为10s |\n| writeTimeout | int\u003cbr\u003e毫秒 | 10000 | 写超时时间，默认为10s |\n\n#### 数据库配置\n\n可以在项目根目录放置一个db.json来做mysql数据库的配置\n\n```json\n{\n  \"test\": {\n    \"type\": \"mysql\",\n    \"user\": \"test\",\n    \"password\": \"34RVCy0rQBSQmLX64xjoyg==\",\t\n    \"host\": \"/tmp/mysql.sock\",\n    \"db\": \"test\",\n    \"maxOpens\": 100,\t\n    \"maxIdles\": 30,\n    \"maxLifeTime\": 0\n  }\n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n| ------ | ------ | ------ | ------ | \n|type|string|mysql|数据库类型|\n|user|string|test|数据库名|\n|password|string|  |经过AES加密的数据库密码|\n|host|string|  |可为sock文件路径或者mysql的地址、端口号|\n|db|string|test|数据库名|\n|maxOpens|int| 100 |最大连接数，0表示不限制|\n|maxIdles|int| 30 |最大空闲连接，0表示不限制|\n|maxLifeTime|int| 0 |每个连接的存活时间，0表示永远|\n\n数据库密码加密可以保障不泄露，和redis加密方法完全相同\n\n也可以以其他方式只要在 init 函数中调用 db.SetEncryptKeys 设置匹配的 key和iv即可\n\n#### 网关代理配置\n\n可以在项目根目录放置一个gateway.json来做网关代理配置\n\n```json\n{\n  \"checkInterval\": 1,\n  \"proxies\": {\n    \"/abc\": \"k1\",\n    \"/def\":\"g1\",\n    \"/cce\":\"g1\"\n  }\n}\n```\n\n| 配置项| 类型 | 样例数据 | 说明 |\n|:------ |:------ |:------ |:------ | \n|checkInterval|int\u003cbr /\u003e|10|每隔配置的秒数到redis中获取最新数据\u003cbr\u003e最小配置值为3|\n|proxies|map|{\"/abc\": \"k1\"}|路由到应用名的映射|\n\n##### proxies\n\nproxies可以从环境变量、配置文件、redis中来获取。其中redis配置是动态配置，获取redis中`_proxies`的值，动态更新到gateway应用上。\n\n#### env配置\n\n可以在应用根目录使用env.json综合配置(service+discover+log+gateway+redis+db)服务：\n\n```json\n{\n  \"redis\":{\n    \"test\":{\n      \"host\":\"127.0.0.1:6379\",\n      \"password\":\"upvNALgTxwS/xUp2Cie4tg==\",\n      \"db\":1\n    }\n  },\n  \"service\":{\n    \"listen\":\":8081\"\n  },\n  \"discover\": {\n    \"app\":\"e1\"\n  },\n  \"log\": {\n    \"level\": \"info\"\n  },\n  \"db\":{\n    \"test\": {\n      \"type\": \"mysql\",\n      \"user\": \"root\",\n      \"password\": \"8wv3Kie3Y4nLArmSWs+hng==\",\n      \"host\": \"127.0.0.1:3306\",\n      \"db\": \"test\"\n     }\n  }\n}\n```\n\n\u003cfont color=red\u003eenv.json的优先级高于其他配置文件。\u003c/font\u003e\n\n如果同级目录下同时出现env和server配置文件，env的配置会对server配置进行覆盖。\n\n#### 环境变量\n\n以下是服务配置\n\n```shell\nexport discover='{\"app\": \"c1\", \"calls\": {\"s1\":\"5000:asfews:1\"}}'\nexport discover_app='c1'\nexport discover_calls_s1='5000:asfews:2'\n```\n\nwindows下：\n\n```cmd\nset discover={\"app\": \"c1\", \"calls\": {\"s1\":\"5000:asfews\"}}\nset discover_app=c1\nset discover_calls_s1=5000:asfews:2\n```\n\n以下是服务发现的redis配置\n\n```shell\nexport discover='{\"REGISTRY\":\"127.0.0.1:6379:1\"}'\nexport discover_registry='127.0.0.1:6379:1:udigzs+oTp2Kau3Gs20xXQ=='\n```\nwindows下：\n\n```shell\nset discover={\"registry\":\"127.0.0.1:6379:1\"}\nset discover_registry=127.0.0.1:6379:udigzs+oTp2Kau3Gs20xXQ==\n```\n\n环境变量单项配置优先级大于总体配置\n\n```shell\nexport service_calls='{\"k1\": {\"accessToken\": \"s1token\"}}'\nexport service_calls_k1_accesstokens='s1-token'\n```\n\nservice_calls_k1_accesstokens的配置会覆盖service_calls对k1服务accessToken的配置\n\n#### 配置优先级顺序\n\ncli设置环境变量(set/export) \u003e 配置文件\n\n## 服务调用\n\n服务调用客户端模式\n\n```go\n\n// 调用已注册的服务，根据权重负载均衡\nfunc (caller *Caller) Get(app, path string, headers ... string) *Result {}\nfunc (caller *Caller) Post(app, path string, data any, headers ... string) *Result {}\nfunc (caller *Caller) Put(app, path string, data any, headers ... string) *Result {}\nfunc (caller *Caller) Head(app, path string, data any, headers ... string) *Result {}\nfunc (caller *Caller) Delete(app, path string, data any, headers ... string) *Result {}\nfunc (caller *Caller) Do(app, path string, data any, headers ... string) *Result {}\n```\n\n## 负载均衡算法\n\n```go\n// 指定节点调用已注册的服务，并返回本次使用的节点\nfunc (caller *Caller) DoWithNode(method, app, withNode, path string, data any, headers ... string) (*Result, string) {}\n\n// 设置一个负载均衡算法\nfunc SetLoadBalancer(lb LoadBalancer) {}\n\ntype LoadBalancer interface {\n\n\t// 每个请求完成后提供信息\n\tResponse(node *NodeInfo, err error, response *http.Response, responseTimeing int64)\n\n\t// 请求时根据节点的得分取最小值发起请求\n\tNext(nodes []*NodeInfo, request *http.Request) *NodeInfo\n}\n```\n\n## 日志输出\n\n使用json格式输出日志\n\n```go\nfunc Debug(logType string, data Map) {}\n\nfunc Info(logType string, data Map) {}\n\nfunc Warning(logType string, data Map) {}\n\nfunc Error(logType string, data Map) {}\n\nfunc Log(logLevel LogLevelType, logType string, data Map) {}\n\nfunc TraceLog(logLevel LogLevelType, logType string, data Map) {}\n\n```\n\n## Document 自动生成接口文档\n\n```go\n// 生成文档数据\nfunc MakeDocument() []Api {}\n\n// 生成文档并存储到 json 文件中\nfunc MakeJsonDocumentFile(file string) {\n\n// 生成文档并存储到 html 文件中，使用默认html模版\nfunc MakeHtmlDocumentFile(title, toFile string) string {}\n\n// 生成文档并存储到 html 文件中，使用指定html模版\nfunc MakeHtmlDocumentFromFile(title, toFile, fromFile string) string {}\n\n```\n\n针对注册好的服务，可轻松实现文档的生成\n\n```go\ns.Register(0, \"/show1\", show1)\ns.Register(0, \"/show2\", show2)\ns.Register(0, \"/show3\", show3)\ns.Register(0, \"/show4\", show4)\n\ns.MakeHtmlDocumentFile(\"测试文档\", \"doc.html\")\n```\n\n生成html文档默认使用s框架根目录下的DocTpl.html作为模板，内部采用`{{ }}`标识语法\n\nDocTpl.html可以作为新建模板的参考\n\n#### 使用命令行创建文档\n\n假设编译好的文件为 ./server\n\n```shell\n// 直接输出 json 格式文档\n./server doc\n\n// 生成 json 格式文档\n./server doc xxx.json\n\n// 生成 html 格式文档，使用默认html模版\n./server doc xxx.html\n\n// 生成 html 格式文档，使用指定html模版\n./server doc xxx.html tpl.html\n\n```","funding_links":[],"categories":["web"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssgo%2Fs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssgo%2Fs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssgo%2Fs/lists"}