diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 66d7c481..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,440 +0,0 @@ -version: 2.1 - -requires_prepared_code: &requires_prepared_code - requires: - - prepare_code - -common_tags_filter: &common_tags_filter - filters: - tags: - only: /^v.*/ - branches: - only: - - "master" - - /^ci\/.*/ - -# Workflow definition -workflows: - # test_and_release: - # jobs: - # - prepare_code: - # <<: *common_tags_filter - # - build_darwin_mruby: - # <<: *common_tags_filter - # - prepare_mruby: - # <<: *requires_prepared_code - # <<: *common_tags_filter - # - test: - # <<: *common_tags_filter - # requires: - # - prepare_mruby - # - build: - # <<: *requires_prepared_code - # <<: *common_tags_filter - # - build_linux_mruby: - # <<: *common_tags_filter - # requires: - # - prepare_mruby - # - conformance: - # <<: *common_tags_filter - # requires: - # - prepare_mruby - # - github_release: - # requires: - # - test - # - conformance - # - build - # - build_linux_mruby - # - build_darwin_mruby - # filters: - # tags: - # only: /^v[0-9]+\.[0-9]+\.[0-9]+((\-|\.)(preview|beta|rc)[\.\d+])?$/ - # branches: - # ignore: /.*/ - - benchmark: - jobs: - - prepare_code - - prepare_mruby: - requires: - - prepare_code - - benchmark: - requires: - - prepare_mruby - -executors: - golang: - docker: - - image: cimg/go:1.20.3 - environment: - CGO_ENABLED: "0" - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - osx: - macos: - xcode: "13.4.1" - environment: - GOPATH: /Users/distiller/go - CGO_ENABLED: "1" - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - GO_VERSION: 1.20.3 - GO_SHA: 7ab4929d3c49d11ea2af8542de01e9ca98cb2e96963423b60aa0f695d52fe82b - docker: - docker: - - image: cimg/base:stable - ruby: - docker: - - image: cimg/ruby:2.7.1 - -commands: - build: - description: Build AnyCable Go binary - parameters: - os: - type: string - arch: - type: string - postfix: - type: string - steps: - - run: env GOOS=<< parameters.os >> GOARCH=<< parameters.arch >> go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${CIRCLE_TAG/v/''} -X github.com/anycable/anycable-go/version.sha=$(echo "$CIRCLE_SHA1" | cut -c -7)" -a -o "/tmp/dist/anycable-go-<< parameters.postfix >>" cmd/anycable-go/main.go - build-mruby: - description: Build AnyCable Go binary with mruby support - parameters: - os: - type: string - arch: - type: string - postfix: - type: string - steps: - - run: env GOOS=<< parameters.os >> GOARCH=<< parameters.arch >> go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${CIRCLE_TAG/v/''} -X github.com/anycable/anycable-go/version.sha=$(echo "$CIRCLE_SHA1" | cut -c -7)" -tags mrb -a -o "/tmp/dist/anycable-go-<< parameters.postfix >>" cmd/anycable-go/main.go - -orbs: - ruby: circleci/ruby@2.0.0 - -# Jobs definitions -jobs: - prepare_code: - executor: golang - steps: - - attach_workspace: - at: . - - checkout - - run: - name: Cache latest master tag - command: | - echo $(git tag --merged master -l --sort=-version:refname "v*" | head -n1) > latest-tag.log - - run: - name: Download Go deps - command: | - go mod vendor - - persist_to_workspace: - root: . - paths: . - - prepare_mruby: - executor: ruby - steps: - - attach_workspace: - at: . - - run: - name: Install bison - command: | - sudo apt update && \ - sudo apt -y install bison - - run: - name: Build libmruby.a - command: bash -c '(cd vendor/github.com/mitchellh/go-mruby && MRUBY_CONFIG=../../../../../../etc/build_config.rb make libmruby.a)' - - persist_to_workspace: - root: . - paths: - - ./vendor/github.com/mitchellh/go-mruby/mruby-build/ - - ./vendor/github.com/mitchellh/go-mruby/libmruby.a - - test: - executor: golang - environment: - CGO_ENABLED: "1" - steps: - - attach_workspace: - at: . - - run: make test - - benchmark: - docker: - - image: cimg/go:1.20.3 - - image: cimg/redis:6.2.6 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - CGO_ENABLED: "1" - BUILD_ARGS: "-race" - GORACE: "halt_on_error=1" - BUNDLE_GEMFILE: .circleci/Gemfile - ANYCABLE_RPC_HOST: "127.0.0.1:50051" - ANYCABLE_DISABLE_TELEMETRY: "true" - steps: - - attach_workspace: - at: . - - ruby/install: - version: '3.1.3' - - run: bundle install - - run: env GO111MODULE=off go get github.com/anycable/websocket-bench - - run: env GO111MODULE=off go get github.com/google/gops - - run: - name: Wait for Redis - command: dockerize -wait tcp://localhost:6379 -timeout 1m - - run: - name: Run benchmarks - command: | - make benchmarks - - build_binary_for_conformance: - executor: golang - environment: - CGO_ENABLED: "1" - steps: - - attach_workspace: - at: . - - run: - name: Building Linux amd64 binary - command: go build -tags mrb -ldflags "-s -w" -race -a -o "/tmp/anycable-go-test" cmd/anycable-go/main.go - - save_cache: - key: binary-for-conformance-{{ .Revision }} - paths: /tmp/anycable-go-test - - build_darwin_mruby: - executor: osx - steps: - - checkout - - run: - name: Installing Golang - command: | - curl --http1.1 -o go.pkg https://dl.google.com/go/go${GO_VERSION}.darwin-amd64.pkg && \ - echo "${GO_SHA} go.pkg" | shasum -a 256 -c - && \ - sudo installer -pkg go.pkg -target / - - run: - name: Add Go binaries to PATH - command: echo 'export PATH="$GOPATH/bin:/usr/local/go/bin:$PATH"' >> $BASH_ENV - - run: go mod vendor - - run: - name: Prepare code for the build - command: | - bash -c "(cd vendor/github.com/mitchellh/go-mruby && MRUBY_CONFIG=../../../../../../etc/build_config.rb make libmruby.a)" - - build-mruby: - os: darwin - arch: amd64 - postfix: mrb-darwin-amd64 - - save_cache: - key: darwin-mruby-{{ .Revision }} - paths: /tmp/dist/ - - build: - executor: golang - steps: - - attach_workspace: - at: . - - build: - os: linux - arch: amd64 - postfix: linux-amd64 - - build: - os: linux - arch: arm - postfix: linux-arm - - build: - os: linux - arch: arm64 - postfix: linux-arm64 - - build: - os: windows - arch: amd64 - postfix: win-amd64.exe - - build: - os: darwin - arch: arm64 - postfix: darwin-arm64 - - build: - os: darwin - arch: amd64 - postfix: darwin-amd64 - - build: - os: freebsd - arch: amd64 - postfix: freebsd-amd64 - - build: - os: freebsd - arch: arm - postfix: freebsd-arm - - save_cache: - key: build-{{ .Revision }} - paths: /tmp/dist/ - - build_linux_mruby: - executor: golang - environment: - CGO_ENABLED: "1" - steps: - - attach_workspace: - at: . - - build-mruby: - os: linux - arch: amd64 - postfix: mrb-linux-amd64 - - save_cache: - key: linux-mruby-{{ .Revision }} - paths: /tmp/dist/ - - conformance: - docker: - - image: cimg/go:1.20.3 - - image: cimg/redis:6.2.6 - environment: - BUNDLE_GEMFILE: ".circleci/Gemfile" - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - CGO_ENABLED: "1" - BUILD_ARGS: "-race" - GORACE: "halt_on_error=1" - REDIS_URL: redis://localhost:6379/3 - ANYCABLE_DISABLE_TELEMETRY: "true" - steps: - - attach_workspace: - at: . - - ruby/install: - version: '3.1.3' - - run: - name: Install deps - command: bundle install - - run: - name: Wait for Redis - command: dockerize -wait tcp://localhost:6379 -timeout 1m - - run: - name: Default (Redis) pubsub test - command: make test-conformance - - run: - name: SSL test - command: make test-conformance-ssl - - run: - name: HTTP broadcast test - command: make test-conformance-http - - build_docker: - parameters: - dockerfile: - type: string - default: Dockerfile.universal - binary_dir: - type: string - default: /tmp/dist - docker_binary_dir: - type: string - default: .docker - platform: - type: string - default: linux/amd64 - version: - type: string - default: ${CIRCLE_TAG/v/''} - mruby: - type: boolean - default: false - executor: docker - steps: - - setup_remote_docker: - version: 20.10.14 - - attach_workspace: - at: . - - restore_cache: - key: build-{{ .Revision }} - - run: - name: Create a new docker buildx builder instance - command: | - docker run --privileged --rm tonistiigi/binfmt --install all - docker context create multiarch-builder - docker buildx create --use multiarch-builder - - run: - name: Buildx supported platforms - command: | - docker buildx ls - - when: - condition: <> - steps: - - restore_cache: - key: linux-mruby-{{ .Revision }} - - run: - name: Replace with mruby binary - command: | - cp -f <>/anycable-go-mrb-linux-amd64 <>/anycable-go-linux-amd64 - - run: - name: Prepare files for Docker build - command: | - cp /etc/ssl/certs/ca-certificates.crt ./.docker/ca-certificates.crt - mkdir -p <>/linux/amd64 - mkdir -p <>/linux/arm64 - cp <>/anycable-go-linux-amd64 <>/linux/amd64/anycable-go - cp <>/anycable-go-linux-arm64 <>/linux/arm64/anycable-go || true - - run: - name: Login to Docker registry - command: docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} - - run: - name: Build Docker image - command: docker buildx build --platform=<> -f .docker/<> -t anycable/anycable-go:<> ./ --push - push_latest_docker: - executor: docker - steps: - - setup_remote_docker: - version: 18.06.0-ce - - run: - name: Login to Docker registry - command: docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} - - run: - name: Pull base Docker images - command: | - docker pull anycable/anycable-go:${CIRCLE_TAG/v/''} - docker pull anycable/anycable-go:${CIRCLE_TAG/v/''}-mrb - docker pull anycable/anycable-go:${CIRCLE_TAG/v/''}-alpine - - run: - name: Push latest Docker images - command: | - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''} anycable/anycable-go:latest - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''} anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/') - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''} anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/') - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/') - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/') - docker push anycable/anycable-go:latest - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-mrb anycable/anycable-go:latest-mrb - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-mrb anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')-mrb - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-mrb anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/')-mrb - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')-mrb - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/')-mrb - docker push anycable/anycable-go:latest-mrb - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-alpine anycable/anycable-go:latest-alpine - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-alpine anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')-alpine - docker tag anycable/anycable-go:${CIRCLE_TAG/v/''}-alpine anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/')-alpine - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')-alpine - docker push anycable/anycable-go:$(echo "$CIRCLE_TAG" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1.\2/')-alpine - docker push anycable/anycable-go:latest-alpine - github_release: - executor: golang - working_directory: /tmp/dist/ - steps: - - restore_cache: - key: darwin-mruby-{{ .Revision }} - - restore_cache: - key: linux-mruby-{{ .Revision }} - - restore_cache: - key: build-{{ .Revision }} - - run: - name: Generate SHA256 checksums - command: sha256sum anycable-go-* > SHA256SUM - - run: - name: Install ghr tool - command: | - curl -L https://github.com/tcnksm/ghr/releases/download/v0.16.0/ghr_v0.16.0_linux_amd64.tar.gz -o - | tar -xz - cp ghr_v0.16.0_linux_amd64/ghr ./ - - run: - name: Upload GitHub release - command: ./ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -prerelease -delete ${CIRCLE_TAG} ./ diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cb791d7e..d67b49d5 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,6 +8,7 @@ on: - "**/*.go" - "go.sum" workflow_dispatch: + pull_request: jobs: benchmark: @@ -18,7 +19,6 @@ jobs: runs-on: ubuntu-latest env: GO111MODULE: on - BUNDLE_GEMFILE: .circleci/Gemfile DEBUG: true services: redis: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0f37f23e..8f0323ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -130,7 +130,6 @@ jobs: GORACE: "halt_on_error=1" COVERAGE: "true" GOCOVERDIR: "_icoverdir_" - BUNDLE_GEMFILE: .circleci/Gemfile BUNDLE_PATH: ./vendor/bundle # Specify REDIS_URL explicitly, so Makefile doesn't check the presence of Redis REDIS_URL: redis://localhost:6379/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b7969950..ffa03799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master +- Fix passing RPC context via headers when using HTTP RPC. ([@palkan][]) + +- Add `exclude_socket` option support to broadcasts. ([@palkan][]) + - Add support for batched broadcasts. ([@palkan][]) It's now possible to publish an array of broadcasting messages (e.g., `[{"stream":"a","data":"..."},"stream":"b","data":"..."}]`). The messages will be delivered in the same order as they were published (within a stream). diff --git a/.circleci/Gemfile b/Gemfile similarity index 61% rename from .circleci/Gemfile rename to Gemfile index 7ee5d28c..bf68cd8b 100644 --- a/.circleci/Gemfile +++ b/Gemfile @@ -5,13 +5,15 @@ gem "nats-pure", "< 2.3.0" gem "colorize" gem "puma" -if File.directory?(File.join(__dir__, "../../anycable")) +gem "activesupport", "~> 7.0.0" + +if File.directory?(File.join(__dir__, "../anycable")) $stdout.puts "\n=== Using local gems for Anyt ===\n\n" gem "debug" - gem "anycable", path: "../../anycable" - gem "anycable-rails", path: "../../anycable-rails" - gem "anyt", path: "../../anyt" - gem "wsdirector-cli", path: "../../wsdirector" + gem "anycable", path: "../anycable" + gem "anycable-rails", path: "../anycable-rails" + gem "anyt", path: "../anyt" + gem "wsdirector-cli", path: "../wsdirector" else gem "anycable", github: "anycable/anycable" gem "anycable-rails", github: "anycable/anycable-rails" diff --git a/Makefile b/Makefile index eb8ed990..dc43324c 100644 --- a/Makefile +++ b/Makefile @@ -119,52 +119,52 @@ test: go test -count=1 -timeout=30s -race -tags mrb ./... $(TEST_FLAGS) benchmarks: build - BUNDLE_GEMFILE=.circleci/Gemfile ruby features/runner.rb features/*.benchfile + ruby features/runner.rb features/*.benchfile tmp/anycable-go-test: go build $(TEST_BUILD_FLAGS) -tags mrb -race -o tmp/anycable-go-test cmd/anycable-go/main.go test-conformance: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" + bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" test-conformance-ssl: tmp/anycable-go-test ANYCABLE_RPC_TLS_CERT=etc/ssl/server.crt \ ANYCABLE_RPC_TLS_KEY=etc/ssl/server.key \ - BUNDLE_GEMFILE=.circleci/Gemfile bundle exec anyt -c \ + bundle exec anyt -c \ "tmp/anycable-go-test --headers=cookie,x-api-token --rpc_enable_tls --rpc_tls_verify=false --ssl_key=etc/ssl/server.key --ssl_cert=etc/ssl/server.crt --port=8443" \ --target-url="wss://localhost:8443/cable" test-conformance-http: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile \ + \ ANYCABLE_BROADCAST_ADAPTER=http ANYCABLE_HTTP_BROADCAST_SECRET=any_secret \ ANYCABLE_HTTP_RPC_SECRET=rpc_secret ANYCABLE_HTTP_RPC_MOUNT_PATH=/_anycable \ - bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token --rpc_impl=http --rpc_host=http://localhost:9292/_anycable" --target-url="ws://localhost:8080/cable" + bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token --rpc_impl=http --rpc_host=http://localhost:9292/_anycable" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broadcast_tests/*.rb test-conformance-nats: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile ANYCABLE_BROADCAST_ADAPTER=nats bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" + ANYCABLE_BROADCAST_ADAPTER=nats bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broadcast_tests/*.rb test-conformance-nats-embedded: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile ANYCABLE_BROADCAST_ADAPTER=nats ANYCABLE_NATS_SERVERS=nats://127.0.0.1:4242 ANYCABLE_EMBED_NATS=true ANYCABLE_ENATS_ADDR=nats://127.0.0.1:4242 bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" + ANYCABLE_BROADCAST_ADAPTER=nats ANYCABLE_NATS_SERVERS=nats://127.0.0.1:4242 ANYCABLE_EMBED_NATS=true ANYCABLE_ENATS_ADDR=nats://127.0.0.1:4242 bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broadcast_tests/*.rb test-conformance-broker-http: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile ANYCABLE_BROKER=memory ANYCABLE_BROADCAST_ADAPTER=http ANYCABLE_HTTP_BROADCAST_SECRET=any_secret bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broker_tests/*.rb + ANYCABLE_BROKER=memory ANYCABLE_BROADCAST_ADAPTER=http ANYCABLE_HTTP_BROADCAST_SECRET=any_secret bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/**/*.rb test-conformance-broker-redis: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile ANYCABLE_BROKER=memory ANYCABLE_BROADCAST_ADAPTER=redisx ANYCABLE_HTTP_BROADCAST_SECRET=any_secret ANYCABLE_PUBSUB=redis bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broker_tests/*.rb + ANYCABLE_BROKER=memory ANYCABLE_BROADCAST_ADAPTER=redisx ANYCABLE_HTTP_BROADCAST_SECRET=any_secret ANYCABLE_PUBSUB=redis bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/**/*.rb test-conformance-broker-nats: tmp/anycable-go-test - BUNDLE_GEMFILE=.circleci/Gemfile ANYCABLE_BROKER=memory ANYCABLE_EMBED_NATS=true ANYCABLE_ENATS_ADDR=nats://127.0.0.1:4343 ANYCABLE_PUBSUB=nats ANYCABLE_BROADCAST_ADAPTER=http ANYCABLE_HTTP_BROADCAST_SECRET=any_secret bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/broker_tests/*.rb + ANYCABLE_BROKER=memory ANYCABLE_EMBED_NATS=true ANYCABLE_ENATS_ADDR=nats://127.0.0.1:4343 ANYCABLE_PUBSUB=nats ANYCABLE_BROADCAST_ADAPTER=http ANYCABLE_HTTP_BROADCAST_SECRET=any_secret bundle exec anyt -c "tmp/anycable-go-test --headers=cookie,x-api-token" --target-url="ws://localhost:8080/cable" --require=etc/anyt/**/*.rb test-conformance-all: test-conformance test-conformance-ssl test-conformance-http TESTFILE ?= features/*.testfile test-features: build - BUNDLE_GEMFILE=.circleci/Gemfile ruby features/runner.rb $(TESTFILE) + ruby features/runner.rb $(TESTFILE) test-ci: prepare prepare-mruby test test-conformance prepare: - BUNDLE_GEMFILE=.circleci/Gemfile bundle install + bundle install gen-ssl: mkdir -p tmp/ssl diff --git a/Readme.md b/Readme.md index 0306fc17..96ad5fa5 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,5 @@ [![Latest Release](https://img.shields.io/github/release/anycable/anycable-go.svg?include_prereleases)](https://github.com/anycable/anycable-go/releases/latest?include_prereleases) [![Build](https://github.com/anycable/anycable-go/workflows/Test/badge.svg)](https://github.com/anycable/anycable-go/actions) -[![CircleCI](https://img.shields.io/circleci/project/github/anycable/anycable-go.svg?label=CircleCI)](https://circleci.com/gh/anycable/anycable-go) [![Docker](https://img.shields.io/docker/pulls/anycable/anycable-go.svg)](https://hub.docker.com/r/anycable/anycable-go/) [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://docs.anycable.io/anycable-go/getting_started) # AnyCable-Go WebSocket Server diff --git a/common/common.go b/common/common.go index 6277f0aa..f15a9b7a 100644 --- a/common/common.go +++ b/common/common.go @@ -224,10 +224,17 @@ type Message struct { History HistoryRequest `json:"history,omitempty"` } +// StreamMessageMetadata describes additional information about a stream message +// which can be used to modify delivery behavior +type StreamMessageMetadata struct { + ExcludeSocket string `json:"exclude_socket,omitempty"` +} + // StreamMessage represents a pub/sub message to be sent to stream type StreamMessage struct { - Stream string `json:"stream"` - Data string `json:"data"` + Stream string `json:"stream"` + Data string `json:"data"` + Meta *StreamMessageMetadata `json:"meta,omitempty"` // Offset is the position of this message in the stream Offset uint64 diff --git a/docs/binary_formats.md b/docs/binary_formats.md index 20ea16a7..46a353d9 100644 --- a/docs/binary_formats.md +++ b/docs/binary_formats.md @@ -224,11 +224,11 @@ export default createCable({protocol: 'actioncable-v1-ext-protobuf', encoder: ne Here is the in/out traffic comparison: -Encoder | Sent | Rcvd ---------|------|------- -protobuf | 315.32MB | 327.1KB -msgpack | 339.58MB | 473.6KB -json | 502.45MB | 571.8KB +| Encoder | Sent | Rcvd | +|----------|------|-------| +| protobuf | 315.32MB | 327.1KB | +| msgpack | 339.58MB | 473.6KB | +| json | 502.45MB | 571.8KB | The data above were captured while running a [websocket-bench][] benchmark with the following parameters: @@ -240,15 +240,15 @@ websocket-bench broadcast ws://0.0.0.0:8080/cable —server-type=actioncable — Here is the encode/decode speed comparison: -Encoder | Decode (ns/op) | Encode (ns/op) ---------|------|------- -protobuf (base) | 425 | 1153 -msgpack (base) | 676 | 1512 -json (base) | 1386 | 1266 -|| -protobuf (long) | 479 | 2370 -msgpack (long) | 763 | 2506 -json (long) | 2457 | 2319 +| Encoder | Decode (ns/op) | Encode (ns/op) | +|--------|------|-------| +| protobuf (base) | 425 | 1153 | +| msgpack (base) | 676 | 1512 | +| json (base) | 1386 | 1266 | +|||| +| protobuf (long) | 479 | 2370 | +| msgpack (long) | 763 | 2506 | +| json (long) | 2457 | 2319 | Where base payload is: diff --git a/etc/anyt/broadcast_tests/options_test.rb b/etc/anyt/broadcast_tests/options_test.rb new file mode 100644 index 00000000..8b54e3af --- /dev/null +++ b/etc/anyt/broadcast_tests/options_test.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +feature "Broadcast with options" do + channel do + def subscribed + stream_from "a" + end + + def speak(data) + data.delete("action") + ActionCable.server.broadcast("a", data, to_others: true) + end + end + + let(:client2) { build_client(ignore: %w[ping welcome]) } + let(:client3) { build_client(ignore: %w[ping welcome]) } + + before do + subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json} + + client.send(subscribe_request) + client2.send(subscribe_request) + client3.send(subscribe_request) + + ack = { + "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription" + } + + assert_message ack, client.receive + assert_message ack, client2.receive + assert_message ack, client3.receive + end + + scenario %( + Only other clients receive the message when broadcasted to others + ) do + perform_request = { + :command => "message", + :identifier => {channel: channel}.to_json, + "data" => {"action" => "speak", "content" => "The Other Side"}.to_json + } + + client.send(perform_request) + + msg = {"identifier" => {channel: channel}.to_json, "message" => {"content" => "The Other Side"}} + + assert_message msg, client2.receive + assert_message msg, client3.receive + assert_raises(Anyt::Client::TimeoutError) do + msg = client.receive(timeout: 0.5) + raise "Client 1 should not receive the message: #{msg}" + end + end +end diff --git a/features/runner.rb b/features/runner.rb index f23c393d..1d523d4a 100644 --- a/features/runner.rb +++ b/features/runner.rb @@ -9,7 +9,7 @@ gem "childprocess", "~> 4.1" gem "jwt" - gem "activesupport", "~> 7.0" + gem "activesupport", "~> 7.0.0" end rescue raise if retried diff --git a/hub/gate.go b/hub/gate.go index 3c59a419..bce08052 100644 --- a/hub/gate.go +++ b/hub/gate.go @@ -144,6 +144,10 @@ func (g *Gate) performBroadcast(streamMsg *common.StreamMessage) { g.mu.RUnlock() for session, ids := range streamSessions { + if streamMsg.Meta != nil && streamMsg.Meta.ExcludeSocket == session.GetID() { + continue + } + for _, id := range ids { if msg, ok := buf[id]; ok { bdata = msg diff --git a/hub/hub_test.go b/hub/hub_test.go index 55b3a596..9e7e9e19 100644 --- a/hub/hub_test.go +++ b/hub/hub_test.go @@ -12,6 +12,7 @@ import ( "github.com/anycable/anycable-go/common" "github.com/anycable/anycable-go/encoders" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type MockSession struct { @@ -250,6 +251,39 @@ func TestBroadcastMessage(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "{\"identifier\":\"test_channel\",\"message\":\"ciao\",\"stream_id\":\"test\",\"epoch\":\"xyz\",\"offset\":2022}", string(msg)) }) + + t.Run("Broadcast with exclude_socket", func(t *testing.T) { + session2 := NewMockSession("234") + hub.AddSession(session2) + hub.SubscribeSession(session2, "test", "test_channel") + + hub.BroadcastMessage(&common.StreamMessage{Stream: "test", Data: "\"ciao\""}) + + msg, err := session.Read() + assert.Nil(t, err) + assert.Equal(t, "{\"identifier\":\"test_channel\",\"message\":\"ciao\"}", string(msg)) + + msg, err = session2.Read() + assert.Nil(t, err) + assert.Equal(t, "{\"identifier\":\"test_channel\",\"message\":\"ciao\"}", string(msg)) + + hub.BroadcastMessage(&common.StreamMessage{ + Stream: "test", + Data: "\"hoi!\"", + Meta: &common.StreamMessageMetadata{ + ExcludeSocket: "234", + }, + }) + + msg, err = session.Read() + assert.Nil(t, err) + assert.Equal(t, "{\"identifier\":\"test_channel\",\"message\":\"hoi!\"}", string(msg)) + + msg, err = session2.Read() + assert.Nil(t, msg) + require.Error(t, err) + assert.Contains(t, err.Error(), "hasn't received any messages") + }) } func TestBroadcastOrder(t *testing.T) { diff --git a/rpc/http.go b/rpc/http.go index 74b1f8d1..d15ec25f 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -178,7 +178,7 @@ func (s *HTTPService) performRequest(ctx context.Context, path string, payload [ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.conf.Secret)) } - if md, _, ok := metadata.FromOutgoingContextRaw(ctx); ok { + if md, ok := metadata.FromIncomingContext(ctx); ok { // Set headers from metadata for k, v := range md { req.Header.Set(fmt.Sprintf("x-anycable-meta-%s", k), v[0]) diff --git a/rpc/http_test.go b/rpc/http_test.go index ee760ac9..554b44f1 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -72,7 +72,7 @@ func TestHTTPServiceRPC(t *testing.T) { } md := metadata.Pairs("album", "Kamni", "year", "2008") - ctx := metadata.NewOutgoingContext(context.Background(), md) + ctx := metadata.NewIncomingContext(context.Background(), md) res, err := service.Connect(ctx, protocol.NewConnectMessage( common.NewSessionEnv("ws://anycable.io/cable", &map[string]string{"cookie": "foo=bar"}), )) @@ -108,7 +108,7 @@ func TestHTTPServiceRPC(t *testing.T) { } md := metadata.Pairs("error", "test error") - ctx := metadata.NewOutgoingContext(context.Background(), md) + ctx := metadata.NewIncomingContext(context.Background(), md) res, err := service.Disconnect(ctx, protocol.NewDisconnectMessage( common.NewSessionEnv("ws://anycable.io/cable", &map[string]string{"cookie": "foo=bar"}), "test-session", @@ -147,7 +147,7 @@ func TestHTTPServiceRPC(t *testing.T) { } md := metadata.Pairs("track", "easy-way-out") - ctx := metadata.NewOutgoingContext(context.Background(), md) + ctx := metadata.NewIncomingContext(context.Background(), md) res, err := service.Command(ctx, protocol.NewCommandMessage( common.NewSessionEnv("ws://anycable.io/cable", &map[string]string{"cookie": "foo=bar"}), "subscribe", diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index 6b16bacf..3cb991d1 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -1,6 +1,7 @@ package rpc import ( + "context" "errors" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" ) type MockState struct { @@ -478,5 +480,15 @@ func TestCustomDialFun(t *testing.T) { assert.Equal(t, "user=john", res.Identifier) assert.Equal(t, map[string]string{"_s_": "test-session"}, res.CState) assert.Empty(t, res.Broadcasts) + + call := service.Calls[0] + requestCtx, ok := call.Arguments[0].(context.Context) + + require.True(t, ok) + + md, ok := metadata.FromIncomingContext(requestCtx) + require.True(t, ok) + + assert.Equal(t, []string{"42"}, md.Get("sid")) }) }