{"id":13412646,"url":"https://github.com/ursiform/sleuth","last_synced_at":"2026-01-22T19:56:35.283Z","repository":{"id":57490105,"uuid":"56923055","full_name":"ursiform/sleuth","owner":"ursiform","description":"A Go library for master-less peer-to-peer autodiscovery and RPC between HTTP services","archived":false,"fork":false,"pushed_at":"2023-07-09T12:47:10.000Z","size":223,"stargazers_count":372,"open_issues_count":0,"forks_count":26,"subscribers_count":11,"default_branch":"main","last_synced_at":"2024-07-31T20:51:14.440Z","etag":null,"topics":["discovery","distributed-computing","distributed-systems","golang","peer-autodiscovery","rpc","service-autodiscovery","sleuth","zeromq"],"latest_commit_sha":null,"homepage":"http://ursiform.github.io/sleuth/","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/ursiform.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}},"created_at":"2016-04-23T14:21:41.000Z","updated_at":"2024-06-28T16:46:31.000Z","dependencies_parsed_at":"2024-01-30T04:55:00.458Z","dependency_job_id":null,"html_url":"https://github.com/ursiform/sleuth","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/ursiform/sleuth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ursiform%2Fsleuth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ursiform%2Fsleuth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ursiform%2Fsleuth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ursiform%2Fsleuth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ursiform","download_url":"https://codeload.github.com/ursiform/sleuth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ursiform%2Fsleuth/sbom","scorecard":{"id":912114,"data":{"date":"2025-08-11","repo":{"name":"github.com/ursiform/sleuth","commit":"3afe97f5dd7c2b8a574f05e65e546cc265785973"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: goCommand not pinned by hash: scripts/drone.sh:27","Warn: goCommand not pinned by hash: scripts/drone.sh:28","Info:   0 out of   2 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T19:35:34.160Z","repository_id":57490105,"created_at":"2025-08-24T19:35:34.161Z","updated_at":"2025-08-24T19:35:34.161Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28669967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T19:36:09.361Z","status":"ssl_error","status_checked_at":"2026-01-22T19:36:05.567Z","response_time":144,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["discovery","distributed-computing","distributed-systems","golang","peer-autodiscovery","rpc","service-autodiscovery","sleuth","zeromq"],"created_at":"2024-07-30T20:01:27.249Z","updated_at":"2026-01-22T19:56:35.251Z","avatar_url":"https://github.com/ursiform.png","language":"Go","readme":"# sleuth \u003cimg src=\"https://cdn.rawgit.com/ursiform/sleuth/662e1c96d211b0d27c30fbfa043edc4b3bd6c35d/logo.svg\" height=\"50\" valign=\"middle\"\u003e\n[![API documentation](https://godoc.org/github.com/ursiform/sleuth?status.svg)](https://godoc.org/github.com/ursiform/sleuth) [![Coverage Status](https://coveralls.io/repos/github/ursiform/sleuth/badge.svg)](https://coveralls.io/github/ursiform/sleuth?branch=master)\n\n`sleuth` is a Go library that provides master-less peer-to-peer autodiscovery and RPC\nbetween HTTP services that reside on the same network. It works with minimal\nconfiguration and provides a mechanism to join a local network both as a\nclient that offers no services and as any service that speaks HTTP. Its\nprimary use case is for microservices on the same network that make calls to\none another.\n\nFor a full introduction and tutorial, check out: [Service autodiscovery in Go with sleuth](http://darian.link/post/master-less-peer-to-peer-micro-service-autodiscovery-in-golang-with-sleuth/)\n\n## Installation\n`sleuth` is dependent on [`libzmq`](https://github.com/zeromq/libzmq), which can be installed either from source or from binaries. For more information, please refer to [ØMQ: \"Get the Software\"](http://zeromq.org/intro:get-the-software) or the [`libzmq` repository](https://github.com/zeromq/libzmq).\n\nAnother option is to use a [Docker container that comes with Go and ZeroMQ](https://hub.docker.com/r/rxwen/golang-zeromq/).\n\nOnce `libzmq` is available on a system, `sleuth` can be installed like any other Go library:\n\n```\ngo get -u github.com/ursiform/sleuth\n```\n## API\nThe [`sleuth` API documentation is available on GoDoc](https://godoc.org/github.com/ursiform/sleuth) or you can simply run:\n\n```\ngodoc github.com/ursiform/sleuth\n```\n\n\n## Examples\n**Example (1):** The `echo-service` is a toy service that merely echoes back anything in an HTTP request body. It has made itself available on a `sleuth` network:\n```go\npackage main\n\nimport (\n  \"io/ioutil\"\n  \"net/http\"\n\n  \"github.com/ursiform/sleuth\"\n)\n\ntype echoHandler struct{}\n\nfunc (h *echoHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {\n  body, _ := ioutil.ReadAll(req.Body)\n  res.Write(body)\n}\n\nfunc main() {\n  handler := new(echoHandler)\n  // In the real world, the Interface field of the sleuth.Config object\n  // should be set so that all services are on the same subnet.\n  config := \u0026sleuth.Config{\n    Handler: handler,\n    LogLevel: \"debug\",\n    Service: \"echo-service\",\n  }\n  server, err := sleuth.New(config)\n  if err != nil {\n    panic(err.Error())\n  }\n  defer server.Close()\n  http.ListenAndServe(\":9873\", handler)\n}\n```\n\nAnd here is a trivial client that waits until it has connected to the network and found the `echo-service` to make a request before it exits. Note that the `*sleuth.Client` works as a drop-in replacement for an `*http.Client` when making requests using the `Do()` method:\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"github.com/ursiform/sleuth\"\n)\n\nfunc main() {\n\tservice := \"echo-service\"\n\t// In the real world, the Interface field of the sleuth.Config object\n\t// should be set so that all services are on the same subnet.\n\tconfig := \u0026sleuth.Config{LogLevel: \"debug\"}\n\tclient, err := sleuth.New(config)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tdefer client.Close()\n\tclient.WaitFor(service)\n\tinput := \"This is the value I am inputting.\"\n\tbody := bytes.NewBuffer([]byte(input))\n\trequest, _ := http.NewRequest(\"POST\", \"sleuth://\"+service+\"/\", body)\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\toutput, _ := ioutil.ReadAll(response.Body)\n\tif string(output) == input {\n\t\tfmt.Println(\"It works.\")\n\t} else {\n\t\tfmt.Println(\"It doesn't work.\")\n\t}\n}\n```\n\n---\n\n**Example (2):**  [`sleuth-example` is a fuller example of two services on a `sleuth` network](https://github.com/afshin/sleuth-example/) that need to communicate with each other.\n\nA complete tutorial based on that example can be found here: [Service autodiscovery in Go with sleuth](http://darian.link/post/master-less-peer-to-peer-micro-service-autodiscovery-in-golang-with-sleuth/).\n\n## Test\n    go test -cover github.com/ursiform/sleuth\n\n## Q \u0026 A\n**Q**: How does it work? I understand *what* `sleuth` does, but I want to know *how* it does it.\n\n**A**: Services that instantiate a `sleuth.Client` create an *ad hoc* [`Gyre`](https://github.com/zeromq/gyre) network. `Gyre` is the Go port of the [`Zyre`](https://github.com/zeromq/zyre) project, which is built on top of [ØMQ](https://github.com/zeromq/libzmq) (ZeroMQ). Nodes in the network discover each other using a UDP beacon on port `5670`. The actual communication between nodes happens on ephemeral `TCP` connections. What `sleuth` does is to manage this life cycle:\n* A peer joins the `Gyre` network as a member of the group `SLEUTH-v1`. If the peer offers a service, *i.e.*, if it has an [`http.Handler`](https://golang.org/pkg/net/http/#Handler), it notifies the rest of the network when it announces itself. The peer might have no service to offer, thus operating in client-only mode, or it may offer *one* service.\n* The peer finds other peers on the network. If you have asked the `sleuth` client to [`WaitFor()`](https://godoc.org/github.com/ursiform/sleuth#Client.WaitFor) one or more services to appear before continuing, that call will block until it has found those services.\n* If the peer is offering a service, `sleuth` automatically listens for incoming requests in a separate goroutine and responds to incoming requests by invoking the [`http.Handler`](https://golang.org/pkg/net/http/#Handler) that was passed in during instantiation.\n* When you make a request to an available service, `sleuth` marshals the request, sends it to one of the available peers that offers that service, and waits for a response. If the response succeeds, it returns an [`http.Response`](https://golang.org/pkg/net/http/#Response); if it times out, it returns an error. The `sleuth` client [`Do()`](https://godoc.org/github.com/ursiform/sleuth#Client.Do) method has the same signature as the `http` client [`Do()`](https://golang.org/pkg/net/http/#Client.Do) method in order to operate as a drop-in replacement.\n* When you want to *leave* the network, *e.g.*, when the application is quitting, the `sleuth` client [`Close()`](https://godoc.org/github.com/ursiform/sleuth#Client.Close) method immediately notifies the rest of the network that the peer is leaving. This is not strictly necessary because peers regularly check in to make sure the network knows they are alive, so the network automatically knows if a service has disappeared; but it is a good idea.\n\n---\n\n**Q**: What is the messaging protocol `sleuth` uses?\n\n**A**: Under the hood, `sleuth` marshals HTTP requests and responses into plain JSON objects and then compresses them via `gzip`. Instead of adding another dependency on something like Protocol Buffers, `sleuth` depends on the fact that most API responses between microservices will be fairly small and it leaves the door open to ports in a wide variety of languages and environments. One hard dependency seemed quite enough.\n\n---\n\n**Q**: What if I have multiple instances of the same service?\n\n**A**: Great! `sleuth` will automatically round-robin the requests each client makes to all services that share the same name.\n\n---\n\n**Q**: What happens if a service goes offline?\n\n**A**: Whenever possible, a service should call its client's [`Close()`](https://godoc.org/github.com/ursiform/sleuth#Client.Close) method before exiting to notify the network of its departure. But even if a service fails to do that, the `sleuth` network's underlying `Gyre` network will detect within about one second that a peer has disappeared. All requests to that service will be routed to other peers offering the same service. If no peers exist for that service, then requests (which are made by calling the `sleuth` client [`Do()`](https://godoc.org/github.com/ursiform/sleuth#Client.Do) method) will return an unknown service error (code `919`), which means that if you're already handling errors when making requests, you're covered.\n\n---\n\n**Q**: It doesn't work.\n\n**A**: That's not a question. But have you checked to make sure your firewall allows `UDP` traffic on port `5670`?\n\n---\n\n**Q**: It still doesn't work.\n\n**A**: That's still not a question. But have you set the `Interface` field of your [`sleuth.Config`](https://godoc.org/github.com/ursiform/sleuth#Config) object? The services you want to connect need to be on the same network and if you leave that field blank, the underlying `Gyre` network may not reside where you think it does. If you run `ifconfig` you'll get a list of available interfaces on your system.\n\n---\n\n**Q**: Why is it called `sleuth`?\n\n**A**: Because \"sleuth\" is the collective noun for a group of bears: the original reason for writing this library was to connect a group of [bear](https://github.com/ursiform/bear)/[forest](https://github.com/ursiform/forest) services. Also because a sleuth searches for things and discovers them. Hence the logo:\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://cdn.rawgit.com/ursiform/sleuth/662e1c96d211b0d27c30fbfa043edc4b3bd6c35d/logo.svg\"\u003e\n\u003c/p\u003e\n\n## License\n`sleuth` is licensed under the [MIT License](LICENSE).\n\nThe underlying libraries that `sleuth` relies on, [`Gyre`](https://github.com/zeromq/gyre) and [`libzmq`](https://github.com/zeromq/libzmq), are licensed under the [LGPL](http://www.gnu.org/licenses/lgpl-3.0.en.html). In effect, users who do not plan on modifying `Gyre` or `libzmq` can release their own applications under any license they see fit.\n\n## Resources\n\n* [API documentation](https://godoc.org/github.com/ursiform/sleuth)\n* [Service autodiscovery in Go with sleuth](http://darian.link/post/master-less-peer-to-peer-micro-service-autodiscovery-in-golang-with-sleuth/) (tutorial)\n","funding_links":[],"categories":["Distributed Systems","Relational Databases","Service Discovery","分布式系统","Go","分佈式系統","\u003cspan id=\"分布式系统-distributed-systems\"\u003e分布式系统 Distributed Systems\u003c/span\u003e"],"sub_categories":["Advanced Console UIs","Vectors","Search and Analytic Databases","检索及分析资料库","高級控制台界面","\u003cspan id=\"高级控制台用户界面-advanced-console-uis\"\u003e高级控制台用户界面 Advanced Console UIs\u003c/span\u003e","SQL 查询语句构建库","高级控制台界面"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fursiform%2Fsleuth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fursiform%2Fsleuth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fursiform%2Fsleuth/lists"}