Serverless WebSocket route responses - websocket

I am using serverless to create a WebSocket service, for which I am able to successfully trigger my WebSocket routes locally, using serverless-offline and for the deployed service.
The issue I am having now is responding to those WS events.
I am able to use AWS.ApiGatewayManagementApi.postToConnection to respond to my local WebSocket events, though I cannot seem to get the return value of my handler to actually send a WebSocket event in response, as these serverless-offline docs and these AWS docs suggest.
The serverless.yml route:
ws-custom:
handler: handler.wsCustom
events:
- websocket:
route: custom
routeResponseSelectionExpression: $default
The handler:
module.exports.wsCustom = async (event) => {
const body = JSON.parse(event.body);
console.log('WebSocket `custom` event!', event);
return {
statusCode: 200,
body: JSON.stringify({ message: `Hello, ${body.name}!` }),
};
}
Invoking the function in my browser JavaScript:
ws.send(JSON.stringify({ 'action': 'custom', 'name': 'kevbot' }));
The result is that my serverless-offline logs (or CloudWatch in the case of the deployed project) will create an event with the log successfully, but my client will receive no events in response.
Does anyone know how I can do this with the return value of the handler?
EDIT:
The problem seems to exist within my serverless-offline setup. I will attempt to get it working locally and will create an issue necessary.

An important point to note when using WebSocket APIs and trying to return a response from your integration back to the client, as mentioned in this doc:
For a route that is configured to use AWS_PROXY or LAMBDA_PROXY integration, communication is one-way, and API Gateway will not pass the backend response through to the route response automatically. For example, in the case of LAMBDA_PROXY integration, the body that the Lambda function returns will not be returned to the client. If you want the client to receive integration responses, you must define a route response to make two-way communication possible.
So, if you are using a proxy integration, then you cannot return a response back to the client. You would have to use the AWS.ApiGatewayManagementApi.postToConnection method to communicate any data.
Alternatively, if you are using a non-proxy integration, then you can set up a route response for your integration.

Related

Monitor network request URL and response content (Throw custom exceptions)

How can network requests be monitored and evaluated for their request URL, parameters, request, and response data?
Desired solution
I want to be notified or being given a custom exception, if specific content occurs in request or response.
Example
Assume a web application with many dynamic Ajax requests. A request or response might contain a broken value, e.g. undefined.
Request URL:
http://localhost:8080/app/?undefined=1
Response JSON data:
{"undefined":"1"}
Attempts
Filtering for request content in Dev Tools is not possible
PostMan tests seem not viable (e.g. no user interactions)
Not tried/found yet
Guesses of what might work ...
Software to intercepts requests and log/alert details
Proxying any URLs on an OS via some standalone application
If importing axios, you could leverage custom interceptors:
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
// Similar functionality can be achieved for request
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Proxy software
For this case, any proxy that comes with logging ability, or can be extended:
https://mitmproxy.org
https://github.com/http-party/node-http-proxy
Exemplary for many variants under NodeJS
Tunneling
Expose localhost to a domain for testing:
https://ngrok.com
Use the logging capability to get request details:
https://ngrok.com/docs/ngrok-agent/api#list-requests
Localhost vs. 127.0.0.1
localhost should better become 127.0.0.1 or a custom dev domain. On MacOS, localhost did not work with HTTP interceptor browser plugins.

SignalR connection failing when calling through an intermittent WebApi Service

I've an angular app, a gateway service(web api) and an asp.net core service(where the SignalR hub is present). I'm trying to connect from the angular app to SignalR hub through the gateway and the request is failing with the below error:
Access to XMLHttpRequest at '(gateway)/signalR/connect/negotiate' from origin '(UI website)' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
//UI code:
this is request I'm posting from angular app.
const connection = new signalR.HubConnectionBuilder()
.withUrl(https://gateway/signalr/connect)
.withAutomaticReconnect()
.build();
//Gateway code:
https://gateway/signalr/connect is a post api in gateway service.
With in the gateway post api, I'm making a connection to signalr hub like below:
https://localhost:19081/(myservice)/SignalR/(myHub)
//SignalR Service:
Also added the below cors policy in my signalr startup:
services.AddCors(options => options.AddPolicy("SignalRPolicy", builder =>
{
builder.AllowAnyHeader()
.AllowCredentials()
.AllowAnyMethod()
.WithOrigins(<allowedorigins>);
}));
app.UseCors("SignalRPolicy");
app.UseAzureSignalR(routes =>
{
routes.MapHub<HubClass>("/<myhub>");
});
If I directly call the SignalR hub from UI - this request is passing and connecting to signalr successfully.
Here both Gateway service and my signalr service are hosted on azure service fabric. The requirement is, all the calls from UI need to go through the gateway.
Please let me know how I can make this work. Thank you.
You must to explicit (withCredentials: false) at hub connection.
const connection = new signalR.HubConnectionBuilder()
.withUrl(https://gateway/signalr/connect,
{ skipNegotiation: false,
withCredentials: false,
transport: signalR.HttpTransportType.WebSockets })
.withAutomaticReconnect()
.build();

API Gateway WebSocket handle errors

I'm setting a websocket app with API Gateway. I configured route responses for my lambda functions, so I'm able to handle these responses in my client side.
In my server, I return my response like this:
callback (null, {
statusCode: 500,
body: 'Some text.'
})
In my Websocket client side, the message event (onmessage) is triggered by this response.
The event.data contains the response body, due to lambda proxy integration response.
However, I can't find a way to trigger the error event (onerror), while my statusCode says that an error occured (500).
Do you have any solutions?
AWS Lambda function callbacks have the following structure: callback(error, result).
So for errors you would call it like callback(error) and for a successful result callback(null, result).
So what you shall be getting on your app is a success response (status code 200) which contains some data that has a field statusCode with value 500. So, as this is a successful response, it should not trigger onerror callback, but rather only onmessage.
My suggestion is that you handle status code response's other than 200 also inside your onmessage method and the onerror handler for errors other than the ones catch and retrieved from your lambda function (for example an AWS Api Gateway issue, a network issue, etc).

API Gateway WebSocket API postConnection timeout

I'm trying to set up a WebSocket API on API Gateway. I'm following the basic tutorial, and I have everything up and running -> Routes for $connect, $disconnect, "test", $default. I am able to connect to the API, store the connectionId in Redis, and retrieve it when accessing from the test route.
The problem is when I try to send back a message from my lambda (single lambda handling all routes). I'm using the following code
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: `https://${event.requestContext.domainName}/${event.requestContext.stage}`
});
Then I call
await apigwManagementApi.postToConnection({
ConnectionId: connectionId,
Data: `Echo: ${data}`
}).promise()
This is only called on the "test" route.
All of this is as per their guide. I had to add a patch to be able to make postConnection work, again, as per their tutorial. The problem is when the above method is called I get a Internal Server Error message from the API Gateway and the lambda times out after 3 seconds.
There is very little info on this method. I'm not sure what is causing the internal server error. I have checked the endpoint and the connectionId, both are correct.
What am I doing wrong? Any suggestions?
So the problem wasn't the actual lambda but the fact that it wasn't set up in a VPC that had access to the Internet. So if you're lambda has VPC enabled, make sure you it has a NAT gateway and Internet gateway set up.

How to get HTTP request and response from WebSocket connection

Currently, we are using a fairly standard setup with Node.js + Passport + AJAX as the webserver, Backbone on the client, and an app server exposing APIs for the back-end. The client makes AJAX request to Node, which does some housecleaning such as checking the session authentication, then passes the request along to the app server.
We are experimenting with replacing the AJAX portion between the client and Node with WebSockets. That part I have working fine. Previously we were using passport to check if the request was authenticated. With a standard Node route, the request and response is always included so this is a trivial task, but I'm not sure how to get it from the socket.
var server = http.createServer(app).listen(port);
var io = socketio.listen(server);
var namespaced = io.of('/socket/test');
namespaced.on('connection', function (socket) {
console.log('a user connected to /socket/test');
socket.on('fetch', function (args) {
// the client has sent a socket message which will functionally replace an AJAX request
// authenticate session here - how to get the request??
});
});

Resources