{"id":32196885,"url":"https://github.com/clojure-link/link","last_synced_at":"2026-02-19T08:35:02.392Z","repository":{"id":2587790,"uuid":"3569298","full_name":"clojure-link/link","owner":"clojure-link","description":"A clojure framework for nonblocking network programming","archived":false,"fork":false,"pushed_at":"2021-08-19T12:11:56.000Z","size":388,"stargazers_count":72,"open_issues_count":4,"forks_count":11,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-01-14T07:43:13.007Z","etag":null,"topics":["clojure","netty","network-programming"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clojure-link.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-02-28T08:19:03.000Z","updated_at":"2025-12-19T03:53:36.000Z","dependencies_parsed_at":"2022-08-29T04:52:48.079Z","dependency_job_id":null,"html_url":"https://github.com/clojure-link/link","commit_stats":null,"previous_names":["sunng87/link"],"tags_count":68,"template":false,"template_full_name":null,"purl":"pkg:github/clojure-link/link","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-link%2Flink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-link%2Flink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-link%2Flink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-link%2Flink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojure-link","download_url":"https://codeload.github.com/clojure-link/link/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-link%2Flink/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29608565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T06:47:36.664Z","status":"ssl_error","status_checked_at":"2026-02-19T06:45:47.551Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["clojure","netty","network-programming"],"created_at":"2025-10-22T02:44:39.409Z","updated_at":"2026-02-19T08:35:02.387Z","avatar_url":"https://github.com/clojure-link.png","language":"Clojure","readme":"# link\n\nlink is the event-driven network library used by\n[slacker](https://github.com/sunng87/slacker). It's a thin wrapper of\nNetty.\n\n[![Build Status](https://travis-ci.org/sunng87/link.png?branch=master)](https://travis-ci.org/sunng87/link)\n\n## Usage\n\n### Leiningen\n\n![https://clojars.org/link](https://clojars.org/link/latest-version.svg)\n\nCurrently, link only works on the JVM implementation of Clojure. We\nmight support nodejs in future.\n\n### API\n\n#### Codec\n\nIn most cases, we use a declarative DSL to define a custom tcp\nprotocol codec: `link.codec`.\n\nWith the codec, you can read/write Clojure data structure in your\nhandler and don't have to read the message byte by byte, and  worry\nabout TCP framing.\n\n```clojure\nuser\u003e (require '[link.codec :refer :all])\n\n;; create a custom codec: [version target-id string-message]\nuser\u003e (def custom-codec\n  (frame\n    (byte)\n    (int32)\n    (string :encoding :utf8 :prefix (uint16))))\n\n;; create an empty buffer\nuser\u003e (def buf (unpooled-buffer))\n\n;; encode clojure data structure on to given buffer, by using codec.\n;; note that you don't have to call `encode*` and `decode*` by\n;; youself, link does it for you.\nuser\u003e (encode* custom-codec [1 348 \"hello world\"] buf)\n#object[io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf 0x4eb69819 \"UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 18, cap: 256)\"]\n\nuser\u003e (decode* custom-codec buf)\n[1 348 \"hello world\"]\n```\n\nFor a more complex codec, check \u003ca\nhref=\"https://github.com/sunng87/slacker/blob/master/src/slacker/protocol.clj\"\u003eslacker's\ncodec definition\u003c/a\u003e.\n\n#### handler\n\nYou need to create a custom handler to process you network\nmessage. Link has provided you a dsl that is easier to understand. And\nalso hide complexity of Netty's default handler API.\n\n```clojure\n(require '[link.core :refer :all])\n\n(def echo-handler\n  (create-handler\n    (on-message [ch msg]\n      (send! ch msg))))\n```\n\nThere are 5 events you can process in a link handler:\n\n* `(on-active [ch])` when channel is open, bound or connected\n* `(on-inacitve [ch])` when channel is no longer open, bound or connected\n* `(on-message [ch msg])` when a packet is read in\n* `(on-error [ch e])` when exception occurs on I/O thread\n* `(on-event [ch evt])` when netty user defined event triggered\n\nAnd for the channel `ch`, you can call following functions as defined\nby `LinkMessageChannel` protocol.\n\n* `(send! [ch msg])` write a msg into channel\n* `(channel-addr [ch])` get the local socket address of the channel\n* `(remote-addr [ch])` get the remote socket address of the channel\n* `(close! [ch])` request to close the channel\n* `(valid? [ch])` test if channel is still open and active\n\n#### the TCP server\n\nlink only supports non-blocking server and client.\n\nTo start a server, you can provide a few argument to customize it:\n\n```clojure\n(require '[link.tcp :refer :all])\n(require '[link.threads :refer :all])\n\n;; Just to demo the usage here, there is no need to run a echo-handler\n;; in a thread pool.\n(def handler-spec {:handler echo-handler :executor (new-executor 10)})\n\n;; you can also provide a few handlers by passing a vector of them\n(tcp-server 8081 [handler-spec]\n            :options {:so-reuseaddr true} ;; netty, ip, tcp and socket options\n            :host ;; if to bind, default \"0.0.0.0\"\n)\n```\n\nFrom link 0.7, ssl handler and codecs are all normal handlers. You will need\nto put them at correct position of handlers.\n\nTo see a full list of TCP options, you can find it on [Netty\ndoc](http://netty.io/4.1/api/io/netty/channel/ChannelOption.html). Change\nthe option name to lowercase and replace the underscore with dash, as\nin Clojure way. Prefixing a `clild-` to specify option for child\nchannels: `:child-tcp-nodelay`.\n\nYou can stop a server by\n``` clojure\n;; calling stop-server with the value returned by tcp-server\n(stop-server *1)\n```\n#### the TCP client\n\nTo create a TCP client, you need to create a connection factory for\nit. Note that, clients created from the same factory will share the\nsame selector and event loop. Managing it carefully if you have a\nlarge number of connections.\n\n```clojure\n(def client-factory\n  (tcp-client-factory handlers\n                      :options ...))\n```\n\nCreate a client\n\n```clojure\n(def client (tcp-client client-factory \"localhost\" 8081))\n```\n\nThe value returned by `tcp-client` is a `LinkMessageChannel` object so\nyou can call any functions of the protocol on it.\n\nTo send some data:\n\n```clojure\n(send! client [1 345 \"hello world\"])\n```\n\nTo close a client, call `close!` on the channel. To close a client\nfactory, call `stop-clients` would work.\n\n\n#### HTTP Server\n\nlink also comes with an HTTP server. Since link is a clojure library,\nit accepts a ring function, so you can use any HTTP framework on link\nhttp server, without pain.\n\n```clojure\n(require '[link.http :refer :all])\n\n(http-server 8080 ring-app-fn\n             :executor ... ;; the thread pool to run ring functions on)\n```\n\n#### HTTP/2 Server\n\n```clojure\n(require '[link.http :as h])\n(require '[link.ssl :as ssl])\n(import '[io.netty.handler.ssl.util SelfSignedCertificate])\n\n(let [ssc (SelfSignedCertificate.)\n      ssl-context (ssl/ssl-context-for-http2 (.certificate ssc) (.privateKey ssc)\n                                             :jdk)]\n  (h/h2-server 8443 ring-app ssl-context :threads 8))\n```\n\n#### Websocket\n\nNew in link 0.5. You can start a websocket server with link.\n\nCreate a websocket handler:\n\n```clojure\n(require '[link.websocket :refer :all])\n(require '[link.tcp :refer :all])\n\n(def ws-echo-handler\n  (create-ws-handler\n    (on-open [ch])\n    (on-close [ch])\n    (on-text [ch string]\n      ;; you can use (text), (binary), (ping), (pong) to generate\n      ;; different types of response\n      (send! ch (text string)))\n    (on-binary [ch ^ByteBuf bytes])\n    (on-ping [ch ^ByteBuf bytes])\n    (on-pong [ch ^ByteBuf bytes])))\n\n(tcp-server 8082 (conj (websocket-codecs \"/chat\") ws-echo-handler))\n\n```\n\n## License\n\nCopyright (C) 2012-2019 Ning Sun \u003csunng@about.me\u003e and contributors\n\nDistributed under the Eclipse Public License, the same as Clojure.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-link%2Flink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojure-link%2Flink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-link%2Flink/lists"}