Skip to content

Latest commit

 

History

History
701 lines (544 loc) · 22.4 KB

socket-io-serverless-function-binding.md

File metadata and controls

701 lines (544 loc) · 22.4 KB
titledescriptionkeywordsauthorms.authorms.datems.servicems.topic
Socket.IO Azure Function trigger and binding
This article explains the usage of Azure Function trigger and binding
Socket.IO, Socket.IO on Azure, serverless, Azure Function, multi-node Socket.IO, scaling Socket.IO, socketio, azure socketio
zackliu
chenyl
9/1/2024
azure-web-pubsub
conceptual

Socket.IO Azure Function trigger and binding (Preview)

This article explains how to use Socket.IO serverless integrate with Azure Functions.

ActionBinding Type
Get client negotiate result including url and access tokenInput binding
Triggered by messages from the serviceTrigger binding
Invoke service to send messages or manage clientsOutput binding

Source code | Package | API reference documentation | Product documentation | Samples

Important

Azure Function bindings can only integrate with Web PubSub for Socket.IO in Serverless Mode.

Authenticate and Connection String

In order to let the extension work with Web PubSub for Socket.IO, you need to provide either access keys or identity based configuration to authenticate with the service.

Access key based configuration

Configuration NameDescription
WebPubSubForSocketIOConnectionStringRequired. Key based connection string to the service

You can find the connection string in Keys blade in you Web PubSub for Socket.IO in the Azure portal.

For the local development, use the local.settings.json file to store the connection string. Set WebPubSubForSocketIOConnectionString to the connection string copied from the previous step:

{ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", `WebPubSubForSocketIOConnectionString`: "Endpoint=https://<webpubsub-name>.webpubsub.azure.com;AccessKey=<access-key>;Version=1.0;" } }

When deployed use the application settings to set the connection string.

Identity based configuration

Configuration NameDescription
WebPubSubForSocketIOConnectionString__endpointRequired. The Endpoint of the service. For example, https://mysocketio.webpubsub.azure.com
WebPubSubForSocketIOConnectionString__credentialDefines how a token should be obtained for the connection. This setting should be set to managedidentity if your deployed Azure Function intends to use managed identity authentication. This value is only valid when a managed identity is available in the hosting environment.
WebPubSubForSocketIOConnectionString__clientIdWhen credential is set to managedidentity, this property can be set to specify the user-assigned identity to be used when obtaining a token. The property accepts a client ID corresponding to a user-assigned identity assigned to the application. If not specified, the system-assigned identity is used.

The function binding follows the common properties for identity based configuration. See Common properties for identity-based connections for more unmentioned properties.

For the local development, use the local.settings.json file to store the connection string. Set WebPubSubForSocketIOConnectionString to the connection string copied from the previous step:

{ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "WebPubSubForSocketIOConnectionString__endpoint": "https://<webpubsub-name>.webpubsub.azure.com", "WebPubSubForSocketIOConnectionString__tenant": "<tenant id you're in>", } }

If you want to use identity based configuration and running online, the AzureWebJobsStorage should refer to Connecting to host storage with an identity.

Input Binding

Socket.IO Input binding generates a SocketIONegotiationResult to the client negotiation request. When a Socket.IO client tries to connect to the service, it needs to know the endpoint, path, and access token for authentication. It's a common practice to have a server to generate these data, which is called negotiation.

[FunctionName("SocketIONegotiate")]publicstaticIActionResultNegotiate([HttpTrigger(AuthorizationLevel.Anonymous,"get")]HttpRequestreq,[SocketIONegotiation(Hub="hub",UserId="userId")]SocketIONegotiationResultresult){returnnewOkObjectResult(result);}

Attribute

The attribute for input binding is [SocketIONegotiation].

Attribute propertyDescription
HubThe hub name that a client needs to connect to.
ConnectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).
UserIdThe userId of the connection. It applies to all sockets in the connection. It becomes the sub claim in the generated token.
import{app,HttpRequest,HttpResponseInit,InvocationContext,input,}from"@azure/functions";constsocketIONegotiate=input.generic({type: 'socketionegotiation',direction: 'in',name: 'result',hub: 'hub',}); export asyncfunctionnegotiate(request: HttpRequest,context: InvocationContext): Promise<HttpResponseInit>{let result =context.extraInputs.get(socketIONegotiate);return{jsonBody: result};};// Negotiationapp.http('negotiate',{methods: ['GET'],authLevel: 'anonymous',extraInputs: [socketIONegotiate],handler: negotiate});

Configuration

PropertyDescription
typeMust be socketionegotiation
directionMust be in
nameVariable name used in function code for input connection binding object
hubThe hub name that a client needs to connect to.
connectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).
userIdThe userId of the connection. It applies to all sockets in the connection. It becomes the sub claim in the generated token.

A function always needs a trigger binding. We use HttpTrigger as an example in codes.

importazure.functionsasfuncapp=func.FunctionApp() @app.function_name(name="negotiate")@app.route(auth_level=func.AuthLevel.ANONYMOUS)@app.generic_input_binding(arg_name="negotiate", type="socketionegotiation", hub="hub")defnegotiate(req: func.HttpRequest, negotiate) ->func.HttpResponse: returnfunc.HttpResponse(negotiate)

Annotation

PropertyDescription
arg_nameThe variable name of the argument in function to represent the input binding.
typeMust be socketionegotiation
hubThe hub name that a client needs to connect to.
connectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).
userIdThe userId of the connection. It applies to all sockets in the connection. It becomes the sub claim in the generated token.

Trigger Binding

Azure Function uses trigger binding to trigger a function to process the events from the Web PubSub for Socket.IO.

Trigger binding exposes a specific path followed the Azure Function endpoint. The url should be set as the URL Template of the service (Portal: settings -> event handler -> URL Template). In the endpoint pattern, the query part code=<API_KEY> is REQUIRED when you're using Azure Function App for security reasons. The key can be found in Azure portal. Find your function app resource and navigate to Functions -> App keys -> System keys -> socketio_extension after you deploy the function app to Azure. Though, this key isn't needed when you're working with local functions.

<Function_App_Endpoint>/runtime/webhooks/socketio?code=<API_KEY> 

Function triggers for socket connect event.

[FunctionName("SocketIOTriggerConnect")]publicstaticasyncTask<SocketIOEventHandlerResponse>Connect([SocketIOTrigger("hub","connect")]SocketIOConnectRequestrequest){returnnewSocketIOConnectResponse();}

Function triggers for socket connected event.

[FunctionName("SocketIOTriggerConnected")]publicstaticasyncTaskConnected([SocketIOTrigger("hub","connected")]SocketIOConnectedRequestrequest){}

Function triggers for socket disconnect event.

[FunctionName("SocketIOTriggerDisconnected")]publicstaticasyncTaskDisconnected([SocketIOTrigger("hub","disconnected")]SocketIODisconnectedRequestrequest){}

Function triggers for normal messages from clients.

[FunctionName("SocketIOTriggerMessage")]publicstaticasyncTaskNewMessage([SocketIOTrigger("hub","new message")]SocketIOMessageRequestrequest,[SocketIOParameter]stringarg){}

Attributes

The attribute for trigger binding is [SocketIOTrigger].

Attribute propertyDescription
HubThe hub name that a client needs to connect to.
NamespaceThe namespace of the socket. Default: "/"
EventNameThe event name that the function triggers for. Some event names are predefined: connect for socket connect event. connected for socket connected event. disconnected for socket disconnected event. And other events are defined by user and it need to match the event name sent by client side.
ParameterNamesThe parameter name list of the event. The length of list should be consistent with event sent from client. And the name uses the Binding expressions and access by the same-name function parameter.

Binding Data

[SocketIOTrigger] binds some variables to binding data. You can learn more about it from Azure Functions binding expression patterns

SocketIOAttribute

SocketIOAttribute is an alternative of ParameterNames, which simplifies the function definition. For example, the following two definitions have the same effect:

[FunctionName("SocketIOTriggerMessage")]publicstaticasyncTaskNewMessage([SocketIOTrigger("hub","new message")]SocketIOMessageRequestrequest,[SocketIOParameter]stringarg){}
[FunctionName("SocketIOTriggerMessage")]publicstaticasyncTaskNewMessage([SocketIOTrigger("hub","new message",ParameterNames=new[]{"arg"})]SocketIOMessageRequestrequest,stringarg){}

Note that ParameterNames and [SocketIOParameter] can't be used together.

Function triggers for socket connect event.

import{app,InvocationContext,input,trigger}from"@azure/functions"; export asyncfunctionconnect(request: any,context: InvocationContext): Promise<any>{ return {};}// Trigger for connectapp.generic('connect',{trigger: trigger.generic({type: 'socketiotrigger',hub: 'hub',eventName: 'connect'}),handler: connect});

Function triggers for socket connected event.

import{app,InvocationContext,trigger}from"@azure/functions";exportasyncfunctionconnected(request: any,context: InvocationContext): Promise<void>{}// Trigger for connectedapp.generic('connected',{trigger: trigger.generic({type: 'socketiotrigger',hub: 'hub',eventName: 'connected'}),handler: connected});

Function triggers for socket disconnected event.

import{app,InvocationContext,trigger}from"@azure/functions";exportasyncfunctiondisconnected(request: any,context: InvocationContext): Promise<void>{}// Trigger for connectedapp.generic('disconnected',{trigger: trigger.generic({type: 'socketiotrigger',hub: 'hub',eventName: 'disconnected'}),handler: disconnected});

Function triggers for normal messages from clients.

import{app,InvocationContext,trigger,output}from"@azure/functions";exportasyncfunctionnewMessage(request: any,context: InvocationContext): Promise<void>{}// Trigger for new messageapp.generic('newMessage',{trigger: trigger.generic({type: 'socketiotrigger',hub: 'hub',eventName: 'new message'}),handler: newMessage});

Configuration

PropertyDescription
typeMust be socketiotrigger
hubThe hub name that a client needs to connect to.
namespaceThe namespace of the socket. Default: "/"
eventNameThe event name that the function triggers for. Some event names are predefined: connect for socket connect event. connected for socket connected event. disconnected for socket disconnected event. And other events are defined by user and it need to match the event name sent by client side.
ParameterNamesThe parameter name list of the event. The length of list should be consistent with event sent from client. And the name uses the Binding expressions and access by context.bindings.<name>.

Function triggers for socket connect event.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.generic_trigger(arg_name="sio", type="socketiotrigger", data_type=DataType.STRING, hub="hub", eventName="connect")defconnect(sio: str) ->str: returnjson.dumps({'statusCode': 200})

Function triggers for socket connected event.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.generic_trigger(arg_name="sio", type="socketiotrigger", data_type=DataType.STRING, hub="hub", eventName="connected")defconnected(sio: str) ->None: print("connected")

Function triggers for socket disconnected event.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.generic_trigger(arg_name="sio", type="socketiotrigger", data_type=DataType.STRING, hub="hub", eventName="disconnected")defconnected(sio: str) ->None: print("disconnected")

Function triggers for normal messages from clients.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.generic_trigger(arg_name="sio", type="socketiotrigger", data_type=DataType.STRING, hub="hub", eventName="chat")defchat(sio: str) ->None: # do something else

Function trigger for normal messages with callback.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.generic_trigger(arg_name="sio", type="socketiotrigger", data_type=DataType.STRING, hub="hub", eventName="chat")defchat(sio: str) ->str: returnjson.dumps({'ack': ["param1"]})

Annotation

PropertyDescription
arg_nameThe variable name of the argument in function to represent the trigger binding.
typeMust be socketiotrigger
hubThe hub name that a client needs to connect to.
data_typeMust be DataType.STRING
namespaceThe namespace of the socket. Default: "/"
eventNameThe event name that the function triggers for. Some event names are predefined: connect for socket connect event. connected for socket connected event. disconnected for socket disconnected event. And other events are defined by user and it need to match the event name sent by client side.

Request of Input Binding

The data structure of input binding arguments varies depending on the message type.

Connect

{ "namespace": "", "socketId": "", "claims": { "<claim-type>": [ "<claim-value>" ] }, "query": { "<query-key>": [ "<query-value>" ] }, "headers":{ "<header-name>": [ "<header-value>" ] }, "clientCertificates":{ { "thumbprint": "", "content": "" } } }
PropertyDescription
namespaceThe namespace of the socket.
socketIdThe unique identity of the socket.
claimsThe claim of JWT of the client connection. Note, it's not the JWT when the service request the function, but the JWT when the Engine.IO client connects to the service.
queryThe query of the client connection. Note, it's not the query when the service request the function, but the query when the Engine.IO client connects to the service.
headersThe headers of the client connection. Note, it's not the headers when the service request the function, but the headers when the Engine.IO client connects to the service.
clientCertificatesThe client certificate if it's enabled

Connected

{ "namespace": "", "socketId": "", }
PropertyDescription
namespaceThe namespace of the socket.
socketIdThe unique identity of the socket.

Disconnected

{ "namespace": "", "socketId": "", "reason": "" }
PropertyDescription
namespaceThe namespace of the socket.
socketIdThe unique identity of the socket.
reasonThe connection close reason description.

Normal events

{ "namespace": "", "socketId": "", "payload": "", "eventName": "", "parameters": [] }
PropertyDescription
namespaceThe namespace of the socket.
socketIdThe unique identity of the socket.
payloadThe message payload in Engine.IO protocol
eventNameThe event name of the request.
parametersList of parameters of the message.

Output Binding

The output binding currently support the following functionality:

  • Add a socket to room
  • Remove a socket from room
  • Send messages to a socket
  • Send messages to a room
  • Send messages to a namespace
  • Disconnect sockets
[FunctionName("SocketIOOutput")]publicstaticasyncTask<IActionResult>SocketIOOutput([SocketIOTrigger("hub","new message")]SocketIOMessageRequestrequest,[SocketIO(Hub="hub")]IAsyncCollector<SocketIOAction>collector){awaitcollector.AddAsync(SocketIOAction.CreateSendToNamespaceAction("new message",new[]{"arguments"}));}

Attribute

The attribute for input binding is [SocketIO].

Attribute propertyDescription
HubThe hub name that a client needs to connect to.
ConnectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).
import{app,InvocationContext,trigger,output}from"@azure/functions";constsocketio=output.generic({type: 'socketio',hub: 'hub',})exportasyncfunctionnewMessage(request: any,context: InvocationContext): Promise<void>{context.extraOutputs.set(socketio,{actionName: 'sendToNamespace',namespace: '/',eventName: 'new message',parameters: ["argument"]});}// Trigger for new messageapp.generic('newMessage',{trigger: trigger.generic({type: 'socketiotrigger',hub: 'hub',eventName: 'new message'}),extraOutputs: [socketio],handler: newMessage});

Configuration

Attribute propertyDescription
typeMust be socketio
hubThe hub name that a client needs to connect to.
connectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).

A function always needs a trigger binding. We use TimerTrigger as an example in codes.

importazure.functionsasfuncfromazure.functions.decorators.coreimportDataTypeimportjsonapp=func.FunctionApp() @app.timer_trigger(schedule="* * * * * *", arg_name="myTimer", run_on_startup=False,use_monitor=False)@app.generic_output_binding(arg_name="sio", type="socketio", data_type=DataType.STRING, hub="hub")defnew_message(myTimer: func.TimerRequest, sio: func.Out[str]) ->None: sio.set(json.dumps({ 'actionName': 'sendToNamespace', 'namespace': '/', 'eventName': 'update', 'parameters': [ "message" ] }))

Annotation

Attribute propertyDescription
arg_nameThe variable name of the argument in function to represent the output binding.
typeMust be socketio
data_typeUse DataType.STRING
hubThe hub name that a client needs to connect to.
connectionThe name of the app setting that contains the Socket.IO connection string (defaults to WebPubSubForSocketIOConnectionString).

Actions

Output binding uses actions to perform operations. Currently, we support the following actions:

AddSocketToRoomAction

{ "type": "AddSocketToRoom", "socketId": "", "room": "" }

RemoveSocketFromRoomAction

{ "type": "RemoveSocketFromRoom", "socketId": "", "room": "" }

SendToNamespaceAction

{ "type": "SendToNamespace", "eventName": "", "parameters": [], "exceptRooms": [] }

SendToRoomsAction

{ "type": "SendToRoom", "eventName": "", "parameters": [], "rooms": [], "exceptRooms": [] }

SendToSocketAction

{ "type": "SendToSocket", "eventName": "", "parameters": [], "socketId": "" }

DisconnectSocketsAction

{ "type": "DisconnectSockets", "rooms": [], "closeUnderlyingConnection": false }
close