├── .github
└── workflows
│ └── tests.yaml
├── .gitignore
├── .gitlab
└── issue_templates
│ └── kind_bug.md
├── .gitmodules
├── .readthedocs.yaml
├── LICENSE
├── Makefile
├── README.md
├── benchmark
├── benchmark_test.go
└── testdata
│ ├── gen_proto.bat
│ ├── gen_proto.sh
│ ├── test.pb.go
│ └── test.proto
├── cmd
├── repl.go
└── root.go
├── deploy
└── kubernetes
│ ├── example-backend.yaml
│ └── example-frontend.yaml
├── docs
├── API.md
├── Makefile
├── builder.md
├── cli.md
├── communication.md
├── conf.py
├── configuration.rst
├── examples.md
├── features.md
├── handshake-validators.md
├── index.rst
├── make.bat
├── overview.md
├── requirements.txt
└── tracing.md
├── e2e
└── e2e_test.go
├── examples
├── demo
│ ├── chat
│ │ ├── README.md
│ │ ├── main.go
│ │ └── web
│ │ │ ├── index.html
│ │ │ ├── protocol.js
│ │ │ └── starx-wsclient.js
│ ├── cluster
│ │ ├── main.go
│ │ └── services
│ │ │ ├── connector.go
│ │ │ └── room.go
│ ├── cluster_grpc
│ │ ├── main.go
│ │ └── services
│ │ │ ├── connector.go
│ │ │ └── room.go
│ ├── custom_metrics
│ │ ├── README.md
│ │ ├── config.yaml
│ │ ├── main.go
│ │ ├── messages
│ │ │ ├── args.go
│ │ │ └── responses.go
│ │ └── services
│ │ │ └── room.go
│ ├── pipeline
│ │ └── main.go
│ ├── protos
│ │ ├── cluster.pb.go
│ │ └── cluster.proto
│ ├── rate_limiting
│ │ ├── main.go
│ │ └── services
│ │ │ └── room.go
│ └── worker
│ │ ├── main.go
│ │ ├── protos
│ │ ├── arg.pb.go
│ │ ├── arg.proto
│ │ ├── response.pb.go
│ │ └── response.proto
│ │ └── services
│ │ ├── metagame.go
│ │ ├── room.go
│ │ └── worker.go
└── testing
│ ├── docker-compose-jaeger.yml
│ ├── docker-compose.yml
│ ├── docker
│ └── Dockerfile
│ ├── main.go
│ └── protos
│ ├── cluster.pb.go
│ └── cluster.proto
├── go.mod
├── go.sum
├── go.work
├── go.work.sum
├── main.go
├── pkg
├── acceptor
│ ├── acceptor.go
│ ├── fixtures
│ │ ├── server.crt
│ │ └── server.key
│ ├── proxyprotowrapper.go
│ ├── tcp_acceptor.go
│ ├── tcp_acceptor_test.go
│ ├── ws_acceptor.go
│ └── ws_acceptor_test.go
├── acceptorwrapper
│ ├── base.go
│ ├── base_test.go
│ ├── rate_limiter.go
│ ├── rate_limiter_test.go
│ ├── rate_limiting_wrapper.go
│ ├── rate_limiting_wrapper_test.go
│ └── wrapper.go
├── agent
│ ├── agent.go
│ ├── agent_remote.go
│ ├── agent_remote_test.go
│ ├── agent_test.go
│ └── mocks
│ │ └── agent.go
├── app.go
├── app_test.go
├── builder.go
├── builder_test.go
├── client
│ ├── client.go
│ ├── client_test.go
│ ├── pitayaclient.go
│ └── protoclient.go
├── cluster
│ ├── cluster.go
│ ├── etcd_service_discovery.go
│ ├── etcd_service_discovery_test.go
│ ├── grpc_rpc_client.go
│ ├── grpc_rpc_client_test.go
│ ├── grpc_rpc_server.go
│ ├── grpc_rpc_server_test.go
│ ├── info_retriever.go
│ ├── info_retriever_test.go
│ ├── mocks
│ │ ├── cluster.go
│ │ └── service_discovery.go
│ ├── nats_rpc_client.go
│ ├── nats_rpc_client_test.go
│ ├── nats_rpc_common.go
│ ├── nats_rpc_common_test.go
│ ├── nats_rpc_server.go
│ ├── nats_rpc_server_test.go
│ ├── server.go
│ ├── server_test.go
│ └── service_discovery.go
├── component.go
├── component
│ ├── base.go
│ ├── component.go
│ ├── method.go
│ ├── method_test.go
│ ├── options.go
│ ├── options_test.go
│ ├── service.go
│ └── service_test.go
├── component_test.go
├── config
│ ├── config.go
│ ├── config_test.go
│ └── viper_config.go
├── conn
│ ├── codec
│ │ ├── constants.go
│ │ ├── mocks
│ │ │ ├── packet_decoder.go
│ │ │ └── packet_encoder.go
│ │ ├── packet_decoder.go
│ │ ├── packet_encoder.go
│ │ ├── pomelo_packet_decoder.go
│ │ ├── pomelo_packet_decoder_test.go
│ │ ├── pomelo_packet_encoder.go
│ │ ├── pomelo_packet_encoder_test.go
│ │ └── utils.go
│ ├── message
│ │ ├── fixtures
│ │ │ ├── test_invalid_message.golden
│ │ │ ├── test_must_gzip.golden
│ │ │ ├── test_notify_type.golden
│ │ │ ├── test_notify_type_compressed.golden
│ │ │ ├── test_push_type.golden
│ │ │ ├── test_push_type_compressed.golden
│ │ │ ├── test_reponse_type.golden
│ │ │ ├── test_reponse_type_with_data.golden
│ │ │ ├── test_reponse_type_with_error.golden
│ │ │ ├── test_reponse_type_with_id.golden
│ │ │ ├── test_request_type.golden
│ │ │ ├── test_request_type_compressed.golden
│ │ │ └── test_wrong_type.golden
│ │ ├── message.go
│ │ ├── message_encoder.go
│ │ ├── message_test.go
│ │ └── mocks
│ │ │ └── message_encoder.go
│ └── packet
│ │ ├── constants.go
│ │ ├── packet.go
│ │ └── packet_test.go
├── constants
│ ├── const.go
│ ├── errors.go
│ └── version.go
├── context
│ ├── context.go
│ ├── context_test.go
│ └── fixtures
│ │ ├── one_element.golden
│ │ └── registered_struct.golden
├── defaultpipelines
│ ├── default_struct_validator.go
│ ├── default_struct_validator_test.go
│ └── struct_validator.go
├── docgenerator
│ ├── descriptors.go
│ ├── descriptors_test.go
│ ├── generator.go
│ └── generator_test.go
├── errors
│ ├── errors.go
│ └── errors_test.go
├── group.go
├── group_test.go
├── groups
│ ├── etcd_group_service.go
│ ├── etcd_group_service_test.go
│ ├── group_service.go
│ ├── group_service_test.go
│ ├── memory_group_service.go
│ └── memory_group_service_test.go
├── helpers
│ ├── helpers.go
│ └── type_support.go
├── interfaces
│ ├── interfaces.go
│ └── mocks
│ │ └── interfaces.go
├── kick.go
├── kick_test.go
├── logger
│ ├── interfaces
│ │ └── interfaces.go
│ ├── logger.go
│ ├── logger_test.go
│ ├── logrus
│ │ └── logrus.go
│ └── test
│ │ └── test.go
├── metrics
│ ├── constants.go
│ ├── mocks
│ │ ├── reporter.go
│ │ └── statsd_reporter.go
│ ├── models
│ │ └── models.go
│ ├── prometheus_reporter.go
│ ├── report.go
│ ├── report_test.go
│ ├── reporter_interfaces.go
│ ├── statsd_reporter.go
│ └── statsd_reporter_test.go
├── mocks
│ ├── acceptor.go
│ └── app.go
├── module.go
├── module_test.go
├── modules
│ ├── api_docs_gen.go
│ ├── base.go
│ ├── binary.go
│ ├── binary_test.go
│ ├── binary_windows.go
│ ├── binding_storage.go
│ └── unique_session.go
├── networkentity
│ ├── mocks
│ │ └── networkentity.go
│ └── networkentity.go
├── pipeline
│ ├── pipeline.go
│ └── pipeline_test.go
├── protos
│ ├── bind.pb.go
│ ├── doc.pb.go
│ ├── docmsg.pb.go
│ ├── error.pb.go
│ ├── kick.pb.go
│ ├── mocks
│ │ └── pitaya.go
│ ├── msg.pb.go
│ ├── pitaya.pb.go
│ ├── protodescriptor.pb.go
│ ├── push.pb.go
│ ├── request.pb.go
│ ├── response.pb.go
│ ├── session.pb.go
│ └── test
│ │ ├── somestruct.pb.go
│ │ ├── testrequest.pb.go
│ │ └── testresponse.pb.go
├── push.go
├── push_test.go
├── remote
│ ├── sys.go
│ └── sys_test.go
├── reporters.go
├── route
│ ├── route.go
│ └── route_test.go
├── router
│ ├── router.go
│ └── router_test.go
├── rpc.go
├── rpc_test.go
├── serialize
│ ├── json
│ │ ├── json.go
│ │ └── json_test.go
│ ├── mocks
│ │ └── serializer.go
│ ├── protobuf
│ │ ├── fixtures
│ │ │ └── TestMarshal
│ │ │ │ └── test_ok.golden
│ │ ├── protobuf.go
│ │ └── protobuf_test.go
│ └── serializer.go
├── service
│ ├── base_service.go
│ ├── fixtures
│ │ ├── unmarshal_remote_test_1.golden
│ │ ├── unmarshal_remote_test_2.golden
│ │ └── unmarshal_remote_test_3.golden
│ ├── handler.go
│ ├── handler_pool.go
│ ├── handler_pool_test.go
│ ├── handler_test.go
│ ├── remote.go
│ ├── remote_test.go
│ ├── util.go
│ └── util_test.go
├── session
│ ├── fixtures
│ │ ├── testSessionSetData_1.golden
│ │ ├── testSessionSetData_2.golden
│ │ ├── testSessionSetData_3.golden
│ │ ├── testSessionSetData_4.golden
│ │ ├── testSessionSetData_5.golden
│ │ ├── testUpdateEncodedData_1.golden
│ │ ├── testUpdateEncodedData_2.golden
│ │ ├── testUpdateEncodedData_3.golden
│ │ ├── testUpdateEncodedData_4.golden
│ │ └── testUpdateEncodedData_5.golden
│ ├── mocks
│ │ └── session.go
│ ├── session.go
│ ├── session_test.go
│ ├── static.go
│ └── test
│ │ └── static_test.go
├── static.go
├── static_test.go
├── timer.go
├── timer
│ ├── timer.go
│ └── timer_test.go
├── timer_test.go
├── tracing
│ ├── otel.go
│ ├── span.go
│ └── span_test.go
├── util
│ ├── compression
│ │ ├── compression.go
│ │ ├── compression_test.go
│ │ └── fixtures
│ │ │ ├── compression_deflate_test_1.golden
│ │ │ ├── compression_deflate_test_2.golden
│ │ │ └── compression_deflate_test_3.golden
│ ├── fixtures
│ │ ├── gob_encode_test_1.golden
│ │ ├── gob_encode_test_2.golden
│ │ └── gob_encode_test_3.golden
│ ├── util.go
│ └── util_test.go
└── worker
│ ├── constants.go
│ ├── mocks
│ └── rpc_job.go
│ ├── models.go
│ ├── report.go
│ ├── report_test.go
│ ├── rpc_job.go
│ ├── worker.go
│ └── worker_test.go
├── repl
├── commands.go
├── file.go
├── helpers.go
├── log.go
├── main.go
└── shell.go
└── xk6-pitaya
├── Dockerfile
├── LICENSE
├── README.md
├── client.go
├── examples
└── scenario1.js
├── go.mod
├── go.sum
├── module.go
├── register.go
└── stats.go
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - v2
8 | pull_request:
9 | branches:
10 | - main
11 | - v2
12 |
13 | jobs:
14 | deps:
15 | name: Dependencies
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | - name: Set up Go
21 | uses: actions/setup-go@v5
22 | with:
23 | go-version: '1.23'
24 | - name: Download dependencies
25 | run: go mod download
26 | unit-test:
27 | name: Unit Test
28 | runs-on: ubuntu-latest
29 | needs: deps
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v4
33 | - name: Set up Go
34 | uses: actions/setup-go@v5
35 | with:
36 | go-version: '1.23'
37 | - name: Setup dependencies
38 | env:
39 | GO111MODULE: auto
40 | run: make setup-ci
41 | - name: Run tests
42 | run: make test-coverage
43 | - name: Send coverage
44 | env:
45 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | run: ~/go/bin/goveralls -coverprofile=coverprofile.out -service=github
47 | e2e-test-nats:
48 | name: Nats Test End to End
49 | runs-on: ubuntu-latest
50 | needs: deps
51 | steps:
52 | - name: Checkout
53 | uses: actions/checkout@v4
54 | - name: Set up Go
55 | uses: actions/setup-go@v5
56 | with:
57 | go-version: '1.23'
58 | - name: Run tests
59 | run: make e2e-test-nats
60 | e2e-test-grpc:
61 | name: GRPC Test End to End
62 | runs-on: ubuntu-latest
63 | needs: deps
64 | steps:
65 | - name: Checkout
66 | uses: actions/checkout@v4
67 | - name: Set up Go
68 | uses: actions/setup-go@v5
69 | with:
70 | go-version: '1.23'
71 | - name: Run tests
72 | run: make e2e-test-grpc
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | # Binaries for programs and plugins
3 | *.exe
4 | *.dll
5 | *.so
6 | *.dylib
7 | *.PID
8 |
9 | # Test binary, build with `go test -c`
10 | *.test
11 |
12 | # Output of the go coverage tool, specifically when used with LiteIDE
13 | *.out
14 | .DS_Store
15 |
16 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
17 | .glide/
18 |
19 | .idea/
20 | vendor
21 | debug
22 | .vscode
23 |
24 | # vim temp files
25 | *.swp
26 | *.swo
27 |
28 | examples/testing/server
29 | examples/demo/cluster_grpc/main
30 |
--------------------------------------------------------------------------------
/.gitlab/issue_templates/kind_bug.md:
--------------------------------------------------------------------------------
1 |
4 | ## Versions
5 |
6 | * Pitaya:
7 | * LibPitaya (if relevant):
8 |
9 | ## Summary
10 |
11 |
16 |
17 | ## Steps to reproduce
18 |
19 |
22 |
23 | ## Suggestion
24 |
25 |
28 |
29 |
30 | /label ~"kind/bug"
31 |
32 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "protos"]
2 | path = pitaya-protos
3 | url = https://github.com/topfreegames/pitaya-protos
4 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file for Sphinx projects
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Set the OS, Python version and other tools you might need
8 | build:
9 | os: ubuntu-22.04
10 | tools:
11 | python: "3.12"
12 | # python: "3.12"
13 | # You can also specify other tool versions:
14 | # nodejs: "20"
15 | # rust: "1.70"
16 | golang: "1.23"
17 |
18 | # Build documentation in the "docs/" directory with Sphinx
19 | sphinx:
20 | configuration: docs/conf.py
21 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
22 | # builder: "dirhtml"
23 | # Fail on all warnings to avoid broken references
24 | # fail_on_warning: true
25 |
26 | # Optionally build your docs in additional formats such as PDF and ePub
27 | # formats:
28 | # - pdf
29 | # - epub
30 |
31 | # Optional but recommended, declare the Python requirements required
32 | # to build your documentation
33 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
34 | python:
35 | install:
36 | - requirements: docs/requirements.txt
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 TFG Co and nano Authors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/benchmark/testdata/gen_proto.bat:
--------------------------------------------------------------------------------
1 | protoc --gogofaster_out=. *.proto
2 |
--------------------------------------------------------------------------------
/benchmark/testdata/gen_proto.sh:
--------------------------------------------------------------------------------
1 | protoc --go_out . *.proto
2 |
--------------------------------------------------------------------------------
/benchmark/testdata/test.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package testdata;
3 |
4 | message Ping {
5 | string Content = 1;
6 | }
7 |
8 | message Pong {
9 | string Content = 2;
10 | }
--------------------------------------------------------------------------------
/cmd/repl.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 Wildlife Studios
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package cmd
17 |
18 | import (
19 | "github.com/spf13/cobra"
20 | "github.com/topfreegames/pitaya/v3/repl"
21 | )
22 |
23 | var docsRoute string
24 | var fileName string
25 | var prettyJSON bool
26 |
27 | // replCmd opens pitaya REPL tool
28 | var replCmd = &cobra.Command{
29 | Use: "repl",
30 | Short: "starts pitaya repl tool",
31 | Long: `starts pitaya repl tool`,
32 | Run: func(cmd *cobra.Command, args []string) {
33 | repl.Start(docsRoute, fileName, prettyJSON)
34 | },
35 | }
36 |
37 | func init() {
38 | replCmd.Flags().StringVarP(&docsRoute, "docs", "d", "", "route containing the documentation")
39 | replCmd.Flags().StringVarP(&fileName, "filename", "f", "", "file containing the commands to run")
40 | replCmd.Flags().BoolVarP(&prettyJSON, "pretty", "p", false, "print pretty jsons")
41 | rootCmd.AddCommand(replCmd)
42 | }
43 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 Wildlife Studios
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package cmd
17 |
18 | import (
19 | "github.com/spf13/cobra"
20 | )
21 |
22 | var cfgFile string
23 |
24 | // rootCmd represents the base command when called without any subcommands
25 | var rootCmd = &cobra.Command{
26 | Use: "pitaya",
27 | Short: "Pitaya command line utilities",
28 | Long: `Pitaya command line utilities`,
29 | // Uncomment the following line if your bare application
30 | // has an action associated with it:
31 | // Run: func(cmd *cobra.Command, args []string) { },
32 | }
33 |
34 | // Execute adds all child commands to the root command and sets flags appropriately.
35 | // This is called by main.main(). It only needs to happen once to the rootCmd.
36 | func Execute() {
37 | cobra.CheckErr(rootCmd.Execute())
38 | }
39 |
40 | func init() {
41 | }
42 |
--------------------------------------------------------------------------------
/deploy/kubernetes/example-backend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | app: pitaya-example
6 | name: backend
7 | namespace: pitaya-example
8 | spec:
9 | replicas: 3
10 | selector:
11 | matchLabels:
12 | app: pitaya-example
13 | template:
14 | metadata:
15 | labels:
16 | app: pitaya-example
17 | server: backend
18 | spec:
19 | containers:
20 | - image: quay.io/felipejfc/pitaya-example-sv:v1
21 | command:
22 | - /server
23 | - -type
24 | - game
25 | - -frontend=false
26 | env:
27 | - name: PITAYA_CLUSTER_RPC_CLIENT_NATS_CONNECT
28 | value: nats://nats-cluster-1.nats-io.svc.cluster.local:4222
29 | - name: PITAYA_CLUSTER_RPC_SERVER_NATS_CONNECT
30 | value: nats://nats-cluster-1.nats-io.svc.cluster.local:4222
31 | - name: PITAYA_CLUSTER_SD_ETCD_ENDPOINTS
32 | value: etcd-cluster-1.etcd.svc.cluster.local:2379
33 | - name: PITAYA_HEARTBEAT_INTERVAL
34 | value: 10s
35 | name: backend
36 | resources:
37 | limits:
38 | cpu: 1000m
39 | memory: 300Mi
40 | requests:
41 | cpu: 100m
42 | memory: 200Mi
43 |
--------------------------------------------------------------------------------
/deploy/kubernetes/example-frontend.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | app: pitaya-example
6 | name: frontend
7 | namespace: pitaya-example
8 | spec:
9 | replicas: 3
10 | selector:
11 | matchLabels:
12 | app: pitaya-example
13 | template:
14 | metadata:
15 | labels:
16 | app: pitaya-example
17 | server: frontend
18 | spec:
19 | containers:
20 | - image: quay.io/felipejfc/pitaya-example-sv:v1
21 | command:
22 | - /server
23 | - -type
24 | - connector
25 | - -frontend=true
26 | env:
27 | - name: PITAYA_CLUSTER_RPC_CLIENT_NATS_CONNECT
28 | value: nats://nats-cluster-1.nats-io.svc.cluster.local:4222
29 | - name: PITAYA_CLUSTER_RPC_SERVER_NATS_CONNECT
30 | value: nats://nats-cluster-1.nats-io.svc.cluster.local:4222
31 | - name: PITAYA_CLUSTER_SD_ETCD_ENDPOINTS
32 | value: etcd-cluster-1.etcd.svc.cluster.local:2379
33 | - name: PITAYA_HEARTBEAT_INTERVAL
34 | value: 10s
35 | name: frontend
36 | resources:
37 | limits:
38 | cpu: 1000m
39 | memory: 300Mi
40 | requests:
41 | cpu: 100m
42 | memory: 200Mi
43 | ---
44 | apiVersion: v1
45 | kind: Service
46 | metadata:
47 | labels:
48 | app: pitaya-example
49 | name: frontend
50 | namespace: pitaya-example
51 | spec:
52 | ports:
53 | - name: tcp
54 | port: 32222
55 | protocol: TCP
56 | targetPort: 32222
57 | selector:
58 | app: pitaya-example
59 | server: frontend
60 | type: LoadBalancer
61 |
62 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SPHINXPROJ = Pitaya
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
22 |
--------------------------------------------------------------------------------
/docs/builder.md:
--------------------------------------------------------------------------------
1 | Builder
2 | ===
3 |
4 | Pitaya offers a [`Builder`](../builder.go) object which can be utilized to define a sort of pitaya properties.
5 |
6 | ### PostBuildHooks
7 |
8 | Post-build hooks can be used to execute additional actions automatically after the build process. It also allows you to interact with the built pitaya app.
9 |
10 | A common use case is where it becomes necessary to perform configuration steps in both the pitaya builder and the pitaya app being built. In such cases, an effective approach is to internalize these configurations, enabling you to handle them collectively in a single operation or process. It simplifies the overall configuration process, reducing the need for separate and potentially repetitive steps.
11 |
12 | ```go
13 | // main.go
14 | cfg := config.NewDefaultBuilderConfig()
15 | builder := pitaya.NewDefaultBuilder(isFrontEnd, "my-server-type", pitaya.Cluster, map[string]string{}, *cfg)
16 |
17 | customModule := NewCustomModule(builder)
18 | customModule.ConfigurePitaya(builder)
19 |
20 | app := builder.Build()
21 |
22 | // custom_object.go
23 | type CustomObject struct {
24 | builder *pitaya.Builder
25 | }
26 |
27 | func NewCustomObject(builder *pitaya.Builder) *CustomObject {
28 | return &CustomObject{
29 | builder: builder,
30 | }
31 | }
32 |
33 | func (object *CustomObject) ConfigurePitaya() {
34 | object.builder.AddAcceptor(...)
35 | object.builder.AddPostBuildHook(func (app pitaya.App) {
36 | app.Register(...)
37 | })
38 | }
39 | ```
40 |
41 | In the above example the `ConfigurePitaya` method of the `CustomObject` is adding an `Acceptor` to the pitaya app being built, and also adding a post-build function which will register a handler `Component` that will expose endpoints to receive calls.
42 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 | Example projects can be found [here](https://github.com/topfreegames/pitaya/tree/master/examples/demo)
5 |
6 |
--------------------------------------------------------------------------------
/docs/handshake-validators.md:
--------------------------------------------------------------------------------
1 | Handshake Validators
2 | =====
3 |
4 | Pitaya allows to defined Handshake Validators.
5 |
6 | The primary purpose of these validators is to perform validation checks on the data transmitted by the client. The validators play a crucial role in verifying the integrity and reliability of the client's input before establishing a connection.
7 |
8 | In addition to data validation, handshake validators can also execute other custom logic to assess the client's compliance with the server-defined requirements. This additional logic may involve evaluating factors such as authenticating credentials, permissions, or any other criteria necessary to determine the client's eligibility to access the server.
9 |
10 | ### Adding handshake validators
11 |
12 | To ensure the effective utilization of these validators, they should be added to the `SessionPool` component. As a result, each newly created session within the `SessionPool` will automatically incorporate the designated validators.
13 |
14 | Once the handshake process is initiated, the validators will be invoked to execute their validation routines.
15 |
16 | ```go
17 | cfg := config.NewDefaultBuilderConfig()
18 | builder := pitaya.NewDefaultBuilder(isFrontEnd, "my-server-type", pitaya.Cluster, map[string]string{}, *cfg)
19 | builder.SessionPool.AddHandshakeValidator("MyCustomValidator", func (data *session.HandshakeData) error {
20 | if data.Sys.Version != "1.0.0" {
21 | return errors.New("Unknown client version")
22 | }
23 |
24 | return nil
25 | })
26 | ```
27 |
28 | As a result of the validation process, if an error is encountered, the server will transmit a message to client within the code 400. This code emulates the widely recognized HTTP Bad Request status code, indicating that the client's request could not be fulfilled due to invalid data. Otherwise, if the validation process succeeds, the server will dispatch a message to client containing a code 200, mirroring the HTTP Ok status code.
29 | **Is important to mention that, when there are many validator functions, the validation will stop as soon it encounters the first error.**
30 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Pitaya documentation master file, created by
2 | sphinx-quickstart on Mon Jul 16 17:01:02 2018.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Pitaya's documentation!
7 | ==================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | overview
14 | features
15 | communication
16 | configuration
17 | API
18 | cli
19 | examples
20 | tracing
21 |
22 |
23 | Indices and tables
24 | ==================
25 |
26 | * :ref:`genindex`
27 | * :ref:`modindex`
28 | * :ref:`search`
29 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 | set SPHINXPROJ=Pitaya
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20 | echo.installed, then set the SPHINXBUILD environment variable to point
21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
22 | echo.may add the Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx-markdown-tables==0.0.17
2 | recommonmark==0.7.1
3 | sphinx-rtd-theme==2.0.0
4 | myst-parser==3.0.1
5 |
--------------------------------------------------------------------------------
/docs/tracing.md:
--------------------------------------------------------------------------------
1 | Tracing
2 | =======
3 |
4 | Pitaya supports tracing using [OpenTelemetry](https://opentelemetry.io/).
5 |
6 | ### Using OpenTelemetry tracing
7 |
8 | Pitaya supports tracing using [OpenTelemetry](https://opentelemetry.io/). To enable and configure OpenTelemetry tracing, you can use standard OpenTelemetry environment variables.
9 |
10 | First, make sure to call the `InitializeOtel` function in your main application:
11 |
12 | ```go
13 | func main() {
14 | // ...
15 | err := tracing.InitializeOtel()
16 | if err != nil {
17 | logger.Log.Errorf("Failed to initialize OpenTelemetry: %v", err)
18 | }
19 | // ...
20 | }
21 | ```
22 |
23 | ### Configuration Options
24 |
25 | OpenTelemetry can be configured using standard environment variables. Here are some key variables you might want to set:
26 |
27 | - `OTEL_SERVICE_NAME`: The name of your service.
28 | - `OTEL_EXPORTER_OTLP_ENDPOINT`: The endpoint of your OpenTelemetry collector.
29 | - `OTEL_EXPORTER_OTLP_PROTOCOL`: The protocol to use (e.g., `grpc` or `http/protobuf`).
30 | - `OTEL_TRACES_SAMPLER`: The sampling strategy to use.
31 | - `OTEL_TRACES_SAMPLER_ARG`: The argument for the sampling strategy.
32 | - `OTEL_SDK_DISABLED`: Set to `true` to disable tracing.
33 |
34 | For a complete list of OpenTelemetry environment variables, refer to the [OpenTelemetry specification](https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/).
35 |
36 | ### Testing Locally
37 |
38 | To test OpenTelemetry tracing locally, you can use Jaeger as your tracing backend. First, start Jaeger using Docker:
39 |
40 | ```bash
41 | make run-jaeger-aio
42 | make run-cluster-example-frontend-tracing
43 | make run-cluster-example-backend-tracing
44 | ```
45 |
46 | The last two commands will run your Pitaya servers with OpenTelemetry configured with the following envs:
47 |
48 | ```bash
49 | OTEL_SERVICE_NAME=example-frontend OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 OTEL_EXPORTER_OTLP_PROTOCOL=grpc OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG="1"
50 |
51 | OTEL_SERVICE_NAME=example-backend OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 OTEL_EXPORTER_OTLP_PROTOCOL=grpc OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG="1"
52 | ```
53 |
54 | Access the Jaeger UI at http://localhost:16686 to view and analyze your traces.
--------------------------------------------------------------------------------
/examples/demo/chat/README.md:
--------------------------------------------------------------------------------
1 | # pitaya-chat-demo
2 | chat room demo base on [pitaya](https://github.com/topfreegames/pitaya) in 100 lines
3 |
4 | refs: https://github.com/topfreegames/pitaya
5 |
6 | ## Required
7 | - golang
8 | - websocket
9 |
10 | ## Run
11 | ```
12 | docker compose -f ../../testing/docker-compose.yml up -d etcd nats
13 | go run main.go
14 | ```
15 |
16 | open browser => http://localhost:3251/web/
17 |
--------------------------------------------------------------------------------
/examples/demo/chat/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Chat Demo
6 |
7 |
8 |
18 |
19 |
20 |
21 |
22 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/examples/demo/cluster_grpc/services/connector.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/topfreegames/pitaya/v3/examples/demo/protos"
8 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
9 | "github.com/topfreegames/pitaya/v3/pkg/component"
10 | )
11 |
12 | // ConnectorRemote is a remote that will receive rpc's
13 | type ConnectorRemote struct {
14 | component.Base
15 | }
16 |
17 | // Connector struct
18 | type Connector struct {
19 | component.Base
20 | app pitaya.Pitaya
21 | }
22 |
23 | // SessionData struct
24 | type SessionData struct {
25 | Data map[string]interface{}
26 | }
27 |
28 | // Response struct
29 | type Response struct {
30 | Code int32
31 | Msg string
32 | }
33 |
34 | // NewConnector ctor
35 | func NewConnector(app pitaya.Pitaya) *Connector {
36 | return &Connector{app: app}
37 | }
38 |
39 | func reply(code int32, msg string) (*Response, error) {
40 | res := &Response{
41 | Code: code,
42 | Msg: msg,
43 | }
44 | return res, nil
45 | }
46 |
47 | // GetSessionData gets the session data
48 | func (c *Connector) GetSessionData(ctx context.Context) (*SessionData, error) {
49 | s := c.app.GetSessionFromCtx(ctx)
50 | res := &SessionData{
51 | Data: s.GetData(),
52 | }
53 | return res, nil
54 | }
55 |
56 | // SetSessionData sets the session data
57 | func (c *Connector) SetSessionData(ctx context.Context, data *SessionData) (*Response, error) {
58 | s := c.app.GetSessionFromCtx(ctx)
59 | err := s.SetData(data.Data)
60 | if err != nil {
61 | return nil, pitaya.Error(err, "CN-000", map[string]string{"failed": "set data"})
62 | }
63 | return reply(200, "success")
64 | }
65 |
66 | // NotifySessionData sets the session data
67 | func (c *Connector) NotifySessionData(ctx context.Context, data *SessionData) {
68 | s := c.app.GetSessionFromCtx(ctx)
69 | err := s.SetData(data.Data)
70 | if err != nil {
71 | fmt.Println("got error on notify", err)
72 | }
73 | }
74 |
75 | // SendPushToUser sends a push to a user
76 | func (c *Connector) SendPushToUser(ctx context.Context, msg *UserMessage) (*Response, error) {
77 | _, err := c.app.SendPushToUsers("onMessage", msg, []string{"2"}, "connector")
78 | if err != nil {
79 | return nil, err
80 | }
81 | return &Response{
82 | Code: 200,
83 | Msg: "boa",
84 | }, nil
85 | }
86 |
87 | // RemoteFunc is a function that will be called remotely
88 | func (c *ConnectorRemote) RemoteFunc(ctx context.Context, msg *protos.RPCMsg) (*protos.RPCRes, error) {
89 | fmt.Printf("received a remote call with this message: %s\n", msg)
90 | return &protos.RPCRes{
91 | Msg: fmt.Sprintf("received msg: %s", msg.GetMsg()),
92 | }, nil
93 | }
94 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/README.md:
--------------------------------------------------------------------------------
1 | Custom Metrics Example
2 | =======================
3 |
4 | # Run
5 | `make run-custom-metrics-example`
6 |
7 | # Call routes
8 | By using [pitaya-cli](https://github.com/topfreegames/pitaya-cli), call:
9 | ```
10 | connect localhost:3250
11 | request room.room.setcounter {"value": 1.0, "tag1": "value1", "tag2": "value2"}
12 | request room.room.setgauge1 {"value": 1.0, "tag1": "value1"}
13 | request room.room.setgauge2 {"value": 1.0, "tag2": "value2"}
14 | request room.room.setsummary {"value": 1.0, "tag1": "value1"}
15 | ```
16 |
17 | Check out the results on `curl localhost:9090/metrics | grep 'my_'`
18 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/config.yaml:
--------------------------------------------------------------------------------
1 | pitaya:
2 | metrics:
3 | prometheus:
4 | enabled: true
5 | custom:
6 | counters:
7 | - subsystem: room
8 | name: my_counter
9 | help: a counter example
10 | labels: ['tag1', 'tag2']
11 | gauges:
12 | - subsystem: room
13 | name: my_gauge_1
14 | help: a gauge example
15 | labels: ['tag1']
16 | - subsystem: room
17 | name: my_gauge_2
18 | help: a gauge example
19 | labels: ['tag2']
20 | summaries:
21 | - subsystem: room
22 | name: my_summary
23 | help: a summary example
24 | objectives: {0.7: 0.05, 0.9: 0.01, 0.99: 0.001}
25 | labels: ['tag1']
26 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/spf13/viper"
9 | "github.com/topfreegames/pitaya/v3/examples/demo/custom_metrics/services"
10 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
11 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
12 | "github.com/topfreegames/pitaya/v3/pkg/component"
13 | "github.com/topfreegames/pitaya/v3/pkg/config"
14 | )
15 |
16 | var app pitaya.Pitaya
17 |
18 | func main() {
19 | port := flag.Int("port", 3250, "the port to listen")
20 | svType := "room"
21 | isFrontend := true
22 |
23 | flag.Parse()
24 |
25 | cfg := viper.New()
26 | cfg.AddConfigPath(".")
27 | cfg.SetConfigName("config")
28 | err := cfg.ReadInConfig()
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | tcp := acceptor.NewTCPAcceptor(fmt.Sprintf(":%d", *port))
34 |
35 | conf := config.NewConfig(cfg)
36 | builder := pitaya.NewBuilderWithConfigs(isFrontend, svType, pitaya.Cluster, map[string]string{}, conf)
37 | builder.AddAcceptor(tcp)
38 | app = builder.Build()
39 |
40 | defer app.Shutdown()
41 |
42 | app.Register(services.NewRoom(app),
43 | component.WithName("room"),
44 | component.WithNameFunc(strings.ToLower),
45 | )
46 |
47 | app.Start()
48 | }
49 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/messages/args.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | // SetCounterArg is the argument on room.setcounter handler
4 | type SetCounterArg struct {
5 | Value float64
6 | Tag1 string
7 | Tag2 string
8 | }
9 |
10 | // SetGaugeArg is the argument on room.setgauge* handler
11 | type SetGaugeArg struct {
12 | Value float64
13 | Tag string
14 | }
15 |
16 | // SetSummaryArg is the argument on room.setsummary handler
17 | type SetSummaryArg struct {
18 | Value float64
19 | Tag string
20 | }
21 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/messages/responses.go:
--------------------------------------------------------------------------------
1 | package messages
2 |
3 | // Response is the basic response on handlers
4 | type Response struct {
5 | Code int
6 | }
7 |
8 | // OKResponse returns a response with code 200
9 | func OKResponse() *Response {
10 | return &Response{Code: 200}
11 | }
12 |
--------------------------------------------------------------------------------
/examples/demo/custom_metrics/services/room.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/topfreegames/pitaya/v3/examples/demo/custom_metrics/messages"
7 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
8 | "github.com/topfreegames/pitaya/v3/pkg/component"
9 | )
10 |
11 | // Room server
12 | type Room struct {
13 | component.Base
14 | app pitaya.Pitaya
15 | }
16 |
17 | // NewRoom ctor
18 | func NewRoom(app pitaya.Pitaya) *Room {
19 | return &Room{
20 | app: app,
21 | }
22 | }
23 |
24 | // SetCounter sets custom my_counter
25 | func (r *Room) SetCounter(
26 | ctx context.Context,
27 | arg *messages.SetCounterArg,
28 | ) (*messages.Response, error) {
29 | counterMetricName := "my_counter"
30 |
31 | for _, reporter := range r.app.GetMetricsReporters() {
32 | reporter.ReportCount(counterMetricName, map[string]string{
33 | "tag1": arg.Tag1,
34 | "tag2": arg.Tag2,
35 | }, arg.Value)
36 | }
37 |
38 | return messages.OKResponse(), nil
39 | }
40 |
41 | // SetGauge1 sets custom my_gauge_1
42 | func (r *Room) SetGauge1(
43 | ctx context.Context,
44 | arg *messages.SetGaugeArg,
45 | ) (*messages.Response, error) {
46 | counterMetricName := "my_gauge_1"
47 |
48 | for _, reporter := range r.app.GetMetricsReporters() {
49 | reporter.ReportGauge(counterMetricName, map[string]string{
50 | "tag1": arg.Tag,
51 | }, arg.Value)
52 | }
53 |
54 | return messages.OKResponse(), nil
55 | }
56 |
57 | // SetGauge2 sets custom my_gauge_2
58 | func (r *Room) SetGauge2(
59 | ctx context.Context,
60 | arg *messages.SetGaugeArg,
61 | ) (*messages.Response, error) {
62 | counterMetricName := "my_gauge_2"
63 |
64 | for _, reporter := range r.app.GetMetricsReporters() {
65 | reporter.ReportGauge(counterMetricName, map[string]string{
66 | "tag2": arg.Tag,
67 | }, arg.Value)
68 | }
69 |
70 | return messages.OKResponse(), nil
71 | }
72 |
73 | // SetSummary sets custom my_summary
74 | func (r *Room) SetSummary(
75 | ctx context.Context,
76 | arg *messages.SetSummaryArg,
77 | ) (*messages.Response, error) {
78 | counterMetricName := "my_summary"
79 |
80 | for _, reporter := range r.app.GetMetricsReporters() {
81 | reporter.ReportSummary(counterMetricName, map[string]string{
82 | "tag1": arg.Tag,
83 | }, arg.Value)
84 | }
85 |
86 | return messages.OKResponse(), nil
87 | }
88 |
--------------------------------------------------------------------------------
/examples/demo/protos/cluster.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package cluster_protos;
4 | option go_package = "examples/demo/protos";
5 |
6 | // RPCMsg message to be sent using rpc
7 | message RPCMsg {
8 | string Msg = 1;
9 | }
10 |
11 | // RPCRes is the rpc response
12 | message RPCRes {
13 | string Msg = 1;
14 | }
15 |
16 | // UserMessage represents a message that user sent
17 | message UserMessage {
18 | string name = 1;
19 | string content = 2;
20 | }
21 |
22 | // Stats exports the room status
23 | message Stats {
24 | int64 outbound_bytes = 1;
25 | int64 inbound_bytes = 2;
26 | }
27 |
28 | // SendRPCMsg represents a rpc message
29 | message SendRPCMsg {
30 | string server_id = 1;
31 | string route = 2;
32 | string msg = 3;
33 | }
34 |
35 | // NewUser message will be received when new user join room
36 | message NewUser {
37 | string content = 1;
38 | }
39 |
40 | // AllMembers contains all members uid
41 | message AllMembers {
42 | repeated string Members = 1;
43 | }
44 |
45 | // JoinResponse represents the result of joining room
46 | message JoinResponse {
47 | int64 code = 1;
48 | string result = 2;
49 | }
50 |
51 | // Response struct
52 | message Response {
53 | int32 code = 1;
54 | string msg = 2;
55 | }
56 |
--------------------------------------------------------------------------------
/examples/demo/rate_limiting/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "strings"
7 | "time"
8 |
9 | "github.com/spf13/viper"
10 | "github.com/topfreegames/pitaya/v3/examples/demo/rate_limiting/services"
11 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
12 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
13 | "github.com/topfreegames/pitaya/v3/pkg/acceptorwrapper"
14 | "github.com/topfreegames/pitaya/v3/pkg/component"
15 | "github.com/topfreegames/pitaya/v3/pkg/config"
16 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
17 | )
18 |
19 | func createAcceptor(port int, reporters []metrics.Reporter) acceptor.Acceptor {
20 |
21 | // 5 requests in 1 minute. Doesn't make sense, just to test
22 | // rate limiting
23 | vConfig := viper.New()
24 | vConfig.Set("pitaya.conn.ratelimiting.limit", 5)
25 | vConfig.Set("pitaya.conn.ratelimiting.interval", time.Minute)
26 | pConfig := config.NewConfig(vConfig)
27 |
28 | rateLimitConfig := config.NewPitayaConfig(pConfig).Conn.RateLimiting
29 |
30 | tcp := acceptor.NewTCPAcceptor(fmt.Sprintf(":%d", port))
31 | return acceptorwrapper.WithWrappers(
32 | tcp,
33 | acceptorwrapper.NewRateLimitingWrapper(reporters, rateLimitConfig))
34 | }
35 |
36 | var app pitaya.Pitaya
37 |
38 | func main() {
39 | port := flag.Int("port", 3250, "the port to listen")
40 | svType := "room"
41 |
42 | flag.Parse()
43 |
44 | config := config.NewDefaultPitayaConfig()
45 | builder := pitaya.NewDefaultBuilder(true, svType, pitaya.Cluster, map[string]string{}, *config)
46 | builder.AddAcceptor(createAcceptor(*port, builder.MetricsReporters))
47 |
48 | app = builder.Build()
49 |
50 | defer app.Shutdown()
51 |
52 | room := services.NewRoom()
53 | app.Register(room,
54 | component.WithName("room"),
55 | component.WithNameFunc(strings.ToLower),
56 | )
57 |
58 | app.Start()
59 | }
60 |
--------------------------------------------------------------------------------
/examples/demo/rate_limiting/services/room.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/topfreegames/pitaya/v3/pkg/component"
7 | )
8 |
9 | // Room represents a component that contains a bundle of room related handler
10 | type Room struct {
11 | component.Base
12 | }
13 |
14 | // NewRoom returns a new room
15 | func NewRoom() *Room {
16 | return &Room{}
17 | }
18 |
19 | // Ping returns a pong
20 | func (r *Room) Ping(ctx context.Context) ([]byte, error) {
21 | return []byte("pong"), nil
22 | }
23 |
--------------------------------------------------------------------------------
/examples/demo/worker/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 |
7 | "strings"
8 |
9 | "github.com/spf13/viper"
10 | "github.com/topfreegames/pitaya/v3/examples/demo/worker/services"
11 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
12 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
13 | "github.com/topfreegames/pitaya/v3/pkg/component"
14 | "github.com/topfreegames/pitaya/v3/pkg/config"
15 | )
16 |
17 | var app pitaya.Pitaya
18 |
19 | func configureWorker() {
20 | worker := services.Worker{}
21 | worker.Configure(app)
22 | }
23 |
24 | func main() {
25 | port := flag.Int("port", 3250, "the port to listen")
26 | svType := flag.String("type", "metagame", "the server type")
27 | isFrontend := flag.Bool("frontend", true, "if server is frontend")
28 |
29 | flag.Parse()
30 |
31 | conf := viper.New()
32 | conf.SetDefault("pitaya.worker.redis.url", "localhost:6379")
33 | conf.SetDefault("pitaya.worker.redis.pool", "3")
34 |
35 | config := config.NewConfig(conf)
36 |
37 | tcp := acceptor.NewTCPAcceptor(fmt.Sprintf(":%d", *port))
38 |
39 | builder := pitaya.NewBuilderWithConfigs(*isFrontend, *svType, pitaya.Cluster, map[string]string{}, config)
40 | if *isFrontend {
41 | builder.AddAcceptor(tcp)
42 | }
43 | app = builder.Build()
44 |
45 | defer app.Shutdown()
46 |
47 | switch *svType {
48 | case "metagame":
49 | app.RegisterRemote(&services.Metagame{},
50 | component.WithName("metagame"),
51 | component.WithNameFunc(strings.ToLower),
52 | )
53 | case "room":
54 | app.Register(services.NewRoom(app),
55 | component.WithName("room"),
56 | component.WithNameFunc(strings.ToLower),
57 | )
58 | case "worker":
59 | configureWorker()
60 | }
61 |
62 | app.Start()
63 | }
64 |
--------------------------------------------------------------------------------
/examples/demo/worker/protos/arg.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: arg.proto
3 |
4 | package protos
5 |
6 | import proto "github.com/golang/protobuf/proto"
7 | import fmt "fmt"
8 | import math "math"
9 |
10 | // Reference imports to suppress errors if they are not otherwise used.
11 | var _ = proto.Marshal
12 | var _ = fmt.Errorf
13 | var _ = math.Inf
14 |
15 | // This is a compile-time assertion to ensure that this generated file
16 | // is compatible with the proto package it is being compiled against.
17 | // A compilation error at this line likely means your copy of the
18 | // proto package needs to be updated.
19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
20 |
21 | type Arg struct {
22 | Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
23 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
24 | XXX_unrecognized []byte `json:"-"`
25 | XXX_sizecache int32 `json:"-"`
26 | }
27 |
28 | func (m *Arg) Reset() { *m = Arg{} }
29 | func (m *Arg) String() string { return proto.CompactTextString(m) }
30 | func (*Arg) ProtoMessage() {}
31 | func (*Arg) Descriptor() ([]byte, []int) {
32 | return fileDescriptor_arg_e1b1c8888ddf9d6e, []int{0}
33 | }
34 | func (m *Arg) XXX_Unmarshal(b []byte) error {
35 | return xxx_messageInfo_Arg.Unmarshal(m, b)
36 | }
37 | func (m *Arg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
38 | return xxx_messageInfo_Arg.Marshal(b, m, deterministic)
39 | }
40 | func (dst *Arg) XXX_Merge(src proto.Message) {
41 | xxx_messageInfo_Arg.Merge(dst, src)
42 | }
43 | func (m *Arg) XXX_Size() int {
44 | return xxx_messageInfo_Arg.Size(m)
45 | }
46 | func (m *Arg) XXX_DiscardUnknown() {
47 | xxx_messageInfo_Arg.DiscardUnknown(m)
48 | }
49 |
50 | var xxx_messageInfo_Arg proto.InternalMessageInfo
51 |
52 | func (m *Arg) GetMsg() string {
53 | if m != nil {
54 | return m.Msg
55 | }
56 | return ""
57 | }
58 |
59 | func init() {
60 | proto.RegisterType((*Arg)(nil), "protos.Arg")
61 | }
62 |
63 | func init() { proto.RegisterFile("arg.proto", fileDescriptor_arg_e1b1c8888ddf9d6e) }
64 |
65 | var fileDescriptor_arg_e1b1c8888ddf9d6e = []byte{
66 | // 66 bytes of a gzipped FileDescriptorProto
67 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0x4a, 0xd7,
68 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0x53, 0xc5, 0x4a, 0xe2, 0x5c, 0xcc, 0x8e, 0x45,
69 | 0xe9, 0x42, 0x02, 0x5c, 0xcc, 0xb9, 0xc5, 0xe9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20,
70 | 0x66, 0x12, 0x44, 0x81, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x57, 0x6c, 0xa7, 0xb1, 0x34, 0x00,
71 | 0x00, 0x00,
72 | }
73 |
--------------------------------------------------------------------------------
/examples/demo/worker/protos/arg.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package protos;
4 |
5 | message Arg {
6 | string msg = 1;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/demo/worker/protos/response.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package protos;
4 |
5 | message Response {
6 | int32 code = 1;
7 | string msg = 2;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/demo/worker/services/metagame.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/topfreegames/pitaya/v3/examples/demo/worker/protos"
7 | "github.com/topfreegames/pitaya/v3/pkg/component"
8 | "github.com/topfreegames/pitaya/v3/pkg/logger"
9 | )
10 |
11 | // Metagame server
12 | type Metagame struct {
13 | component.Base
14 | }
15 |
16 | // LogRemote logs argument when called
17 | func (m *Metagame) LogRemote(ctx context.Context, arg *protos.Arg) (*protos.Response, error) {
18 | logger.Log.Infof("argument %+v\n", arg)
19 | return &protos.Response{Code: 200, Msg: "ok"}, nil
20 | }
21 |
--------------------------------------------------------------------------------
/examples/demo/worker/services/room.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/topfreegames/pitaya/v3/examples/demo/worker/protos"
7 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
8 | "github.com/topfreegames/pitaya/v3/pkg/component"
9 | "github.com/topfreegames/pitaya/v3/pkg/logger"
10 | )
11 |
12 | // Room server
13 | type Room struct {
14 | component.Base
15 | app pitaya.Pitaya
16 | }
17 |
18 | // NewRoom ctor
19 | func NewRoom(app pitaya.Pitaya) *Room {
20 | return &Room{app: app}
21 | }
22 |
23 | // CallLog makes ReliableRPC to metagame LogRemote
24 | func (r *Room) CallLog(ctx context.Context, arg *protos.Arg) (*protos.Response, error) {
25 | route := "metagame.metagame.logremote"
26 | reply := &protos.Response{}
27 | jid, err := r.app.ReliableRPC(route, nil, reply, arg)
28 | if err != nil {
29 | logger.Log.Infof("failed to enqueue rpc: %q", err)
30 | return nil, err
31 | }
32 |
33 | logger.Log.Infof("enqueue rpc job: %d", jid)
34 | return &protos.Response{Code: 200, Msg: "ok"}, nil
35 | }
36 |
--------------------------------------------------------------------------------
/examples/demo/worker/services/worker.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/golang/protobuf/proto"
7 | "github.com/topfreegames/pitaya/v3/examples/demo/worker/protos"
8 | pitaya "github.com/topfreegames/pitaya/v3/pkg"
9 | "github.com/topfreegames/pitaya/v3/pkg/component"
10 | )
11 |
12 | // Worker server
13 | type Worker struct {
14 | component.Base
15 | }
16 |
17 | // Configure starts workers and register rpc job
18 | func (w *Worker) Configure(app pitaya.Pitaya) {
19 | app.StartWorker()
20 | app.RegisterRPCJob(&RPCJob{app: app})
21 | }
22 |
23 | // RPCJob implements worker.RPCJob
24 | type RPCJob struct {
25 | app pitaya.Pitaya
26 | }
27 |
28 | // ServerDiscovery returns a serverID="", meaning any server
29 | // is ok
30 | func (r *RPCJob) ServerDiscovery(
31 | route string,
32 | rpcMetadata map[string]interface{},
33 | ) (serverID string, err error) {
34 | return "", nil
35 | }
36 |
37 | // RPC calls pitaya's rpc
38 | func (r *RPCJob) RPC(
39 | ctx context.Context,
40 | serverID, routeStr string,
41 | reply, arg proto.Message,
42 | ) error {
43 | return r.app.RPCTo(ctx, serverID, routeStr, reply, arg)
44 | }
45 |
46 | // GetArgReply returns reply and arg of LogRemote,
47 | // since we have no other methods in this example
48 | func (r *RPCJob) GetArgReply(
49 | route string,
50 | ) (arg, reply proto.Message, err error) {
51 | return &protos.Arg{}, &protos.Response{}, nil
52 | }
53 |
--------------------------------------------------------------------------------
/examples/testing/docker-compose-jaeger.yml:
--------------------------------------------------------------------------------
1 | services:
2 | jaeger:
3 | image: jaegertracing/all-in-one:latest
4 | ports:
5 | - "16686:16686"
6 | - "14268:14268"
7 | - "6831:6831/udp"
8 | - "6832:6832/udp"
9 | - "4317:4317"
10 | - "4318:4318"
11 | environment:
12 | - COLLECTOR_OTLP_ENABLED=true
13 | - COLLECTOR_OTLP_HTTP_HOST_PORT=0.0.0.0:4318
14 | - COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317
15 | - LOG_LEVEL=debug
16 |
--------------------------------------------------------------------------------
/examples/testing/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | nats:
3 | image: nats
4 | ports:
5 | - 4222:4222
6 | etcd:
7 | image: bitnami/etcd
8 | environment:
9 | - ALLOW_NONE_AUTHENTICATION=yes
10 | ports:
11 | - 2379:2379
12 | redis:
13 | image: redis
14 | ports:
15 | - 6379:6379
16 |
--------------------------------------------------------------------------------
/examples/testing/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.7
2 |
3 | ADD ./server /server
4 |
5 | CMD /server
6 |
--------------------------------------------------------------------------------
/examples/testing/protos/cluster.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package protos;
4 |
5 | // TestRequest message to be sent using rpc
6 | message TestRequest {
7 | string msg = 1;
8 | }
9 |
10 | // TestResponse is the rpc response
11 | message TestResponse {
12 | int32 code = 1;
13 | string msg = 2;
14 | }
15 |
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.23
2 |
3 | toolchain go1.23.4
4 |
5 | use (
6 | .
7 | ./xk6-pitaya
8 | )
9 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 Wildlife Studios
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import "github.com/topfreegames/pitaya/v3/cmd"
20 |
21 | func main() {
22 | cmd.Execute()
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/acceptor/acceptor.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptor
22 |
23 | import "net"
24 |
25 | // PlayerConn iface
26 | type PlayerConn interface {
27 | GetNextMessage() (b []byte, err error)
28 | RemoteAddr() net.Addr
29 | net.Conn
30 | }
31 |
32 | // Acceptor type interface
33 | type Acceptor interface {
34 | ListenAndServe()
35 | Stop()
36 | GetAddr() string
37 | GetConnChan() chan PlayerConn
38 | EnableProxyProtocol()
39 | IsRunning() bool
40 | GetConfiguredAddress() string
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/acceptor/fixtures/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDdDCCAlwCCQDfEyb9MASNvjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJC
3 | UjELMAkGA1UECAwCU1AxCzAJBgNVBAcMAlNQMQ8wDQYDVQQKDAZQaXRheWExEDAO
4 | BgNVBAsMB0JhY2tlbmQxEjAQBgNVBAMMCWxvY2FsaG9zdDEcMBoGCSqGSIb3DQEJ
5 | ARYNcGl0YXlhQHBpdGF5YTAeFw0xODA0MTAyMjEwMTNaFw0yODA0MDcyMjEwMTNa
6 | MHwxCzAJBgNVBAYTAkJSMQswCQYDVQQIDAJTUDELMAkGA1UEBwwCU1AxDzANBgNV
7 | BAoMBlBpdGF5YTEQMA4GA1UECwwHQmFja2VuZDESMBAGA1UEAwwJbG9jYWxob3N0
8 | MRwwGgYJKoZIhvcNAQkBFg1waXRheWFAcGl0YXlhMIIBIjANBgkqhkiG9w0BAQEF
9 | AAOCAQ8AMIIBCgKCAQEArUveZUOOW2Yl92KpvaAYCwjGy3krUbeSEC0m/FbogWz6
10 | 3PhFNsobKwp4z8UrgJEGQJZTpA4XU4O1MIw9f+u7qbdJxn4sM9aeYyw5ks7VdgdQ
11 | Wy70EJ2qOElhq2I7n0/Dg6cYmOVYif4BHq5mlVYCujKcpsU+bscPaTqxHu+/4QUH
12 | cBAkcm6nC2ucRZsSdTD/M5uxatSjJK6ustgUN3TLMzFSRC6/kvS2/dwIgWeePnID
13 | nFZaAWxq4tlE3oG2aIH7oRMSDpRj29d9DjvoQUPSdnADbkq6+n3GKPPqLo3ArQxx
14 | gvfKKQFgGyTtTUKZQ95BJCFE+SDIxTc/9YxcTsdxDQIDAQABMA0GCSqGSIb3DQEB
15 | CwUAA4IBAQApWiKBLHDrDuwUMS1OESzr1obWesaA3hGXtgEn31+tr0bNeV1F/ZjZ
16 | KhUBGm4kcLERuc7w+J268PDyU3C8tf4ar5NFgAbUgfE2mD6+LGTsSdS06fJVakjl
17 | v1i4mK6eijC3XkWyP8yIVJRSn+uvR7PGXmRFJoQIv96y918UYLgwvh/do4P/JZYy
18 | VNR99XvznijV7Hq0aP4W742PjWjj5/cPFnRJOMNPKFupZvYaKWVv2c1IO4HKkCQ4
19 | FRyO6PIxgNDGOz8Bkc4Z7CMbOyBRpElvOFAE5hsxIufn8DgRkQahfiBOjqpSHq73
20 | eyuaAd8sURZSDx8ToT2loVh+dlSsqtNI
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/pkg/acceptor/fixtures/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtS95lQ45bZiX3
3 | Yqm9oBgLCMbLeStRt5IQLSb8VuiBbPrc+EU2yhsrCnjPxSuAkQZAllOkDhdTg7Uw
4 | jD1/67upt0nGfiwz1p5jLDmSztV2B1BbLvQQnao4SWGrYjufT8ODpxiY5ViJ/gEe
5 | rmaVVgK6MpymxT5uxw9pOrEe77/hBQdwECRybqcLa5xFmxJ1MP8zm7Fq1KMkrq6y
6 | 2BQ3dMszMVJELr+S9Lb93AiBZ54+cgOcVloBbGri2UTegbZogfuhExIOlGPb130O
7 | O+hBQ9J2cANuSrr6fcYo8+oujcCtDHGC98opAWAbJO1NQplD3kEkIUT5IMjFNz/1
8 | jFxOx3ENAgMBAAECggEBAIglsvObv5vPBMT2nqR7wmfo3UW+TGpG7loHHzngjYoh
9 | NHWmF7qTzgRilcemACdhyKe1csDQ0UKrlw4tH6QAb89GUI/BULjpREZl2FWeadXw
10 | qRD6MoVbWQAfprwe0Pi4kcI85PY6OPUwuXCBU9a/XCUs3iWnkQU3HsRtd/n2IamG
11 | HooKGz5Ij1+FaB2dKUiwYvxsdA9w2Yg54VuuLce/6VgnR79eYEseGvN44Y68JAsU
12 | nwzsXv9eVmn8T22/uWaaDPOaIwysVNI5YI+WyfcI9HQIaJxVAPggoZh4N6pnD6UM
13 | BjI+EJWD3Aiq1Z6CDG4c2FV4Ku0f/l456ykvZrosbAECgYEA3eMTLC0l7H+Msndz
14 | aUzimKqstPKBWqyJ3Z6sXYGO26/z3mZjcl1+A4gZGWegptMk6PsP27jevWRVPuNi
15 | o/eagGcJ3wpNISkwoJgvkhPDmFSFH+d74yCQQRgPsuJbyaHX+RiDTj4sWgBWoq+T
16 | UR9GQxFmGObklMmBF0o/VeU9XjUCgYEAx/BixVC4gvqBKZdlhLF8e6HWFXhNCIsj
17 | 0H6I5vgtSex8I/Zsy320kA0hfzZEuaaGVNrQiYWBAAdAwTKS3W13X8ryBDS6mrCs
18 | BKuM7PDJrS4vKVmiTqMwDpEpL/c9SoOCiPiseZeteoHfKkVsPoQrc2bQzvfttQa6
19 | aHRvyjkTgnkCgYByYbRoeX2rRLVK6rjb9354JMOTI8/65ibL0Bgau8bhCPs2EqIB
20 | OqDTQT1vEzSFyyKj14h9Q/fOugIXwTAARoum1XcJO043YfxnTQx4ySdzR16466O5
21 | mffDFpxBIt8eOggTqMdHdlV2r+X5R3kxwGV//WAcRipfaODbGLM9nEbeYQKBgQCL
22 | JJ8IZLHT7VQARu2OmBpiy/D6RhuOK48EJVtPKj4SaTLHsFJsl5IWghzitDjF3r8z
23 | xIhGfJOXGnUVPwX4dZsTHmCpTqzixLsiEOVla/levXpy039iLK1gJeO9DtonxjgM
24 | 7MrTYByJ2mIdv+yh4Ud/63i74M0cI4+M7CN0X55VOQKBgDw0A5cdenE5VIH2nEsx
25 | SWQ8nSz2qli+59sUcRfDnAWT7iJ5DCzxOVV+wQ/A3D/SbufWJQZpJBSJlXnyY1BX
26 | qFiOrseNsU2R0EDmi8agdgVBqxX/NmF9+Np0G3dFyoeYI68BwpiteCa/Aw2HzPl5
27 | 2SJrVnL52ZOK5WAumUbuWQqA
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/pkg/acceptor/proxyprotowrapper.go:
--------------------------------------------------------------------------------
1 | package acceptor
2 |
3 | import (
4 | "net"
5 | "sync"
6 |
7 | "github.com/mailgun/proxyproto"
8 | "github.com/topfreegames/pitaya/v3/pkg/logger"
9 | )
10 |
11 | // Listener is used to wrap an underlying listener,
12 | // whose connections may be using the HAProxy Proxy Protocol.
13 | // If the connection is using the protocol, the RemoteAddr() will return
14 | // the correct client address.
15 | type ProxyProtocolListener struct {
16 | net.Listener
17 | proxyProtocolEnabled *bool
18 | }
19 |
20 | // Accept waits for and returns the next connection to the listener.
21 | func (p *ProxyProtocolListener) Accept() (net.Conn, error) {
22 | // Get the underlying connection
23 | conn, err := p.Listener.Accept()
24 | if err != nil {
25 | return nil, err
26 | }
27 | connP := &Conn{Conn: conn, proxyProtocolEnabled: p.proxyProtocolEnabled}
28 | if *p.proxyProtocolEnabled {
29 | err = connP.checkPrefix()
30 | if err != nil {
31 | return connP, err
32 | }
33 | }
34 | return connP, nil
35 | }
36 |
37 | // Conn is used to wrap and underlying connection which
38 | // may be speaking the Proxy Protocol. If it is, the RemoteAddr() will
39 | // return the address of the client instead of the proxy address.
40 | type Conn struct {
41 | net.Conn
42 | dstAddr *net.Addr
43 | srcAddr *net.Addr
44 | once sync.Once
45 | proxyProtocolEnabled *bool
46 | }
47 |
48 | func (p *Conn) LocalAddr() net.Addr {
49 | if p.dstAddr != nil {
50 | return *p.dstAddr
51 | }
52 | return p.Conn.LocalAddr()
53 | }
54 |
55 | // RemoteAddr returns the address of the client if the proxy
56 | // protocol is being used, otherwise just returns the address of
57 | // the socket peer. If there is an error parsing the header, the
58 | // address of the client is not returned, and the socket is closed.
59 | // Once implication of this is that the call could block if the
60 | // client is slow. Using a Deadline is recommended if this is called
61 | // before Read()
62 | func (p *Conn) RemoteAddr() net.Addr {
63 | if p.srcAddr != nil {
64 | return *p.srcAddr
65 | }
66 | return p.Conn.RemoteAddr()
67 | }
68 |
69 | func (p *Conn) checkPrefix() error {
70 |
71 | h, err := proxyproto.ReadHeader(p)
72 | if err != nil {
73 | logger.Log.Errorf("Failed to read Proxy Protocol TCP header: %s", err.Error())
74 | p.Close()
75 | return err
76 |
77 | } else if h.Source == nil {
78 | p.Close()
79 | } else {
80 | p.srcAddr = &h.Source
81 | p.dstAddr = &h.Destination
82 | }
83 |
84 | return nil
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/acceptorwrapper/base.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptorwrapper
22 |
23 | import (
24 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
25 | )
26 |
27 | // BaseWrapper implements Wrapper by saving the acceptor as an attribute.
28 | // Conns from acceptor.GetConnChan are processed by wrapConn and
29 | // forwarded to its own connChan.
30 | // Any new wrapper can inherit from BaseWrapper and just implement wrapConn.
31 | type BaseWrapper struct {
32 | acceptor.Acceptor
33 | connChan chan acceptor.PlayerConn
34 | wrapConn func(acceptor.PlayerConn) acceptor.PlayerConn
35 | }
36 |
37 | // NewBaseWrapper returns an instance of BaseWrapper.
38 | func NewBaseWrapper(wrapConn func(acceptor.PlayerConn) acceptor.PlayerConn) BaseWrapper {
39 | return BaseWrapper{
40 | connChan: make(chan acceptor.PlayerConn),
41 | wrapConn: wrapConn,
42 | }
43 | }
44 |
45 | // ListenAndServe starts a goroutine that wraps acceptor's conn
46 | // and calls acceptor's listenAndServe
47 | func (b *BaseWrapper) ListenAndServe() {
48 | go b.pipe()
49 | b.Acceptor.ListenAndServe()
50 | }
51 |
52 | // GetConnChan returns the wrapper conn chan
53 | func (b *BaseWrapper) GetConnChan() chan acceptor.PlayerConn {
54 | return b.connChan
55 | }
56 |
57 | func (b *BaseWrapper) pipe() {
58 | for conn := range b.Acceptor.GetConnChan() {
59 | b.connChan <- b.wrapConn(conn)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/acceptorwrapper/base_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptorwrapper
22 |
23 | import (
24 | "testing"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
27 |
28 | "github.com/golang/mock/gomock"
29 | "github.com/stretchr/testify/assert"
30 | "github.com/topfreegames/pitaya/v3/pkg/mocks"
31 | )
32 |
33 | func TestListenAndServe(t *testing.T) {
34 | t.Parallel()
35 |
36 | ctrl := gomock.NewController(t)
37 | defer ctrl.Finish()
38 |
39 | mockAcceptor := mocks.NewMockAcceptor(ctrl)
40 | mockConn := mocks.NewMockPlayerConn(ctrl)
41 |
42 | conns := make(chan acceptor.PlayerConn)
43 | exit := make(chan struct{})
44 | reads := 3
45 | go func() {
46 | for i := 0; i < reads; i++ {
47 | mockConn.EXPECT().Read([]byte{})
48 | conns <- mockConn
49 | }
50 | }()
51 |
52 | mockAcceptor.EXPECT().GetConnChan().Return(conns)
53 | wrapper := &BaseWrapper{
54 | Acceptor: mockAcceptor,
55 | connChan: make(chan acceptor.PlayerConn),
56 | wrapConn: func(c acceptor.PlayerConn) acceptor.PlayerConn {
57 | _, err := c.Read([]byte{})
58 | assert.NoError(t, err)
59 | return c
60 | },
61 | }
62 |
63 | go func() {
64 | i := 0
65 | for range wrapper.GetConnChan() {
66 | i++
67 | if i == reads {
68 | close(exit)
69 | }
70 | }
71 | }()
72 |
73 | mockAcceptor.EXPECT().ListenAndServe().Do(func() { <-exit })
74 | wrapper.ListenAndServe()
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/acceptorwrapper/rate_limiting_wrapper.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptorwrapper
22 |
23 | import (
24 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
25 | "github.com/topfreegames/pitaya/v3/pkg/config"
26 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
27 | )
28 |
29 | // RateLimitingWrapper rate limits for each connection
30 | // received
31 | type RateLimitingWrapper struct {
32 | BaseWrapper
33 | }
34 |
35 | // NewRateLimitingWrapper returns an instance of *RateLimitingWrapper
36 | func NewRateLimitingWrapper(reporters []metrics.Reporter, c config.RateLimitingConfig) *RateLimitingWrapper {
37 | r := &RateLimitingWrapper{}
38 |
39 | r.BaseWrapper = NewBaseWrapper(func(conn acceptor.PlayerConn) acceptor.PlayerConn {
40 | return NewRateLimiter(reporters, conn, c.Limit, c.Interval, c.ForceDisable)
41 | })
42 |
43 | return r
44 | }
45 |
46 | // Wrap saves acceptor as an attribute
47 | func (r *RateLimitingWrapper) Wrap(a acceptor.Acceptor) acceptor.Acceptor {
48 | r.Acceptor = a
49 | return r
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/acceptorwrapper/rate_limiting_wrapper_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptorwrapper
22 |
23 | import (
24 | "testing"
25 | "time"
26 |
27 | "github.com/stretchr/testify/assert"
28 | "github.com/topfreegames/pitaya/v3/pkg/config"
29 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
30 | )
31 |
32 | func TestNewRateLimitingWrapper(t *testing.T) {
33 | t.Parallel()
34 |
35 | reporters := []metrics.Reporter{}
36 |
37 | rateLimitingWrapper := NewRateLimitingWrapper(reporters, config.NewDefaultPitayaConfig().Conn.RateLimiting)
38 | expected := NewRateLimiter(reporters, nil, 20, time.Second, false)
39 | assert.Equal(t, expected, rateLimitingWrapper.wrapConn(nil))
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/acceptorwrapper/wrapper.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package acceptorwrapper
22 |
23 | import (
24 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
25 | )
26 |
27 | // Wrapper has a method that receives an acceptor and the struct
28 | // that implements must encapsulate it. The main goal is to create
29 | // a middleware for packets of net.Conn from acceptor.GetConnChan before
30 | // giving it to serviceHandler.
31 | type Wrapper interface {
32 | Wrap(acceptor.Acceptor) acceptor.Acceptor
33 | }
34 |
35 | // WithWrappers walks through wrappers calling Wrapper
36 | func WithWrappers(
37 | a acceptor.Acceptor,
38 | wrappers ...Wrapper,
39 | ) acceptor.Acceptor {
40 | for _, w := range wrappers {
41 | a = w.Wrap(a)
42 | }
43 | return a
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/builder_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package pitaya
22 |
23 | import (
24 | "testing"
25 |
26 | "github.com/stretchr/testify/assert"
27 | "github.com/topfreegames/pitaya/v3/pkg/acceptor"
28 | "github.com/topfreegames/pitaya/v3/pkg/config"
29 | )
30 |
31 | func TestPostBuildHooks(t *testing.T) {
32 | acc := acceptor.NewTCPAcceptor("0.0.0.0:0")
33 | for _, table := range tables {
34 | builderConfig := config.NewDefaultPitayaConfig()
35 |
36 | t.Run("with_post_build_hooks", func(t *testing.T) {
37 | called := false
38 | builder := NewDefaultBuilder(table.isFrontend, table.serverType, table.serverMode, table.serverMetadata, *builderConfig)
39 | builder.AddAcceptor(acc)
40 | builder.AddPostBuildHook(func(app Pitaya) {
41 | called = true
42 | })
43 | app := builder.Build()
44 |
45 | assert.True(t, called)
46 | assert.NotNil(t, app)
47 | })
48 |
49 | t.Run("without_post_build_hooks", func(t *testing.T) {
50 | called := false
51 | builder := NewDefaultBuilder(table.isFrontend, table.serverType, table.serverMode, table.serverMetadata, *builderConfig)
52 | builder.AddAcceptor(acc)
53 | app := builder.Build()
54 |
55 | assert.False(t, called)
56 | assert.NotNil(t, app)
57 | })
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/client/client_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/golang/mock/gomock"
8 | "github.com/sirupsen/logrus"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/topfreegames/pitaya/v3/pkg/conn/message"
11 | "github.com/topfreegames/pitaya/v3/pkg/helpers"
12 | "github.com/topfreegames/pitaya/v3/pkg/mocks"
13 | )
14 |
15 | func TestSendRequestShouldTimeout(t *testing.T) {
16 | c := New(logrus.InfoLevel, 100*time.Millisecond)
17 | ctrl := gomock.NewController(t)
18 | defer ctrl.Finish()
19 |
20 | mockConn := mocks.NewMockPlayerConn(ctrl)
21 | c.conn = mockConn
22 | go c.pendingRequestsReaper()
23 |
24 | route := "com.sometest.route"
25 | data := []byte{0x02, 0x03, 0x04}
26 |
27 | m := message.Message{
28 | Type: message.Request,
29 | ID: 1,
30 | Route: route,
31 | Data: data,
32 | Err: false,
33 | }
34 |
35 | pkt, err := c.buildPacket(m)
36 | assert.NoError(t, err)
37 |
38 | mockConn.EXPECT().Write(pkt)
39 |
40 | c.IncomingMsgChan = make(chan *message.Message, 10)
41 |
42 | c.nextID = 0
43 | c.SendRequest(route, data)
44 |
45 | msg := helpers.ShouldEventuallyReceive(t, c.IncomingMsgChan, 2*time.Second).(*message.Message)
46 |
47 | assert.Equal(t, true, msg.Err)
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/client/pitayaclient.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package client
22 |
23 | import (
24 | "crypto/tls"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/conn/message"
27 | "github.com/topfreegames/pitaya/v3/pkg/session"
28 | )
29 |
30 | // PitayaClient iface
31 | type PitayaClient interface {
32 | ConnectTo(addr string, tlsConfig ...*tls.Config) error
33 | ConnectToWS(addr string, path string, tlsConfig ...*tls.Config) error
34 | ConnectedStatus() bool
35 | Disconnect()
36 | MsgChannel() chan *message.Message
37 | SendNotify(route string, data []byte) error
38 | SendRequest(route string, data []byte) (uint, error)
39 | SetClientHandshakeData(data *session.HandshakeData)
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/cluster/grpc_rpc_server_test.go:
--------------------------------------------------------------------------------
1 | package cluster
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "testing"
7 |
8 | "github.com/golang/mock/gomock"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/topfreegames/pitaya/v3/pkg/config"
12 | "github.com/topfreegames/pitaya/v3/pkg/helpers"
13 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
14 | protosmocks "github.com/topfreegames/pitaya/v3/pkg/protos/mocks"
15 | )
16 |
17 | func TestNewGRPCServer(t *testing.T) {
18 | t.Parallel()
19 | sv := getServer()
20 | gs, err := NewGRPCServer(config.NewDefaultPitayaConfig().Cluster.RPC.Server.Grpc, sv, []metrics.Reporter{})
21 | assert.NoError(t, err)
22 | assert.NotNil(t, gs)
23 | }
24 |
25 | func TestGRPCServerInit(t *testing.T) {
26 | t.Parallel()
27 | c := config.NewDefaultPitayaConfig().Cluster.RPC.Server.Grpc
28 | c.Port = helpers.GetFreePort(t)
29 | ctrl := gomock.NewController(t)
30 | defer ctrl.Finish()
31 | mockPitayaServer := protosmocks.NewMockPitayaServer(ctrl)
32 |
33 | sv := getServer()
34 | gs, err := NewGRPCServer(c, sv, []metrics.Reporter{})
35 | gs.SetPitayaServer(mockPitayaServer)
36 | err = gs.Init()
37 | assert.NoError(t, err)
38 | assert.NotNil(t, gs)
39 |
40 | conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", c.Port))
41 | assert.NoError(t, err)
42 | assert.NotNil(t, conn)
43 | assert.NotNil(t, gs.grpcSv)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/cluster/info_retriever.go:
--------------------------------------------------------------------------------
1 | package cluster
2 |
3 | import "github.com/topfreegames/pitaya/v3/pkg/config"
4 |
5 | // infoRetriever gets cluster info from config
6 | // Implements InfoRetriever interface
7 | type infoRetriever struct {
8 | region string
9 | }
10 |
11 | // NewInfoRetriever returns a *infoRetriever
12 | func NewInfoRetriever(config config.InfoRetrieverConfig) InfoRetriever {
13 | return &infoRetriever{
14 | region: config.Region,
15 | }
16 | }
17 |
18 | // Region gets server's region from config
19 | func (c *infoRetriever) Region() string {
20 | return c.region
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/cluster/info_retriever_test.go:
--------------------------------------------------------------------------------
1 | package cluster
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/spf13/viper"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/topfreegames/pitaya/v3/pkg/config"
9 | )
10 |
11 | func TestInfoRetrieverRegion(t *testing.T) {
12 | t.Parallel()
13 |
14 | c := viper.New()
15 | c.Set("pitaya.cluster.info.region", "us")
16 | conf := config.NewConfig(c)
17 |
18 | infoRetriever := NewInfoRetriever(*&config.NewPitayaConfig(conf).Cluster.Info)
19 |
20 | assert.Equal(t, "us", infoRetriever.Region())
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/cluster/nats_rpc_common.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package cluster
22 |
23 | import (
24 | "fmt"
25 |
26 | nats "github.com/nats-io/nats.go"
27 | "github.com/topfreegames/pitaya/v3/pkg/logger"
28 | )
29 |
30 | func getChannel(serverType, serverID string) string {
31 | return fmt.Sprintf("pitaya/servers/%s/%s", serverType, serverID)
32 | }
33 |
34 | func setupNatsConn(connectString string, appDieChan chan bool, options ...nats.Option) (*nats.Conn, error) {
35 | natsOptions := append(
36 | options,
37 | nats.DisconnectErrHandler(func(_ *nats.Conn, err error) {
38 | logger.Log.Warnf("disconnected from nats! Reason: %q\n", err)
39 | }),
40 | nats.ReconnectHandler(func(nc *nats.Conn) {
41 | logger.Log.Warnf("reconnected to nats server %s with address %s in cluster %s!", nc.ConnectedServerName(), nc.ConnectedAddr(), nc.ConnectedClusterName())
42 | }),
43 | nats.ClosedHandler(func(nc *nats.Conn) {
44 | err := nc.LastError()
45 | if err == nil {
46 | logger.Log.Warn("nats connection closed with no error.")
47 | return
48 | }
49 |
50 | logger.Log.Errorf("nats connection closed. reason: %q", nc.LastError())
51 | if appDieChan != nil {
52 | appDieChan <- true
53 | }
54 | }),
55 | nats.ErrorHandler(func(nc *nats.Conn, sub *nats.Subscription, err error) {
56 | if err == nats.ErrSlowConsumer {
57 | dropped, _ := sub.Dropped()
58 | logger.Log.Warn("nats slow consumer on subject %q: dropped %d messages\n",
59 | sub.Subject, dropped)
60 | } else {
61 | logger.Log.Errorf(err.Error())
62 | }
63 | }),
64 | )
65 |
66 | nc, err := nats.Connect(connectString, natsOptions...)
67 | if err != nil {
68 | return nil, err
69 | }
70 | return nc, nil
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/cluster/nats_rpc_common_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package cluster
22 |
23 | import (
24 | "fmt"
25 | "testing"
26 | "time"
27 |
28 | nats "github.com/nats-io/nats.go"
29 | "github.com/stretchr/testify/assert"
30 | "github.com/topfreegames/pitaya/v3/pkg/helpers"
31 | )
32 |
33 | func getServer() *Server {
34 | return &Server{
35 | ID: "id1",
36 | Type: "type1",
37 | Frontend: true,
38 | }
39 | }
40 |
41 | func TestNatsRPCCommonGetChannel(t *testing.T) {
42 | t.Parallel()
43 | assert.Equal(t, "pitaya/servers/type1/sv1", getChannel("type1", "sv1"))
44 | assert.Equal(t, "pitaya/servers/2type1/2sv1", getChannel("2type1", "2sv1"))
45 | }
46 |
47 | func TestNatsRPCCommonSetupNatsConn(t *testing.T) {
48 | t.Parallel()
49 | s := helpers.GetTestNatsServer(t)
50 | defer s.Shutdown()
51 | conn, err := setupNatsConn(fmt.Sprintf("nats://%s", s.Addr()), nil)
52 | assert.NoError(t, err)
53 | assert.NotNil(t, conn)
54 | }
55 |
56 | func TestNatsRPCCommonSetupNatsConnShouldError(t *testing.T) {
57 | t.Parallel()
58 | conn, err := setupNatsConn("nats://localhost:1234", nil)
59 | assert.Error(t, err)
60 | assert.Nil(t, conn)
61 | }
62 |
63 | func TestNatsRPCCommonCloseHandler(t *testing.T) {
64 | t.Parallel()
65 | s := helpers.GetTestNatsServer(t)
66 |
67 | dieChan := make(chan bool)
68 |
69 | conn, err := setupNatsConn(fmt.Sprintf("nats://%s", s.Addr()), dieChan, nats.MaxReconnects(1),
70 | nats.ReconnectWait(1*time.Millisecond))
71 | assert.NoError(t, err)
72 | assert.NotNil(t, conn)
73 |
74 | s.Shutdown()
75 |
76 | value, ok := <-dieChan
77 | assert.True(t, ok)
78 | assert.True(t, value)
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/cluster/server.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package cluster
22 |
23 | import (
24 | "encoding/json"
25 | "os"
26 |
27 | "github.com/topfreegames/pitaya/v3/pkg/logger"
28 | )
29 |
30 | // Server struct
31 | type Server struct {
32 | ID string `json:"id"`
33 | Type string `json:"type"`
34 | Metadata map[string]string `json:"metadata"`
35 | Frontend bool `json:"frontend"`
36 | Hostname string `json:"hostname"`
37 | }
38 |
39 | // NewServer ctor
40 | func NewServer(id, serverType string, frontend bool, metadata ...map[string]string) *Server {
41 | d := make(map[string]string)
42 | h, err := os.Hostname()
43 | if err != nil {
44 | logger.Log.Errorf("failed to get hostname: %s", err.Error())
45 | }
46 | if len(metadata) > 0 {
47 | d = metadata[0]
48 | }
49 | return &Server{
50 | ID: id,
51 | Type: serverType,
52 | Metadata: d,
53 | Frontend: frontend,
54 | Hostname: h,
55 | }
56 | }
57 |
58 | // AsJSONString returns the server as a json string
59 | func (s *Server) AsJSONString() string {
60 | str, err := json.Marshal(s)
61 | if err != nil {
62 | logger.Log.Errorf("error getting server as json: %s", err.Error())
63 | return ""
64 | }
65 | return string(str)
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/cluster/server_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package cluster
22 |
23 | import (
24 | "encoding/json"
25 | "testing"
26 |
27 | "github.com/stretchr/testify/assert"
28 | )
29 |
30 | var svTestTables = []struct {
31 | id string
32 | svType string
33 | metadata map[string]string
34 | frontend bool
35 | }{
36 | {"someid-1", "somesvtype", map[string]string{"bla": "ola"}, true},
37 | {"someid-2", "somesvtype", nil, true},
38 | {"someid-3", "somesvtype", map[string]string{"sv": "game"}, false},
39 | {"someid-4", "somesvtype", map[string]string{}, false},
40 | }
41 |
42 | func TestNewServer(t *testing.T) {
43 | t.Parallel()
44 | for _, table := range svTestTables {
45 | t.Run(table.id, func(t *testing.T) {
46 | s := NewServer(table.id, table.svType, table.frontend, table.metadata)
47 | assert.Equal(t, table.id, s.ID)
48 | assert.Equal(t, table.metadata, s.Metadata)
49 | assert.Equal(t, table.frontend, s.Frontend)
50 | assert.NotNil(t, s.Hostname)
51 | })
52 | }
53 | }
54 |
55 | func TestAsJSONString(t *testing.T) {
56 | t.Parallel()
57 | for _, table := range svTestTables {
58 | t.Run(table.id, func(t *testing.T) {
59 | s := NewServer(table.id, table.svType, table.frontend, table.metadata)
60 | b, err := json.Marshal(s)
61 | assert.NoError(t, err)
62 | assert.Equal(t, string(b), s.AsJSONString())
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/cluster/service_discovery.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package cluster
22 |
23 | import "github.com/topfreegames/pitaya/v3/pkg/interfaces"
24 |
25 | // ServiceDiscovery is the interface for a service discovery client
26 | type ServiceDiscovery interface {
27 | GetServersByType(serverType string) (map[string]*Server, error)
28 | GetServer(id string) (*Server, error)
29 | GetServers() []*Server
30 | SyncServers(firstSync bool) error
31 | AddListener(listener SDListener)
32 | interfaces.Module
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/component/base.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package component
22 |
23 | // Base implements a default component for Component.
24 | type Base struct{}
25 |
26 | // Init was called to initialize the component.
27 | func (c *Base) Init() {}
28 |
29 | // AfterInit was called after the component is initialized.
30 | func (c *Base) AfterInit() {}
31 |
32 | // BeforeShutdown was called before the component to shutdown.
33 | func (c *Base) BeforeShutdown() {}
34 |
35 | // Shutdown was called to shutdown the component.
36 | func (c *Base) Shutdown() {}
37 |
--------------------------------------------------------------------------------
/pkg/component/component.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package component
22 |
23 | // Component is the interface that represent a component.
24 | type Component interface {
25 | Init()
26 | AfterInit()
27 | BeforeShutdown()
28 | Shutdown()
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/component/options.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Authors and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package component
22 |
23 | type (
24 | options struct {
25 | name string // component name
26 | nameFunc func(string) string // rename handler name
27 | }
28 |
29 | // Option used to customize handler
30 | Option func(options *options)
31 | )
32 |
33 | // WithName used to rename component name
34 | func WithName(name string) Option {
35 | return func(opt *options) {
36 | opt.name = name
37 | }
38 | }
39 |
40 | // WithNameFunc override handler name by specific function
41 | // such as: strings.ToUpper/strings.ToLower
42 | func WithNameFunc(fn func(string) string) Option {
43 | return func(opt *options) {
44 | opt.nameFunc = fn
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/component/options_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package component
22 |
23 | import (
24 | "strings"
25 | "testing"
26 |
27 | "github.com/stretchr/testify/assert"
28 | )
29 |
30 | func TestWithName(t *testing.T) {
31 | name := "someName"
32 | opt := &options{}
33 | WithName(name)(opt)
34 | assert.Equal(t, name, opt.name)
35 | }
36 |
37 | func TestWithNameFunc(t *testing.T) {
38 | name := "somename"
39 | opt := &options{}
40 | nameFunc := func(s string) string {
41 | return strings.ToUpper(s)
42 | }
43 | WithNameFunc(nameFunc)(opt)
44 | assert.Equal(t, opt.nameFunc(name), strings.ToUpper(name))
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/conn/codec/constants.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package codec
22 |
23 | import "errors"
24 |
25 | // Codec constants.
26 | const (
27 | HeadLength = 4
28 | MaxPacketSize = 1 << 24 //16MB
29 | )
30 |
31 | // ErrPacketSizeExcced is the error used for encode/decode.
32 | var ErrPacketSizeExcced = errors.New("codec: packet size exceed")
33 |
--------------------------------------------------------------------------------
/pkg/conn/codec/mocks/packet_decoder.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: conn/codec/packet_decoder.go
3 |
4 | // Package mocks is a generated GoMock package.
5 | package mocks
6 |
7 | import (
8 | gomock "github.com/golang/mock/gomock"
9 | packet "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
10 | reflect "reflect"
11 | )
12 |
13 | // MockPacketDecoder is a mock of PacketDecoder interface
14 | type MockPacketDecoder struct {
15 | ctrl *gomock.Controller
16 | recorder *MockPacketDecoderMockRecorder
17 | }
18 |
19 | // MockPacketDecoderMockRecorder is the mock recorder for MockPacketDecoder
20 | type MockPacketDecoderMockRecorder struct {
21 | mock *MockPacketDecoder
22 | }
23 |
24 | // NewMockPacketDecoder creates a new mock instance
25 | func NewMockPacketDecoder(ctrl *gomock.Controller) *MockPacketDecoder {
26 | mock := &MockPacketDecoder{ctrl: ctrl}
27 | mock.recorder = &MockPacketDecoderMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use
32 | func (m *MockPacketDecoder) EXPECT() *MockPacketDecoderMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // Decode mocks base method
37 | func (m *MockPacketDecoder) Decode(data []byte) ([]*packet.Packet, error) {
38 | ret := m.ctrl.Call(m, "Decode", data)
39 | ret0, _ := ret[0].([]*packet.Packet)
40 | ret1, _ := ret[1].(error)
41 | return ret0, ret1
42 | }
43 |
44 | // Decode indicates an expected call of Decode
45 | func (mr *MockPacketDecoderMockRecorder) Decode(data interface{}) *gomock.Call {
46 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decode", reflect.TypeOf((*MockPacketDecoder)(nil).Decode), data)
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/conn/codec/mocks/packet_encoder.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: conn/codec/packet_encoder.go
3 |
4 | // Package mocks is a generated GoMock package.
5 | package mocks
6 |
7 | import (
8 | gomock "github.com/golang/mock/gomock"
9 | packet "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
10 | reflect "reflect"
11 | )
12 |
13 | // MockPacketEncoder is a mock of PacketEncoder interface
14 | type MockPacketEncoder struct {
15 | ctrl *gomock.Controller
16 | recorder *MockPacketEncoderMockRecorder
17 | }
18 |
19 | // MockPacketEncoderMockRecorder is the mock recorder for MockPacketEncoder
20 | type MockPacketEncoderMockRecorder struct {
21 | mock *MockPacketEncoder
22 | }
23 |
24 | // NewMockPacketEncoder creates a new mock instance
25 | func NewMockPacketEncoder(ctrl *gomock.Controller) *MockPacketEncoder {
26 | mock := &MockPacketEncoder{ctrl: ctrl}
27 | mock.recorder = &MockPacketEncoderMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use
32 | func (m *MockPacketEncoder) EXPECT() *MockPacketEncoderMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // Encode mocks base method
37 | func (m *MockPacketEncoder) Encode(typ packet.Type, data []byte) ([]byte, error) {
38 | ret := m.ctrl.Call(m, "Encode", typ, data)
39 | ret0, _ := ret[0].([]byte)
40 | ret1, _ := ret[1].(error)
41 | return ret0, ret1
42 | }
43 |
44 | // Encode indicates an expected call of Encode
45 | func (mr *MockPacketEncoderMockRecorder) Encode(typ, data interface{}) *gomock.Call {
46 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encode", reflect.TypeOf((*MockPacketEncoder)(nil).Encode), typ, data)
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/conn/codec/packet_decoder.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package codec
22 |
23 | import "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
24 |
25 | // PacketDecoder interface
26 | type PacketDecoder interface {
27 | Decode(data []byte) ([]*packet.Packet, error)
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/conn/codec/packet_encoder.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package codec
22 |
23 | import "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
24 |
25 | // PacketEncoder interface
26 | type PacketEncoder interface {
27 | Encode(typ packet.Type, data []byte) ([]byte, error)
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/conn/codec/pomelo_packet_decoder.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package codec
22 |
23 | import (
24 | "bytes"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
27 | )
28 |
29 | // PomeloPacketDecoder reads and decodes network data slice following pomelo's protocol
30 | type PomeloPacketDecoder struct{}
31 |
32 | // NewPomeloPacketDecoder returns a new decoder that used for decode network bytes slice.
33 | func NewPomeloPacketDecoder() *PomeloPacketDecoder {
34 | return &PomeloPacketDecoder{}
35 | }
36 |
37 | func (c *PomeloPacketDecoder) forward(buf *bytes.Buffer) (int, packet.Type, error) {
38 | header := buf.Next(HeadLength)
39 | return ParseHeader(header)
40 | }
41 |
42 | // Decode decode the network bytes slice to packet.Packet(s)
43 | func (c *PomeloPacketDecoder) Decode(data []byte) ([]*packet.Packet, error) {
44 | buf := bytes.NewBuffer(nil)
45 | buf.Write(data)
46 |
47 | var (
48 | packets []*packet.Packet
49 | err error
50 | )
51 | // check length
52 | if buf.Len() < HeadLength {
53 | return nil, nil
54 | }
55 |
56 | // first time
57 | size, typ, err := c.forward(buf)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | for size <= buf.Len() {
63 | p := &packet.Packet{Type: typ, Length: size, Data: buf.Next(size)}
64 | packets = append(packets, p)
65 |
66 | // if no more packets, break
67 | if buf.Len() < HeadLength {
68 | break
69 | }
70 |
71 | size, typ, err = c.forward(buf)
72 | if err != nil {
73 | return nil, err
74 | }
75 | }
76 |
77 | return packets, nil
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/conn/codec/pomelo_packet_decoder_test.go:
--------------------------------------------------------------------------------
1 | package codec
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
9 | )
10 |
11 | var forwardTables = map[string]struct {
12 | buf []byte
13 | err error
14 | }{
15 | "test_handshake_type": {[]byte{packet.Handshake, 0x00, 0x00, 0x00}, nil},
16 | "test_handshake_ack_type": {[]byte{packet.HandshakeAck, 0x00, 0x00, 0x00}, nil},
17 | "test_heartbeat_type": {[]byte{packet.Heartbeat, 0x00, 0x00, 0x00}, nil},
18 | "test_data_type": {[]byte{packet.Data, 0x00, 0x00, 0x00}, nil},
19 | "test_kick_type": {[]byte{packet.Kick, 0x00, 0x00, 0x00}, nil},
20 |
21 | "test_wrong_packet_type": {[]byte{0x06, 0x00, 0x00, 0x00}, packet.ErrWrongPomeloPacketType},
22 | }
23 |
24 | var (
25 | handshakeHeaderPacket = []byte{packet.Handshake, 0x00, 0x00, 0x01, 0x01}
26 | invalidHeader = []byte{0xff, 0x00, 0x00, 0x01}
27 | )
28 |
29 | var decodeTables = map[string]struct {
30 | data []byte
31 | packet []*packet.Packet
32 | err error
33 | }{
34 | "test_not_enough_bytes": {[]byte{0x01}, nil, nil},
35 | "test_error_on_forward": {invalidHeader, nil, packet.ErrWrongPomeloPacketType},
36 | "test_forward": {handshakeHeaderPacket, []*packet.Packet{{packet.Handshake, 1, []byte{0x01}}}, nil},
37 | "test_forward_many": {append(handshakeHeaderPacket, handshakeHeaderPacket...), []*packet.Packet{{packet.Handshake, 1, []byte{0x01}}, {packet.Handshake, 1, []byte{0x01}}}, nil},
38 | }
39 |
40 | func TestNewPomeloPacketDecoder(t *testing.T) {
41 | t.Parallel()
42 |
43 | ppd := NewPomeloPacketDecoder()
44 |
45 | assert.NotNil(t, ppd)
46 | }
47 |
48 | func TestForward(t *testing.T) {
49 | t.Parallel()
50 |
51 | for name, table := range forwardTables {
52 | t.Run(name, func(t *testing.T) {
53 | ppd := NewPomeloPacketDecoder()
54 |
55 | sz, typ, err := ppd.forward(bytes.NewBuffer(table.buf))
56 | if table.err == nil {
57 | assert.Equal(t, packet.Type(table.buf[0]), typ)
58 | assert.Equal(t, 0, sz)
59 | }
60 |
61 | assert.Equal(t, table.err, err)
62 | })
63 | }
64 | }
65 |
66 | func TestDecode(t *testing.T) {
67 | t.Parallel()
68 |
69 | for name, table := range decodeTables {
70 | t.Run(name, func(t *testing.T) {
71 | ppd := NewPomeloPacketDecoder()
72 |
73 | packet, err := ppd.Decode(table.data)
74 |
75 | assert.Equal(t, table.err, err)
76 | assert.ElementsMatch(t, table.packet, packet)
77 | })
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/conn/codec/pomelo_packet_encoder.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package codec
22 |
23 | import (
24 | "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
25 | )
26 |
27 | // PomeloPacketEncoder struct
28 | type PomeloPacketEncoder struct {
29 | }
30 |
31 | // NewPomeloPacketEncoder ctor
32 | func NewPomeloPacketEncoder() *PomeloPacketEncoder {
33 | return &PomeloPacketEncoder{}
34 | }
35 |
36 | // Encode create a packet.Packet from the raw bytes slice and then encode to network bytes slice
37 | // Protocol refs: https://github.com/NetEase/pomelo/wiki/Communication-Protocol
38 | //
39 | // --|----------------|--
40 | // --------|------------------------|--------
41 | // 1 byte packet type, 3 bytes packet data length(big end), and data segment
42 | func (e *PomeloPacketEncoder) Encode(typ packet.Type, data []byte) ([]byte, error) {
43 | if typ < packet.Handshake || typ > packet.Kick {
44 | return nil, packet.ErrWrongPomeloPacketType
45 | }
46 |
47 | if len(data) > MaxPacketSize {
48 | return nil, ErrPacketSizeExcced
49 | }
50 |
51 | p := &packet.Packet{Type: typ, Length: len(data)}
52 | buf := make([]byte, p.Length+HeadLength)
53 | buf[0] = byte(p.Type)
54 |
55 | copy(buf[1:HeadLength], IntToBytes(p.Length))
56 | copy(buf[HeadLength:], data)
57 |
58 | return buf, nil
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/conn/codec/pomelo_packet_encoder_test.go:
--------------------------------------------------------------------------------
1 | package codec
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
8 | )
9 |
10 | func helperConcatBytes(packetType packet.Type, length, data []byte) []byte {
11 | if data == nil {
12 | return nil
13 | }
14 |
15 | bytes := []byte{}
16 | bytes = append(bytes, byte(packetType))
17 | bytes = append(bytes, length...)
18 | bytes = append(bytes, data...)
19 | return bytes
20 | }
21 |
22 | var tooBigData = make([]byte, 1<<25)
23 |
24 | var encodeTables = map[string]struct {
25 | packetType packet.Type
26 | length []byte
27 | data []byte
28 | err error
29 | }{
30 | "test_encode_handshake": {packet.Handshake, []byte{0x00, 0x00, 0x02}, []byte{0x01, 0x00}, nil},
31 | "test_invalid_packet_type": {0xff, nil, nil, packet.ErrWrongPomeloPacketType},
32 | "test_too_big_packet": {packet.Data, nil, tooBigData, ErrPacketSizeExcced},
33 | }
34 |
35 | func TestEncode(t *testing.T) {
36 | t.Parallel()
37 |
38 | for name, table := range encodeTables {
39 | t.Run(name, func(t *testing.T) {
40 | ppe := NewPomeloPacketEncoder()
41 |
42 | encoded, err := ppe.Encode(table.packetType, table.data)
43 | if table.err != nil {
44 | assert.Equal(t, table.err, err)
45 | } else {
46 | expectedEncoded := helperConcatBytes(table.packetType, table.length, table.data)
47 | assert.Equal(t, expectedEncoded, encoded)
48 | }
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/conn/codec/utils.go:
--------------------------------------------------------------------------------
1 | package codec
2 |
3 | import "github.com/topfreegames/pitaya/v3/pkg/conn/packet"
4 |
5 | // ParseHeader parses a packet header and returns its dataLen and packetType or an error
6 | func ParseHeader(header []byte) (int, packet.Type, error) {
7 | if len(header) != HeadLength {
8 | return 0, 0x00, packet.ErrInvalidPomeloHeader
9 | }
10 | typ := header[0]
11 | if typ < packet.Handshake || typ > packet.Kick {
12 | return 0, 0x00, packet.ErrWrongPomeloPacketType
13 | }
14 |
15 | size := BytesToInt(header[1:])
16 |
17 | if size > MaxPacketSize {
18 | return 0, 0x00, ErrPacketSizeExcced
19 | }
20 |
21 | return size, packet.Type(typ), nil
22 | }
23 |
24 | // BytesToInt decode packet data length byte to int(Big end)
25 | func BytesToInt(b []byte) int {
26 | result := 0
27 | for _, v := range b {
28 | result = result<<8 + int(v)
29 | }
30 | return result
31 | }
32 |
33 | // IntToBytes encode packet data length to bytes(Big end)
34 | func IntToBytes(n int) []byte {
35 | buf := make([]byte, 3)
36 | buf[0] = byte((n >> 16) & 0xFF)
37 | buf[1] = byte((n >> 8) & 0xFF)
38 | buf[2] = byte(n & 0xFF)
39 | return buf
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_invalid_message.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/conn/message/fixtures/test_invalid_message.golden
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_must_gzip.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/conn/message/fixtures/test_must_gzip.golden
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_notify_type.golden:
--------------------------------------------------------------------------------
1 | a
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_notify_type_compressed.golden:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_push_type.golden:
--------------------------------------------------------------------------------
1 | a
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_push_type_compressed.golden:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_reponse_type.golden:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_reponse_type_with_data.golden:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_reponse_type_with_error.golden:
--------------------------------------------------------------------------------
1 | $
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_reponse_type_with_id.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/conn/message/fixtures/test_reponse_type_with_id.golden
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_request_type.golden:
--------------------------------------------------------------------------------
1 | a
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_request_type_compressed.golden:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/conn/message/fixtures/test_wrong_type.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/conn/message/fixtures/test_wrong_type.golden
--------------------------------------------------------------------------------
/pkg/conn/message/mocks/message_encoder.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: ./message_encoder.go
3 |
4 | // Package mocks is a generated GoMock package.
5 | package mocks
6 |
7 | import (
8 | gomock "github.com/golang/mock/gomock"
9 | message "github.com/topfreegames/pitaya/v3/pkg/conn/message"
10 | reflect "reflect"
11 | )
12 |
13 | // MockEncoder is a mock of Encoder interface
14 | type MockEncoder struct {
15 | ctrl *gomock.Controller
16 | recorder *MockEncoderMockRecorder
17 | }
18 |
19 | // MockEncoderMockRecorder is the mock recorder for MockEncoder
20 | type MockEncoderMockRecorder struct {
21 | mock *MockEncoder
22 | }
23 |
24 | // NewMockEncoder creates a new mock instance
25 | func NewMockEncoder(ctrl *gomock.Controller) *MockEncoder {
26 | mock := &MockEncoder{ctrl: ctrl}
27 | mock.recorder = &MockEncoderMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use
32 | func (m *MockEncoder) EXPECT() *MockEncoderMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // IsCompressionEnabled mocks base method
37 | func (m *MockEncoder) IsCompressionEnabled() bool {
38 | ret := m.ctrl.Call(m, "IsCompressionEnabled")
39 | ret0, _ := ret[0].(bool)
40 | return ret0
41 | }
42 |
43 | // IsCompressionEnabled indicates an expected call of IsCompressionEnabled
44 | func (mr *MockEncoderMockRecorder) IsCompressionEnabled() *gomock.Call {
45 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCompressionEnabled", reflect.TypeOf((*MockEncoder)(nil).IsCompressionEnabled))
46 | }
47 |
48 | // Encode mocks base method
49 | func (m *MockEncoder) Encode(message *message.Message) ([]byte, error) {
50 | ret := m.ctrl.Call(m, "Encode", message)
51 | ret0, _ := ret[0].([]byte)
52 | ret1, _ := ret[1].(error)
53 | return ret0, ret1
54 | }
55 |
56 | // Encode indicates an expected call of Encode
57 | func (mr *MockEncoderMockRecorder) Encode(message interface{}) *gomock.Call {
58 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encode", reflect.TypeOf((*MockEncoder)(nil).Encode), message)
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/conn/packet/constants.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package packet
22 |
23 | import "errors"
24 |
25 | // Type represents the network packet's type such as: handshake and so on.
26 | type Type byte
27 |
28 | const (
29 | _ Type = iota
30 | // Handshake represents a handshake: request(client) <====> handshake response(server)
31 | Handshake = 0x01
32 |
33 | // HandshakeAck represents a handshake ack from client to server
34 | HandshakeAck = 0x02
35 |
36 | // Heartbeat represents a heartbeat
37 | Heartbeat = 0x03
38 |
39 | // Data represents a common data packet
40 | Data = 0x04
41 |
42 | // Kick represents a kick off packet
43 | Kick = 0x05 // disconnect message from server
44 | )
45 |
46 | // ErrWrongPomeloPacketType represents a wrong packet type.
47 | var ErrWrongPomeloPacketType = errors.New("wrong packet type")
48 |
49 | // ErrInvalidPomeloHeader represents an invalid header
50 | var ErrInvalidPomeloHeader = errors.New("invalid header")
51 |
--------------------------------------------------------------------------------
/pkg/conn/packet/packet.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package packet
22 |
23 | import (
24 | "fmt"
25 | )
26 |
27 | // Packet represents a network packet.
28 | type Packet struct {
29 | Type Type
30 | Length int
31 | Data []byte
32 | }
33 |
34 | //New create a Packet instance.
35 | func New() *Packet {
36 | return &Packet{}
37 | }
38 |
39 | //String represents the Packet's in text mode.
40 | func (p *Packet) String() string {
41 | return fmt.Sprintf("Type: %d, Length: %d, Data: %s", p.Type, p.Length, string(p.Data))
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/conn/packet/packet_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package packet
22 |
23 | import (
24 | "fmt"
25 | "testing"
26 |
27 | "github.com/stretchr/testify/assert"
28 | )
29 |
30 | func TestNewPacket(t *testing.T) {
31 | p := New()
32 | assert.NotNil(t, p)
33 | }
34 |
35 | func TestString(t *testing.T) {
36 | tables := []struct {
37 | tp Type
38 | data []byte
39 | strOut string
40 | }{
41 | {Handshake, []byte{0x01}, fmt.Sprintf("Type: %d, Length: %d, Data: %s", Handshake, 1, string([]byte{0x01}))},
42 | {Data, []byte{0x01, 0x02, 0x03}, fmt.Sprintf("Type: %d, Length: %d, Data: %s", Data, 3, string([]byte{0x01, 0x02, 0x03}))},
43 | {Kick, []byte{0x05, 0x02, 0x03, 0x04}, fmt.Sprintf("Type: %d, Length: %d, Data: %s", Kick, 4, string([]byte{0x05, 0x02, 0x03, 0x04}))},
44 | }
45 |
46 | for _, table := range tables {
47 | t.Run(string(table.data), func(t *testing.T) {
48 | p := &Packet{}
49 | p.Data = table.data
50 | p.Type = table.tp
51 | p.Length = len(table.data)
52 | assert.Equal(t, table.strOut, p.String())
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/constants/version.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package constants
22 |
23 | // VERSION returns current pitaya version
24 | var VERSION = "0.1.0"
25 |
--------------------------------------------------------------------------------
/pkg/context/fixtures/one_element.golden:
--------------------------------------------------------------------------------
1 | {"key1":"val1"}
--------------------------------------------------------------------------------
/pkg/context/fixtures/registered_struct.golden:
--------------------------------------------------------------------------------
1 | {"key1":{}}
--------------------------------------------------------------------------------
/pkg/defaultpipelines/default_struct_validator.go:
--------------------------------------------------------------------------------
1 | package defaultpipelines
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | validator "github.com/go-playground/validator/v10"
8 | )
9 |
10 | // DefaultValidator is the default arguments validator for handlers
11 | // in pitaya
12 | type DefaultValidator struct {
13 | once sync.Once
14 | validate *validator.Validate
15 | }
16 |
17 | // Validate is the the function responsible for validating the 'in' parameter
18 | // based on the struct tags the parameter has.
19 | // This function has the pipeline.Handler signature so
20 | // it is possible to use it as a pipeline function
21 | func (v *DefaultValidator) Validate(ctx context.Context, in interface{}) (context.Context, interface{}, error) {
22 | if in == nil {
23 | return ctx, in, nil
24 | }
25 |
26 | v.lazyinit()
27 | if err := v.validate.Struct(in); err != nil {
28 | return ctx, nil, err
29 | }
30 |
31 | return ctx, in, nil
32 | }
33 |
34 | func (v *DefaultValidator) lazyinit() {
35 | v.once.Do(func() {
36 | v.validate = validator.New()
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/defaultpipelines/default_struct_validator_test.go:
--------------------------------------------------------------------------------
1 | package defaultpipelines
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type TestingStruct struct {
12 | Name string `json:"name"`
13 | Email string `json:"email" validate:"email"`
14 | SoftCurrency int `json:"softCurrency" validate:"gte=0,lte=1000"`
15 | HardCurrency int `json:"hardCurrency" validate:"gte=0,lte=200"`
16 | }
17 |
18 | func TestDefaultValidator(t *testing.T) {
19 | validator := &DefaultValidator{}
20 |
21 | var tableTest = map[string]struct {
22 | s *TestingStruct
23 | shouldFail bool
24 | }{
25 | "validation_error": {
26 | s: &TestingStruct{
27 | Email: "notvalid",
28 | },
29 | shouldFail: true,
30 | },
31 | "validation_success": {
32 | s: &TestingStruct{
33 | Name: "foo",
34 | Email: "foo@tfgco.com",
35 | SoftCurrency: 100,
36 | HardCurrency: 10,
37 | },
38 | },
39 | "validate_nil_object": {
40 | s: nil,
41 | shouldFail: false,
42 | },
43 | }
44 |
45 | for tname, tbl := range tableTest {
46 | t.Run(tname, func(t *testing.T) {
47 | var err error
48 | if tbl.s == nil {
49 | _, _, err = validator.Validate(context.Background(), nil)
50 | } else {
51 | _, _, err = validator.Validate(context.Background(), tbl.s)
52 | }
53 |
54 | if tbl.shouldFail {
55 | assert.Error(t, err)
56 | } else {
57 | fmt.Println(err)
58 | assert.NoError(t, err)
59 | }
60 | })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/defaultpipelines/struct_validator.go:
--------------------------------------------------------------------------------
1 | package defaultpipelines
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // StructValidator is the interface that must be implemented
8 | // by a struct validator for the request arguments on pitaya.
9 | //
10 | // The default struct validator used by pitaya is https://github.com/go-playground/validator.
11 | type StructValidator interface {
12 | Validate(context.Context, interface{}) (context.Context, interface{}, error)
13 | }
14 |
15 | // StructValidatorInstance holds the default validator
16 | // on start but can be overridden if needed.
17 | var StructValidatorInstance StructValidator = &DefaultValidator{}
18 |
--------------------------------------------------------------------------------
/pkg/docgenerator/descriptors.go:
--------------------------------------------------------------------------------
1 | package docgenerator
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 |
7 | "github.com/golang/protobuf/proto"
8 | "github.com/topfreegames/pitaya/v3/pkg/constants"
9 | )
10 |
11 | // ProtoDescriptors returns the descriptor for a given message name or .proto file
12 | func ProtoDescriptors(protoName string) ([]byte, error) {
13 | if strings.HasSuffix(protoName, ".proto") {
14 | descriptor := proto.FileDescriptor(protoName)
15 | if descriptor == nil {
16 | return nil, constants.ErrProtodescriptor
17 | }
18 | return descriptor, nil
19 | }
20 |
21 | if strings.HasPrefix(protoName, "types.") {
22 | protoName = strings.Replace(protoName, "types.", "google.protobuf.", 1)
23 | }
24 | protoReflectTypePointer := proto.MessageType(protoName)
25 | if protoReflectTypePointer == nil {
26 | return nil, constants.ErrProtodescriptor
27 | }
28 |
29 | protoReflectType := protoReflectTypePointer.Elem()
30 | protoValue := reflect.New(protoReflectType)
31 | descriptorMethod, ok := protoReflectTypePointer.MethodByName("Descriptor")
32 | if !ok {
33 | return nil, constants.ErrProtodescriptor
34 | }
35 |
36 | descriptorValue := descriptorMethod.Func.Call([]reflect.Value{protoValue})
37 | protoDescriptor := descriptorValue[0].Bytes()
38 |
39 | return protoDescriptor, nil
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/docgenerator/descriptors_test.go:
--------------------------------------------------------------------------------
1 | package docgenerator
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/topfreegames/pitaya/v3/pkg/constants"
8 | _ "github.com/topfreegames/pitaya/v3/pkg/protos"
9 | )
10 |
11 | func TestProtoDescriptors(t *testing.T) {
12 | t.Parallel()
13 | tables := []struct {
14 | name string
15 | messageName string
16 | err error
17 | }{
18 | {"fail filename", "not_exists.proto", constants.ErrProtodescriptor},
19 | {"success filename", "kick.proto", nil},
20 | {"success message", "protos.Push", nil},
21 | {"fail message", "protos.DoNotExist", constants.ErrProtodescriptor},
22 | }
23 | for _, table := range tables {
24 | t.Run(table.name, func(t *testing.T) {
25 | bts, err := ProtoDescriptors(table.messageName)
26 | if table.err != nil {
27 | assert.EqualError(t, table.err, err.Error())
28 | assert.Nil(t, bts)
29 | } else {
30 | assert.NoError(t, err)
31 | assert.NotNil(t, bts)
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/groups/group_service.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | type (
9 | // GroupService has ranking methods
10 | GroupService interface {
11 | GroupAddMember(ctx context.Context, groupName, uid string) error
12 | GroupContainsMember(ctx context.Context, groupName, uid string) (bool, error)
13 | GroupCountMembers(ctx context.Context, groupName string) (int, error)
14 | GroupCreate(ctx context.Context, groupName string) error
15 | GroupCreateWithTTL(ctx context.Context, groupName string, ttlTime time.Duration) error
16 | GroupDelete(ctx context.Context, groupName string) error
17 | GroupMembers(ctx context.Context, groupName string) ([]string, error)
18 | GroupRemoveAll(ctx context.Context, groupName string) error
19 | GroupRemoveMember(ctx context.Context, groupName, uid string) error
20 | GroupRenewTTL(ctx context.Context, groupName string) error
21 | Close()
22 | }
23 | )
24 |
25 | func elementIndex(slice []string, element string) (int, bool) {
26 | for i, sliceElement := range slice {
27 | if element == sliceElement {
28 | return i, true
29 | }
30 | }
31 | return 0, false
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/groups/memory_group_service_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package groups
22 |
23 | import (
24 | "os"
25 | "testing"
26 | "time"
27 |
28 | "github.com/topfreegames/pitaya/v3/pkg/config"
29 | )
30 |
31 | var memoryGroupService *MemoryGroupService
32 |
33 | func TestMain(m *testing.M) {
34 | mconfig := *&config.NewDefaultPitayaConfig().Groups.Memory
35 | mconfig.TickDuration = 10 * time.Millisecond
36 | memoryGroupService = NewMemoryGroupService(mconfig)
37 | exit := m.Run()
38 | time.Sleep(mconfig.TickDuration)
39 | os.Exit(exit)
40 | }
41 |
42 | func TestMemoryCreateDuplicatedGroup(t *testing.T) {
43 | testCreateDuplicatedGroup(memoryGroupService, t)
44 | }
45 |
46 | func TestMemoryCreateGroup(t *testing.T) {
47 | testCreateGroup(memoryGroupService, t)
48 | }
49 |
50 | func TestMemoryCreateGroupWithTTL(t *testing.T) {
51 | testCreateGroupWithTTL(memoryGroupService, t)
52 | }
53 |
54 | func TestMemoryGroupAddMember(t *testing.T) {
55 | testGroupAddMember(memoryGroupService, t)
56 | }
57 |
58 | func TestMemoryGroupAddDuplicatedMember(t *testing.T) {
59 | testGroupAddDuplicatedMember(memoryGroupService, t)
60 | }
61 |
62 | func TestMemoryGroupContainsMember(t *testing.T) {
63 | testGroupContainsMember(memoryGroupService, t)
64 | }
65 |
66 | func TestMemoryRemove(t *testing.T) {
67 | testRemove(memoryGroupService, t)
68 | }
69 |
70 | func TestMemoryDelete(t *testing.T) {
71 | testDelete(memoryGroupService, t)
72 | }
73 |
74 | func TestMemoryRemoveAll(t *testing.T) {
75 | testRemoveAll(memoryGroupService, t)
76 | }
77 |
78 | func TestMemoryCount(t *testing.T) {
79 | testCount(memoryGroupService, t)
80 | }
81 |
82 | func TestMemoryMembers(t *testing.T) {
83 | testMembers(memoryGroupService, t)
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/helpers/type_support.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import "reflect"
4 |
5 | func isFunction(f interface{}) bool {
6 | actual := reflect.TypeOf(f)
7 | return actual.Kind() == reflect.Func && actual.NumIn() == 0 && actual.NumOut() > 0
8 | }
9 |
10 | func isChan(a interface{}) bool {
11 | if isNil(a) {
12 | return false
13 | }
14 | return reflect.TypeOf(a).Kind() == reflect.Chan
15 | }
16 |
17 | func isNil(a interface{}) bool {
18 | if a == nil {
19 | return true
20 | }
21 |
22 | switch reflect.TypeOf(a).Kind() {
23 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
24 | return reflect.ValueOf(a).IsNil()
25 | }
26 |
27 | return false
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/interfaces/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package interfaces
22 |
23 | // Module is the interface that represent a module.
24 | type Module interface {
25 | Init() error
26 | AfterInit()
27 | BeforeShutdown()
28 | Shutdown() error
29 | }
30 |
31 | type SessionModule interface {
32 | Module
33 | StartSessionDraining()
34 | SessionCount() int64
35 | }
36 |
37 | // BindingStorage interface
38 | type BindingStorage interface {
39 | GetUserFrontendID(uid, frontendType string) (string, error)
40 | PutBinding(uid string) error
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/kick.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package pitaya
22 |
23 | import (
24 | "context"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/constants"
27 | "github.com/topfreegames/pitaya/v3/pkg/logger"
28 | "github.com/topfreegames/pitaya/v3/pkg/protos"
29 | )
30 |
31 | // SendKickToUsers sends kick to an user array
32 | func (app *App) SendKickToUsers(uids []string, frontendType string) ([]string, error) {
33 | if !app.server.Frontend && frontendType == "" {
34 | return uids, constants.ErrFrontendTypeNotSpecified
35 | }
36 |
37 | var notKickedUids []string
38 |
39 | for _, uid := range uids {
40 | if s := app.sessionPool.GetSessionByUID(uid); s != nil {
41 | if err := s.Kick(context.Background()); err != nil {
42 | notKickedUids = append(notKickedUids, uid)
43 | logger.Log.Errorf("Session kick error, ID=%d, UID=%s, ERROR=%s", s.ID(), s.UID(), err.Error())
44 | }
45 | } else if app.rpcClient != nil {
46 | kick := &protos.KickMsg{UserId: uid}
47 | if err := app.rpcClient.SendKick(uid, frontendType, kick); err != nil {
48 | notKickedUids = append(notKickedUids, uid)
49 | logger.Log.Errorf("RPCClient send kick error, UID=%s, SvType=%s, Error=%s", uid, frontendType, err.Error())
50 | }
51 | } else {
52 | notKickedUids = append(notKickedUids, uid)
53 | }
54 |
55 | }
56 |
57 | if len(notKickedUids) != 0 {
58 | return notKickedUids, constants.ErrKickingUsers
59 | }
60 |
61 | return nil, nil
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/logger/interfaces/interfaces.go:
--------------------------------------------------------------------------------
1 | package interfaces
2 |
3 | // Logger interface for pitaya loggers
4 | type Logger interface {
5 | Fatal(format ...interface{})
6 | Fatalf(format string, args ...interface{})
7 | Fatalln(args ...interface{})
8 |
9 | Debug(args ...interface{})
10 | Debugf(format string, args ...interface{})
11 | Debugln(args ...interface{})
12 |
13 | Error(args ...interface{})
14 | Errorf(format string, args ...interface{})
15 | Errorln(args ...interface{})
16 |
17 | Info(args ...interface{})
18 | Infof(format string, args ...interface{})
19 | Infoln(args ...interface{})
20 |
21 | Warn(args ...interface{})
22 | Warnf(format string, args ...interface{})
23 | Warnln(args ...interface{})
24 |
25 | Panic(args ...interface{})
26 | Panicf(format string, args ...interface{})
27 | Panicln(args ...interface{})
28 |
29 | WithFields(fields map[string]interface{}) Logger
30 | WithField(key string, value interface{}) Logger
31 | WithError(err error) Logger
32 |
33 | GetInternalLogger() any
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/logger/logger.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package logger
22 |
23 | import (
24 | "github.com/sirupsen/logrus"
25 | "github.com/topfreegames/pitaya/v3/pkg/logger/interfaces"
26 | logruswrapper "github.com/topfreegames/pitaya/v3/pkg/logger/logrus"
27 | )
28 |
29 | // Log is the default logger
30 | var Log = initLogger()
31 |
32 | func initLogger() interfaces.Logger {
33 | plog := logrus.New()
34 | plog.Formatter = new(logrus.TextFormatter)
35 | plog.Level = logrus.DebugLevel
36 |
37 | log := plog.WithFields(logrus.Fields{
38 | "source": "pitaya",
39 | })
40 | return logruswrapper.NewWithFieldLogger(log)
41 | }
42 |
43 | // SetLogger rewrites the default logger
44 | func SetLogger(l interfaces.Logger) {
45 | if l != nil {
46 | Log = l
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/logger/logger_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package logger
22 |
23 | import (
24 | "testing"
25 |
26 | "github.com/stretchr/testify/assert"
27 | logruswrapper "github.com/topfreegames/pitaya/v3/pkg/logger/logrus"
28 | )
29 |
30 | func TestInitLogger(t *testing.T) {
31 | initLogger()
32 | assert.NotNil(t, Log)
33 | }
34 |
35 | func TestSetLogger(t *testing.T) {
36 | l := logruswrapper.New()
37 | SetLogger(l)
38 | assert.Equal(t, l, Log)
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/logger/logrus/logrus.go:
--------------------------------------------------------------------------------
1 | package logrus
2 |
3 | import (
4 | "github.com/sirupsen/logrus"
5 | "github.com/topfreegames/pitaya/v3/pkg/logger/interfaces"
6 | )
7 |
8 | type logrusImpl struct {
9 | logrus.FieldLogger
10 | }
11 |
12 | // New returns a new interfaces.Logger implementation based on logrus
13 | func New() interfaces.Logger {
14 | log := logrus.New()
15 | return NewWithLogger(log)
16 | }
17 |
18 | // NewWithEntry returns a new interfaces.Logger implementation based on a provided logrus entry instance
19 | // Deprecated: NewWithEntry is deprecated.
20 | func NewWithEntry(logger *logrus.Entry) interfaces.Logger {
21 | return &logrusImpl{FieldLogger: logger}
22 | }
23 |
24 | // NewWithLogger returns a new interfaces.Logger implementation based on a provided logrus instance
25 | // Deprecated: NewWithLogger is deprecated.
26 | func NewWithLogger(logger *logrus.Logger) interfaces.Logger {
27 | return &logrusImpl{FieldLogger: logrus.NewEntry(logger)}
28 | }
29 |
30 | // NewWithFieldLogger returns a new interfaces.Logger implementation based on a provided logrus instance
31 | func NewWithFieldLogger(logger logrus.FieldLogger) interfaces.Logger {
32 | return &logrusImpl{FieldLogger: logger}
33 | }
34 |
35 | func (l *logrusImpl) WithFields(fields map[string]interface{}) interfaces.Logger {
36 | return &logrusImpl{FieldLogger: l.FieldLogger.WithFields(fields)}
37 | }
38 |
39 | func (l *logrusImpl) WithField(key string, value interface{}) interfaces.Logger {
40 | return &logrusImpl{FieldLogger: l.FieldLogger.WithField(key, value)}
41 | }
42 |
43 | func (l *logrusImpl) WithError(err error) interfaces.Logger {
44 | return &logrusImpl{FieldLogger: l.FieldLogger.WithError(err)}
45 | }
46 |
47 | func (l *logrusImpl) GetInternalLogger() any {
48 | return l.FieldLogger
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/logger/test/test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | tests "github.com/sirupsen/logrus/hooks/test"
5 | "github.com/topfreegames/pitaya/v3/pkg/logger/interfaces"
6 | lwrapper "github.com/topfreegames/pitaya/v3/pkg/logger/logrus"
7 | "io"
8 | )
9 |
10 | // NewNullLogger creates a discarding logger and installs the test hook.
11 | func NewNullLogger() (interfaces.Logger, *tests.Hook) {
12 | logger, hook := tests.NewNullLogger()
13 | logger.Out = io.Discard
14 | return lwrapper.NewWithFieldLogger(logger), hook
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/metrics/constants.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | var (
4 | // ResponseTime reports the response time of handlers and rpc
5 | ResponseTime = "response_time_ns"
6 | // ConnectedClients represents the number of current connected clients in frontend servers
7 | ConnectedClients = "connected_clients"
8 | // CountServers counts the number of servers of different types
9 | CountServers = "count_servers"
10 | // ChannelCapacity represents the capacity of a channel as a histogram (distribution of available slots)
11 | ChannelCapacity = "channel_capacity"
12 | // DroppedMessages reports the number of dropped messages in rpc server (messages that will not be handled)
13 | DroppedMessages = "dropped_messages"
14 | // ProcessDelay reports the message processing delay to handle the messages at the handler service
15 | ProcessDelay = "handler_delay_ns"
16 | // Goroutines reports the number of goroutines
17 | Goroutines = "goroutines"
18 | // HeapSize reports the size of heap
19 | HeapSize = "heapsize"
20 | // HeapObjects reports the number of allocated heap objects
21 | HeapObjects = "heapobjects"
22 | // WorkerJobsTotal reports the number of executed jobs
23 | WorkerJobsTotal = "worker_jobs_total"
24 | // WorkerJobsRetry reports the number of retried jobs
25 | WorkerJobsRetry = "worker_jobs_retry_total"
26 | // WorkerQueueSize reports the queue size on worker
27 | WorkerQueueSize = "worker_queue_size"
28 | // ExceededRateLimiting reports the number of requests made in a connection
29 | // after the rate limit was exceeded
30 | ExceededRateLimiting = "exceeded_rate_limiting"
31 | )
32 |
--------------------------------------------------------------------------------
/pkg/metrics/mocks/statsd_reporter.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/topfreegames/pitaya/v3/pkg/metrics (interfaces: Client)
3 |
4 | // Package mocks is a generated GoMock package.
5 | package mocks
6 |
7 | import (
8 | reflect "reflect"
9 |
10 | gomock "github.com/golang/mock/gomock"
11 | )
12 |
13 | // MockClient is a mock of Client interface.
14 | type MockClient struct {
15 | ctrl *gomock.Controller
16 | recorder *MockClientMockRecorder
17 | }
18 |
19 | // MockClientMockRecorder is the mock recorder for MockClient.
20 | type MockClientMockRecorder struct {
21 | mock *MockClient
22 | }
23 |
24 | // NewMockClient creates a new mock instance.
25 | func NewMockClient(ctrl *gomock.Controller) *MockClient {
26 | mock := &MockClient{ctrl: ctrl}
27 | mock.recorder = &MockClientMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use.
32 | func (m *MockClient) EXPECT() *MockClientMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // Count mocks base method.
37 | func (m *MockClient) Count(arg0 string, arg1 int64, arg2 []string, arg3 float64) error {
38 | m.ctrl.T.Helper()
39 | ret := m.ctrl.Call(m, "Count", arg0, arg1, arg2, arg3)
40 | ret0, _ := ret[0].(error)
41 | return ret0
42 | }
43 |
44 | // Count indicates an expected call of Count.
45 | func (mr *MockClientMockRecorder) Count(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
46 | mr.mock.ctrl.T.Helper()
47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockClient)(nil).Count), arg0, arg1, arg2, arg3)
48 | }
49 |
50 | // Gauge mocks base method.
51 | func (m *MockClient) Gauge(arg0 string, arg1 float64, arg2 []string, arg3 float64) error {
52 | m.ctrl.T.Helper()
53 | ret := m.ctrl.Call(m, "Gauge", arg0, arg1, arg2, arg3)
54 | ret0, _ := ret[0].(error)
55 | return ret0
56 | }
57 |
58 | // Gauge indicates an expected call of Gauge.
59 | func (mr *MockClientMockRecorder) Gauge(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
60 | mr.mock.ctrl.T.Helper()
61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gauge", reflect.TypeOf((*MockClient)(nil).Gauge), arg0, arg1, arg2, arg3)
62 | }
63 |
64 | // TimeInMilliseconds mocks base method.
65 | func (m *MockClient) TimeInMilliseconds(arg0 string, arg1 float64, arg2 []string, arg3 float64) error {
66 | m.ctrl.T.Helper()
67 | ret := m.ctrl.Call(m, "TimeInMilliseconds", arg0, arg1, arg2, arg3)
68 | ret0, _ := ret[0].(error)
69 | return ret0
70 | }
71 |
72 | // TimeInMilliseconds indicates an expected call of TimeInMilliseconds.
73 | func (mr *MockClientMockRecorder) TimeInMilliseconds(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
74 | mr.mock.ctrl.T.Helper()
75 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TimeInMilliseconds", reflect.TypeOf((*MockClient)(nil).TimeInMilliseconds), arg0, arg1, arg2, arg3)
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/metrics/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // Summary defines a summary metric
4 | type Summary struct {
5 | Subsystem string
6 | Name string
7 | Help string
8 | Objectives map[float64]float64
9 | Labels []string
10 | }
11 |
12 | // Histogram defines a histogram metric
13 | type Histogram struct {
14 | Subsystem string
15 | Name string
16 | Help string
17 | Buckets []float64
18 | Labels []string
19 | }
20 |
21 | // Gauge defines a gauge metric
22 | type Gauge struct {
23 | Subsystem string
24 | Name string
25 | Help string
26 | Labels []string
27 | }
28 |
29 | // Counter defines a counter metric
30 | type Counter struct {
31 | Subsystem string
32 | Name string
33 | Help string
34 | Labels []string
35 | }
36 |
37 | // CustomMetricsSpec has all metrics specs
38 | type CustomMetricsSpec struct {
39 | Summaries []*Summary
40 | Histograms []*Histogram
41 | Gauges []*Gauge
42 | Counters []*Counter
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/metrics/reporter_interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package metrics
22 |
23 | // Reporter interface
24 | type Reporter interface {
25 | ReportCount(metric string, tags map[string]string, count float64) error
26 | ReportSummary(metric string, tags map[string]string, value float64) error
27 | ReportHistogram(metric string, tags map[string]string, value float64) error
28 | ReportGauge(metric string, tags map[string]string, value float64) error
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/modules/api_docs_gen.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package modules
22 |
23 | import (
24 | "github.com/topfreegames/pitaya/v3/pkg/component"
25 | "github.com/topfreegames/pitaya/v3/pkg/logger"
26 | )
27 |
28 | // APIDocsGen is a pitaya module that generates api docs for pitaya servers
29 | type APIDocsGen struct {
30 | Base
31 | basePath string
32 | services []*component.Service
33 | }
34 |
35 | // NewAPIDocsGen creates a new APIDocsGen
36 | func NewAPIDocsGen(basePath string, services []*component.Service) *APIDocsGen {
37 | return &APIDocsGen{
38 | basePath: basePath,
39 | services: services,
40 | }
41 | }
42 |
43 | // Init is called on init method
44 | func (a *APIDocsGen) Init() error {
45 | for _, s := range a.services {
46 | logger.Log.Infof("loaded svc: %s", s.Name)
47 | }
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/modules/base.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package modules
22 |
23 | // Base implements a default component for Component.
24 | type Base struct{}
25 |
26 | // Init was called to initialize the component.
27 | func (c *Base) Init() error {
28 | return nil
29 | }
30 |
31 | // AfterInit was called after the component is initialized.
32 | func (c *Base) AfterInit() {}
33 |
34 | // BeforeShutdown was called before the component to shutdown.
35 | func (c *Base) BeforeShutdown() {}
36 |
37 | // Shutdown was called to shutdown the component.
38 | func (c *Base) Shutdown() error {
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/modules/unique_session.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package modules
22 |
23 | import (
24 | "context"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/cluster"
27 | "github.com/topfreegames/pitaya/v3/pkg/session"
28 | )
29 |
30 | // UniqueSession module watches for sessions using the same UID and kicks them
31 | type UniqueSession struct {
32 | Base
33 | server *cluster.Server
34 | rpcClient cluster.RPCClient
35 | sessionPool session.SessionPool
36 | }
37 |
38 | // NewUniqueSession creates a new unique session module
39 | func NewUniqueSession(server *cluster.Server, rpcServer cluster.RPCServer, rpcClient cluster.RPCClient, sessionPool session.SessionPool) *UniqueSession {
40 | return &UniqueSession{
41 | server: server,
42 | rpcClient: rpcClient,
43 | sessionPool: sessionPool,
44 | }
45 | }
46 |
47 | // OnUserBind method should be called when a user binds a session in remote servers
48 | func (u *UniqueSession) OnUserBind(uid, fid string) {
49 | if u.server.ID == fid {
50 | return
51 | }
52 | oldSession := u.sessionPool.GetSessionByUID(uid)
53 | if oldSession != nil {
54 | // TODO: it would be nice to set this correctly
55 | oldSession.Kick(context.Background())
56 | }
57 | }
58 |
59 | // Init initializes the module
60 | func (u *UniqueSession) Init() error {
61 | u.sessionPool.OnSessionBind(func(ctx context.Context, s session.Session) error {
62 | oldSession := u.sessionPool.GetSessionByUID(s.UID())
63 | if oldSession != nil {
64 | return oldSession.Kick(ctx)
65 | }
66 | err := u.rpcClient.BroadcastSessionBind(s.UID())
67 | return err
68 | })
69 | return nil
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/networkentity/networkentity.go:
--------------------------------------------------------------------------------
1 | package networkentity
2 |
3 | import (
4 | "context"
5 | "net"
6 |
7 | "github.com/topfreegames/pitaya/v3/pkg/protos"
8 | )
9 |
10 | // NetworkEntity represent low-level network instance
11 | type NetworkEntity interface {
12 | Push(route string, v interface{}) error
13 | ResponseMID(ctx context.Context, mid uint, v interface{}, isError ...bool) error
14 | Close() error
15 | Kick(ctx context.Context) error
16 | RemoteAddr() net.Addr
17 | SendRequest(ctx context.Context, serverID, route string, v interface{}) (*protos.Response, error)
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/pipeline/pipeline_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package pipeline
22 |
23 | import (
24 | "context"
25 | "errors"
26 | "testing"
27 |
28 | "github.com/stretchr/testify/assert"
29 | )
30 |
31 | var (
32 | handler1 = func(ctx context.Context, in interface{}) (context.Context, interface{}, error) {
33 | return ctx, in, errors.New("ohno")
34 | }
35 | handler2 = func(ctx context.Context, in interface{}) (context.Context, interface{}, error) {
36 | return ctx, nil, nil
37 | }
38 | p = &Channel{}
39 | )
40 |
41 | func TestPushFront(t *testing.T) {
42 | p.PushFront(handler1)
43 | p.PushFront(handler2)
44 | defer p.Clear()
45 |
46 | _, _, err := p.Handlers[0](nil, nil)
47 | assert.Nil(t, nil, err)
48 | }
49 |
50 | func TestPushBack(t *testing.T) {
51 | p.PushFront(handler1)
52 | p.PushBack(handler2)
53 | defer p.Clear()
54 |
55 | _, _, err := p.Handlers[0](nil, nil)
56 | assert.EqualError(t, errors.New("ohno"), err.Error())
57 | }
58 |
59 | func TestClear(t *testing.T) {
60 | p.PushFront(handler1)
61 | p.PushBack(handler2)
62 | assert.Len(t, p.Handlers, 2)
63 | p.Clear()
64 | assert.Len(t, p.Handlers, 0)
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/protos/doc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: doc.proto
3 |
4 | package protos
5 |
6 | import proto "github.com/golang/protobuf/proto"
7 | import fmt "fmt"
8 | import math "math"
9 |
10 | // Reference imports to suppress errors if they are not otherwise used.
11 | var _ = proto.Marshal
12 | var _ = fmt.Errorf
13 | var _ = math.Inf
14 |
15 | // This is a compile-time assertion to ensure that this generated file
16 | // is compatible with the proto package it is being compiled against.
17 | // A compilation error at this line likely means your copy of the
18 | // proto package needs to be updated.
19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
20 |
21 | type Doc struct {
22 | Doc string `protobuf:"bytes,1,opt,name=doc" json:"doc,omitempty"`
23 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
24 | XXX_unrecognized []byte `json:"-"`
25 | XXX_sizecache int32 `json:"-"`
26 | }
27 |
28 | func (m *Doc) Reset() { *m = Doc{} }
29 | func (m *Doc) String() string { return proto.CompactTextString(m) }
30 | func (*Doc) ProtoMessage() {}
31 | func (*Doc) Descriptor() ([]byte, []int) {
32 | return fileDescriptor_doc_ddb39afaa46ee6d6, []int{0}
33 | }
34 | func (m *Doc) XXX_Unmarshal(b []byte) error {
35 | return xxx_messageInfo_Doc.Unmarshal(m, b)
36 | }
37 | func (m *Doc) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
38 | return xxx_messageInfo_Doc.Marshal(b, m, deterministic)
39 | }
40 | func (dst *Doc) XXX_Merge(src proto.Message) {
41 | xxx_messageInfo_Doc.Merge(dst, src)
42 | }
43 | func (m *Doc) XXX_Size() int {
44 | return xxx_messageInfo_Doc.Size(m)
45 | }
46 | func (m *Doc) XXX_DiscardUnknown() {
47 | xxx_messageInfo_Doc.DiscardUnknown(m)
48 | }
49 |
50 | var xxx_messageInfo_Doc proto.InternalMessageInfo
51 |
52 | func (m *Doc) GetDoc() string {
53 | if m != nil {
54 | return m.Doc
55 | }
56 | return ""
57 | }
58 |
59 | func init() {
60 | proto.RegisterType((*Doc)(nil), "protos.Doc")
61 | }
62 |
63 | func init() { proto.RegisterFile("doc.proto", fileDescriptor_doc_ddb39afaa46ee6d6) }
64 |
65 | var fileDescriptor_doc_ddb39afaa46ee6d6 = []byte{
66 | // 66 bytes of a gzipped FileDescriptorProto
67 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0xc9, 0x4f, 0xd6,
68 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0x53, 0xc5, 0x4a, 0xe2, 0x5c, 0xcc, 0x2e, 0xf9,
69 | 0xc9, 0x42, 0x02, 0x5c, 0xcc, 0x29, 0xf9, 0xc9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20,
70 | 0x66, 0x12, 0x44, 0x81, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x98, 0xdb, 0x15, 0x9b, 0x34, 0x00,
71 | 0x00, 0x00,
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/protos/docmsg.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: docmsg.proto
3 |
4 | package protos
5 |
6 | import proto "github.com/golang/protobuf/proto"
7 | import fmt "fmt"
8 | import math "math"
9 |
10 | // Reference imports to suppress errors if they are not otherwise used.
11 | var _ = proto.Marshal
12 | var _ = fmt.Errorf
13 | var _ = math.Inf
14 |
15 | // This is a compile-time assertion to ensure that this generated file
16 | // is compatible with the proto package it is being compiled against.
17 | // A compilation error at this line likely means your copy of the
18 | // proto package needs to be updated.
19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
20 |
21 | type DocMsg struct {
22 | GetProtos bool `protobuf:"varint,1,opt,name=getProtos" json:"getProtos,omitempty"`
23 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
24 | XXX_unrecognized []byte `json:"-"`
25 | XXX_sizecache int32 `json:"-"`
26 | }
27 |
28 | func (m *DocMsg) Reset() { *m = DocMsg{} }
29 | func (m *DocMsg) String() string { return proto.CompactTextString(m) }
30 | func (*DocMsg) ProtoMessage() {}
31 | func (*DocMsg) Descriptor() ([]byte, []int) {
32 | return fileDescriptor_docmsg_6d699a2b0ab5525e, []int{0}
33 | }
34 | func (m *DocMsg) XXX_Unmarshal(b []byte) error {
35 | return xxx_messageInfo_DocMsg.Unmarshal(m, b)
36 | }
37 | func (m *DocMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
38 | return xxx_messageInfo_DocMsg.Marshal(b, m, deterministic)
39 | }
40 | func (dst *DocMsg) XXX_Merge(src proto.Message) {
41 | xxx_messageInfo_DocMsg.Merge(dst, src)
42 | }
43 | func (m *DocMsg) XXX_Size() int {
44 | return xxx_messageInfo_DocMsg.Size(m)
45 | }
46 | func (m *DocMsg) XXX_DiscardUnknown() {
47 | xxx_messageInfo_DocMsg.DiscardUnknown(m)
48 | }
49 |
50 | var xxx_messageInfo_DocMsg proto.InternalMessageInfo
51 |
52 | func (m *DocMsg) GetGetProtos() bool {
53 | if m != nil {
54 | return m.GetProtos
55 | }
56 | return false
57 | }
58 |
59 | func init() {
60 | proto.RegisterType((*DocMsg)(nil), "protos.DocMsg")
61 | }
62 |
63 | func init() { proto.RegisterFile("docmsg.proto", fileDescriptor_docmsg_6d699a2b0ab5525e) }
64 |
65 | var fileDescriptor_docmsg_6d699a2b0ab5525e = []byte{
66 | // 75 bytes of a gzipped FileDescriptorProto
67 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xc9, 0x4f, 0xce,
68 | 0x2d, 0x4e, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0x53, 0xc5, 0x4a, 0x6a, 0x5c,
69 | 0x6c, 0x2e, 0xf9, 0xc9, 0xbe, 0xc5, 0xe9, 0x42, 0x32, 0x5c, 0x9c, 0xe9, 0xa9, 0x25, 0x01, 0x60,
70 | 0x61, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x84, 0x40, 0x12, 0x44, 0xbd, 0x31, 0x20, 0x00,
71 | 0x00, 0xff, 0xff, 0x77, 0x5a, 0xd8, 0xbe, 0x46, 0x00, 0x00, 0x00,
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/protos/test/testrequest.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: testrequest.proto
3 |
4 | package test
5 |
6 | import proto "github.com/golang/protobuf/proto"
7 | import fmt "fmt"
8 | import math "math"
9 |
10 | // Reference imports to suppress errors if they are not otherwise used.
11 | var _ = proto.Marshal
12 | var _ = fmt.Errorf
13 | var _ = math.Inf
14 |
15 | // This is a compile-time assertion to ensure that this generated file
16 | // is compatible with the proto package it is being compiled against.
17 | // A compilation error at this line likely means your copy of the
18 | // proto package needs to be updated.
19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
20 |
21 | type TestRequest struct {
22 | Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
23 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
24 | XXX_unrecognized []byte `json:"-"`
25 | XXX_sizecache int32 `json:"-"`
26 | }
27 |
28 | func (m *TestRequest) Reset() { *m = TestRequest{} }
29 | func (m *TestRequest) String() string { return proto.CompactTextString(m) }
30 | func (*TestRequest) ProtoMessage() {}
31 | func (*TestRequest) Descriptor() ([]byte, []int) {
32 | return fileDescriptor_testrequest_64ede5bd0b1b0648, []int{0}
33 | }
34 | func (m *TestRequest) XXX_Unmarshal(b []byte) error {
35 | return xxx_messageInfo_TestRequest.Unmarshal(m, b)
36 | }
37 | func (m *TestRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
38 | return xxx_messageInfo_TestRequest.Marshal(b, m, deterministic)
39 | }
40 | func (dst *TestRequest) XXX_Merge(src proto.Message) {
41 | xxx_messageInfo_TestRequest.Merge(dst, src)
42 | }
43 | func (m *TestRequest) XXX_Size() int {
44 | return xxx_messageInfo_TestRequest.Size(m)
45 | }
46 | func (m *TestRequest) XXX_DiscardUnknown() {
47 | xxx_messageInfo_TestRequest.DiscardUnknown(m)
48 | }
49 |
50 | var xxx_messageInfo_TestRequest proto.InternalMessageInfo
51 |
52 | func (m *TestRequest) GetMsg() string {
53 | if m != nil {
54 | return m.Msg
55 | }
56 | return ""
57 | }
58 |
59 | func init() {
60 | proto.RegisterType((*TestRequest)(nil), "test.TestRequest")
61 | }
62 |
63 | func init() { proto.RegisterFile("testrequest.proto", fileDescriptor_testrequest_64ede5bd0b1b0648) }
64 |
65 | var fileDescriptor_testrequest_64ede5bd0b1b0648 = []byte{
66 | // 79 bytes of a gzipped FileDescriptorProto
67 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0x49, 0x2d, 0x2e,
68 | 0x29, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01,
69 | 0x09, 0x29, 0xc9, 0x73, 0x71, 0x87, 0xa4, 0x16, 0x97, 0x04, 0x41, 0xa4, 0x84, 0x04, 0xb8, 0x98,
70 | 0x73, 0x8b, 0xd3, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x40, 0xcc, 0x24, 0x36, 0xb0, 0x6a,
71 | 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xca, 0x49, 0xd7, 0x42, 0x00, 0x00, 0x00,
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/reporters.go:
--------------------------------------------------------------------------------
1 | package pitaya
2 |
3 | import (
4 | "github.com/topfreegames/pitaya/v3/pkg/config"
5 | "github.com/topfreegames/pitaya/v3/pkg/logger"
6 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
7 | "github.com/topfreegames/pitaya/v3/pkg/metrics/models"
8 | )
9 |
10 | // CreatePrometheusReporter create a Prometheus reporter instance
11 | func CreatePrometheusReporter(serverType string, config config.MetricsConfig, customSpecs models.CustomMetricsSpec) (*metrics.PrometheusReporter, error) {
12 | logger.Log.Infof("prometheus is enabled, configuring reporter on port %d", config.Prometheus.Port)
13 | prometheus, err := metrics.GetPrometheusReporter(serverType, config, customSpecs)
14 | if err != nil {
15 | logger.Log.Errorf("failed to start prometheus metrics reporter, skipping %v", err)
16 | }
17 | return prometheus, err
18 | }
19 |
20 | // CreateStatsdReporter create a Statsd reporter instance
21 | func CreateStatsdReporter(serverType string, config config.MetricsConfig) (*metrics.StatsdReporter, error) {
22 | logger.Log.Infof(
23 | "statsd is enabled, configuring the metrics reporter with host: %s",
24 | config.Statsd.Host,
25 | )
26 | metricsReporter, err := metrics.NewStatsdReporter(
27 | config,
28 | serverType,
29 | )
30 | if err != nil {
31 | logger.Log.Errorf("failed to start statds metrics reporter, skipping %v", err)
32 | }
33 | return metricsReporter, err
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/route/route.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co and nano Authors. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package route
22 |
23 | import (
24 | "errors"
25 | "fmt"
26 | "strings"
27 |
28 | "github.com/topfreegames/pitaya/v3/pkg/logger"
29 | )
30 |
31 | var (
32 | // ErrRouteFieldCantEmpty error
33 | ErrRouteFieldCantEmpty = errors.New("route field can not be empty")
34 | // ErrInvalidRoute error
35 | ErrInvalidRoute = errors.New("invalid route")
36 | )
37 |
38 | // Route struct
39 | type Route struct {
40 | SvType string
41 | Service string
42 | Method string
43 | }
44 |
45 | // NewRoute creates a new route
46 | func NewRoute(server, service, method string) *Route {
47 | return &Route{server, service, method}
48 | }
49 |
50 | // String transforms the route into a string
51 | func (r *Route) String() string {
52 | if r.SvType != "" {
53 | return fmt.Sprintf("%s.%s.%s", r.SvType, r.Service, r.Method)
54 | }
55 | return r.Short()
56 | }
57 |
58 | // Short transforms the route into a string without the server type
59 | func (r *Route) Short() string {
60 | return fmt.Sprintf("%s.%s", r.Service, r.Method)
61 | }
62 |
63 | // Decode decodes the route
64 | func Decode(route string) (*Route, error) {
65 | r := strings.Split(route, ".")
66 | for _, s := range r {
67 | if strings.TrimSpace(s) == "" {
68 | return nil, ErrRouteFieldCantEmpty
69 | }
70 | }
71 | switch len(r) {
72 | case 3:
73 | return NewRoute(r[0], r[1], r[2]), nil
74 | case 2:
75 | return NewRoute("", r[0], r[1]), nil
76 | default:
77 | logger.Log.Errorf("invalid route: " + route)
78 | return nil, ErrInvalidRoute
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/serialize/json/json.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package json
22 |
23 | import (
24 | "encoding/json"
25 | )
26 |
27 | // Serializer implements the serialize.Serializer interface
28 | type Serializer struct{}
29 |
30 | // NewSerializer returns a new Serializer.
31 | func NewSerializer() *Serializer {
32 | return &Serializer{}
33 | }
34 |
35 | // Marshal returns the JSON encoding of v.
36 | func (s *Serializer) Marshal(v interface{}) ([]byte, error) {
37 | return json.Marshal(v)
38 | }
39 |
40 | // Unmarshal parses the JSON-encoded data and stores the result
41 | // in the value pointed to by v.
42 | func (s *Serializer) Unmarshal(data []byte, v interface{}) error {
43 | return json.Unmarshal(data, v)
44 | }
45 |
46 | // GetName returns the name of the serializer.
47 | func (s *Serializer) GetName() string {
48 | return "json"
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/serialize/mocks/serializer.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/topfreegames/pitaya/v3/pkg/serialize (interfaces: Serializer)
3 |
4 | // Package mocks is a generated GoMock package.
5 | package mocks
6 |
7 | import (
8 | reflect "reflect"
9 |
10 | gomock "github.com/golang/mock/gomock"
11 | )
12 |
13 | // MockSerializer is a mock of Serializer interface.
14 | type MockSerializer struct {
15 | ctrl *gomock.Controller
16 | recorder *MockSerializerMockRecorder
17 | }
18 |
19 | // MockSerializerMockRecorder is the mock recorder for MockSerializer.
20 | type MockSerializerMockRecorder struct {
21 | mock *MockSerializer
22 | }
23 |
24 | // NewMockSerializer creates a new mock instance.
25 | func NewMockSerializer(ctrl *gomock.Controller) *MockSerializer {
26 | mock := &MockSerializer{ctrl: ctrl}
27 | mock.recorder = &MockSerializerMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use.
32 | func (m *MockSerializer) EXPECT() *MockSerializerMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // GetName mocks base method.
37 | func (m *MockSerializer) GetName() string {
38 | m.ctrl.T.Helper()
39 | ret := m.ctrl.Call(m, "GetName")
40 | ret0, _ := ret[0].(string)
41 | return ret0
42 | }
43 |
44 | // GetName indicates an expected call of GetName.
45 | func (mr *MockSerializerMockRecorder) GetName() *gomock.Call {
46 | mr.mock.ctrl.T.Helper()
47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockSerializer)(nil).GetName))
48 | }
49 |
50 | // Marshal mocks base method.
51 | func (m *MockSerializer) Marshal(arg0 interface{}) ([]byte, error) {
52 | m.ctrl.T.Helper()
53 | ret := m.ctrl.Call(m, "Marshal", arg0)
54 | ret0, _ := ret[0].([]byte)
55 | ret1, _ := ret[1].(error)
56 | return ret0, ret1
57 | }
58 |
59 | // Marshal indicates an expected call of Marshal.
60 | func (mr *MockSerializerMockRecorder) Marshal(arg0 interface{}) *gomock.Call {
61 | mr.mock.ctrl.T.Helper()
62 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockSerializer)(nil).Marshal), arg0)
63 | }
64 |
65 | // Unmarshal mocks base method.
66 | func (m *MockSerializer) Unmarshal(arg0 []byte, arg1 interface{}) error {
67 | m.ctrl.T.Helper()
68 | ret := m.ctrl.Call(m, "Unmarshal", arg0, arg1)
69 | ret0, _ := ret[0].(error)
70 | return ret0
71 | }
72 |
73 | // Unmarshal indicates an expected call of Unmarshal.
74 | func (mr *MockSerializerMockRecorder) Unmarshal(arg0, arg1 interface{}) *gomock.Call {
75 | mr.mock.ctrl.T.Helper()
76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unmarshal", reflect.TypeOf((*MockSerializer)(nil).Unmarshal), arg0, arg1)
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/serialize/protobuf/fixtures/TestMarshal/test_ok.golden:
--------------------------------------------------------------------------------
1 |
2 | dataerror
--------------------------------------------------------------------------------
/pkg/serialize/protobuf/protobuf.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package protobuf
22 |
23 | import (
24 | "github.com/golang/protobuf/proto"
25 | "github.com/topfreegames/pitaya/v3/pkg/constants"
26 | )
27 |
28 | // Serializer implements the serialize.Serializer interface
29 | type Serializer struct{}
30 |
31 | // NewSerializer returns a new Serializer.
32 | func NewSerializer() *Serializer {
33 | return &Serializer{}
34 | }
35 |
36 | // Marshal returns the protobuf encoding of v.
37 | func (s *Serializer) Marshal(v interface{}) ([]byte, error) {
38 | pb, ok := v.(proto.Message)
39 | if !ok {
40 | return nil, constants.ErrWrongValueType
41 | }
42 | return proto.Marshal(pb)
43 | }
44 |
45 | // Unmarshal parses the protobuf-encoded data and stores the result
46 | // in the value pointed to by v.
47 | func (s *Serializer) Unmarshal(data []byte, v interface{}) error {
48 | pb, ok := v.(proto.Message)
49 | if !ok {
50 | return constants.ErrWrongValueType
51 | }
52 | return proto.Unmarshal(data, pb)
53 | }
54 |
55 | // GetName returns the name of the serializer.
56 | func (s *Serializer) GetName() string {
57 | return "protobuf"
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/serialize/serializer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) nano Author and TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package serialize
22 |
23 | import (
24 | "errors"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/serialize/json"
27 | "github.com/topfreegames/pitaya/v3/pkg/serialize/protobuf"
28 | )
29 |
30 | const (
31 | JSON Type = 1
32 | PROTOBUF Type = 2
33 | )
34 |
35 | type (
36 | // Type is the Serializer type.
37 | Type uint16
38 |
39 | // Marshaler represents a marshal interface
40 | Marshaler interface {
41 | Marshal(interface{}) ([]byte, error)
42 | }
43 |
44 | // Unmarshaler represents a Unmarshal interface
45 | Unmarshaler interface {
46 | Unmarshal([]byte, interface{}) error
47 | }
48 |
49 | // Serializer is the interface that groups the basic Marshal and Unmarshal methods.
50 | Serializer interface {
51 | Marshaler
52 | Unmarshaler
53 | GetName() string
54 | }
55 | )
56 |
57 | // All recognized and expected serializer type values.
58 |
59 | // NewSerializer returns a new serializer of the respective type (JSON or PROTOBUF) according to serializerType Type.
60 | // If serializerType is a JSON, then a JSON serializer is returned.
61 | // If serializerType is a PROTOBUF, then a PROTOBUF serializer is returned.
62 | // Otherwise, if serializerType is not a valid serializer type, then it returns nil.
63 | func NewSerializer(serializerType Type) (Serializer, error) { //nolint:ireturn
64 | switch serializerType {
65 | case JSON:
66 | return json.NewSerializer(), nil
67 | case PROTOBUF:
68 | return protobuf.NewSerializer(), nil
69 | default:
70 | return nil, errors.New("serializer type unknown")
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/service/base_service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "github.com/topfreegames/pitaya/v3/pkg/pipeline"
4 |
5 | type baseService struct {
6 | handlerHooks *pipeline.HandlerHooks
7 | }
8 |
9 | func (h *baseService) SetHandlerHooks(handlerHooks *pipeline.HandlerHooks) {
10 | h.handlerHooks = handlerHooks
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/service/fixtures/unmarshal_remote_test_1.golden:
--------------------------------------------------------------------------------
1 | blah
--------------------------------------------------------------------------------
/pkg/service/fixtures/unmarshal_remote_test_2.golden:
--------------------------------------------------------------------------------
1 | aaa
--------------------------------------------------------------------------------
/pkg/service/fixtures/unmarshal_remote_test_3.golden:
--------------------------------------------------------------------------------
1 | aab
--------------------------------------------------------------------------------
/pkg/session/fixtures/testSessionSetData_1.golden:
--------------------------------------------------------------------------------
1 | {"byte":"AQ=="}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testSessionSetData_2.golden:
--------------------------------------------------------------------------------
1 | {"int":1}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testSessionSetData_3.golden:
--------------------------------------------------------------------------------
1 | {"struct":{"A":1,"B":"aaa"}}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testSessionSetData_4.golden:
--------------------------------------------------------------------------------
1 | {"string":"aaa"}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testSessionSetData_5.golden:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testUpdateEncodedData_1.golden:
--------------------------------------------------------------------------------
1 | {"byte":"AQ=="}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testUpdateEncodedData_2.golden:
--------------------------------------------------------------------------------
1 | {"int":1}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testUpdateEncodedData_3.golden:
--------------------------------------------------------------------------------
1 | {"struct":{"A":1,"B":"aaa"}}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testUpdateEncodedData_4.golden:
--------------------------------------------------------------------------------
1 | {"string":"aaa"}
--------------------------------------------------------------------------------
/pkg/session/fixtures/testUpdateEncodedData_5.golden:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pkg/session/static.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | var DefaultSessionPool SessionPool
8 |
9 | // GetSessionByUID return a session bound to an user id
10 | func GetSessionByUID(uid string) Session {
11 | return DefaultSessionPool.GetSessionByUID(uid)
12 | }
13 |
14 | // GetSessionByID return a session bound to a frontend server id
15 | func GetSessionByID(id int64) Session {
16 | return DefaultSessionPool.GetSessionByID(id)
17 | }
18 |
19 | // OnSessionBind adds a method to be called when a session is bound
20 | // same function cannot be added twice!
21 | func OnSessionBind(f func(ctx context.Context, s Session) error) {
22 | DefaultSessionPool.OnSessionBind(f)
23 | }
24 |
25 | // OnAfterSessionBind adds a method to be called when session is bound and after all sessionBind callbacks
26 | func OnAfterSessionBind(f func(ctx context.Context, s Session) error) {
27 | DefaultSessionPool.OnAfterSessionBind(f)
28 | }
29 |
30 | // OnSessionClose adds a method that will be called when every session closes
31 | func OnSessionClose(f func(s Session)) {
32 | DefaultSessionPool.OnSessionClose(f)
33 | }
34 |
35 | // CloseAll calls Close on all sessions
36 | func CloseAll() {
37 | DefaultSessionPool.CloseAll()
38 | }
39 |
40 | // GetNumberOfConnectedClients returns the number of connected clients
41 | func GetNumberOfConnectedClients() int64 {
42 | return DefaultSessionPool.GetNumberOfConnectedClients()
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/timer_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package pitaya
22 |
23 | import (
24 | "testing"
25 | "time"
26 |
27 | "github.com/stretchr/testify/assert"
28 | "github.com/topfreegames/pitaya/v3/pkg/constants"
29 | "github.com/topfreegames/pitaya/v3/pkg/timer"
30 | )
31 |
32 | type MyCond struct{}
33 |
34 | func (m *MyCond) Check(now time.Time) bool {
35 | return false
36 | }
37 |
38 | func TestNewTimer(t *testing.T) {
39 | t.Parallel()
40 | tt := NewTimer(100*time.Millisecond, func() {})
41 | assert.NotNil(t, tt)
42 | }
43 |
44 | func TestNewCountTimer(t *testing.T) {
45 | t.Parallel()
46 | tt := NewCountTimer(100*time.Millisecond, 10, func() {})
47 | assert.NotNil(t, tt)
48 | }
49 |
50 | func TestNewAfterTimer(t *testing.T) {
51 | t.Parallel()
52 | tt := NewAfterTimer(100*time.Millisecond, func() {})
53 | assert.NotNil(t, tt)
54 | }
55 |
56 | func TestNewCondTimer(t *testing.T) {
57 | t.Parallel()
58 | _, err := NewCondTimer(nil, func() {})
59 | assert.EqualError(t, constants.ErrNilCondition, err.Error())
60 |
61 | tt, err := NewCondTimer(&MyCond{}, func() {})
62 | assert.NoError(t, err)
63 | assert.NotNil(t, tt)
64 | }
65 |
66 | func TestSetTimerPrecision(t *testing.T) {
67 | t.Parallel()
68 | dur := 33 * time.Millisecond
69 | SetTimerPrecision(dur)
70 | assert.Equal(t, dur, timer.Precision)
71 | }
72 |
73 | func TestSetTimerBacklog(t *testing.T) {
74 | backlog := 1 << 4
75 | SetTimerBacklog(backlog)
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/tracing/otel.go:
--------------------------------------------------------------------------------
1 | package tracing
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strings"
8 |
9 | "github.com/topfreegames/pitaya/v3/pkg/logger"
10 | "go.opentelemetry.io/otel"
11 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
13 | "go.opentelemetry.io/otel/propagation"
14 | "go.opentelemetry.io/otel/sdk/resource"
15 | sdktrace "go.opentelemetry.io/otel/sdk/trace"
16 | )
17 |
18 | func InitializeOtel() error {
19 | // Print OpenTelemetry configuration
20 | printOtelConfig()
21 |
22 | ctx := context.Background()
23 |
24 | res, err := resource.New(ctx,
25 | resource.WithFromEnv(),
26 | resource.WithOS(),
27 | resource.WithHost(),
28 | )
29 | if err != nil {
30 | return err
31 | }
32 |
33 | client := otlptracegrpc.NewClient()
34 |
35 | exporter, err := otlptrace.New(ctx, client)
36 | if err != nil {
37 | return err
38 | }
39 |
40 | tp := sdktrace.NewTracerProvider(
41 | sdktrace.WithBatcher(exporter),
42 | sdktrace.WithResource(res),
43 | )
44 |
45 | otel.SetTracerProvider(tp)
46 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
47 |
48 | logger.Log.Info("OpenTelemetry initialized using environment variables")
49 | return nil
50 | }
51 |
52 | func printOtelConfig() {
53 | vars := []string{
54 | "OTEL_SERVICE_NAME",
55 | "OTEL_EXPORTER_OTLP_ENDPOINT",
56 | "OTEL_EXPORTER_OTLP_PROTOCOL",
57 | "OTEL_TRACES_SAMPLER",
58 | "OTEL_TRACES_SAMPLER_ARG",
59 | "OTEL_RESOURCE_ATTRIBUTES",
60 | "OTEL_SDK_DISABLED",
61 | }
62 |
63 | config := make([]string, 0, len(vars))
64 | for _, v := range vars {
65 | value := os.Getenv(v)
66 | if value == "" {
67 | value = ""
68 | }
69 | config = append(config, fmt.Sprintf("%s=%s", v, value))
70 | }
71 |
72 | logger.Log.Info(fmt.Sprintf("OpenTelemetry Configuration: %s", strings.Join(config, ", ")))
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/util/compression/compression.go:
--------------------------------------------------------------------------------
1 | package compression
2 |
3 | import (
4 | "bytes"
5 | "compress/zlib"
6 | "io"
7 | )
8 |
9 | func DeflateData(data []byte) ([]byte, error) {
10 | var bb bytes.Buffer
11 | z := zlib.NewWriter(&bb)
12 | _, err := z.Write(data)
13 | if err != nil {
14 | return nil, err
15 | }
16 | z.Close()
17 | return bb.Bytes(), nil
18 | }
19 |
20 | func InflateData(data []byte) ([]byte, error) {
21 | zr, err := zlib.NewReader(bytes.NewBuffer(data))
22 | if err != nil {
23 | return nil, err
24 | }
25 | defer zr.Close()
26 |
27 | return io.ReadAll(zr)
28 | }
29 |
30 | func IsCompressed(data []byte) bool {
31 | return len(data) > 2 &&
32 | (
33 | // zlib
34 | (data[0] == 0x78 &&
35 | (data[1] == 0x9C ||
36 | data[1] == 0x01 ||
37 | data[1] == 0xDA ||
38 | data[1] == 0x5E)) ||
39 | // gzip
40 | (data[0] == 0x1F && data[1] == 0x8B))
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/util/compression/compression_test.go:
--------------------------------------------------------------------------------
1 | package compression
2 |
3 | import (
4 | "flag"
5 | "path/filepath"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | "github.com/topfreegames/pitaya/v3/pkg/helpers"
11 | )
12 |
13 | var update = flag.Bool("update", false, "update .golden files")
14 |
15 | var ins = []struct {
16 | name string
17 | data string
18 | }{
19 | {"compression_deflate_test_1", "test"},
20 | {"compression_deflate_test_2", "{a:1,b:2}"},
21 | {"compression_deflate_test_3", "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit"},
22 | }
23 |
24 | func TestCompressionDeflate(t *testing.T) {
25 | for _, in := range ins {
26 | t.Run(in.name, func(t *testing.T) {
27 | b, err := DeflateData([]byte(in.data))
28 | require.NoError(t, err)
29 | gp := filepath.Join("fixtures", in.name+".golden")
30 | if *update {
31 | t.Log("updating golden file")
32 | helpers.WriteFile(t, gp, b)
33 | }
34 | expected := helpers.ReadFile(t, gp)
35 |
36 | assert.Equal(t, expected, b)
37 | })
38 | }
39 | }
40 |
41 | func TestCompressionInflate(t *testing.T) {
42 | for _, in := range ins {
43 | t.Run(in.name, func(t *testing.T) {
44 | inputFile := filepath.Join("fixtures", in.name+".golden")
45 | input := helpers.ReadFile(t, inputFile)
46 |
47 | result, err := InflateData(input)
48 | require.NoError(t, err)
49 |
50 | assert.Equal(t, string(result), in.data)
51 | })
52 | }
53 | }
54 |
55 | func TestCompressionInflateIncorrectData(t *testing.T) {
56 | t.Run("compression_deflate_incorrect_data", func(t *testing.T) {
57 | input := "arbitrary data"
58 |
59 | result, err := InflateData([]byte(input))
60 | require.Error(t, err)
61 | assert.Nil(t, result)
62 | })
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/util/compression/fixtures/compression_deflate_test_1.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/compression/fixtures/compression_deflate_test_1.golden
--------------------------------------------------------------------------------
/pkg/util/compression/fixtures/compression_deflate_test_2.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/compression/fixtures/compression_deflate_test_2.golden
--------------------------------------------------------------------------------
/pkg/util/compression/fixtures/compression_deflate_test_3.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/compression/fixtures/compression_deflate_test_3.golden
--------------------------------------------------------------------------------
/pkg/util/fixtures/gob_encode_test_1.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/fixtures/gob_encode_test_1.golden
--------------------------------------------------------------------------------
/pkg/util/fixtures/gob_encode_test_2.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/fixtures/gob_encode_test_2.golden
--------------------------------------------------------------------------------
/pkg/util/fixtures/gob_encode_test_3.golden:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfreegames/pitaya/08d34a00bfc535b4e280cf74fd1092562375402d/pkg/util/fixtures/gob_encode_test_3.golden
--------------------------------------------------------------------------------
/pkg/worker/constants.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package worker
22 |
23 | const (
24 | rpcQueue = "rpc"
25 | class = ""
26 | )
27 |
--------------------------------------------------------------------------------
/pkg/worker/models.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package worker
22 |
23 | import "github.com/golang/protobuf/proto"
24 |
25 | type rpcInfo struct {
26 | Route string
27 | Metadata map[string]interface{}
28 | Arg proto.Message
29 | Reply proto.Message
30 | }
31 |
32 | type rpcRoute struct {
33 | Route string
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/worker/report.go:
--------------------------------------------------------------------------------
1 | package worker
2 |
3 | import (
4 | "strconv"
5 | "time"
6 |
7 | "github.com/topfreegames/pitaya/v3/pkg/logger"
8 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
9 |
10 | workers "github.com/topfreegames/go-workers"
11 | )
12 |
13 | // Report sends periodic of worker reports
14 | func Report(reporters []metrics.Reporter, period time.Duration) {
15 | for {
16 | time.Sleep(period)
17 |
18 | workerStats := workers.GetStats()
19 | for _, r := range reporters {
20 | reportJobsRetry(r, workerStats.Retries)
21 | reportQueueSizes(r, workerStats.Enqueued)
22 | reportJobsTotal(r, workerStats.Failed, workerStats.Processed)
23 | }
24 | }
25 | }
26 |
27 | func reportJobsRetry(r metrics.Reporter, retries int64) {
28 | err := r.ReportGauge(metrics.WorkerJobsRetry, map[string]string{}, float64(retries))
29 | checkReportErr(metrics.WorkerJobsRetry, err)
30 | }
31 |
32 | func reportQueueSizes(r metrics.Reporter, queues map[string]string) {
33 | for queue, size := range queues {
34 | tags := map[string]string{"queue": queue}
35 | sizeFlt, err := strconv.ParseFloat(size, 64)
36 | if err != nil {
37 | logger.Log.Errorf("queue size is not int: queue=%s size=%s", queue, size)
38 | continue
39 | }
40 |
41 | err = r.ReportGauge(metrics.WorkerQueueSize, tags, sizeFlt)
42 | checkReportErr(metrics.WorkerQueueSize, err)
43 | }
44 | }
45 |
46 | func reportJobsTotal(r metrics.Reporter, failed, processed int) {
47 | // "failed" and "processed" always grow up,
48 | // so they work as count but must be reported as gauge
49 | err := r.ReportGauge(metrics.WorkerJobsTotal, map[string]string{
50 | "status": "failed",
51 | }, float64(failed))
52 | checkReportErr(metrics.WorkerJobsTotal, err)
53 |
54 | err = r.ReportGauge(metrics.WorkerJobsTotal, map[string]string{
55 | "status": "ok",
56 | }, float64(processed))
57 | checkReportErr(metrics.WorkerJobsTotal, err)
58 | }
59 |
60 | func checkReportErr(metric string, err error) {
61 | if err != nil {
62 | logger.Log.Errorf("failed to report to %s: %q\n", metric, err)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/worker/report_test.go:
--------------------------------------------------------------------------------
1 | package worker
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | "github.com/golang/mock/gomock"
8 | "github.com/topfreegames/pitaya/v3/pkg/metrics"
9 | "github.com/topfreegames/pitaya/v3/pkg/metrics/mocks"
10 | )
11 |
12 | func TestReportJobsRetry(t *testing.T) {
13 | t.Parallel()
14 |
15 | t.Run("success", func(t *testing.T) {
16 | ctrl := gomock.NewController(t)
17 | defer ctrl.Finish()
18 |
19 | mockReporter := mocks.NewMockReporter(ctrl)
20 | mockReporter.EXPECT().ReportGauge(
21 | metrics.WorkerJobsRetry,
22 | map[string]string{},
23 | float64(10))
24 |
25 | reportJobsRetry(mockReporter, 10)
26 | })
27 |
28 | t.Run("error", func(t *testing.T) {
29 | ctrl := gomock.NewController(t)
30 | defer ctrl.Finish()
31 |
32 | mockReporter := mocks.NewMockReporter(ctrl)
33 | mockReporter.EXPECT().ReportGauge(
34 | metrics.WorkerJobsRetry,
35 | map[string]string{},
36 | float64(10)).Return(errors.New("err"))
37 |
38 | reportJobsRetry(mockReporter, 10)
39 | })
40 | }
41 |
42 | func TestReportQueueSizes(t *testing.T) {
43 | t.Parallel()
44 |
45 | var (
46 | queue1 = "queuename1"
47 | queue2 = "queuename2"
48 | )
49 |
50 | ctrl := gomock.NewController(t)
51 | defer ctrl.Finish()
52 |
53 | mockReporter := mocks.NewMockReporter(ctrl)
54 | mockReporter.EXPECT().ReportGauge(
55 | metrics.WorkerQueueSize,
56 | map[string]string{"queue": queue1},
57 | float64(10))
58 | mockReporter.EXPECT().ReportGauge(
59 | metrics.WorkerQueueSize,
60 | map[string]string{"queue": queue2},
61 | float64(20))
62 |
63 | reportQueueSizes(mockReporter, map[string]string{
64 | queue1: "10",
65 | queue2: "20",
66 | })
67 | }
68 |
69 | func TestReportJobsTotal(t *testing.T) {
70 | t.Parallel()
71 |
72 | ctrl := gomock.NewController(t)
73 | defer ctrl.Finish()
74 |
75 | mockReporter := mocks.NewMockReporter(ctrl)
76 | mockReporter.EXPECT().ReportGauge(
77 | metrics.WorkerJobsTotal,
78 | map[string]string{"status": "failed"},
79 | float64(10))
80 |
81 | mockReporter.EXPECT().ReportGauge(
82 | metrics.WorkerJobsTotal,
83 | map[string]string{"status": "ok"},
84 | float64(20))
85 |
86 | reportJobsTotal(mockReporter, 10, 20)
87 | }
88 |
--------------------------------------------------------------------------------
/pkg/worker/rpc_job.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package worker
22 |
23 | import (
24 | "context"
25 |
26 | "github.com/golang/protobuf/proto"
27 | )
28 |
29 | // RPCJob has infos to execute a rpc on worker
30 | type RPCJob interface {
31 | // ServerDiscovery returns a serverID based on the route
32 | // and any metadata that is necessary to decide
33 | ServerDiscovery(
34 | route string,
35 | rpcMetadata map[string]interface{},
36 | ) (serverID string, err error)
37 |
38 | // RPC executes the RPC
39 | // It is expected that if serverID is "" the RPC
40 | // happens to any destiny server
41 | RPC(
42 | ctx context.Context,
43 | serverID, routeStr string,
44 | reply, arg proto.Message,
45 | ) error
46 |
47 | // GetArgReply returns the arg and reply of the
48 | // method
49 | GetArgReply(route string) (arg, reply proto.Message, err error)
50 | }
51 |
--------------------------------------------------------------------------------
/repl/file.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package repl
22 |
23 | import (
24 | "bufio"
25 | "errors"
26 | "log"
27 | "os"
28 | "strings"
29 | )
30 |
31 | func executeFromFile(fileName string) {
32 | logger := log.New(os.Stdout, "", log.LstdFlags)
33 |
34 | var err error
35 | defer func() {
36 | if err != nil {
37 | logger.Printf("error: %s", err.Error())
38 | }
39 | }()
40 |
41 | var file *os.File
42 | file, err = os.Open(fileName)
43 | if err != nil {
44 | return
45 | }
46 |
47 | scanner := bufio.NewScanner(file)
48 | for scanner.Scan() {
49 | command := scanner.Text()
50 | err = executeCommand(logger, command)
51 | if err != nil {
52 | return
53 | }
54 | }
55 |
56 | err = scanner.Err()
57 | if err != nil {
58 | return
59 | }
60 | }
61 |
62 | func executeCommand(logger Log, command string) error {
63 | parts := strings.Split(command, " ")
64 |
65 | switch parts[0] {
66 | case "connect":
67 | return connect(logger, parts[1], func(data []byte) {
68 | log.Printf("sv-> %s\n", string(data))
69 | wait.Done()
70 | })
71 |
72 | case "sethandshake":
73 | return setHandshake(logger, parts[1:])
74 |
75 | case "request":
76 | wait.Add(1)
77 | if err := request(logger, parts[1:]); err != nil {
78 | return err
79 | }
80 | wait.Wait()
81 | return nil
82 |
83 | case "notify":
84 | return notify(logger, parts[1:])
85 |
86 | case "push":
87 | return push(logger, parts[1:])
88 | case "routes":
89 | return routes(logger)
90 | case "disconnect":
91 | disconnect()
92 | return nil
93 |
94 | default:
95 | return errors.New("command not found")
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/repl/log.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package repl
22 |
23 | // Log has log methods
24 | type Log interface {
25 | Print(...interface{})
26 | Println(...interface{})
27 | Printf(string, ...interface{})
28 | }
29 |
--------------------------------------------------------------------------------
/repl/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) TFG Co. All Rights Reserved.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | package repl
22 |
23 | import (
24 | "sync"
25 |
26 | "github.com/topfreegames/pitaya/v3/pkg/client"
27 | "github.com/topfreegames/pitaya/v3/pkg/session"
28 | )
29 |
30 | var (
31 | pClient client.PitayaClient
32 | disconnectedCh chan bool
33 | docsString string
34 | fileName string
35 | pushInfo map[string]string
36 | wait sync.WaitGroup
37 | prettyJSON bool
38 | handshake *session.HandshakeData
39 | )
40 |
41 | func Start(docs, filename string, prettyJSON bool) {
42 | docsString = docs
43 | fileName = filename
44 | prettyJSON = prettyJSON
45 | handshake = &session.HandshakeData{
46 | Sys: session.HandshakeClientData{
47 | Platform: "repl",
48 | LibVersion: "0.3.5-release",
49 | BuildNumber: "20",
50 | Version: "1.0.0",
51 | },
52 | User: map[string]interface{}{
53 | "client": "repl",
54 | },
55 | }
56 |
57 | switch {
58 | case fileName != "":
59 | executeFromFile(fileName)
60 | default:
61 | repl()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/xk6-pitaya/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build the k6 binary with the extension
2 | FROM golang:1.23 as builder
3 |
4 | ARG pitaya_revision
5 |
6 | RUN go install go.k6.io/xk6/cmd/xk6@latest
7 | # For our example, we'll add support for output of test metrics to InfluxDB v2.
8 | # Feel free to add other extensions using the '--with ...'.
9 | RUN xk6 build \
10 | --with github.com/topfreegames/pitaya/xk6-pitaya@$pitaya_revision \
11 | --replace github.com/topfreegames/pitaya/v3=github.com/topfreegames/pitaya/v3@$pitaya_revision \
12 | --output /k6
13 |
14 | # Use the operator's base image and override the k6 binary
15 | FROM grafana/k6:latest
16 | COPY --from=builder /k6 /usr/bin/k6
17 |
--------------------------------------------------------------------------------
/xk6-pitaya/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023 Wildlife Studios
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/xk6-pitaya/examples/scenario1.js:
--------------------------------------------------------------------------------
1 | import pitaya from 'k6/x/pitaya';
2 | import { check } from 'k6';
3 |
4 | export const options = {
5 | vus: 10,
6 | duration: '10s',
7 | }
8 |
9 | const opts = {
10 | handshakeData: {
11 | sys: {
12 | clientVersion: "1.0.1",
13 | clientBuildNumber: "1",
14 | platform: "android"
15 | },
16 | user: {
17 | fiu: "c0a78b27-dd34-4e0d-bff7-36168fce0df5",
18 | bundleId: "com.game.test",
19 | deviceType: "ios",
20 | language: "en",
21 | osVersion: "12.0",
22 | region: "US",
23 | stack: "green-stack"
24 | }
25 | },
26 | requestTimeoutMs: 1000,
27 | useTLS: false,
28 | }
29 |
30 | const pitayaClient = new pitaya.Client(opts)
31 |
32 | export default async () => {
33 | if (!pitayaClient.isConnected()) {
34 | pitayaClient.connect("localhost:3250")
35 | }
36 |
37 | check(pitayaClient.isConnected(), { 'pitaya client is connected': (r) => r === true })
38 |
39 | var res = await pitayaClient.request("room.room.entry")
40 | check(res.result, { 'contains an result field': (r) => r !== undefined })
41 | check(res.result, { 'result is ok': (r) => r === "ok" })
42 | var res = await pitayaClient.request("room.room.setsessiondata", { data: {"testKey": "testVal"} })
43 | check(res, { 'res is success': (r) => String.fromCharCode.apply(null,r) === "success"} )
44 | var res = await pitayaClient.request("room.room.getsessiondata")
45 | check(res.Data, { 'res contains set data': (r) => r.testKey === "testVal"} )
46 |
47 | pitayaClient.notify("room.room.notifypush")
48 | res = await pitayaClient.consumePush("testPush", 100)
49 | check(res.Msg, { 'push contains msg': (m) => m === "test"} )
50 |
51 | res = await pitayaClient.request("room.room.join")
52 | check(res.result, { 'result from join is successful': (r) => r === "success"} )
53 | res = await pitayaClient.consumePush("onMembers", 1000)
54 | check(res.Members, { 'res contains a member group': (m) => m !== undefined } )
55 | res = await pitayaClient.request("room.room.leave")
56 | check(res, { 'result from leave is successful': (r) => String.fromCharCode.apply(null,r) === "success"})
57 |
58 | pitayaClient.disconnect()
59 | }
60 |
61 | export function teardown() {
62 | }
63 |
--------------------------------------------------------------------------------
/xk6-pitaya/register.go:
--------------------------------------------------------------------------------
1 | // Package pitays only exists to register the pitaya extension
2 | package pitaya
3 |
4 | import (
5 | "go.k6.io/k6/js/modules"
6 | )
7 |
8 | // Register the extension on module initialization, available to
9 | // import from JS as "k6/x/pitaya".
10 | func init() {
11 | modules.Register("k6/x/pitaya", new(RootModule))
12 | }
13 |
--------------------------------------------------------------------------------
/xk6-pitaya/stats.go:
--------------------------------------------------------------------------------
1 | package pitaya
2 |
3 | import (
4 | "errors"
5 |
6 | "go.k6.io/k6/js/modules"
7 | "go.k6.io/k6/metrics"
8 | )
9 |
10 | type pitayaMetrics struct {
11 | RequestResponseTime *metrics.Metric
12 | TimeoutRequests *metrics.Metric
13 | TagsAndMeta *metrics.TagsAndMeta
14 | }
15 |
16 | // registerMetrics registers the metrics for the mqtt module in the metrics registry
17 | func registerMetrics(vu modules.VU) (pitayaMetrics, error) {
18 | p := pitayaMetrics{}
19 | env := vu.InitEnv()
20 | if env == nil {
21 | return p, errors.New("missing env")
22 | }
23 | registry := env.Registry
24 | if registry == nil {
25 | return p, errors.New("missing registry")
26 | }
27 |
28 | var err error
29 | p.RequestResponseTime, err = registry.NewMetric("pitaya_client_request_duration_ms", metrics.Trend)
30 | if err != nil {
31 | return p, err
32 | }
33 |
34 | p.TimeoutRequests, err = registry.NewMetric("pitaya_client_request_timeout_count", metrics.Counter)
35 | if err != nil {
36 | return p, err
37 | }
38 |
39 | p.TagsAndMeta = &metrics.TagsAndMeta{
40 | Tags: registry.RootTagSet(),
41 | }
42 | return p, nil
43 | }
44 |
--------------------------------------------------------------------------------