Code Monkey home page Code Monkey logo

proxy's Introduction

@colyseus/proxy

Proxy and Service Discovery for Colyseus

Architecture representation

For a quickstart see Configuring Proxy + Colyseus + PM2

Running the Proxy

The easiest way to get @colyseus/proxy running is to install it globally.

This can be done by running:

npm install -g @colyseus/proxy

Edit your runtime environment to contain the following environment variables:

  • PORT is the port the proxy will be running on.
  • REDIS_URL is the path to the same Redis instance you're using on Colyseus' processes.
  • HEALTH_CHECK_PATH is an optional health check path, for example /health. The health check path always returns an HTTP 200 response. Prefix the value with an asterisk to match a path ending, for example */health.

Once installed it can be run with

colyseus-proxy

Running the Proxy From Source

Clone, this project and install its dependencies:

git clone https://github.com/colyseus/proxy.git
cd proxy
npm install

Edit your environment to contain the following environment variables:

  • PORT is the port the proxy will be running on.
  • REDIS_URL is the path to the same Redis instance you're using on Colyseus' processes.
  • HEALTH_CHECK_PATH is an optional health check path, for example /health. The health check path always returns an HTTP 200 response. Prefix the value with an asterisk to match a path ending, for example */health.

Start the proxy server:

npx ts-node proxy.ts

Configuring Proxy + Colyseus + PM2

  • Install @colyseus/proxy (npm install --save @colyseus/proxy)
  • Configure RedisPresence
  • Configure MongooseDriver
  • Bind each instance of the server on a different port
  • Use PM2 to manage Colyseus and Proxy instances

Configure the colyseus application:

import { Server, RedisPresence } from "colyseus";
import { MongooseDriver } from "colyseus/lib/matchmaker/drivers/MongooseDriver"

// binds each instance of the server on a different port.
const PORT = Number(process.env.PORT) + Number(process.env.NODE_APP_INSTANCE);

const gameServer = new Server({
    presence: new RedisPresence({
        url: "redis://127.0.0.1:6379/0"
    }),
    driver: new MongooseDriver(),
})

gameServer.listen(PORT);
console.log("Listening on", PORT);

It's recommended to use PM2 to manage your server instances. PM2 allows to scale Node.js processes up and down within your server. PM2 can also be used to manage and scale up the proxy instances.

npm install -g pm2

Use the following ecosystem.config.js configuration:

// ecosystem.config.js
const os = require('os');
module.exports = {
    apps: [
         {
            port        : 80,
            name        : "colyseus-proxy",
            script      : "./node_modules/@colyseus/proxy/bin/proxy",
            instances   : 1, // scale this up if the proxy becomes the bottleneck
            exec_mode   : 'cluster',
            env: {
                PORT: 80,
                REDIS_URL: "redis://127.0.0.1:6379/0"
            }
        },
        {
            port        : 8080,
            name        : "colyseus",
            script      : "lib/index.js", // your entrypoint file
            watch       : true,           // optional
            instances   : os.cpus().length,
            exec_mode   : 'fork',         // IMPORTANT: do not use cluster mode.
            env: {
                DEBUG: "colyseus:errors",
                NODE_ENV: "production",
            }
        }
    ]
}

Now you're ready to start multiple Colyseus proceses.

pm2 start

If you're using TypeScript, compile your project before running pm2 start, via npx tsc.

You should see the following output, depending on the amount of processes your server have:

[PM2][WARN] Applications colyseus not running, starting...
[PM2] App [colyseus] launched (2 instances)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Name     β”‚ id β”‚ mode    β”‚ status β”‚ β†Ί β”‚ cpu β”‚ memory    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ proxy    β”‚ 0  β”‚ cluster β”‚ online β”‚ 0 β”‚ 0%  β”‚ 7.4 MB    β”‚
β”‚ colyseus β”‚ 1  β”‚ fork    β”‚ online β”‚ 0 β”‚ 0%  β”‚ 15.4 MB   β”‚
β”‚ colyseus β”‚ 2  β”‚ fork    β”‚ online β”‚ 0 β”‚ 0%  β”‚ 12.3 MB   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Use `pm2 show <id|name>` to get more details about an app

Now, run pm2 logs to check if you don't have any errors.

LICENSE

MIT

proxy's People

Contributors

carlosfusion avatar endel avatar epiphone avatar jldinh avatar lpsandaruwan avatar lukewood avatar mobyjames avatar mramadhanrh avatar theweiweiway avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proxy's Issues

seat reservation expired when using proxy with more than 3 instances randomly

Hi.
I'm opening this issue because when i use pm2 and use more than 2/3 instances the server throws

Error: seat reservation expired.
0|colyseus-app  |     at WebSocketServer.onConnection (/Users/user/Documents/GitHub/dverso/multiplayer_server/node_modules/@colyseus/ws-transport/build/WebSocketTransport.js:105:23)
0|colyseus-app  |     at processTicksAndRejections (node:internal/process/task_queues:96:5)

It happens casually, not everytime, but the more instances i set the more frequently it pops.

here my pm2 config

/* PM2 config file */
const os = require('os');

module.exports = {
    apps : [
      /**
       * Colyseus processes
       */
      {
        name      : 'colyseus-app',
        script    : './dist/multiplayer_server/index.js',
        instances : 8,
        exec_mode : 'fork',
        port      : 3567,
        env: {
          NODE_ENV: 'development',
        },
      },
      /**
       * Proxy process
       */
      {
        name: 'proxy',
        script: './node_modules/@colyseus/proxy/bin/proxy',
        instances: 1, // os.cpus().length,
        exec_mode: 'fork',
        env: {
          PORT : 2567,
          // Required options:
          REDIS_URL: 'redis://127.0.0.1:6379/0',
  
          // // SSL (optional)
          // SSL_KEY: "/Users/endel/.localhost-ssl/localhost.key",
          // SSL_CERT: "/Users/endel/.localhost-ssl/localhost.crt",
        }
      }],
  
  };

this is my server setup code

const gameServer = new Server({
  presence: new RedisPresence({
    db : 0,
    host : "127.0.0.1",
    port : 6379,
  }),
  driver : new MongooseDriver(),
  transport: new WebSocketTransport({
  })
});

always get error when I run comand β€œnpm start”

system: Win10
node:14.15.4

I got error when I run comand β€œnpm start” PM2][ERROR] Error: Interpreter F:\proxy-master\pm2-example\node_modules\pm2\node_modules\.bin\ts-node is NOT AVAILABLE in PATH. (type 'which F:\proxy-master\pm2-example\node_modules\pm2\node_modules\.bin\ts-node' to double check.),I don,t know why locate to this dir,because ts-node is under F:\proxy-master\pm2-example\node_modules/ts-node.
my ecosystem.config.js:
module.exports = { apps : [ { name : 'colyseus-app', script : 'index.ts', interpreter: "ts-node", watch : false, instances : 3, exec_mode : 'fork', port : 8080, env: { NODE_ENV: 'development', DEBUG: 'colyseus:errors,colyseus:matchmaking' }, }, { name: 'proxy', script: './node_modules/@colyseus/proxy/bin/proxy', instances: 1, // os.cpus().length, exec_mode: 'fork', env: { REDIS_URL: process.env.REDIS_URL, } }], };

proxy scale not work

nodejs version is : v14.15.3
my config is :

module.exports = {
    apps: [
        /**
         * Colyseus processes
         */
        {
            name: 'colyseus-app',
            script: 'index.ts',
            exec_interpreter: "ts-node",
            watch: false,
            instances: 3,
            exec_mode: 'fork',
            port: 8080,
            env: {
                NODE_ENV: 'development',
                DEBUG: 'colyseus:errors,colyseus:matchmaking'
            },
        },

        /**
         * Proxy process
         */
        {
            name: 'proxy',
            script: './node_modules/@colyseus/proxy/bin/proxy',
            instances: 3, // os.cpus().length,
            exec_mode: 'fork', //fork is from pm2-example, but in readme it's cluster, cluster not work too
            env: {
                // Required options:
                REDIS_URL: 'redis://:[email protected]:9030/0',

                // // SSL (optional)
                // SSL_KEY: "/Users/endel/.localhost-ssl/localhost.key",
                // SSL_CERT: "/Users/endel/.localhost-ssl/localhost.crt",
            }
        }],

};

then run npm start

pm2 output is :
image

what should I do?thanks.

stress-test failed

I run pm2-example Project use pm2 , my ecosystem.config.js below:

module.exports = { apps : [ { name : 'colyseus-app', script : './lib', watch : false, instances : 3, exec_mode : 'fork', port : 8080, env: { NODE_ENV: 'development', DEBUG: 'colyseus:errors,colyseus:matchmaking' }, }, { name: 'proxy', script: './node_modules/@colyseus/proxy/bin/proxy', instances: 1, // os.cpus().length, exec_mode: 'fork', port : 80, env: { PORT: 80, // Required options: REDIS_URL: process.env.REDIS_URL, } }], };

and I think there is nothin wrong,I got message when I use pm2,

β”‚ 1 β”‚ colyseus-app β”‚ fork β”‚ 3 β”‚ online β”‚ 0% β”‚ 75.4mb β”‚
β”‚ 2 β”‚ colyseus-app β”‚ fork β”‚ 3 β”‚ online β”‚ 0% β”‚ 75.0mb β”‚
β”‚ 3 β”‚ colyseus-app β”‚ fork β”‚ 3 β”‚ online β”‚ 0% β”‚ 51.8mb β”‚
β”‚ 0 β”‚ proxy β”‚ fork β”‚ 11 β”‚ online β”‚ 0% β”‚ 38.3mb `

but when I run ts-node tress-test.ts,I got Error :UnhandledPromiseRejectionWarning: Error: Service Unavailable.
I didn't change the file.

Android cannot connect proxy

I have issue when connect react-native Android app to proxy

If I DON'T use proxy : iOS - ok, android - ok
If I use proxy : iOS - ok, android cannot connect

Please check

Have a hosted container to reduce CI effort on user side

I'm suggesting a hosted container image of colyseus-proxy on Docker Hub or GCR to make it easier for the end user to deploy.
Current status:

  • End user has to run its own CI to build a ColP container

Implications: Many, oftentimes insecure, containerized versions of Colyseus Proxy seem to be wandering about. This poses a threat for clusters of (e.g. Kubernetes-) beginners who might not be familiar with container security best practices and unknowingly deploy web-facing, insecure containers, which could easily compromise their app regarding connections to the backend (via Redis)

Seat Reservation Expired after a few connections when using Colyseus Proxy + Redis

I've been following the guideline of scalability on the Colyseus website. It works great when there were a few connections to my game room. Then I saw something in pm2 logs saying Seat Reservation Expired and every connection trying to connect to a room after that error was failed.
I was kinda sure this problem came from Redis because it was working so great, if there was something going wrong in my implementation, it would be crashed on the first try.
So I captured the Redis log when it was normal and when it was failed. I placed them here

This one was normal behavior of Redis
Screenshot from 2021-09-24 00-39-19

This one was failed during the attempt to connect to a room. I saw Redis unsubscribed the room immediately after it was created, and this led to the error.
Screenshot from 2021-09-24 00-34-57

This is the Seat Reservation Expired error
Screenshot from 2021-09-24 00-54-47

This is my ecosystem.config.js for pm2. I run 2 instances of colyseus proxy when deploy. It will go wrong the same way even I only use one instance of colyseus proxy.
Screenshot from 2021-09-24 00-57-47

This is my server setup

import { MongooseDriver } from "@colyseus/mongoose-driver"
import { constructSkinsData, loadSkinBase64sbyId } from "./datas/skins";
import GameRoom from "./rooms/game-room";
import Routes from "./routes/routes";

const http = require('http');
const express = require('express');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 5 * 60 * 1000, // 5 minutes
  max: 10, // 10 requeqst during 5 minutes
})

const app = express();

const PORT = Number(process.env.PORT) + Number(process.env.NODE_APP_INSTANCE);

/*Create an app*/
app.set('trust proxy', 1);

app.use( express.json() );       // to support JSON-encoded bodies

app.use( express.urlencoded({     // to support URL-encoded bodies
  extended: true
})); 

app.use(cors());

app.use(express.static(__dirname + '../../../dist/client'));

app.get('/', function (req : any, res : any) {
  res.sendFile(__dirname + '../../../dist/client/index.html');
});

app.use('/api', new Routes().router);

app.use('/matchmake/', apiLimiter);
/*-------------*/

const colyseus = require("colyseus");
const { WebSocketTransport } = require('@colyseus/ws-transport');

const server = http.createServer(app);

const gameServer = new colyseus.Server({
  transport: new WebSocketTransport({
    server
  }),
  presence: new colyseus.RedisPresence({
    url: `redis://127.0.0.1:6379/0`
  }),
  driver: new MongooseDriver(),
});

gameServer.define('game-room', GameRoom);

/*Initialize essential assets for this server*/
console.log('Initializing essential assets...');
constructSkinsData().then(() => {
  console.log('DONE LOADING ASSETS!');

  gameServer.listen(PORT);

  console.log('Starting listening on ', PORT);
});

Enable CORS on the proxy

My Express app has CORS enabled, but it doesn't work becouse the proxy doesn't have the CORS enabled.

How mongoDB being used by Colyseus?

Just curious about this because I've tried to run and connect with proxy and it's working without Mongo connection, is it being used for room listing?

how does this proxy work

I read the sourcecode,I see the createRoom function has a Ipc call that means if there is one proxy instance A and two severs instance B and C when player live in B but he maybe create Room in C sever. but with my test when this player reconnect he could get failed because of room not found. so it means that our developer need do something to reconnect sever that player connect first time ,is it ? so when any severs instance failed player reconnect handler could get influence

App Crash with invalid URL submitted

A malicious user can send an invalid URL in the HTTP header and the proxy throws an unhandled exception, killing the process and disconnecting all active sockets.

Can be trivially fixed with try/catch in the request and upgrade handlers.

Socket hang up

When I run this proxy, I get this error

Error: socket hang up
at createHangUpError (_http_client.js:330:15)
at Socket.socketOnEnd (_http_client.js:431:23)
at Socket.emit (events.js:202:15)
at Socket.EventEmitter.emit (domain.js:446:20)
at endReadableNT (_stream_readable.js:1129:12)
at processTicksAndRejections (internal/process/next_tick.js:76:17)

I tried to fix it and I found this, if I replace the target in original code, from this

  const proxy = httpProxy.createProxy({
    agent: http.globalAgent,
    target: { host, port },
    ws: true
  });

to this

  const proxy = httpProxy.createProxy({
    agent: http.globalAgent,
    target: `http://localhost:${port}`,
    ws: true
  });

It works. I don't really understand why. Maybe you can help me explain it.
I fixed the code in my case, and I also replace the backend to localhost if it's the same with proxy IP.

Colyseus nodejs and nginx with ecosystem.config.js

Please give me instruction for nginx server where port 2567 is default
In new version colyseus i dont understand how this work

I am try run step by step of your instruction, but my app not working.

feature: 200-code health check endpoint

When using this in Docker, it'd be useful to have a built-in /health route which just returned an empty 200 response, currently I'm making the health check URL my.proxy.com/matchmaker which forwards it to a Colyseus server for a 200, which poses a couple problems:

  • It has to matchmake to be healthy
  • If servers fail and none are available then the proxy would be considered unhealthy

create Room Failed

system:Win10
node :14.15.4

I create a colyseus instance and bind port 8000,then I run stress-test and location is 127.0.0.1:8000, I connect to sever and call const ranked = await client.joinOrCreate("ranked", {maxWaitingTime:12,numClientsToMatch:1});,but It didn't get ranked back.please help.

`

Memory leak + seat reservation expired error

Yesterday, we switched our live system to our new Kubernetes setup, utilizing the Colyseus Proxy together with MongoDB and Redis for load balancing. We had a public beta over the last month with about 800 players a day and everything worked fine. But after about 20k players played for a day we were seeing seat reservation expired more and more often up to a point where nobody was able to join or create any lobby.

What we found:

Examining the resource consumption of the Colyseus Proxy over the last 24 hours suggests a memory leak:
Colyseus Proxy resource consumption

Our logs repeatedly show these errors:

Using proxy 1 /NLK_aUr7s/HVCFC?sessionId=eGUwvAl7F
Error: seat reservation expired.
  at uWebSocketsTransport.onConnection (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:118:23)
  at open (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:59:28)
  at uWS.HttpResponse.upgrade (<anonymous>)
  at upgrade (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:47:21)
2022-07-21T06:01:15.208Z colyseus:errors Error: seat reservation expired.
  at uWebSocketsTransport.onConnection (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:118:23)
  at open (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:59:28)
  at uWS.HttpResponse.upgrade (<anonymous>)
  at upgrade (/usr/app/node_modules/@colyseus/uwebsockets-transport/build/uWebSocketsTransport.js:47:21)

Restarting the proxies fixes the problem temporarily.

Setup:

  • Colyseus version: 0.14.29
  • Colyseus proxy version: 0.12.8
  • Node version: 16.15.1-alpine

Edit: We were running 2 proxies behind a load balancer and 5 gameserver instances. This might be related to #30.

We really need help with this issue, as I am at my wit's end.
Thank you in advance! πŸ™

Seat reservation expired, room is always null

After configuring pm2 and redis, when trying to enter a room I get the following logs:

2020-03-18T15:55:37.243Z colyseus:matchmaking reserving seat. sessionId: 'h9_b_g-LA', roomId: '5d7f7dbb7fa1810ad880b62c', processId: 'C2AxdKOt3'

2020-03-18T15:55:37.283Z colyseus:patch 5419 bytes sent to 0 clients, ........

Error: seat reservation expired.

In WebSocketTransport.ts, onConnection function

    const room = matchMaker.getRoomById(roomId);

    // set client id
    client.pingCount = 0;

    // set client options
    client.id = sessionId;
    client.sessionId = sessionId;

    try {
      if (!room || !room.hasReservedSeat(sessionId)) {
        throw new Error('seat reservation expired.');
      }

      await room._onJoin(client, upgradeReq);

    }

I noticed that room is always null. this problem occurs whenever I create more than one instance. couldn't understand what may be happening.

My room:

export class PlaceRoom extends BaseRoom<Place> {
  pathfinder: Pathfinder;
  deltaTime = 1000 / 20;

  async onCreate(options: any) {
    console.log('PlaceRoom created!');

    await this.configureRoom(options);
    this.setPatchRate(this.deltaTime);
    this.setSimulationInterval(dt => this.update(dt));
  }

  requestJoin(options: any, isNew: boolean) {
    if (isNew && !options.id && this.state === undefined) {
      return false;
    } else {
      return !this.clients.some(client => client.id === options.clientId) ||
        !this.state.bannedUserIds.includes(options.id);
    }
  }

  onAuth(client: Client, options: any, request?: IncomingMessage): any | Promise<any> {
    if (!options.token) {
      return Promise.reject(new Error('No token'));
    }

    const token = verifyToken(options.token);

    return User.findById(token.id).exec();
  }

  onJoin(client: Client, options?: any, auth?: any) {
    console.log('client joined!', client.sessionId);

    const avatar = this.state.addAvatar(client.sessionId, client.auth);
  }

  async onLeave(client: Client, consented: boolean) {
    this.state.avatars[client.sessionId].connected = false;

    console.log('disconnected!', client.sessionId);
    delete this.state.avatars[client.sessionId];

    this.state.removeAvatar(client.sessionId);
  }

  public onDispose(): void | Promise<any> {
    PlaceRepository.update(
      this.state.id,
      this.getData()
    ).then(result => {
      console.log(`${this.roomName} closed and saved`);
    }).catch(error => {
      console.log(error.message);
    });
  }

  public async dispose() {
    for (const client of this.clients) {
      client.close(Protocol.WS_CLOSE_NORMAL);
    }

    return await this._dispose();
  }

  public isOwner(client: Client) {
    return client.auth.id === this.state.ownerId;
  }

  private async configureRoom(options: any) {
    this.roomId = options.id;
    const place = await PlaceRepository.findById(options.id).exec();

    if (place) {
      this.setState(new Place(place));
    } else {
      this.dispose();
    }
  }
}

colyseus: ^0.12.0
NodeJS: v12.13.0

The following code causes is No proxy available!

           const errorMessage = err.message.toLowerCase();
	if(!errorMessage.includes("socket hang up") || !errorMessage.includes("ECONNRESET")) {
		console.warn(`node ${node.processId}/${node.address} failed, unregistering`);
		unregister(node);
		cleanUpNode(node).then(() => console.log(`cleaned up ${node.processId} presence`));

		reqHandler(req, res); // try again!
	} else {
	res.end();
	}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.