Improve our compliance to the JSON RPC spec
This commit is contained in:
parent
f510f9df99
commit
7661cfc85e
@ -40,7 +40,7 @@ Several environmental variables can be set to configure the server:
|
|||||||
and accept connections from. When this is not set, we default to 8080.
|
and accept connections from. When this is not set, we default to 8080.
|
||||||
|
|
||||||
**Requests**
|
**Requests**
|
||||||
The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any subscribe or unsubscribe functionality because the client implicitly subscribes and unsubscribes by connecting to the server.
|
The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any `subscribe` or `unsubscribe` functionality because the WebSocket server keeps a single subscription open for all clients.
|
||||||
|
|
||||||
The first step for making a request is establishing a connection with the server. In Javascript:
|
The first step for making a request is establishing a connection with the server. In Javascript:
|
||||||
|
|
||||||
@ -58,9 +58,9 @@ wsClient = create_connection("ws://127.0.0.1:8080")
|
|||||||
|
|
||||||
With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification):
|
With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification):
|
||||||
|
|
||||||
* `id`: All requests require you to specify a string as an id. When the server responds to the request, it provides an id as well to allow you to determine which request it is responding to.
|
* `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request.
|
||||||
* `jsonrpc`: This is always the string `'2.0'`.
|
* `jsonrpc`: This is always the string `'2.0'`.
|
||||||
* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'`, and `'GET_STATS'`.
|
* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`.
|
||||||
* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: <your signedOrder> }`. For `REMOVE_ORDER`, provide `{ orderHash: <your orderHash> }`. For `GET_STATS`, no parameters are needed, so you may leave this empty.
|
* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: <your signedOrder> }`. For `REMOVE_ORDER`, provide `{ orderHash: <your orderHash> }`. For `GET_STATS`, no parameters are needed, so you may leave this empty.
|
||||||
|
|
||||||
Next, convert the payload to a string and send it through the connection.
|
Next, convert the payload to a string and send it through the connection.
|
||||||
@ -68,7 +68,7 @@ In Javascript:
|
|||||||
|
|
||||||
```
|
```
|
||||||
const addOrderPayload = {
|
const addOrderPayload = {
|
||||||
id: 'order32',
|
id: 1,
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: 'ADD_ORDER',
|
method: 'ADD_ORDER',
|
||||||
params: { signedOrder: <your signedOrder> },
|
params: { signedOrder: <your signedOrder> },
|
||||||
@ -81,7 +81,7 @@ In Python:
|
|||||||
```
|
```
|
||||||
import json
|
import json
|
||||||
remove_order_payload = {
|
remove_order_payload = {
|
||||||
'id': 'order33',
|
'id': 1,
|
||||||
'jsonrpc': '2.0',
|
'jsonrpc': '2.0',
|
||||||
'method': 'REMOVE_ORDER',
|
'method': 'REMOVE_ORDER',
|
||||||
'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'},
|
'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'},
|
||||||
@ -90,13 +90,13 @@ wsClient.send(json.dumps(remove_order_payload));
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Response**
|
**Response**
|
||||||
The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields:
|
The server responds to all requests in a similar format. In the data field, you'll find another object containing the following fields:
|
||||||
|
|
||||||
* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is `null`.
|
* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`.
|
||||||
* `jsonrpc`: Always `'2.0'`.
|
* `jsonrpc`: Always `'2.0'`.
|
||||||
* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`.
|
* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`.
|
||||||
* `result`: This field varies based on the method. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is `null`.
|
* `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted.
|
||||||
* `error`: When there is an error executing a request, the error message is listed here. When the server responds successfully, this field is `null`.
|
* `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted.
|
||||||
|
|
||||||
In Javascript, the responses can be parsed using the `onmessage` callback:
|
In Javascript, the responses can be parsed using the `onmessage` callback:
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { assert } from '../utils/assert';
|
|||||||
import { OrderWatcher } from './order_watcher';
|
import { OrderWatcher } from './order_watcher';
|
||||||
|
|
||||||
const DEFAULT_HTTP_PORT = 8080;
|
const DEFAULT_HTTP_PORT = 8080;
|
||||||
const JSONRPC_VERSION = '2.0';
|
const JSON_RPC_VERSION = '2.0';
|
||||||
|
|
||||||
// Wraps the OrderWatcher functionality in a WebSocket server. Motivations:
|
// Wraps the OrderWatcher functionality in a WebSocket server. Motivations:
|
||||||
// 1) Users can watch orders via non-typescript programs.
|
// 1) Users can watch orders via non-typescript programs.
|
||||||
@ -77,16 +77,17 @@ export class OrderWatcherWebSocketServer {
|
|||||||
connection.on('close', this._onCloseCallback.bind(this, connection));
|
connection.on('close', this._onCloseCallback.bind(this, connection));
|
||||||
this._connectionStore.add(connection);
|
this._connectionStore.add(connection);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Have the WebSocket server subscribe to the OrderWatcher to receive updates.
|
|
||||||
// These updates are then broadcast to clients in the _connectionStore.
|
|
||||||
this._orderWatcher.subscribe(this._broadcastCallback.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activates the WebSocket server by having its HTTP server start listening.
|
* Activates the WebSocket server by subscribing to the OrderWatcher and
|
||||||
|
* starting the WebSocket's HTTP server
|
||||||
*/
|
*/
|
||||||
public listen(): void {
|
public start(): void {
|
||||||
|
// Have the WebSocket server subscribe to the OrderWatcher to receive updates.
|
||||||
|
// These updates are then broadcast to clients in the _connectionStore.
|
||||||
|
this._orderWatcher.subscribe(this._broadcastCallback.bind(this));
|
||||||
|
|
||||||
const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT;
|
const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT;
|
||||||
this._httpServer.listen(port, () => {
|
this._httpServer.listen(port, () => {
|
||||||
logUtils.log(`${new Date()} [Server] Listening on port ${port}`);
|
logUtils.log(`${new Date()} [Server] Listening on port ${port}`);
|
||||||
@ -95,29 +96,30 @@ export class OrderWatcherWebSocketServer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deactivates the WebSocket server by stopping the HTTP server from accepting
|
* Deactivates the WebSocket server by stopping the HTTP server from accepting
|
||||||
* new connections.
|
* new connections and unsubscribing from the OrderWatcher
|
||||||
*/
|
*/
|
||||||
public close(): void {
|
public stop(): void {
|
||||||
this._httpServer.close();
|
this._httpServer.close();
|
||||||
|
this._orderWatcher.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise<void> {
|
private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise<void> {
|
||||||
let response: WebSocketResponse;
|
let response: WebSocketResponse;
|
||||||
|
assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema);
|
||||||
|
const request: WebSocketRequest = JSON.parse(message.utf8Data);
|
||||||
|
assert.doesConformToSchema('request', request, webSocketRequestSchema);
|
||||||
|
assert.isString(request.jsonrpc, JSON_RPC_VERSION);
|
||||||
try {
|
try {
|
||||||
assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema);
|
|
||||||
const request: WebSocketRequest = JSON.parse(message.utf8Data);
|
|
||||||
assert.doesConformToSchema('request', request, webSocketRequestSchema);
|
|
||||||
assert.isString(request.jsonrpc, JSONRPC_VERSION);
|
|
||||||
response = {
|
response = {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
jsonrpc: JSONRPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
result: await this._routeRequestAsync(request),
|
result: await this._routeRequestAsync(request),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
response = {
|
response = {
|
||||||
id: null,
|
id: request.id,
|
||||||
jsonrpc: JSONRPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
method: null,
|
method: null,
|
||||||
error: err.toString(),
|
error: err.toString(),
|
||||||
};
|
};
|
||||||
@ -165,12 +167,12 @@ export class OrderWatcherWebSocketServer {
|
|||||||
const response =
|
const response =
|
||||||
err === null
|
err === null
|
||||||
? {
|
? {
|
||||||
jsonrpc: JSONRPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
method,
|
method,
|
||||||
result: orderState,
|
result: orderState,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
jsonrpc: JSONRPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
method,
|
method,
|
||||||
error: {
|
error: {
|
||||||
code: -32000,
|
code: -32000,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// TODO: Move these schemas to the `json-schemas` package and convert to JSON
|
||||||
|
// Rename to `OrderWatcherWebSocketRequestSchema`, etc...
|
||||||
export const webSocketUtf8MessageSchema = {
|
export const webSocketUtf8MessageSchema = {
|
||||||
id: '/webSocketUtf8MessageSchema',
|
id: '/webSocketUtf8MessageSchema',
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -49,21 +49,21 @@ export enum OrderWatcherMethod {
|
|||||||
export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest;
|
export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest;
|
||||||
|
|
||||||
interface AddOrderRequest {
|
interface AddOrderRequest {
|
||||||
id: string;
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
method: OrderWatcherMethod.AddOrder;
|
method: OrderWatcherMethod.AddOrder;
|
||||||
params: { signedOrder: SignedOrder };
|
params: { signedOrder: SignedOrder };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoveOrderRequest {
|
interface RemoveOrderRequest {
|
||||||
id: string;
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
method: OrderWatcherMethod.RemoveOrder;
|
method: OrderWatcherMethod.RemoveOrder;
|
||||||
params: { orderHash: string };
|
params: { orderHash: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetStatsRequest {
|
interface GetStatsRequest {
|
||||||
id: string;
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
method: OrderWatcherMethod.GetStats;
|
method: OrderWatcherMethod.GetStats;
|
||||||
}
|
}
|
||||||
@ -73,14 +73,14 @@ interface GetStatsRequest {
|
|||||||
export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse;
|
export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse;
|
||||||
|
|
||||||
interface SuccessfulWebSocketResponse {
|
interface SuccessfulWebSocketResponse {
|
||||||
id: string | null; // id is null for UPDATE
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
method: OrderWatcherMethod;
|
method: OrderWatcherMethod;
|
||||||
result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER
|
result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErrorWebSocketResponse {
|
interface ErrorWebSocketResponse {
|
||||||
id: null;
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
method: null;
|
method: null;
|
||||||
error: JSONRPCError;
|
error: JSONRPCError;
|
||||||
|
@ -105,11 +105,11 @@ describe.only('OrderWatcherWebSocketServer', async () => {
|
|||||||
// Prepare OrderWatcher WebSocket server
|
// Prepare OrderWatcher WebSocket server
|
||||||
const orderWatcherConfig = {};
|
const orderWatcherConfig = {};
|
||||||
wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig);
|
wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig);
|
||||||
wsServer.listen();
|
wsServer.start();
|
||||||
});
|
});
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
wsServer.close();
|
wsServer.stop();
|
||||||
});
|
});
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user