How to disable ssl verification for aiohttp test server - python

I want to write tests for a web client. As it was recommended by the aiohttp library author, I set up a local test server. This setup (boiling down this example) works well with plain http/port 80.
import ssl
import socket as skt
import unittest as ut
import aiohttp as ah
import aiohttp.web as ahweb
import aiohttp.test_utils as ahtest
EXPECTED_SERVER_URL = 'https://anywhere.com'
async def function_to_test(client_session):
async with client_session.get(EXPECTED_SERVER_URL) as response:
return await response.json()
class ResolverMock(ah.resolver.AbstractResolver):
def __init__(self, server_port, host, request_port):
self.__server_port = server_port
self.__host = host.rstrip('/').rpartition('://')[-1]
self.__request_port = request_port
async def resolve(self, host, request_port=0, family=skt.AF_INET):
if host != self.__host or request_port != self.__request_port:
raise OSError('No test server known for %s' % host)
return [dict(hostname=host, host='127.0.0.1', port=self.__server_port,
family=skt.AF_INET, proto=0, flags=skt.AI_NUMERICHOST)]
async def close(self) -> None:
pass
class ServerMock(ahtest.RawTestServer):
def __init__(self, **kwargs):
super().__init__(self.__handle_request, **kwargs)
async def __handle_request(self, _request):
return ahweb.json_response({})
class Fetch(ut.IsolatedAsyncioTestCase):
async def test_request_server(self):
async with ServerMock(port=443) as server_mock:
ssl_ctx = False
resolver = ResolverMock(server_port=server_mock.port, host=EXPECTED_SERVER_URL, request_port=443)
connector = ah.TCPConnector(resolver=resolver, ssl=ssl_ctx, use_dns_cache=False)
async with ah.ClientSession(connector=connector, trust_env=True) as session:
response = await function_to_test(session)
self.assertEqual(response, {})
if __name__ == '__main__':
ut.main()
But it fails for https/port 443 with
aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host anywhere.com:443 ssl:default [[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1108)]
Using a non_validating ssl context did not change anything:
ssl_ctx = ssl.create_default_context()
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
I have no idea how I could set up a compatible protocol or whatever (I am far away from being an ssl expert) on the test server. What do I miss? How can I make this work for https?

Related

Paramiko. Reverse Forward Tunnel Question- Help Appreciated

So I've been working with the Paramiko Libary. I have a client and two servers Server A & Server B. The client connects to Server A, and then requests a reverse-forwarded tunnel to Server B, there is a lot more functionality to write into it but my problem at the moment is very fundamental and likely has a very simply answer I'm just somehow overlooking or not understanding.
What I am trying to do at this point is have Server A send some information to Server B every time it connects to it, which due to a timer on the client should after a connection is closed be each minute. (reconnecting each time)
I want to have Server A send, Server B some information every time it connects to it. Now my question relates to how I'd achieve that.
My first thought was to have the client send a command to Server A after the reverse tunnel is connected, I suspect and here my understanding may be wrong, thus why I'm checking here. The command (which is a string) will be forwarded by Server A to Server B, whilst I am looking for the response to that command to be sent Server B.
The other option as I see it is to have Server A push the data to Server B. But I don't know how to check for when a reverse-forwarded-tunnel is created, I could do it for any connection but then that seems inefficient, as the client will get some data, then the data will once again be sent as the reverse forward tunnel is created. (Again likely overlooking something simple here)
So I'm curious given my code's present state what could I do, could change that would let me check for when a reverse-forward-tunnel is made to Server B so I can send the data I want to send to it.
Thank you for taking the time to try and help me here, and yes I understand hardcoding in passwords etc is a bad idea for application security.
The code below is the client & server code (which again need some work but are getting there.)
client Code
import getpass
import os
import socket
import select
import sys
import threading
import paramiko
from paramiko import Channel
import schedule
import time
import string
import random
from optparse import OptionParser
IP = '127.0.0.1'
USER = 'user'
PASSWORD = 'CrabRave'
PORT = 900
REMOTE_PORT = 443
REMOTE_IP = ... ###Remote IP will go here.
def handler(chan, host, port):
sock = socket.socket()
try:
sock.connect((IP, PORT))
except Exception as e:
Timer()
def ssh_client(IP, PORT, USER, PASSWORD):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(IP, PORT, USER, PASSWORD)
ssh_session = client.get_transport().open_session()
def reverse_forward_tunnel(PORT, REMOTE_IP, REMOTE_PORT, transport):
transport.request_port_forward("", PORT)
while True:
chan = transport.accept(1000)
if chan is None:
continue
thr = threading.Thread(
target=handler, args=(chan, REMOTE_IP, REMOTE_PORT))
thr.setDaemon(True)
thr.start()
def Timer():
if Channel.is_active():
schedule.every(1).seconds.do(Timer)
else: schedule.every(1).minutes.do(main)
def main():
client = ssh_client
try:
ssh_client(IP, PORT, USER, PASSWORD)
except Exception as E:
Timer()
try:
reverse_forward_tunnel(PORT, REMOTE_IP, REMOTE_PORT, client.get_transport())
except KeyboardInterrupt:
Timer()
try: Timer()
except Exception as E:
Timer
if __name__ == '__main__':
main()
Server Code
from ABC import abstractmethod
from sys import platform
from Shell import Shell
from cmd import Cmd
from src.server_base import ServerBase
from src.ssh_server_interface import SshServerInterface
from src.shell import Shell
from src.ssh_server import SshServer
import base64
import os
import socket
import sys
import paramiko
import threading
import string
import random
my_key = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(100))
class Shell(Cmd):
use_rawinput=False
promt='My Shell> '
def __init__(self, stdin=None, stdout=None):
super(Shell, self).__init__(completkey='tab', stdin=stdin, stdout =stdout)
def print(self, value):
if self.stdout and not self.stdout.closed:
self.stdout.write(value)
self.stdout.flush()
def printline(self, value):
self.print(value + '\r\n')
def emptyline(self):
self.print('\r\n')
class ServerBase(ABC):
def __init__(self):
self._is_running = threading.Event()
self._socket = None
self.client_shell = None
self._listen_thread = None
def start(self, address='127.0.0.1', port=900, timeout=1):
if not self._is_running.is_set():
self._is_running.set()
self._socket(socket.AF_INET, socket.SOCK_STREAM)
if platform == "linux" or platform == "linux2":
self._socket.setsocketopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
self._socket.settimeout(timeout)
self._socket.bind((address, port))
self._listen_thread = threading.Thread(target=self._listen)
self._listen_thread.start()
def stop(self):
if self._is_running.is_set():
self._is_running.clear()
self._listen_thread.join()
self._socket.close()
def listen(self):
while self._is_running.is_set():
try:
self._socket.listen()
client, addr = self._socket.accept()
self.connection_function(client)
except socket.timeout:
pass
#abstractmethod
def connection_function(self, client):
pass
class SshServerInterface(paramiko.ServerInterface):
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username: str, password: str) -> int:
if (username == "user") and (password == "CrabRave"):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_channel_pty_request(self, channel: Channel, term: bytes, width: int, height: int, pixelwidth: int, pixelheight: int, modes: bytes):
return True
def check_channel_shell_request(self, channel: Channel) -> bool:
return True
def check_channel_env_request(self, channel: Channel, name: bytes, value: bytes) -> bool:
return True
def check_port_forward_request(self, address: str, port: int) -> int:
return port
class SshServer(ServerBase):
def __init__(self, host_key_file, host_key_file_password=None):
super(SshServer, self).__init__()
self._host_key = paramiko.RSAKey.from_private_key_file(StringIO.StringIO(my_key))
def connection_function(self, client):
try:
session = paramiko.Transport(client)
session.add_server_key(self._host_key)
server = SshServerInterface()
try:
session.start_server(server=server)
except paramiko.SSHException:
return
channel = session.accept()
stdio = channel.makefile('rwU')
self.client = Shell(stdio, stdio)
self.client_shell.cmdloop()
session.close()
except:
pass
if __name__ == '__main__':
server = SshServer(my_key)
server.start()

How can I create a Python SSL Client/Server pair where only the server authenticates the client

I have been trying to get a simple Python SSL example working for a day now with no luck. I want to create an SSL server and SSL client. The server should authenticate the client. The Python docs are pretty light on examples for the SSL module, and in general I can't find many working examples. The code I am working with is as follows;
Client:
import socket
import ssl
class SSLClient:
def __init__(self, server_host, server_port, client_cert, client_key):
self.server_host = server_host
self.server_port = server_port
self._context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
self._context.load_cert_chain(client_cert, client_key)
self._sock = None
self._ssock = None
def __del__(self):
self.close()
def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._ssock = self._context.wrap_socket(
self._sock, server_hostname=self.server_host
)
self._ssock.connect((self.server_host, self.server_port))
def send(self, msg):
self._ssock.send(msg.encode())
def close(self):
self._ssock.close()
Server:
import socket
import ssl
from threading import Thread
class SSLServer:
def __init__(self, host, port, cafile, chunk_size=1024):
self.host = host
self.port = port
self.chunk_size = chunk_size
self._context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
self._context.load_verify_locations(cafile)
self._ssock = None
def __del__(self):
self.close()
def connect(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind((self.host, self.port))
sock.listen(5)
with self._context.wrap_socket(sock, server_side=True) as self._ssock:
conn, _ = self._ssock.accept()
while True:
data = conn.recv(self.chunk_size).decode()
print(data)
if data is None:
break
def close(self):
self._ssock.close()
class SSLServerThread(Thread):
def __init__(self, server):
super().__init__()
self._server = server
self.daemon = True
def run(self):
self._server.connect()
def stop(self):
self._server.close()
Test script:
import client, server
from os import path
from time import sleep
server_host = "localhost"
server_port = 11234
client_cert = path.join(path.dirname(__file__), "client.crt")
client_key = path.join(path.dirname(__file__), "client.key")
s = server.SSLServer(server_host, server_port, client_cert)
s_thread = server.SSLServerThread(s)
s_thread.start()
sleep(2)
c = client.SSLClient(server_host, server_port, client_cert, client_key)
c.connect()
c.send("This is a test message!")
c.close()
s.close()
I generated my client certificate and key using the following command:
openssl req -newkey rsa:2048 \
-x509 \
-sha256 \
-days 3650 \
-nodes \
-out client.crt \
-keyout client.key \
-subj "/C=UK/ST=Scotland/L=Glasgow/O=Company A/OU=Testing/CN=MyName"
The test script seems to start the server and allow the client to connect, but I am getting a BrokenPipeError when I try to send the test message.
Annoyingly I have been getting various different error messages as I go, so it's likely a combination of things. This is a simple example I created to try and get something working. On my more complex example I get "NO_SHARED_CIPHERS" when the client attempts to connect to the server. Annoyingly I can't see why this simple example seems to get further than the more complex one (i.e. the connection seems to be established successfully) even though they are set up almost identically.
I have uploaded a repo at git#github.com:stevengillies87/python-ssl-client-auth-example.git if anyone would like to test it.
I realised the first bug came from copy pasting and example and not realising how it differed from my code in its setup. It used socket.socket() to create the socket whereas my example used socket.create_connection(), which also connects the socket. This was the reason I was getting a BrokenPipeError. Now both my simple example and the actual code I am writing both have a consistent NO_SHARED_CIPHER error. I added a line to the source code to connect the client after the socket has been wrapped.
So, as expected it was a combination of things.
Before I added the SSL layer to my code it worked with TCP sockets. I was using socket.create_connection() in the client to create and connect a socket in one call. When I added SSL I continued to do this but because I was attempting to connect to an SSL server via a TCP socket I was getting a NO_SHARED_CIPHER error.
The solution to this problem was to only create the TCP socket with sock = socket.socket(), wrap it with ssock = ssl_context.wrap_context(sock) and then call connect on the SSL layer, ssock.connect((host, port)).
However, I was still getting a handshaking error on connection. I found this link, https://www.electricmonk.nl/log/2018/06/02/ssl-tls-client-certificate-verification-with-python-v3-4-sslcontext/, which provided a detailed example of how to create mutually authenticating SSL client/server. Crucially, the author pointed out that hostname used for server authentication must match the "common name" entered when creating the server.crt and server.key files. Previously I had just been using the same host that I was connecting to, "localhost" in this case. They also noted that the ssl_context verify mode should be set to verify_mode = ssl.CERT_REQUIRED for client auth.
Once the example worked I set about removing the client auth of the server. This was done by changing the client SSL context from ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) to ssl.SSLContext(). The client now does not require the server.crt file to connect successfully.
Frustratingly I still need to create server cert/key files and load them into the server using ssl_context.load_cert_chain(), even though I do not need the server to be authenticated. If I try to remove this step from the server I get a NO_SHARED_CIPHER error again. If anyone knows how I can avoid this then please let me know, or explain why it is necessary.
Working code below, and updated at the github link in the question.
Client:
import socket
import ssl
class SSLClient:
def __init__(
self, server_host, server_port, sni_hostname, client_cert, client_key,
):
self.server_host = server_host
self.server_port = server_port
self.sni_hostname = sni_hostname
self._context = ssl.SSLContext()
self._context.load_cert_chain(client_cert, client_key)
self._sock = None
self._ssock = None
def __del__(self):
self.close()
def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._ssock = self._context.wrap_socket(self._sock,)
self._ssock.connect((self.server_host, self.server_port))
def send(self, msg):
self._ssock.send(msg.encode())
def close(self):
self._ssock.close()
Server:
import socket
import ssl
from threading import Thread
class SSLServer:
def __init__(
self, host, port, server_cert, server_key, client_cert, chunk_size=1024
):
self.host = host
self.port = port
self.chunk_size = chunk_size
self._context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
self._context.verify_mode = ssl.CERT_REQUIRED
self._context.load_cert_chain(server_cert, server_key)
self._context.load_verify_locations(client_cert)
def connect(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind((self.host, self.port))
sock.listen(5)
while True:
conn, _ = sock.accept()
with self._context.wrap_socket(conn, server_side=True) as sconn:
self._recv(sconn)
def _recv(self, sock):
while True:
data = sock.recv(self.chunk_size)
if data:
print(data.decode())
else:
break
class SSLServerThread(Thread):
def __init__(self, server):
super().__init__()
self._server = server
self.daemon = True
def run(self):
self._server.connect()
Test:
import client, server
from os import path
from time import sleep
server_host = "127.0.0.1"
server_port = 35689
server_sni_hostname = "www.company-b.com"
client_cert = path.join(path.dirname(__file__), "client.crt")
client_key = path.join(path.dirname(__file__), "client.key")
server_cert = path.join(path.dirname(__file__), "server.crt")
server_key = path.join(path.dirname(__file__), "server.key")
s = server.SSLServer(server_host, server_port, server_cert, server_key, client_cert)
s_thread = server.SSLServerThread(s)
s_thread.start()
sleep(2)
c = client.SSLClient(
server_host, server_port, server_sni_hostname, client_cert, client_key
)
c.connect()
c.send("This is a test message!")
c.close()

Maintaining another persistent TCP connection with TCPServer

I am connecting to a XMPP server using slixmpp, I need access to this connection while serving a HTTP protocol, I am trying to maintain a persistent connection, rather than connecting connecting to XMPP server for each HTTP request. I am using TCPServer to get the functionality of HTTP. I wrote this code.
import logging
from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
import socketserver
from time import sleep
class EchoBot(ClientXMPP):
def __init__(self, jid, password):
ClientXMPP.__init__(self, jid, password)
self.add_event_handler("session_start", self.session_start)
self.add_event_handler("message", self.message)
def session_start(self, event):
self.send_presence()
self.get_roster()
def message(self, msg):
print(msg)
if msg['type'] in ('chat', 'normal'):
msg.reply("Thanks for sending\n%(body)s" % msg).send()
class MyTCPHandler(socketserver.BaseRequestHandler):
xmpp = EchoBot('xxx#fcm.googleapis.com', 'xyz')
def __init__(self,request, client_address,server):
super().__init__(request, client_address,server)
self.xmpp.connect(address=('fcm-xmpp.googleapis.com',5235),use_ssl=True,disable_starttls=True)
self.xmpp.process(forever=True)
def handle(self):
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,format='%(levelname)-8s %(message)s')
HOST, PORT = "localhost", 9999
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
This works for first time. MyTCPHandler handle function works only first time, second time, it doesn't return any response. I am using telnet localhost 9999 to test the connection. What might be going wrong here? Is there a better way to achieve the result I'm looking for?
if I comment these three lines TCPServer works as expected.
# xmpp = EchoBot('xxx#fcm.googleapis.com', 'xyz')
def __init__(self,request, client_address,server):
super().__init__(request, client_address,server)
# self.xmpp.connect(address=('fcm-xmpp.googleapis.com',5235),use_ssl=True,disable_starttls=True)
# self.xmpp.process(forever=True)
I solved the problem using asyncio
import logging
from slixmpp import ClientXMPP
from slixmpp.exceptions import IqError, IqTimeout
import logging
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
log = logging.getLogger(__name__)
import asyncio
import base64
import slixmpp
from aiohttp import web
XMPP = None
class EchoBot(ClientXMPP):
def __init__(self, jid, password):
ClientXMPP.__init__(self, jid, password)
self.connected_future = asyncio.Future()
self.add_event_handler("session_start", self.session_start)
self.add_event_handler("message", self.message)
def session_start(self, event):
self.send_presence()
self.get_roster()
def message(self, msg):
if msg['type'] in ('chat', 'normal'):
msg.reply("Thanks for sending\n%(body)s" % msg).send()
def reset_future(self):
"Reset the future in case of disconnection"
self.connected_future = asyncio.Future()
async def handle(request):
"Handle the HTTP request and block until the vcard is fetched"
err_404 = web.Response(status=404, text='Not found')
print(await request.json())
try:
XMPP.send_raw('<message id="gsgsfssdfds"> <gcm xmlns="google:mobile:data">{ "notification": {"title": "change","body": "body changed","sound":"default"},"to" : "efsfdsf","message_id":"flajlfdjlfdklajflda","priority":"high","delivery_receipt_requested":true}</gcm></message>')
except Exception as e:
print(e)
log.warning("cannot send message")
return err_404
return web.Response(text="yes")
async def init(loop, host: str, port: str, avatar_prefix: str):
"Initialize the HTTP server"
app = web.Application(loop=loop)
app.router.add_route('POST', '/', handle)
srv = await loop.create_server(app.make_handler(), host, port)
log.info("Server started at http://%s:%s", host, port)
return srv
def main(namespace):
"Start the xmpp client and delegate the main loop to asyncio"
loop = asyncio.get_event_loop()
global XMPP
XMPP = EchoBot('xxx#gcm.googleapis.com', 'ysfafdafdsfa')
XMPP.connect(use_ssl=True,disable_starttls=False)
#XMPP.connect()
loop.run_until_complete(init(loop, namespace.host, namespace.port,
namespace.avatar_prefix))
XMPP.reset_future()
loop.run_until_complete(XMPP.connected_future)
try:
loop.run_forever()
except KeyboardInterrupt:
import sys
def parse_args():
"Parse the command-line arguments"
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('--jid', '-j', dest='jid', default=JID,
help='JID to use for fetching the vcards')
parser.add_argument('--password', '-p', dest='password', default=PASSWORD,
help='Password linked to the JID')
parser.add_argument('--host', dest='host', default=HOST,
help='Host on which the HTTP server will listen')
parser.add_argument('--port', dest='port', default=PORT,
help='Port on which the HTTP server will listen')
parser.add_argument('--avatar_prefix', dest='avatar_prefix',
default=AVATAR_PREFIX,
help='Prefix path for the avatar request')
return parser.parse_args()
HOST = '127.0.0.1'
PORT = 8765
JID = 'changeme#example.com'
PASSWORD = 'changemetoo'
AVATAR_PREFIX = 'avatar/'
if __name__ == "__main__":
print(parse_args())
main(parse_args())

Integrate Autobahn|Python with aiohttp

I'm trying to integrate an aiohttp web server into a Crossbar+Autobahn system architecture.
More in detail, when the aiohttp server receive a certain API call, it has to publish a message to a Crossbar router.
I've seen this example on the official repos but i've no clue on how to integrate it on my application.
Ideally, i would like to be able to do this
# class SampleTaskController(object):
async def handle_get_request(self, request: web.Request) -> web.Response:
self.publisher.publish('com.myapp.topic1', 'Hello World!')
return web.HTTPOk()
where self il an instance of SampleTaskController(object) which defines all the routes handler of the web server.
def main(argv):
cfg_path = "./task_cfg.json"
if len(argv) > 1:
cfg_path = argv[0]
logging.basicConfig(level=logging.DEBUG,
format=LOG_FORMAT)
loop = zmq.asyncio.ZMQEventLoop()
asyncio.set_event_loop(loop)
app = web.Application(loop=loop)
with open(cfg_path, 'r') as f:
task_cfg = json.load(f)
task_cfg['__cfg_path'] = cfg_path
controller = SampleTaskController(task_cfg)
controller.restore()
app['controller'] = controller
controller.setup_routes(app)
app.on_startup.append(controller.on_startup)
app.on_cleanup.append(controller.on_cleanup)
web.run_app(app,
host=task_cfg['webserver_address'],
port=task_cfg['webserver_port'])
Notice that i'm using an zmq.asyncio.ZMQEventLoop because the server is also listening on a zmq socket, which is configured inside the controller.on_startup method.
Instead of using autobahn, i've also tried to publish the message to Crossbar using wampy and it works, but the autobahn subscribers couldn't correctly parse the message.
# autobahn subscriber
class ClientSession(ApplicationSession):
async def onJoin(self, details):
self.log.info("Client session joined {details}", details=details)
self.log.info("Connected: {details}", details=details)
self._ident = details.authid
self._type = u'Python'
self.log.info("Component ID is {ident}", ident=self._ident)
self.log.info("Component type is {type}", type=self._type)
# SUBSCRIBE
def gen_on_something(thing):
def on_something(counter, id, type):
print('----------------------------')
self.log.info("'on_{something}' event, counter value: {message}",something=thing, message=counter)
self.log.info("from component {id} ({type})", id=id, type=type)
return on_something
await self.subscribe(gen_on_something('landscape'), 'landscape')
await self.subscribe(gen_on_something('nature'), 'nature')
-
# wampy publisher
async def publish():
router = Crossbar(config_path='./crossbar.json')
logging.getLogger().debug(router.realm)
logging.getLogger().debug(router.url)
logging.getLogger().debug(router.port)
client = Client(router=router)
client.start()
result = client.publish(topic="nature", message=0)
logging.getLogger().debug(result)
With this configuration the subscriber receive the message published, but it get an exception while parsing it.
TypeError: on_something() got an unexpected keyword argument 'message'
Recently I tried to use aiohttp and autobahn simultaneously. I reworked the example from crossbar documentation (originally using twisted) and got the following code:
import asyncio
import logging
from aiohttp import web
from aiohttp.web_exceptions import HTTPOk, HTTPInternalServerError
from autobahn.asyncio.component import Component
# Setup logging
logger = logging.getLogger(__name__)
class WebApplication(object):
"""
A simple Web application that publishes an event every time the
url "/" is visited.
"""
count = 0
def __init__(self, app, wamp_comp):
self._app = app
self._wamp = wamp_comp
self._session = None # "None" while we're disconnected from WAMP router
# associate ourselves with WAMP session lifecycle
self._wamp.on('join', self._initialize)
self._wamp.on('leave', self._uninitialize)
self._app.router.add_get('/', self._render_slash)
def _initialize(self, session, details):
logger.info("Connected to WAMP router (session: %s, details: %s)", session, details)
self._session = session
def _uninitialize(self, session, reason):
logger.warning("Lost WAMP connection (session: %s, reason: %s)", session, reason)
self._session = None
async def _render_slash(self, request):
if self._session is None:
return HTTPInternalServerError(reason="No WAMP session")
self.count += 1
self._session.publish(u"com.myapp.request_served", self.count, count=self.count)
return HTTPOk(text="Published to 'com.myapp.request_served'")
def main():
REALM = "crossbardemo"
BROKER_URI = "ws://wamp_broker:9091/ws"
BIND_ADDR = "0.0.0.0"
BIND_PORT = 8080
logging.basicConfig(
level='DEBUG',
format='[%(asctime)s %(levelname)s %(name)s:%(lineno)d]: %(message)s')
logger.info("Starting aiohttp backend at %s:%s...", BIND_ADDR, BIND_PORT)
loop = asyncio.get_event_loop()
component = Component(
transports=BROKER_URI,
realm=REALM,
)
component.start(loop=loop)
# When not using run() we also must start logging ourselves.
import txaio
txaio.start_logging(level='info')
app = web.Application(
loop=loop)
_ = WebApplication(app, component)
web.run_app(app, host=BIND_ADDR, port=BIND_PORT)
if __name__ == '__main__':
main()

How to communicate RabbitMQ(Pika library) in tornado application

Pika library support tornado adapter, here is an example about how to publish message using Asynchronous adapter.
I want use pika in tornado application, just an example, I want put tornado request data to RabbitMQ, But don't know how to do it.
Two question don't know how to solve.
1 Pika use tornado adapter has its own ioloop,
self._connection = pika.SelectConnection(pika.URLParameters(self._url),
self.on_connection_open)
self._connection.ioloop.start()
Tornado application has its own ioloop,
tornado.ioloop.IOLoop.instance().start()
How to combine those two ioloop?
2 The Pika example publish same message again and again, but I want to publish request data, how to pass request data to publish method?
On my search for exactly the same thing I found this blog post of Kevin Jing Qiu.
I went the rabbitmq hole a bit further to give every websocket his own set of channel and queues.
The extract from my project can be found below. A tornado application bound to RabbitMQ consists of these parts:
The Tornado Application that will handle web requests. I only see long lived websockets here, but you can do so with short lived http requests as well.
A (one) RabbitMQ connection hold by the PikaClient Instance
a web connection that defines its channels, queues and exchanges when the open method is triggered.
Now a websocket connection can receive data from tornado (data from the browser) via on_message and send it to RabbitMQ.
The websocket connection will receive data from RabbitMQ via basic_consume.
This is not fully functional, but you should get the idea.
class PikaClient(object):
def __init__(self, io_loop):
logger.info('PikaClient: __init__')
self.io_loop = io_loop
self.connected = False
self.connecting = False
self.connection = None
self.channel = None
self.message_count = 0
"""
Pika-Tornado connection setup
The setup process is a series of callback methods.
connect:connect to rabbitmq and build connection to tornado io loop ->
on_connected: create a channel to rabbitmq ->
on_channel_open: declare queue tornado, bind that queue to exchange
chatserver_out and start consuming messages.
"""
def connect(self):
if self.connecting:
#logger.info('PikaClient: Already connecting to RabbitMQ')
return
#logger.info('PikaClient: Connecting to RabbitMQ')
self.connecting = True
cred = pika.PlainCredentials('guest', 'guest')
param = pika.ConnectionParameters(
host='localhost',
port=5672,
virtual_host='/',
credentials=cred
)
self.connection = TornadoConnection(param,
on_open_callback=self.on_connected,stop_ioloop_on_close=False)
self.connection.add_on_close_callback(self.on_closed)
def on_connected(self, connection):
logger.info('PikaClient: connected to RabbitMQ')
self.connected = True
self.connection = connection
# now you are able to call the pika api to do things
# this could be exchange setup for websocket connections to
# basic_publish to later.
self.connection.channel(self.on_channel_open)
def on_channel_open(self, channel):
logger.info('PikaClient: Channel %s open, Declaring exchange' % channel)
self.channel = channel
def on_closed(self, connection):
logger.info('PikaClient: rabbit connection closed')
self.io_loop.stop()
class MyWebSocketHandler(websocket.WebSocketHandler):
def __init__(self):
self.status = 'not connected yet'
def open(self, *args, **kwargs):
self.status = "ws open"
self.rabbit_connect() # connect this websocket object to rabbitmq
def rabbit_connect():
self.application.pc.connection.channel(self.rabbit_channel_in_ok)
def rabbit_channel_in_ok(self,channel):
self.channel_in = channel
self.channel_in.queue_declare(self.rabbit_declare_ok,
exclusive=True,auto_delete=True)
# and so on...
handlers = [ your_definitions_here_like_websockets_or_such ]
settings = { your_settings_here }
application = tornado.web.Application(handlers,**settings)
def main():
io_loop = tornado.ioloop.IOLoop.instance()
# PikaClient is our rabbitmq consumer
pc = PikaClient(io_loop)
application.pc = pc
application.pc.connect()
application.listen(config.tornadoport)
try:
io_loop.start()
except KeyboardInterrupt:
io_loop.stop()
if __name__ == '__main__':
main()
Finally,i figure out it!
the previous solution is outdated for the newest pika component!
1.my pika Version is 1.0.1.
warning :
TornadoConnection Class has changed is package by the newest push request.
from pika.adapters import tornado_connection
2.there is an example: log() and config() should ignore(delete it)
import tornado.web
from handlers.notify import NotifyHandler
from function.function import config
from utils.utils import log
import pika
from pika.adapters import tornado_connection
HANDLERS = [(r'/notify', NotifyHandler)]
class PikaClient():
def __init__(self, io_loop):
self.io_loop = io_loop
self.connected = False
self.connecting = False
self.connection = None
self.channel = None
self.message_count = 9
def connect(self):
if self.connecting:
return
self.connecting = True
cred = pika.PlainCredentials('guest', 'guest')
param = pika.ConnectionParameters(host="10.xxx.xxx.75", credentials=cred)
self.connection = tornado_connection.TornadoConnection(param, custom_ioloop = self.io_loop, on_open_callback = self.on_connected)
self.connection.add_on_open_error_callback(self.err)
self.connection.add_on_close_callback(self.on_closed)
def err(self, conn):
log('socket error', conn)
pass
def on_connected(self, conn):
log('connected')
self.connected = True
self.connection = conn
self.connection.channel(channel_number = 1, on_open_callback = self.on_channel_open)
def on_message(self, channel, method, properties, body):
log(body)
print('body : ', body)
pass
def on_channel_open(self, channel):
self.channel = channel
channel.basic_consume(on_message_callback = self.on_message, queue='hello', auto_ack=True)
return
def on_closed(self, conn, c):
log('pika close!')
self.io_loop.stop()
pass
def main():
port = 3002
is_debug = config('sys', 'debug')
print('DEBUG', is_debug)
app = tornado.web.Application(
HANDLERS,
debug = is_debug,
)
io_loop = tornado.ioloop.IOLoop.instance()
app.pc = PikaClient(io_loop)
app.pc.connect()
http_server = tornado.httpserver.HTTPServer(app)
app.listen(port)
io_loop.start()
print('listen {}'.format(port))
if __name__ == '__main__':
main()

Categories

Resources