API Gateway doesn’t actually handle WebSocket connections; it acts as a proxy, passing messages between your client and your backend service.
Let’s see this in action. Imagine you have a simple backend service running on EC2, and you want to expose it via API Gateway to handle WebSocket traffic.
First, your backend application needs to be able to receive and process WebSocket messages. Here’s a simplified Python example using Flask-Sock:
from flask import Flask, request
from flask_sock import Sock
app = Flask(__name__)
sock = Sock(app)
@app.route('/')
def index():
return 'WebSocket server is running!'
@sock.route('/chat')
def chat(ws):
while True:
data = ws.receive()
if data:
print(f"Received: {data}")
ws.send(f"Echo: {data}")
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=5000)
This app listens on /chat for WebSocket connections. When it receives a message, it prints it and sends an "Echo:" message back to the client.
Now, to expose this via API Gateway, you’ll need to create a WebSocket API in API Gateway. You’ll define routes for connecting, disconnecting, and for custom events. For this example, let’s focus on a $default route, which acts as a catch-all for any messages not explicitly routed.
The key here is how API Gateway translates WebSocket messages into HTTP requests for your backend. When a client sends a message to API Gateway, it’s transformed into an HTTP POST request. The WebSocket connection information is passed in headers, and the message payload is in the request body.
Here’s what a request from API Gateway to your backend might look like for a $connect event:
POST /connect HTTP/1.1
Host: your-backend-host.com
Content-Type: application/json
x-amzn-apigateway-event-type: CONNECT
x-amzn-apigateway-connection-id: ABCDEFG1234567890=
x-amzn-apigateway-request-id: XYZ9876543210
x-amzn-trace-id: Root=1-abcdef12-34567890abcdef1234567890;Parent=1234567890abcdef12;Sampled=1
{}
And for a message sent via the $default route:
POST /default HTTP/1.1
Host: your-backend-host.com
Content-Type: application/json
x-amzn-apigateway-event-type: MESSAGE
x-amzn-apigateway-connection-id: ABCDEFG1234567890=
x-amzn-apigateway-request-id: XYZ9876543210
x-amzn-trace-id: Root=1-abcdef12-34567890abcdef1234567890;Parent=1234567890abcdef12;Sampled=1
{
"message": "Hello from client!"
}
Your backend service receives these HTTP requests. It needs to extract the x-amzn-apigateway-event-type header to know what kind of event it is, and the x-amzn-apigateway-connection-id to know which client sent the message. For MESSAGE events, the actual message content is in the JSON body.
To send a message back to a specific client, your backend doesn’t directly send it over the WebSocket. Instead, it makes an HTTP POST request to the API Gateway Management API.
For example, to send "Echo: Hello from client!" back to the connection ABCDEFG1234567890=, your backend would make a request like this:
POST /@connections/ABCDEFG1234567890= HTTP/1.1
Host: your-api-gateway-id.execute-api.your-region.amazonaws.com
Content-Type: application/json
X-Amz-Date: 20231027T100000Z
Authorization: AWS4-HMAC-SHA256 Credential=YOUR_ACCESS_KEY_ID/20231027/your-region/execute-api/aws4_request, SignedHeaders=host;x-amz-date, Signature=...
"Echo: Hello from client!"
API Gateway then takes this payload and delivers it to the correct WebSocket connection.
The most surprising thing is how API Gateway abstracts away the persistent connection management. You don’t deal with sockets directly; you deal with HTTP requests and responses, and API Gateway handles the underlying WebSocket framing, keep-alives, and reconnections. This allows you to use standard HTTP-based backend frameworks without needing WebSocket-specific libraries for the API Gateway interaction layer.
This event-driven model, where API Gateway translates WebSocket events into HTTP and uses a separate management API for sending messages, is fundamental to how it scales WebSocket applications without you needing to manage server infrastructure for each connection.
The next challenge is handling message routing and state management across potentially many backend instances.