Skip to content

Commit

Permalink
Serve API and Game Server on same port with option to split (#343)
Browse files Browse the repository at this point in the history
* Add option to use eigther one or two ports for socket and http api

* Use deployable single port mode by default for server.js

* Update docs and examples with single-port default server mode

* edit some comments

* fix TODO about koa mock not being called

* wrap ports in Number()
  • Loading branch information
lehaSVV2009 authored and nicolodavis committed Jan 28, 2019
1 parent d7d6b44 commit 1f33d43
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 48 deletions.
7 changes: 1 addition & 6 deletions docs/api/API.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# API

The [Server](/api/Server) object hosts a REST API that can be used to
create and join games. It is particularly useful when you want to
The [Server](/api/Server) hosts a REST API that can be used to create and join games. It is particularly useful when you want to
authenticate clients to prove that they have the right to send
actions on behalf of a player.

Expand All @@ -11,10 +10,6 @@ A game that is authenticated will not accept moves from a client on behalf of a

Use the `create` API call to create a game that requires credential tokens. When you call the `join` API, you can retrieve the credential token for a particular player.

The API is available at server `port + 1` by default (i.e. if your
server is at `localhost:8000`, then the API is hosted at
`localhost:8001`.

### Creating a game

#### POST `/games/{name}/create`
Expand Down
36 changes: 30 additions & 6 deletions docs/api/Server.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,38 @@ broadcasts updates to those clients so that all browsers
that are connected to the same game are kept in sync in
realtime.

The server also hosts a REST [API](/api/API) that is used for creating
and joining games. This is hosted on the same port, but can
be configured to run on a separate port.

### Arguments

obj(_object_): A config object with the following options:
A config object with the following options:

1. `games`: a list of game implementations
1. `games` (_array_): a list of game implementations
(each is the return value of [Game](/api/Game.md)).

2. `db`: the [database connector](/storage).
2. `db` (_object_): the [database connector](/storage).
If not provided, an in-memory implementation is used.

3. `transport` (_object_): the transport implementation.
If not provided, socket.io is used.

### Returns

An object that contains:

1. run (_function_): A function to run the server.
Signature: (port, callback) => {}
2. app (_object_): The Koa app.
3. db (_object_): The `db` implementation.
_(portOrConfig, callback) => ({ apiServer, appServer })_
2. kill (_function_): A function to stop the server.
_({ apiServer, appServer }) => {}_
3. app (_object_): The Koa app.
4. db (_object_): The `db` implementation.

### Usage

##### Basic

```js
const Server = require('boardgame.io/server').Server;

Expand All @@ -39,3 +51,15 @@ const server = Server({

server.run(8000);
```

##### With callback

```
server.run(8000, () => console.log("server running..."));
```

##### Running the API server on a separate port

```js
server.run({ port: 8000, apiPort: 8001 });
```
2 changes: 1 addition & 1 deletion examples/react-web/src/lobby/lobby.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const LobbyView = () => (

<Lobby
gameServer="http://localhost:8000"
lobbyServer="http://localhost:8001"
lobbyServer="http://localhost:8000"
gameComponents={importedGames}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions examples/react-web/src/tic-tac-toe/authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AuthenticatedClient extends React.Component {
const PORT = 8000;

const newGame = await request
.post(`http://localhost:${PORT + 1}/games/${gameName}/create`)
.post(`http://localhost:${PORT}/games/${gameName}/create`)
.send({ numPlayers: 2 });

const gameID = newGame.body.gameID;
Expand All @@ -50,7 +50,7 @@ class AuthenticatedClient extends React.Component {

for (let playerID of [0, 1]) {
const player = await request
.post(`http://localhost:${PORT + 1}/games/${gameName}/${gameID}/join`)
.post(`http://localhost:${PORT}/games/${gameName}/${gameID}/join`)
.send({
gameName,
playerID,
Expand Down
4 changes: 4 additions & 0 deletions src/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export const CreateGame = async (db, game, numPlayers, setupData) => {

export const createApiServer = ({ db, games }) => {
const app = new Koa();
return addApiToServer({ app, db, games });
};

export const addApiToServer = ({ app, db, games }) => {
const router = new Router();

router.get('/games', async ctx => {
Expand Down
37 changes: 36 additions & 1 deletion src/server/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

import request from 'supertest';

import { isActionFromAuthenticPlayer, createApiServer } from './api';
import {
isActionFromAuthenticPlayer,
addApiToServer,
createApiServer,
} from './api';
import Game from '../core/game';

jest.setTimeout(2000000000);
Expand Down Expand Up @@ -637,3 +641,34 @@ describe('.createApiServer', () => {
});
});
});

describe('.addApiToServer', () => {
describe('when server app is provided', () => {
let setSpy;
let db;
let server;
let useChain;
let games;

beforeEach(async () => {
setSpy = jest.fn();
useChain = jest.fn(() => ({ use: useChain }));
server = { use: useChain };
db = {
set: async (id, state) => setSpy(id, state),
};
games = [
Game({
name: 'foo',
setup: () => {},
}),
];

addApiToServer({ app: server, db, games });
});

test('call .use method several times', async () => {
expect(server.use.mock.calls.length).toBeGreaterThan(1);
});
});
});
59 changes: 50 additions & 9 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,31 @@

const Koa = require('koa');

import { addApiToServer, createApiServer } from './api';
import { DBFromEnv } from './db';
import { createApiServer } from './api';
import * as logger from '../core/logger';
import { SocketIO } from './transport/socketio';

/**
* Build config object from server run arguments.
*
* @param {number} portOrConfig - Either port or server config object. Optional.
* @param {function} callback - Server run callback. Optional.
*/
export const createServerRunConfig = (portOrConfig, callback) => {
const config = {};
if (portOrConfig && typeof portOrConfig === 'object') {
config.port = portOrConfig.port;
config.callback = portOrConfig.callback || callback;
config.apiPort = portOrConfig.apiPort;
config.apiCallback = portOrConfig.apiCallback;
} else {
config.port = portOrConfig;
config.callback = callback;
}
return config;
};

/**
* Instantiate a game server.
*
Expand All @@ -33,23 +53,44 @@ export function Server({ games, db, transport }) {
}
transport.init(app, games);

const api = createApiServer({ db, games });

return {
app,
api,
db,

run: async (port, callback) => {
run: async (portOrConfig, callback) => {
const serverRunConfig = createServerRunConfig(portOrConfig, callback);

// DB
await db.connect();
let apiServer = await api.listen(port + 1);
let appServer = await app.listen(port, callback);
logger.info('listening...');

// API
let apiServer;
if (!serverRunConfig.apiPort) {
addApiToServer({ app, db, games });
} else {
// Run API in a separate Koa app.
const api = createApiServer({ db, games });
apiServer = await api.listen(
Number(serverRunConfig.apiPort),
serverRunConfig.apiCallback
);
logger.info(`API serving on ${apiServer.address().port}...`);
}

// Run Game Server (+ API, if necessary).
const appServer = await app.listen(
Number(serverRunConfig.port),
serverRunConfig.callback
);
logger.info(`App serving on ${appServer.address().port}...`);

return { apiServer, appServer };
},

kill: ({ apiServer, appServer }) => {
apiServer.close();
if (apiServer) {
apiServer.close();
}
appServer.close();
},
};
Expand Down
Loading

0 comments on commit 1f33d43

Please sign in to comment.