-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python Example #28
Comments
Here's a quick run at a Python Relay HybridConnection Listener which can process HTTP Requests. I'm not accustomed to working in Python so things like error handling and class/module encapsulation are lacking. Program.py import asyncio
import json
import relaylib
import urllib
import websockets
async def run_application():
serviceNamespace = 'contoso-sb.servicebus.windows.net'
entityPath = 'unauthenticated'
sasKeyName = 'RootManageSharedAccessKey'
sasKey = 'GgRm12TgutmThtiRHo6DZqUpeZMbjQh0fNytng+oAcM='
token = relaylib.createSasToken(serviceNamespace, entityPath, sasKeyName, sasKey)
wssUri = relaylib.createListenUrl(serviceNamespace, entityPath, token)
async with websockets.connect(wssUri) as websocket:
keepGoing = True
while keepGoing:
commandStr = await websocket.recv()
command = json.loads(commandStr)
request = command['request']
operationwebsocket = websocket
rendezvouswebsocket = None
if request is not None:
print("\nRequest:\n{}".format(commandStr))
if 'method' not in request:
print('Need to rendezvous to accept the actual request')
rendezvouswebsocket = await websockets.connect(request['address'])
operationwebsocket = rendezvouswebsocket
commandStr = await rendezvouswebsocket.recv()
print(commandStr)
command = json.loads(commandStr)
request = command['request']
if request['body']:
#request has a body, read it from the websocket from which the request was read
requestBody = await operationwebsocket.recv()
print('RequestBody is {0} bytes'.format(len(requestBody)))
response = {}
response['requestId'] = request['id']
response['body'] = True
response['statusCode'] = 200
response['responseHeaders'] = {}
response['responseHeaders']['Content-Type'] = 'text/html'
responseStr = json.dumps({'response' : response })
print("\nResponse:\n{}".format(responseStr))
await operationwebsocket.send(responseStr)
#Response body, if present, must be binary(bytes)
#TODO: If the response body is > 64kb it must be sent over the rendezvouswebsocket (which may need to be created for the response)
responseBodyStr = "<html><body><H1>Hello World</H1><p>From a Python Listener</p><body></html>"
print("ResponseBody:{}".format(responseBodyStr))
responseBodyBytes = responseBodyStr.encode('utf-8')
await operationwebsocket.send(responseBodyBytes)
if rendezvouswebsocket is not None:
await rendezvouswebsocket.close()
asyncio.get_event_loop().run_until_complete(run_application()) relaylib.py import base64
import hashlib
import hmac
import math
import time
import urllib
def hmac_sha256(key, msg):
hash_obj = hmac.new(key=key, msg=msg, digestmod=hashlib._hashlib.openssl_sha256)
return hash_obj.digest()
def createListenUrl(serviceNamespace, entityPath, token = None):
url = 'wss://' + serviceNamespace + '/$hc/' + entityPath + '?sb-hc-action=listen'
if token is not None:
url = url + '&sb-hc-token=' + urllib.parse.quote(token)
return url
# Function which creates the Service Bus SAS token.
def createSasToken(serviceNamespace, entityPath, sasKeyName, sasKey):
uri = "http://" + serviceNamespace + "/" + entityPath
encodedResourceUri = urllib.parse.quote(uri, safe = '')
tokenValidTimeInSeconds = 60 * 60 # One Hour
unixSeconds = math.floor(time.time())
expiryInSeconds = unixSeconds + tokenValidTimeInSeconds
plainSignature = encodedResourceUri + "\n" + str(expiryInSeconds)
sasKeyBytes = sasKey.encode("utf-8")
plainSignatureBytes = plainSignature.encode("utf-8")
hashBytes = hmac_sha256(sasKeyBytes, plainSignatureBytes)
base64HashValue = base64.b64encode(hashBytes)
token = "SharedAccessSignature sr=" + encodedResourceUri + "&sig=" + urllib.parse.quote(base64HashValue) + "&se=" + str(expiryInSeconds) + "&skn=" + sasKeyName
return token |
I have two questions in this python example
|
Any help you could contribute would be wonderful. The entire protocol is documented here:
If the sender were a websocket instead of normal HTTP this Accept message would arrive much like the Request command does: command = json.loads(commandStr)
request = command['request'] <==== Instead of 'request' this would be 'accept' The pseudo-code would be something similar to the following: # command['request'] would be empty, instead check for accept command
accept = command['accept']
if accept is not None:
print("\nAccept:\n{}".format(commandStr))
print('Need to rendezvous to accept the websocket client')
rendezvouswebsocket = await websockets.connect(accept['address'])
# All bytes over rendezvouswebsocket would now be the application data (sender and listener).
|
Thanks for the detailed information 👍 |
If you can even lay out the scaffolding for a good python library with some unit tests it would help greatly. I think this should probably end up getting checked in at https://github.com/Azure/azure-relay/tree/master/samples/hybrid-connections in a new subfolder, "python". Heck, if you've even made any improvements on this and are willing please submit a PR adding these at that path. |
Yeah sure. I am also following a lot of naming conventions from the dotnet library. I have something basic working for http and will add support for websockets. I will also need to optimize on the performance like threads and locks before issuing a PR. |
@adyada, would you please let us know if you are planning to have an improved sample for Python (PR) ready soon? |
@dlstucki Does the following imply that you'd be happy for this code snippet to be treated as MIT licensed like the rest of the samples in the project? (I'd like to start from it in a commercial project - that will hopefully result in something generic that we can contribute back to this project - but I'm not exactly sure where it will go)
|
Can you write an example of a Relay server for a Python?
The text was updated successfully, but these errors were encountered: