mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-11-16 08:44:52 +02:00
JSON-RPC over Websockets implementation.
This commit is contained in:
parent
798f0367b9
commit
e28079096e
@ -2,4 +2,4 @@ GNS3-server
|
||||
===========
|
||||
|
||||
GNS3 server manages emulators such as Dynamips, VirtualBox or Qemu/KVM.
|
||||
Clients like the GNS3 GUI controls the server using a HTTP REST API.
|
||||
Clients like the GNS3 GUI controls the server using an API over Websockets.
|
||||
|
148
gns3server/handlers/jsonrpc_websocket.py
Normal file
148
gns3server/handlers/jsonrpc_websocket.py
Normal file
@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
JSON-RPC protocol over Websockets.
|
||||
"""
|
||||
|
||||
import zmq
|
||||
import uuid
|
||||
import tornado.websocket
|
||||
from tornado.escape import json_decode
|
||||
from ..jsonrpc import JSONRPCParseError, JSONRPCInvalidRequest, JSONRPCMethodNotFound
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JSONRPCWebSocket(tornado.websocket.WebSocketHandler):
|
||||
"""
|
||||
STOMP protocol over Tornado Websockets with message
|
||||
routing to ZeroMQ dealer clients.
|
||||
|
||||
:param application: Tornado Application object
|
||||
:param request: Tornado Request object
|
||||
:param zmq_router: ZeroMQ router socket
|
||||
"""
|
||||
|
||||
clients = set()
|
||||
destinations = {}
|
||||
version = 2.0 # only JSON-RPC version 2.0 is supported
|
||||
|
||||
def __init__(self, application, request, zmq_router):
|
||||
tornado.websocket.WebSocketHandler.__init__(self, application, request)
|
||||
self._session_id = str(uuid.uuid4())
|
||||
self.zmq_router = zmq_router
|
||||
|
||||
@property
|
||||
def session_id(self):
|
||||
"""
|
||||
Session ID uniquely representing a Websocket client
|
||||
|
||||
:returns: the session id
|
||||
"""
|
||||
|
||||
return self._session_id
|
||||
|
||||
@classmethod
|
||||
def dispatch_message(cls, message):
|
||||
"""
|
||||
Sends a message to Websocket client
|
||||
|
||||
:param message: message from a module (received via ZeroMQ)
|
||||
"""
|
||||
|
||||
# Module name that is replying
|
||||
module = message[0].decode("utf-8")
|
||||
|
||||
# ZMQ responses are encoded in JSON
|
||||
# format is a JSON array: [session ID, JSON-RPC response]
|
||||
json_message = json_decode(message[1])
|
||||
session_id = json_message[0]
|
||||
jsonrpc_response = json_message[1]
|
||||
|
||||
log.debug("Received message from module {}: {}".format(module, json_message))
|
||||
|
||||
for client in cls.clients:
|
||||
if client.session_id == session_id:
|
||||
client.write_message(jsonrpc_response)
|
||||
|
||||
@classmethod
|
||||
def register_destination(cls, destination, module):
|
||||
"""
|
||||
Registers a destination handled by a module.
|
||||
Used to route requests to the right module.
|
||||
|
||||
:param destination: destination string
|
||||
:param module: module string
|
||||
"""
|
||||
|
||||
# Make sure the destination is not already registered
|
||||
# by another module for instance
|
||||
assert destination not in cls.destinations
|
||||
log.info("registering {} as a destination for {}".format(destination,
|
||||
module))
|
||||
cls.destinations[destination] = module
|
||||
|
||||
def open(self):
|
||||
"""
|
||||
Invoked when a new WebSocket is opened.
|
||||
"""
|
||||
|
||||
log.info("Websocket client {} connected".format(self.session_id))
|
||||
self.clients.add(self)
|
||||
|
||||
def on_message(self, message):
|
||||
"""
|
||||
Handles incoming messages.
|
||||
|
||||
:param message: message received over the Websocket
|
||||
"""
|
||||
|
||||
log.debug("Received Websocket message: {}".format(message))
|
||||
|
||||
try:
|
||||
request = json_decode(message)
|
||||
jsonrpc_version = request["jsonrpc"]
|
||||
method = request["method"]
|
||||
# warning: notifications cannot be sent by a client because check for an "id" here
|
||||
request_id = request["id"]
|
||||
except:
|
||||
return self.write_message(JSONRPCParseError()())
|
||||
|
||||
if jsonrpc_version != self.version:
|
||||
return self.write_message(JSONRPCInvalidRequest()())
|
||||
|
||||
if method not in self.destinations:
|
||||
return self.write_message(JSONRPCMethodNotFound(request_id)())
|
||||
|
||||
module = self.destinations[method]
|
||||
# ZMQ requests are encoded in JSON
|
||||
# format is a JSON array: [session ID, JSON-RPC request]
|
||||
zmq_request = [self.session_id, request]
|
||||
# Route to the correct module
|
||||
self.zmq_router.send_string(module, zmq.SNDMORE)
|
||||
# Send the encoded JSON request
|
||||
self.zmq_router.send_json(zmq_request)
|
||||
|
||||
def on_close(self):
|
||||
"""
|
||||
Invoked when the WebSocket is closed.
|
||||
"""
|
||||
|
||||
log.info("Websocket client {} disconnected".format(self.session_id))
|
||||
self.clients.remove(self)
|
182
gns3server/jsonrpc.py
Normal file
182
gns3server/jsonrpc.py
Normal file
@ -0,0 +1,182 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
JSON-RPC protocol implementation.
|
||||
http://www.jsonrpc.org/specification
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
|
||||
class JSONRPCObject(object):
|
||||
"""
|
||||
Base object for JSON-RPC requests, responses,
|
||||
notifications and errors.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
return JSONRPCEncoder().default(self)
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
return json.dumps(self, cls=JSONRPCEncoder)
|
||||
|
||||
def __call__(self):
|
||||
return JSONRPCEncoder().default(self)
|
||||
|
||||
|
||||
class JSONRPCEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Creates the JSON-RPC message.
|
||||
"""
|
||||
|
||||
def default(self, obj):
|
||||
"""
|
||||
Returns a Python dictionary corresponding to a JSON-RPC message.
|
||||
"""
|
||||
|
||||
if isinstance(obj, JSONRPCObject):
|
||||
message = {"jsonrpc": 2.0}
|
||||
for field in dir(obj):
|
||||
if not field.startswith('_'):
|
||||
value = getattr(obj, field)
|
||||
message[field] = value
|
||||
return message
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
class JSONRPCInvalidRequest(JSONRPCObject):
|
||||
"""
|
||||
Error response for an invalid request.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = None
|
||||
self.error = {"code": -32600, "message": "Invalid Request"}
|
||||
|
||||
|
||||
class JSONRPCMethodNotFound(JSONRPCObject):
|
||||
"""
|
||||
Error response for an method not found.
|
||||
|
||||
:param request_id: JSON-RPC identifier
|
||||
"""
|
||||
|
||||
def __init__(self, request_id):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.error = {"code": -32601, "message": "Method not found"}
|
||||
|
||||
|
||||
class JSONRPCInvalidParams(JSONRPCObject):
|
||||
"""
|
||||
Error response for invalid parameters.
|
||||
|
||||
:param request_id: JSON-RPC identifier
|
||||
"""
|
||||
|
||||
def __init__(self, request_id):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.error = {"code": -32602, "message": "Invalid params"}
|
||||
|
||||
|
||||
class JSONRPCInternalError(JSONRPCObject):
|
||||
"""
|
||||
Error response for an internal error.
|
||||
|
||||
:param request_id: JSON-RPC identifier (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, request_id=None):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.error = {"code": -32603, "message": "Internal error"}
|
||||
|
||||
|
||||
class JSONRPCParseError(JSONRPCObject):
|
||||
"""
|
||||
Error response for parsing error.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = None
|
||||
self.error = {"code": -32700, "message": "Parse error"}
|
||||
|
||||
|
||||
class JSONRPCCustomError(JSONRPCObject):
|
||||
"""
|
||||
Error response for an custom error.
|
||||
|
||||
:param code: JSON-RPC error code
|
||||
:param message: JSON-RPC error message
|
||||
:param request_id: JSON-RPC identifier (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, code, message, request_id=None):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.error = {"code": code, "message": message}
|
||||
|
||||
|
||||
class JSONRPCResponse(JSONRPCObject):
|
||||
"""
|
||||
JSON-RPC successful response.
|
||||
|
||||
:param result: JSON-RPC result
|
||||
:param request_id: JSON-RPC identifier
|
||||
"""
|
||||
|
||||
def __init__(self, result, request_id):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.result = result
|
||||
|
||||
|
||||
class JSONRPCRequest(JSONRPCObject):
|
||||
"""
|
||||
JSON-RPC request.
|
||||
|
||||
:param method: JSON-RPC destination method
|
||||
:param params: JSON-RPC params for the corresponding method (optional)
|
||||
:param request_id: JSON-RPC identifier (generated by default)
|
||||
"""
|
||||
|
||||
def __init__(self, method, params=None, request_id=str(uuid.uuid1())):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.id = request_id
|
||||
self.method = method
|
||||
if params:
|
||||
self.params = params
|
||||
|
||||
|
||||
class JSONRPCNotification(JSONRPCObject):
|
||||
"""
|
||||
JSON-RPC notification.
|
||||
|
||||
:param method: JSON-RPC destination method
|
||||
:param params: JSON-RPC params for the corresponding method (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, method, params=None):
|
||||
JSONRPCObject.__init__(self)
|
||||
self.method = method
|
||||
if params:
|
||||
self.params = params
|
@ -19,8 +19,8 @@
|
||||
import datetime
|
||||
import sys
|
||||
import logging
|
||||
import gns3server
|
||||
import tornado.options
|
||||
import gns3server
|
||||
|
||||
# command line options
|
||||
from tornado.options import define
|
||||
|
@ -19,6 +19,7 @@
|
||||
Base class (interface) for modules
|
||||
"""
|
||||
|
||||
import gns3server.jsonrpc as jsonrpc
|
||||
import multiprocessing
|
||||
import zmq
|
||||
|
||||
@ -28,7 +29,11 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class IModule(multiprocessing.Process):
|
||||
"""
|
||||
Module interface
|
||||
Module interface.
|
||||
|
||||
:param name: module name
|
||||
:param args: arguments for the module
|
||||
:param kwargs: named arguments for the module
|
||||
"""
|
||||
|
||||
destination = {}
|
||||
@ -47,6 +52,7 @@ class IModule(multiprocessing.Process):
|
||||
self._port = args[1]
|
||||
self._current_session = None
|
||||
self._current_destination = None
|
||||
self._current_call_id = None
|
||||
|
||||
def _setup(self):
|
||||
"""
|
||||
@ -59,7 +65,9 @@ class IModule(multiprocessing.Process):
|
||||
|
||||
def _create_stream(self, host=None, port=0, callback=None):
|
||||
"""
|
||||
Creates a new ZMQ stream
|
||||
Creates a new ZMQ stream.
|
||||
|
||||
:returns: ZMQ stream object
|
||||
"""
|
||||
|
||||
socket = self._context.socket(zmq.DEALER)
|
||||
@ -98,27 +106,66 @@ class IModule(multiprocessing.Process):
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the event loop
|
||||
Stops the event loop.
|
||||
"""
|
||||
|
||||
#zmq.eventloop.ioloop.IOLoop.instance().stop()
|
||||
self._ioloop.stop()
|
||||
|
||||
def send_response(self, response):
|
||||
def send_response(self, results):
|
||||
"""
|
||||
Sends a response back to the requester
|
||||
Sends a response back to the requester.
|
||||
|
||||
:param response:
|
||||
:param results: JSON results to the ZeroMQ server
|
||||
"""
|
||||
|
||||
# add session and destination to the response
|
||||
response = [self._current_session, self._current_destination, response]
|
||||
jsonrpc_response = jsonrpc.JSONRPCResponse(results, self._current_call_id)()
|
||||
|
||||
# add session to the response
|
||||
response = [self._current_session, jsonrpc_response]
|
||||
log.debug("ZeroMQ client ({}) sending: {}".format(self.name, response))
|
||||
self._stream.send_json(response)
|
||||
|
||||
def send_param_error(self):
|
||||
"""
|
||||
Sends a param error back to the requester.
|
||||
"""
|
||||
|
||||
jsonrpc_response = jsonrpc.JSONRPCInvalidParams(self._current_call_id)()
|
||||
|
||||
# add session to the response
|
||||
response = [self._current_session, jsonrpc_response]
|
||||
log.info("ZeroMQ client ({}) sending JSON-RPC param error for call id {}".format(self.name, self._current_call_id))
|
||||
self._stream.send_json(response)
|
||||
|
||||
def send_internal_error(self):
|
||||
"""
|
||||
Sends a param error back to the requester.
|
||||
"""
|
||||
|
||||
jsonrpc_response = jsonrpc.JSONRPCInternalError()()
|
||||
|
||||
# add session to the response
|
||||
response = [self._current_session, jsonrpc_response]
|
||||
log.critical("ZeroMQ client ({}) sending JSON-RPC internal error".format(self.name))
|
||||
self._stream.send_json(response)
|
||||
|
||||
def send_custom_error(self, message, code=-3200):
|
||||
"""
|
||||
Sends a custom error back to the requester.
|
||||
"""
|
||||
|
||||
jsonrpc_response = jsonrpc.JSONRPCCustomError(code, message, self._current_call_id)()
|
||||
|
||||
# add session to the response
|
||||
response = [self._current_session, jsonrpc_response]
|
||||
log.info("ZeroMQ client ({}) sending JSON-RPC custom error {} for call id {}".format(self.name,
|
||||
message,
|
||||
self._current_call_id))
|
||||
self._stream.send_json(response)
|
||||
|
||||
def _decode_request(self, request):
|
||||
"""
|
||||
Decodes the request to JSON
|
||||
Decodes the request to JSON.
|
||||
|
||||
:param request: request from ZeroMQ server
|
||||
"""
|
||||
@ -126,18 +173,22 @@ class IModule(multiprocessing.Process):
|
||||
try:
|
||||
request = zmq.utils.jsonapi.loads(request[0])
|
||||
except ValueError:
|
||||
self.send_response("ValueError") # FIXME: explicit json error
|
||||
self._current_session = None
|
||||
self.send_internal_error()
|
||||
return
|
||||
|
||||
log.debug("ZeroMQ client ({}) received: {}".format(self.name, request))
|
||||
self._current_session = request[0]
|
||||
self._current_destination = request[1]
|
||||
self._current_call_id = request[1].get("id")
|
||||
destination = request[1].get("method")
|
||||
params = request[1].get("params")
|
||||
|
||||
if self._current_destination not in self.destination:
|
||||
# FIXME: return error if destination not found!
|
||||
if destination not in self.destination:
|
||||
self.send_internal_error()
|
||||
return
|
||||
log.debug("Routing request to {}: {}".format(self._current_destination, request[2]))
|
||||
self.destination[self._current_destination](self, request[2])
|
||||
|
||||
log.debug("Routing request to {}: {}".format(destination, request[1]))
|
||||
self.destination[destination](self, params)
|
||||
|
||||
def destinations(self):
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ import socket
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import tornado.autoreload
|
||||
from .handlers.stomp_websocket import StompWebSocket
|
||||
from .handlers.jsonrpc_websocket import JSONRPCWebSocket
|
||||
from .handlers.version_handler import VersionHandler
|
||||
from .module_manager import ModuleManager
|
||||
|
||||
@ -57,7 +57,7 @@ class Server(object):
|
||||
|
||||
def load_modules(self):
|
||||
"""
|
||||
Loads the modules
|
||||
Loads the modules.
|
||||
"""
|
||||
|
||||
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
@ -70,17 +70,17 @@ class Server(object):
|
||||
self._modules.append(instance)
|
||||
destinations = instance.destinations()
|
||||
for destination in destinations:
|
||||
StompWebSocket.register_destination(destination, module.name)
|
||||
JSONRPCWebSocket.register_destination(destination, module.name)
|
||||
instance.start() # starts the new process
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Starts the Tornado web server and ZeroMQ server
|
||||
Starts the Tornado web server and ZeroMQ server.
|
||||
"""
|
||||
|
||||
router = self._create_zmq_router()
|
||||
# Add our Stomp Websocket handler to Tornado
|
||||
self.handlers.extend([(r"/", StompWebSocket, dict(zmq_router=router))])
|
||||
# Add our JSON-RPC Websocket handler to Tornado
|
||||
self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))])
|
||||
tornado_app = tornado.web.Application(self.handlers, debug=True) # FIXME: debug mode!
|
||||
try:
|
||||
print("Starting server on port {}".format(self._port))
|
||||
@ -92,7 +92,7 @@ class Server(object):
|
||||
|
||||
ioloop = tornado.ioloop.IOLoop.instance()
|
||||
stream = zmqstream.ZMQStream(router, ioloop)
|
||||
stream.on_recv(StompWebSocket.dispatch_message)
|
||||
stream.on_recv(JSONRPCWebSocket.dispatch_message)
|
||||
tornado.autoreload.add_reload_hook(functools.partial(self._cleanup, stop=False))
|
||||
|
||||
def signal_handler(signum=None, frame=None):
|
||||
|
91
tests/test_jsonrpc.py
Normal file
91
tests/test_jsonrpc.py
Normal file
@ -0,0 +1,91 @@
|
||||
import uuid
|
||||
from tornado.testing import AsyncTestCase
|
||||
from tornado.escape import json_encode, json_decode
|
||||
from ws4py.client.tornadoclient import TornadoWebSocketClient
|
||||
import gns3server.jsonrpc as jsonrpc
|
||||
|
||||
"""
|
||||
Tests for JSON-RPC protocol over Websockets
|
||||
"""
|
||||
|
||||
|
||||
class JSONRPC(AsyncTestCase):
|
||||
|
||||
URL = "ws://127.0.0.1:8000/"
|
||||
|
||||
def test_request(self):
|
||||
|
||||
params = {"echo": "test"}
|
||||
request = jsonrpc.JSONRPCRequest("dynamips.echo", params)
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, str(request))
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["jsonrpc"] == 2.0
|
||||
assert json_response["id"] == request.id
|
||||
assert json_response["result"] == params
|
||||
|
||||
def test_request_with_invalid_method(self):
|
||||
|
||||
message = {"echo": "test"}
|
||||
request = jsonrpc.JSONRPCRequest("dynamips.non_existent", message)
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, str(request))
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["error"].get("code") == -32601
|
||||
assert json_response["id"] == request.id
|
||||
|
||||
def test_request_with_invalid_version(self):
|
||||
|
||||
request = {"jsonrpc": "1.0", "method": "dynamips.echo", "id": 1}
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, json_encode(request))
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["id"] == None
|
||||
assert json_response["error"].get("code") == -32600
|
||||
|
||||
def test_request_with_invalid_json(self):
|
||||
|
||||
request = "my non JSON request"
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, request)
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["id"] == None
|
||||
assert json_response["error"].get("code") == -32700
|
||||
|
||||
def test_request_with_invalid_jsonrpc_field(self):
|
||||
|
||||
request = {"jsonrpc": "2.0", "method_bogus": "dynamips.echo", "id": 1}
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, json_encode(request))
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["id"] == None
|
||||
assert json_response["error"].get("code") == -32700
|
||||
|
||||
def test_request_with_no_params(self):
|
||||
|
||||
request = jsonrpc.JSONRPCRequest("dynamips.echo")
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, str(request))
|
||||
response = self.wait()
|
||||
json_response = json_decode(response)
|
||||
assert json_response["id"] == request.id
|
||||
assert json_response["error"].get("code") == -32602
|
||||
|
||||
|
||||
class AsyncWSRequest(TornadoWebSocketClient):
|
||||
"""
|
||||
Very basic Websocket client for tests
|
||||
"""
|
||||
|
||||
def __init__(self, url, io_loop, callback, message):
|
||||
TornadoWebSocketClient.__init__(self, url, io_loop=io_loop)
|
||||
self._callback = callback
|
||||
self._message = message
|
||||
self.connect()
|
||||
|
||||
def opened(self):
|
||||
self.send(self._message, binary=False)
|
||||
|
||||
def received_message(self, message):
|
||||
self.close()
|
||||
if self._callback:
|
||||
self._callback(message.data)
|
@ -6,7 +6,7 @@ from gns3server.stomp import frame as stomp_frame
|
||||
from gns3server.stomp import protocol as stomp_protocol
|
||||
|
||||
"""
|
||||
Tests STOMP protocol over Websockets
|
||||
Tests for STOMP protocol over Websockets
|
||||
"""
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ class Stomp(AsyncTestCase):
|
||||
and check for a STOMP MESSAGE with echoed message and destination.
|
||||
"""
|
||||
|
||||
destination = "dynamips/echo"
|
||||
destination = "dynamips.echo"
|
||||
message = {"ping": "test"}
|
||||
request = self.stomp.send(destination, json_encode(message), "application/json")
|
||||
AsyncWSRequest(self.URL, self.io_loop, self.stop, request)
|
||||
@ -109,7 +109,7 @@ class Stomp(AsyncTestCase):
|
||||
|
||||
class AsyncWSRequest(TornadoWebSocketClient):
|
||||
"""
|
||||
Very basic Websocket client for the tests
|
||||
Very basic Websocket client for tests
|
||||
"""
|
||||
|
||||
def __init__(self, url, io_loop, callback, message):
|
||||
|
Loading…
Reference in New Issue
Block a user