From d34ab52b612123fd55200a068823a83d6e4e4b7a Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Thu, 3 Aug 2023 20:31:56 +0200 Subject: [PATCH] [new] Add wiki content to main repo --- .gitignore | 1 + wiki/0-Breaking-changes.md | 67 +++++++++++ wiki/1-Getting-started.md | 199 +++++++++++++++++++++++++++++++++ wiki/2-Client-and-user-ids.md | 56 ++++++++++ wiki/3-Example-projects.md | 26 +++++ wiki/3-FAQ.md | 103 +++++++++++++++++ wiki/4-Connection-debugging.md | 36 ++++++ wiki/Home.md | 8 ++ 8 files changed, 496 insertions(+) create mode 100644 wiki/0-Breaking-changes.md create mode 100644 wiki/1-Getting-started.md create mode 100644 wiki/2-Client-and-user-ids.md create mode 100644 wiki/3-Example-projects.md create mode 100644 wiki/3-FAQ.md create mode 100644 wiki/4-Connection-debugging.md create mode 100644 wiki/Home.md diff --git a/.gitignore b/.gitignore index 34af8da..ef8d6f1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pom.xml* /target/ /checkouts/ /logs/ +/wiki/.git diff --git a/wiki/0-Breaking-changes.md b/wiki/0-Breaking-changes.md new file mode 100644 index 0000000..e75b2d1 --- /dev/null +++ b/wiki/0-Breaking-changes.md @@ -0,0 +1,67 @@ +This page details possible **breaking changes and migration instructions** for Sente. + +My apologies for the trouble. I'm very mindful of the costs involved in breaking changes, and I try hard to avoid them whenever possible. When there is a very good reason to break, I'll try to batch breaks and to make migration as easy as possible. + +Thanks for your understanding - [Peter Taoussanis](https://www.taoensso.com) + +# Sente `v1.17` to `v1.18` + +This upgrade involves **4 possible breaking changes** detailed below: + +**Change 1/4** + +The default `wrap-recv-evs?` option has changed in [`make-channel-socket-client!`](http://ptaoussanis.github.io/sente/taoensso.sente.html#var-make-channel-socket-client.21). + +- **Old** default behaviour: events from server are **wrapped** with `[:chsk/recv ]` +- **New** default behaviour: events from server are **unwrapped** + +**Motivation for change**: there's no benefit to wrapping events from the server, and this wrapping often causes confusion. + +More info at: [#319](../issues/319) + +--- + +**Change 2/4** + +The default [`*write-legacy-pack-format?*`](http://ptaoussanis.github.io/sente/taoensso.sente.html#var-*write-legacy-pack-format.3F*) value has changed from `true` to `false`. + +This change is only relevant for the small minority of folks that use a custom (non-standard) [`IPacker`](https://github.com/ptaoussanis/sente/blob/f69a5df6d1f3e88d66a148c74e1b5a9084c9c0b9/src/taoensso/sente/interfaces.cljc#L55). + +If you do use a custom (non-standard) `IPacker`, please see the [relevant docstring](http://ptaoussanis.github.io/sente/taoensso.sente.html#var-*write-legacy-pack-format.3F*) for details. + +**Motivation for change**: the new default value is part of a phased transition to a new Sente message format that better supports non-string (e.g. binary) payloads. + +More info at: [#398](../issues/398), [#404](../issues/404) + +--- + +**Change 3/4** + +Unofficial adapters have been moved to `community` dir. + +This change is only relevant for folks using a server other than http-kit. + +If you're using a different server, the adapter's namespace will now include a `.community` part, e.g.: + +- **Old** adapter namespace: `taoensso.sente.server-adapters.undertow` +- **New** adapter namespace: `taoensso.sente.server-adapters.community.undertow` + +**Motivation for change**: the new namespace structure is intended to more clearly indicate which adapters are/not officially maintained as part of the core project. + +More info at: [#412](../issues/412) + +--- + +**Change 4/4** + +The `jetty9-ring-adapter` has been removed. + +This change is only relevant for folks using `jetty9-ring-adapter`. + +**Motivation for change**: it looks like the previous adapter may have been broken for some time. And despite [some effort](../issues/426) from the community, a new/fixed adapter isn't currently available. Further investigation is necessary, but it looks like it's _possible_ that the current `jetty9-ring-adapter` API might not support the kind of functionality that Sente needs for its Ajax fallback behaviour. + +Apologies for this! + +More info at: [#424](../issues/424), [#426](../issues/426) + +--- \ No newline at end of file diff --git a/wiki/1-Getting-started.md b/wiki/1-Getting-started.md new file mode 100644 index 0000000..5943c60 --- /dev/null +++ b/wiki/1-Getting-started.md @@ -0,0 +1,199 @@ +> See also [here](3-Example-projects) for **full example projects** 👈 + +# Setup +## Dependency + +Add the [relevant dependency](../#latest-releases) to your project: + +```clojure +Leiningen: [com.taoensso/sente "x-y-z"] ; or +deps.edn: com.taoensso/sente {:mvn/version "x-y-z"} +``` + +## Server-side setup + +First make sure that you're using one of the [supported web servers](https://github.com/ptaoussanis/sente/tree/master/src/taoensso/sente/server_adapters) (PRs for additional server adapters welcome!). + +Somewhere in your web app's code you'll already have a routing mechanism in place for handling Ring requests by request URL. If you're using [Compojure](https://github.com/weavejester/compojure) for example, you'll have something that looks like this: + +```clojure +(defroutes my-app + (GET "/" req (my-landing-pg-handler req)) + (POST "/submit-form" req (my-form-submit-handler req))) +``` + +For Sente, we're going to add 2 new URLs and setup their handlers: + +```clojure +(ns my-server-side-routing-ns ; Usually a .clj file + (:require + ;; + [taoensso.sente :as sente] ; <--- Add this + + [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] ; <--- Recommended + + ;; Uncomment a web-server adapter ---> + ;; [taoensso.sente.server-adapters.http-kit :refer [get-sch-adapter]] + ;; [taoensso.sente.server-adapters.immutant :refer [get-sch-adapter]] + ;; [taoensso.sente.server-adapters.nginx-clojure :refer [get-sch-adapter]] + ;; [taoensso.sente.server-adapters.aleph :refer [get-sch-adapter]] + )) + +;;; Add this: ---> +(let [{:keys [ch-recv send-fn connected-uids + ajax-post-fn ajax-get-or-ws-handshake-fn]} + (sente/make-channel-socket-server! (get-sch-adapter) {})] + + (def ring-ajax-post ajax-post-fn) + (def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn) + (def ch-chsk ch-recv) ; ChannelSocket's receive channel + (def chsk-send! send-fn) ; ChannelSocket's send API fn + (def connected-uids connected-uids) ; Watchable, read-only atom + ) + +(defroutes my-app-routes + ;; + + ;;; Add these 2 entries: ---> + (GET "/chsk" req (ring-ajax-get-or-ws-handshake req)) + (POST "/chsk" req (ring-ajax-post req)) + ) + +(def my-app + (-> my-app-routes + ;; Add necessary Ring middleware: + ring.middleware.keyword-params/wrap-keyword-params + ring.middleware.params/wrap-params + ring.middleware.anti-forgery/wrap-anti-forgery + ring.middleware.session/wrap-session)) +``` + +> The `ring-ajax-post` and `ring-ajax-get-or-ws-handshake` fns will automatically handle Ring GET and POST requests to our channel socket URL (`"/chsk"`). Together these take care of the messy details of establishing + maintaining WebSocket or long-polling requests. + +Add a CSRF token somewhere in your HTML: + +``` +(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)] + [:div#sente-csrf-token {:data-csrf-token csrf-token}]) +``` + +## Client-side setup + +You'll setup something similar on the client side: + +```clojure +(ns my-client-side-ns ; Usually a .cljs file + (:require-macros + [cljs.core.async.macros :as asyncm :refer (go go-loop)]) + (:require + ;; + [cljs.core.async :as async :refer (! put! chan)] + [taoensso.sente :as sente :refer (cb-success?)] ; <--- Add this + )) + +;;; Add this: ---> + +(def ?csrf-token + (when-let [el (.getElementById js/document "sente-csrf-token")] + (.getAttribute el "data-csrf-token"))) + +(let [{:keys [chsk ch-recv send-fn state]} + (sente/make-channel-socket-client! + "/chsk" ; Note the same path as before + ?csrf-token + {:type :auto ; e/o #{:auto :ajax :ws} + })] + + (def chsk chsk) + (def ch-chsk ch-recv) ; ChannelSocket's receive channel + (def chsk-send! send-fn) ; ChannelSocket's send API fn + (def chsk-state state) ; Watchable, read-only atom + ) +``` + +# Usage + +After setup, the client will automatically initiate a WebSocket or repeating long-polling connection to your server. Client<->server events are now ready to transmit over the `ch-chsk` channel. + +**Last step**: you'll want to **hook your own event handlers up to this channel**. Please see one of the [example projects](3-Example-projects) and/or [API docs](http://ptaoussanis.github.io/sente/) for details. + +## Client-side API + + * `ch-recv` is a **core.async channel** that'll receive `event-msg`s + * `chsk-send!` is a `(fn [event & [?timeout-ms ?cb-fn]])` for standard **client>server req>resp calls** + +Let's compare some Ajax and Sente code for sending an event from the client to the server: + +```clojure +(jayq/ajax ; Using the jayq wrapper around jQuery + {:type :post :url "/some-url-on-server/" + :data {:name "Rich Hickey" + :type "Awesome"} + :timeout 8000 + :success (fn [content text-status xhr] (do-something! content)) + :error (fn [xhr text-status] (error-handler!))}) + +(chsk-send! ; Using Sente + [:some/request-id {:name "Rich Hickey" :type "Awesome"}] ; Event + 8000 ; Timeout + ;; Optional callback: + (fn [reply] ; Reply is arbitrary Clojure data + (if (sente/cb-success? reply) ; Checks for :chsk/closed, :chsk/timeout, :chsk/error + (do-something! reply) + (error-handler!)))) +``` + +Note: + + * The Ajax request is slow to initialize, and bulky (HTTP overhead) + * The Sente request is pre-initialized (usu. WebSocket), and lean (edn/Transit protocol) + +## Server-side API + + * `ch-recv` is a **core.async channel** that'll receive `event-msg`s + * `chsk-send!` is a `(fn [user-id event])` for async **server>user PUSH calls** + +For asynchronously pushing an event from the server to the client: + + * Ajax would require a clumsy long-polling setup, and wouldn't easily support users connected with multiple clients simultaneously + * Sente: `(chsk-send! "destination-user-id" [:some/alert-id ])` + +**Important**: note that Sente intentionally offers server to [user](2-Client-and-user-ids) push rather than server>client push. A single user may have >=0 associated clients. + +## Types and terminology + +Term | Form +---------------- | ---------------------------------------------------------------------- +event | `[ ]`, e.g. `[:my-app/some-req {:data "data"}]` +server event-msg | `{:keys [event id ?data send-fn ?reply-fn uid ring-req client-id]}` +client event-msg | `{:keys [event id ?data send-fn]}` +`` | A _namespaced_ keyword like `:my-app/some-req` +`` | An optional _arbitrary edn value_ like `{:data "data"}` +`:ring-req` | Ring map for Ajax request or WebSocket's initial handshake request +`:?reply-fn` | Present only when client requested a reply + + +## Summary + + * Clients use `chsk-send!` to send `event`s to the server and optionally request a reply with timeout + * Server uses `chsk-send!` to send `event`s to _all_ the clients (browser tabs, devices, etc.) of a particular connected user by his/her [user-id](2-Client-and-user-ids). + * The server can also use an `event-msg`'s `?reply-fn` to _reply_ to a particular client `event` using an _arbitrary edn value_ + +## Channel socket state + +Each time the client's channel socket state changes, a client-side `:chsk/state` event will fire that you can watch for and handle like any other event. + +The event form is `[:chsk/state [ ]]` with the following possible state map keys: + +Key | Value +--------------- | -------------------------------------------------------- +:type | e/o `#{:auto :ws :ajax}` +:open? | Truthy iff chsk appears to be open (connected) now +:ever-opened? | Truthy iff chsk handshake has ever completed successfully +:first-open? | Truthy iff chsk just completed first successful handshake +:uid | User id provided by server on handshake, or nil +:csrf-token | CSRF token provided by server on handshake, or nil +:handshake-data | Arb user data provided by server on handshake +:last-ws-error | `?{:udt _ :ev }` +:last-ws-close | `?{:udt _ :ev :clean? _ :code _ :reason _}` +:last-close | `?{:udt _ :reason _}`, with reason e/o `#{nil :requested-disconnect :requested-reconnect :downgrading-ws-to-ajax :unexpected}` \ No newline at end of file diff --git a/wiki/2-Client-and-user-ids.md b/wiki/2-Client-and-user-ids.md new file mode 100644 index 0000000..705d78b --- /dev/null +++ b/wiki/2-Client-and-user-ids.md @@ -0,0 +1,56 @@ +Sente uses two types of connection identifiers: **client-ids** and **user-ids**. + +# Client ids + +A client-id is a unique identifier for one particular Sente client: i.e. one particular invocation of `make-channel-socket-client!`. This typically means **one particular browser tab** on one device. + +By default, clients generate their own random (uuid) client-id. You can override this in your call to [`make-channel-socket-client!`](http://ptaoussanis.github.io/sente/taoensso.sente.html#var-make-channel-socket-client.21). + +Note: +1. Each client chooses its _own_ client-id with no input from the server. +2. By default, each browser tab has its _own_ client-id. +3. By default, reloading a browser tab (or closing a tab + opening a new one) means a _new_ client-id. + +# User ids + +This is the more important concept in Sente, and is actually the only type of identifier supported by Sente's server>client push API. + +A user-id is a unique application-level identifier associated with >=0 Sente clients (client-ids). + +It is determined _server-side_ as a configurable function of each connecting channel socket's Ring request, i.e. `(fn user-id [ring-req]) => ?user-id`. + +Typically, you'll configure Sente's user-id to be something like your application's username: if Alice logs into your application with 6 different browser tabs over 3 different devices - she'll have 6 client-ids associated with her user-id. And when your server sends an event "to Alice", it'll be delivered to all 6 of her connected clients. + +By default, Sente will use `(fn user-id [ring-req] (get-in ring-req [:session :uid]))` as your user-id function. You can override this in your call to [`make-channel-socket-server!`](http://ptaoussanis.github.io/sente/taoensso.sente.html#var-make-channel-socket-server.21). + +Note: + +1. One user-id may be associated with 0, 1, or _many_ clients (client-ids). +2. By default (i.e. with the sessionized `:uid` value), user-ids are persistent and shared among multiple tabs in one browser as a result of the way browser sessions work. + +# Examples + +## Per-session persistent user-id + +This is probably a good default choice. + +1. `:client-id`: use default (random uuid) +2. `:user-id-fn`: use default, ensure that you sessionize a sensible `:uid` value on user login + +## Per-tab transient user-id + +I.e. each tab has its own user-id, and reloading a tab means a new user-id. + +1. `:client-id`: use default (random uuid) +2. `:user-id-fn`: use `(fn [ring-req] (:client-id ring-req))` + +I.e. we don't use sessions for anything. User-ids are equal to client-ids, which are random per-tab uuids. + +## Per-tab transient user-id with session security + +I.e. as above, but users must be signed in with a session. + +1. `:client-id`: leave unchanged. +2. `:user-id-fn`: `(fn [ring-req] (str (get-in ring-req [:session :base-user-id]) "/" (:client-id ring-req)))` + +I.e. sessions (+ some kind of login procedure) are used to determine a `:base-user-id`. That base user-id is then joined with each unique client-id. Each tab therefore retains its own user-id, but each user-id is dependent on a secure login procedure. \ No newline at end of file diff --git a/wiki/3-Example-projects.md b/wiki/3-Example-projects.md new file mode 100644 index 0000000..306739e --- /dev/null +++ b/wiki/3-Example-projects.md @@ -0,0 +1,26 @@ +# Reference example + +[This](../tree/master/example-project) is the official example used for testing Sente, and makes a great starting point for the basics. + +It's kept up-to-date and includes basic client+server setup, routing, auth, CSRF protection, etc. + +# Community examples + +Please note that unofficial examples are **provided by the community** and may contain out-of-date or inaccurate information. If you spot issues with any of the community examples, please contact the relevant authors to let them know! + +Link | Description +---| --- +[@fiv0/spa-ws-template](https://github.com/FiV0/spa-ws-template) | SPA with [re-frame](https://github.com/day8/re-frame), [http-kit](https://github.com/http-kit/http-kit), [shadow-cljs](https://github.com/thheller/shadow-cljs) +[@dharrigan/websockets](https://github.com/dharrigan/websockets) | With [Reitit](https://github.com/metosin/reitit), Jetty 9/10 and [@dharrigan/websockets-js](https://github.com/dharrigan/websockets-js) +[@laforge49/sente-boot](https://github.com/laforge49/sente-boot/) | With Sente v1.11, [Boot](https://github.com/boot-clj/boot), works with Windows +[@laforge49/sente-boot-reagent](https://github.com/laforge49/sente-boot-reagent) | With Sente v1.11, [Boot](https://github.com/boot-clj/boot), and [Reagent](https://github.com/reagent-project/reagent) +[@tiensonqin/lymchat](https://github.com/tiensonqin/lymchat) | Chat app with [React Native](https://github.com/facebook/react-native) +[@danielsz/system-websockets](https://github.com/danielsz/system-websockets) | Client-side UI, login and wiring of components +[@timothypratley/snakelake](https://github.com/timothypratley/snakelake) | Multiplayer snake game with screencast walkthrough +[@theasp/sente-nodejs-example](https://github.com/theasp/sente-nodejs-example) | Ref. example adapted for Node.js servers ([Express](https://github.com/expressjs/express), [Dog Fort](https://github.com/whamtet/dogfort)), and Node.js client +[@ebellani/carpet](https://github.com/ebellani/carpet) | Web+mobile interface for a remmitance application +[@danielsz/sente-system](https://github.com/danielsz/sente-system) | Ref. example adapted for [@danielsz/system](https://github.com/danielsz/system) +[@danielsz/sente-boot](https://github.com/danielsz/sente-boot) | Ref. example adapted for [boot](https://github.com/boot-clj/boot) +[@seancorfield/om-sente](https://github.com/seancorfield/om-sente) | With [Om](https://github.com/swannodette/om) +[@tfoldi/data15-blackjack](https://github.com/tfoldi/data15-blackjack) | Multiplayer blackjack game +[@davidvujic/sente-with-reagent-and-re-frame](https://github.com/DavidVujic/sente-with-reagent-and-re-frame) | SPA with [re-frame](https://github.com/day8/re-frame) \ No newline at end of file diff --git a/wiki/3-FAQ.md b/wiki/3-FAQ.md new file mode 100644 index 0000000..c4f1d9d --- /dev/null +++ b/wiki/3-FAQ.md @@ -0,0 +1,103 @@ +# What is the `user-id` provided to the server>user push fn? + +For the server to push events, we need a destination. Traditionally we might push to a _client_ (e.g. browser tab). But with modern rich web applications and the increasing use of multiple simultaneous devices (tablets, mobiles, etc.) - the value of a _client_ push is diminishing. You'll often see applications (even by Google) struggling to deal with these cases. + +Sente offers an out-the-box solution by pulling the concept of identity one level higher and dealing with unique _users_ rather than clients. **What constitutes a user is entirely at the discretion of each application**: + + * Each user-id may have zero _or more_ connected clients at any given time + * Each user-id _may_ survive across clients (browser tabs, devices), and sessions + +**To give a user an identity, either set the user's `:uid` Ring session key OR supply a `:user-id-fn` (takes request, returns an identity string) to the `make-channel-socket-server!` constructor.** + +If you want a simple _per-session_ identity, generate a _random uuid_. If you want an identity that persists across sessions, try use something with _semantic meaning_ that you may already have like a database-generated user-id, a login email address, a secure URL fragment, etc. + +> Note that user-ids are used **only** for server>user push. client>server requests don't take a user-id. + +See [here](2-Client-and-user-ids) for more info. + +# How do I integrate Sente with my usual login/auth procedure? + +This should be pretty easy to do, please see one of the [example projects](3-Example-projects) for details! + +# Will Sente work with Reactjs/Reagent/Om/Pedestel/etc.? + +Sure! Sente's just a client<->server comms mechanism so it'll work with any view/rendering approach you'd like. + +I have a strong preference for [Reagent](http://reagent-project.github.io/) myself, so would recommend checking that out first if you're still evaluating options. + +# What if I need to use JSON, XML, raw strings, etc.? + +Sente uses an extensible client<->server serialization mechanism. It uses edn by default since this usu. gives good performance and doesn't require any external dependencies. The [reference example project](3-Example-projects#reference-example) shows how you can plug in an alternative de/serializer. In particular, note that Sente ships with a Transit de/serializer that allows manual or smart (automatic) per-payload format selection. + +# How do I add custom Transit read and write handlers? + +To add custom handlers to the TransitPacker, pass them in as `writer-opts` and `reader-opts` when creating a `TransitPacker`. These arguments are the same as the `opts` map you would pass directly to `transit/writer`. The code sample below shows how you would do this to add a write handler to convert [Joda-Time](http://www.joda.org/joda-time/) `DateTime` objects to Transit `time` objects. + +```clj +(ns my-ns.app + (:require [cognitect.transit :as transit] + [taoensso.sente.packers.transit :as sente-transit]) + (:import [org.joda.time DateTime ReadableInstant])) + +;; From https://increasinglyfunctional.com/2014/09/02/custom-transit-writers-clojure-joda-time.html +(def joda-time-writer + (transit/write-handler + (constantly "m") + (fn [v] (-> ^ReadableInstant v .getMillis)) + (fn [v] (-> ^ReadableInstant v .getMillis .toString)))) + +(def packer (sente-transit/->TransitPacker :json {:handlers {DateTime joda-time-writer}} {})) +``` + +# How do I route client/server events? + +However you like! If you don't have many events, a simple `cond` will probably do. Otherwise a multimethod dispatching against event ids works well (this is the approach taken in the [reference example project](3-Example-projects#reference-example)). + +# Security: is there HTTPS support? + +Yes, it's automatic for both Ajax and WebSockets. If the page serving your JavaScript (ClojureScript) is running HTTPS, your Sente channel sockets will run over HTTPS and/or the WebSocket equivalent (WSS). + +# Security: CSRF protection? + +**This is important**. Sente has support, and use is **strongly recommended**. You'll need to use middleware like [ring-anti-forgery](https://github.com/ring-clojure/ring-anti-forgery) or [ring-defaults](https://github.com/ring-clojure/ring-defaults) to generate and check CSRF codes. The `ring-ajax-post` handler should be covered (i.e. protected). + +Please see one of the [example projects](3-Example-projects) for a fully-baked example. + +# Pageload: How do I know when Sente is ready client-side? + +You'll want to listen on the receive channel for a `[:chsk/state [_ {:first-open? true}]]` event. That's the signal that the socket's been established. + +# How can server-side channel socket events modify a user's session? + +Recall that server-side `event-msg`s are of the form `{:ring-req _ :event _ :?reply-fn _}`, so each server-side event is accompanied by the relevant[*] Ring request. + +> For WebSocket events this is the initial Ring HTTP handshake request, for Ajax events it's just the Ring HTTP Ajax request. + +The Ring request's `:session` key is an immutable value, so how do you modify a session in response to an event? You won't be doing this often, but it can be handy (e.g. for login/logout forms). + +You've got two choices: + +1. Write any changes directly to your Ring SessionStore (i.e. the mutable state that's actually backing your sessions). You'll need the relevant user's session key, which you can find under your Ring request's `:cookies` key. This is flexible, but requires that you know how+where your session data is being stored. + +2. Just use regular HTTP Ajax requests for stuff that needs to modify sessions (like login/logout), since these will automatically go through the usual Ring session middleware and let you modify a session with a simple `{:status 200 :session }` response. This is the strategy the reference example takes. + +[@danielsz](https://github.com/danielsz) has kindly provided a detailed example [here](https://github.com/ptaoussanis/sente/issues/62#issuecomment-58790741). + +# Lifecycle management (component management/shutdown, etc.) + +Using something like [@stuartsierra/component](https://github.com/stuartsierra/component) or [@palletops/leaven](https://github.com/palletops/leaven)? + +Most of Sente's state is held internally to each channel socket (the map returned from client/server calls to `make-channel-socket!`). The absence of global state makes things like testing, and running multiple concurrent connections easy. It also makes integration with your component management easy. + +The only thing you _may_[1] want to do on component shutdown is stop any router loops that you've created to dispatch events to handlers. The client/server side `start-chsk-router!` fns both return a `(fn stop [])` that you can call to do this. + +> [1] The cost of _not_ doing this is actually negligible (a single parked go thread). + +There's also a couple lifecycle libraries that include Sente components: + + 1. [@danielsz/system](https://github.com/danielsz/system) for use with [@stuartsierra/component](https://github.com/stuartsierra/component) + 2. [@palletops/bakery](https://github.com/palletops/bakery) for use with [@palletops/leaven](https://github.com/palletops/leaven) + +# How to debug/benchmark Sente at the protocol level? + +[@arichiardi](https://github.com/arichiardi) has kindly provided some notes on this [here](4-Connection-debugging). \ No newline at end of file diff --git a/wiki/4-Connection-debugging.md b/wiki/4-Connection-debugging.md new file mode 100644 index 0000000..9837caf --- /dev/null +++ b/wiki/4-Connection-debugging.md @@ -0,0 +1,36 @@ +Some info is provided here on **protocol-level debugging and profiling** for Sente connections. + +# Ajax connections + +These are easily debugged and profiled via your browser's usual network tools. + +# WebSocket connections + +You can inspect Sente packets using `Wireshark` or similar tools. + +Assuming Sente doesn't degrade to Ajax, the initial [WebSocket upgrade](https://tools.ietf.org/html/rfc6455#section-1.2) handshake will include a `:client-id` parameter: + +``` +GET /chsk?client-id=bd5ee0f2-dc22-47b5-98ab-618711f34b45 HTTP/1.1 +Host: localhost:3000 +Connection: Upgrade +Upgrade: websocket +Origin: http://localhost:3000 +Sec-WebSocket-Version: 13 +... +``` + +This is important if you want to emulate Sente's behavior using benchmarking tools like [tcpkali](https://github.com/machinezone/tcpkali), etc. Without this Sente will throw an exception and the benchmark will fail. + +Afterwards, you'll see a series of TCP packets as per the Websocket protocol and containing the `[ ]` vector encoded according to the selected Packer. For instance with Transit: + +``` +`e@7.KcJam +43C-["~:chsk/handshake",["bd5ee0f2-dc22-47b5-98ab-618711f34b45",null]] +``` + +## What is the `+` character I see attached to my Websocket `?ev-data`? + +This is a normal part of Sente's payload encoding. + +See [here](https://github.com/arichiardi/sente/blob/162149663e63fcda0348fb8d28d5533c4d0004cd/src/taoensso/sente.cljc#L212) for the gory details if you need to reproduce this behaviour. \ No newline at end of file diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..af3d591 --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,8 @@ +See the menu to the right for content 👉 + +# Contributions welcome + +**PRs very welcome** to help improve this documentation! +See the [wiki](../tree/master/wiki) folder in the main repo for the relevant files. + +\- [Peter Taoussanis](https://www.taoensso.com) \ No newline at end of file