{"id":21436764,"url":"https://github.com/modfin/wsrpc","last_synced_at":"2025-03-16T23:23:44.141Z","repository":{"id":57503044,"uuid":"200204966","full_name":"modfin/wsrpc","owner":"modfin","description":null,"archived":false,"fork":false,"pushed_at":"2023-05-08T14:11:35.000Z","size":89,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-23T09:33:58.150Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/modfin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2019-08-02T09:16:36.000Z","updated_at":"2023-05-08T14:11:41.000Z","dependencies_parsed_at":"2024-06-20T04:17:14.674Z","dependency_job_id":"c5e4b417-f14a-4b1f-b0eb-5a82d8b35202","html_url":"https://github.com/modfin/wsrpc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Fwsrpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Fwsrpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Fwsrpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Fwsrpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modfin","download_url":"https://codeload.github.com/modfin/wsrpc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243945766,"owners_count":20372929,"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":[],"created_at":"2024-11-23T00:15:20.189Z","updated_at":"2025-03-16T23:23:44.117Z","avatar_url":"https://github.com/modfin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web Socket RPC\nA simple web socket api framework for sending json rpc requests.\nThe framework is similar too but not necessarily compliant with the json rpc 2.0 standard.\n\n[![GoDoc](https://godoc.org/github.com/modfin/wsrpc?status.svg)](https://godoc.org/github.com/modfin/wsrpc)\n[![Go Report Card](https://goreportcard.com/badge/github.com/modfin/wsrpc)](https://goreportcard.com/report/github.com/modfin/wsrpc)\n[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/modfin/wsrpc/master/LICENSE.md)\n\nWeb sockets are the primary transport method but the framework allows the client to fallback on long polls in case of unsuitable conditions.\n\nA client library is available at: http://github.com/modfin/wsrpc-js\n\n## Implementing the server\nThe framework is similar to regular http frameworks by design to make it more intuitive, you have access to common resources like:\n* Middlewares \n* Context\n* Registering Handlers\n* Sticky Headers\n* Cookies\n* The raw HTTP request\n\n### Create the router\n```go\n    import \"github.com/modfin/wsrpc\"\n\n    ...\n\n    router := wsrpc.NewRouter()\n```\n### Apply middleware you wish to use or skip.\nMiddlewares can be appended to the router to affect all handlers on the router. It can also be specified in a specific handler registration to affect only that handler.\n```go\nfunc someMiddleware(c wsrpc.Context, next wsrpc.NextFunc) error {\n    err := next(ctx)\n    if err != nil {\n        log.Error(err)\n    }\n\n    return nil\n}\n\n    ...\n\n\nfunc main() {\n    ...\n    router.Use(someMiddleware)\n    ...\n}\n```\n\n### Extracting parameters\nParamaters are passed to the handler contexts request object as a raw json message.\n```go\nfunc someCallHandler(ctx wsrpc.Context) error {\n    ...\n    var num int64\n    err := json.Unmarshal(ctx.Request().Params, \u0026num)\n    if err != nil {\n        return err\n    }\n\n    ...\n\n}\n```\n\n### Extracting and setting headers\nHeaders are passed to the handler contexts request object as key value objects.\n```go\nfunc someCallHandler(ctx wsrpc.Context) error {\n    ...\n\tnum, ok := ctx.Request().Header.Get(\"state\").Int()\n\tif !ok {\n\t\treturn errors.New(\"missing valid request state\")\n\t}\n    ...\n\n}\n```\n\n### Working with the Context\nThe wsrpc Context used by handlers implements the standard go Context interface in addition to it's own features.\n\nContext cancellations will cascade down from the top level connection down to each the connections batch of jobs and finally down to each job in said batches. Allowing us to cancel any action based on the handlers context. \n```go\nfunc someStreamHandler(ctx wsrpc.Context, ch *wsrpc.ResponseChannel) error {\n\tcountdown := 3\n\t\n\tfor countdown \u003e 0 {\n\t\tselect {\n\t\t\tcase \u003c-ctx.Done():\n\t\t\t\tlog.Println(\"context cancelled\")\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t}\n\t\t\n\t\tcountdown -= 1\n\t}\n\t\n\treturn nil\n}\n``` \n\n### The raw HTTP request\nIf we need something from the original HTTP request, e.g. the original request URL we can access it through the handler context\n```go\nfunc someCallHandler(ctx wsrpc.Context) error {\n...\n\tconnectionUrl := ctx.HttpRequest().URL.String()\n...\n}\n```\n\n### Sending responses to the client\nResponses can be sent by setting the Content field on the contexts response object. THe response is automatically sent to the client when the handler returns. \n\nIf you wish to trigger the response right away, i.e. in the case of a streaming handler with multiple responses there is a channel available to stream handlers.\n```go\nfunc someCallHandler(ctx wsrpc.Context) error {\n...\n\tctx.Response().Result, err = json.Marshal(num * num)\n\tif err != nil {\n\t\treturn nil\n\t}}\n...\n}\n\nfunc someStreamHandler(ctx wsrpc.Context, ch *wsrpc.ResponseChannel) error {\n...\n\t\trsp := ctx.NewResponse()\n\t\t\n\t\tvar err error \n\t\trsp.Result, err = json.Marshal(42)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t\n\t\terr = ch.Write(rsp)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n...\n}\n```\n\n### Registering Handlers\nThere are two handler funcs available. One for simple call and return once jobs, and one for streaming jobs where the server may respond an unknown number of times.\nKeep in min that a handler is expected to function in both web socket and long poll mode. Make sure your handlers are reentrant for long polls.\n```go\n// type CallHandler func(ctx Context) (err error)\n// type StreamHandler func(ctx Context, ch *ResponseChannel) (err error)\n\nfunc main() {\n    ...\n    router.SetHandler(\"answerLife\", someCallHandler)\n\n    router.SetStream(\"countdown\", someStreamHandler)\n    ...\n\n}\n\nfunc myCallHandler(ctx wsrpc.Context) error {\n    var err error\n    ctx.Response().Content, err = json.Marshal(42)\n    if err != nil {\n        return err\n    }\n    \n    return nil\n}\n\nfunc myStreamHandler(ctx wsrpc.Context, ch *wsrpc.ResponseChannel) (err error) {\n    countdown := 3\n\n    for countdown \u003e 0 {\n        rsp := ctx.NewResponse()\n        rsp.Content, err = json.Marshal(countdown)\n        if err != nil {\n           return err\n        }\n     \n        err = ch.Write(rsp)\n        if err != nil {\n            return err\n        }\n\n        countdown -= 1\n    }\n\n    return nil\n}\n```\n\n### A small reference setup\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"math/rand\"\n\n\t\"github.com/modfin/wsrpc\"\n)\n\nfunc main() {\n\trouter := wsrpc.NewRouter()\n\n    router.Use(someMiddleware)\n\t\n\trouter.SetHandler(\"cubify\", someCallHandler, func(ctx wsrpc.Context, next wsrpc.NextFunc) error {\n\t\tif rand.Int() \u003c 53200 {\n\t\t\treturn errors.New(\"user is not authenticated\")\n\t\t}\n\t\t\n\t\treturn next(ctx)\n\t})\n\t\n\trouter.SetStream(\"countdown\", someStreamHandler)\n\t\n\terr := router.Start(\":8080\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc someMiddleware(ctx wsrpc.Context, next wsrpc.NextFunc) error {\n    err := next(ctx)\n    if err != nil {\n    \tlog.Println(err)\n        return err\n    }\n\n    return next(ctx)\n}\n\nfunc someCallHandler(ctx wsrpc.Context) error {\n\tvar num int64\n\terr := json.Unmarshal(ctx.Request().Params, \u0026num)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx.Response().Result, err = json.Marshal(num * num)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\nfunc someStreamHandler(ctx wsrpc.Context, ch *wsrpc.ResponseChannel) error {\n\tcountdown, ok := ctx.Request().Header.Get(\"countdown\").Int()\n\tif !ok {\n\t\treturn errors.New(\"missing countdown\")\n\t}\n\n\tfor countdown \u003e 0 {\n\t\tselect {\n\t\t\tcase \u003c-ctx.Done():\n\t\t\t\tlog.Println(\"context cancelled\")\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t}\n\n\t\trsp := ctx.NewResponse()\n\n\t\tvar err error\n\t\trsp.Result, err = json.Marshal(countdown)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = ch.Write(rsp)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcountdown -= 1\n\t}\n\n\treturn nil\n}\n```\n\n## General guidelines\n### Server\n* Registered handlers are responsible for checking the provided input\n* Stream handlers channels go straight to client, mind your output\n\n## Issues\n* The error handling could probably be done with middleware, alternatively a logger could be attached \n* A data race occurs for both wrapped channel types when a stream handler is called with long polling.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodfin%2Fwsrpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodfin%2Fwsrpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodfin%2Fwsrpc/lists"}