asterisk / node-ari-client

Node.js client for ARI. This library is best effort with limited support.
Other
253 stars 99 forks source link

Problem with ari-client : sendMessage #30

Open bof22 opened 9 years ago

bof22 commented 9 years ago

I use your API, ari-client, but I have a problem with the function "sendMessageToEndpoint". I use it like this :

ari.endpoints.sendMessageToEndpoint({
        from: 'AstSae',
        tech: 'SIP', resource: '91',
        body: 'ESSAI',
        variables: []},
        function (err) {
            if (err) {
                throw err;
            }
        });

But the ARI request is :

PUT /ari/endpoints/SIP/91/sendMessage?from=AstSae HTTP/1.1
user-agent: Shred
Content-Type: application/json
Accept: application/json
Authorization: Basic YXJpdGVzdDp0ZXN0bWU=
Content-Length: 16
Host: 192.168.50.90:8088
Connection: keep-alive

{"variables":[]}

I can not see the body message. And the resulting SIP message sending to SIP/91 is :

MESSAGE sip:91@192.168.1.91;line=b6e881de237c150 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.90:5060;branch=z9hG4bK5c0bc190
Max-Forwards: 70
From: "AstSae" <sip:asterisk@192.168.1.90>;tag=as43280f06
To: <sip:91@192.168.1.91;line=b6e881de237c150>
Contact: <sip:asterisk@192.168.1.90:5060>
Call-ID: 6399e2172b93f6092e82f6a229f7139c@[fe80::a65d:36ff:fecf:db94]:5060
CSeq: 102 MESSAGE
User-Agent: Asterisk PBX 13.5.0
Content-Type: text/plain;charset=UTF-8
Content-Length: 0

There is no Body Message.

Could you help me to understand ?

Best regards,

robgamlin commented 8 years ago

After much struggling with this I found the following solution: If "variables" are passed, or at least the variables argument exists, then the "body" argument is ignored, (perhaps destroyed). So to get a message "body" do one of two things:

  1. Do not provide any 'variables' argument.
  2. If you need the variables to provide your own headers etc. Include the body content inside the variables argument. eg:

var variables = { "body":"My Message Content", "variables": {"Event":"myowneventname"} };

ari.endpoints.sendMessage( {from:"from", to:"techprefix:to", body: "this gets ignored", variables: variables, },function (err){});

Hope it helps. Just as an aside, we use sendMessage within ARI to generate out of dialogue NOTIFYs. We create our own tech c module as an extension of message.c and adapt some of the header handling, but it seems a good solution where no simple alternative seems to exist.

samuelg commented 8 years ago

Thanks for researching this. This looks like a bug. I believe it's due to another API parameter called variables for channel variables requiring the body type and special handling we have for that parameter. I should be able to get a PR that handles both situations correctly.

chadxz commented 8 years ago

probably related to this function? https://github.com/asterisk/node-ari-client/blob/master/lib/utils.js#L26

chadxz commented 8 years ago

I dug into the Asterisk source code a bit to understand this more

for reference, the API endpoint of this method is

PUT /endpoints/{tech}/{resource}/sendMessage

in ast_ari_endpoints_send_message_to_endpoint_cb() of res/res_ari_endpoints.c, we are setting from and body from the "params" (query string i think?) and tech and resource from the path, as expected. Then below that, it attempts to parse any JSON passed in the request body, and puts that in the variables property of the args.

then in ast_ari_endpoints_send_message() of res/ari/resource_endpoints.c, it checks to see if the args->variables is set. Remember, at this point, this is the entire request body. If it is set, it parses the request body (with ast_ari_endpoints_send_message_parse_body()) and overwrites any to, from, and body passed on the query string with the values set in the request body, if they exist. After that, it grabs the variables property out of the request body, and calls json_to_ast_variables() in an if statement, returning if the function returns a non-zero result. Assuming everything is okay up to this point, it dispatches the params it now holds in args to send_message() which handles actually sending the SIP message.

Anyway, I still need to dig into the code here in node-ari-client, but at least we know now what the Asterisk side is doing on this specific call...

robgamlin commented 8 years ago

Thanks chadxz, that seems to fall in with our findings. Hopefully any fix won't break the workaround.

chadxz commented 8 years ago

Inside the version of the swagger-client that we are using (v2.0.26), there is some code that prevents a request from having a query parameter named body, which is what this request and a few other ARI requests expect. I'm pretty sure this was considered a bug in that library, because in later versions of the library it looks like it was fixed. However, those later versions heavily refactored the internals of the library, which is the reason that we're currently pinned to the version that we are on. You can see more details about that in #47.

While looking at this, I also noticed that when the variables param is not set and the body param is set when you call sendMessageToEndpoint(), the request has its body set to the value of the body param, even though its paramType is query. This is due to the same bug as above - body is never treated as a queryParam by swagger-client, only as a literal json body to be passed on the request. For the example given by @bof22 above, this would actually result in invalid JSON, since only a string was passed.

Things got even worse when I looked at the example of how @robgamlin got this to actually work. Our code in parseBodyParams is setting the body option to the entire value of the param that is passed that has a paramType of 'body'. For the request in question, the only param that has this type is variables. So whatever you set as the value for variables is what ends up in the body.

This behavior is just wrong. The swagger paramType of 'body' is meant to indicate that the property should be a part of the body, not that the property should have its value set as the entire body. We have coded in a few exceptions, one specifically for the variables, which causes the variables to be set to the body's variables param, but instead of augmenting any existing request body, it overwrites it with an entirely new object. So regardless, this is not the behavior that we really want.

So to actually fix this bug, I think we have these options:

Option 1:

or

Option 2:

In my opinion, neither of these options is that great. I've already tried Option 2 and after a few hours of work, I felt like I was digging myself into a hole. Option 1 is also not compelling, because it would be digging the hole of being even more stuck on an outdated dependency.

For me, this is yet another push in the direction of just dropping the swagger-client dependency completely, and instead performing the network requests ourselves.

samuelg commented 8 years ago

Seeing as there's a workaround, it doesn't feel like we should go with option 1 at this time until we encounter a scenario that cannot be resolved otherwise. Option 2 feels like a waste of time at this point.

What I would like to see is us carving out some time to rip swagger code gen out of this project. I don't think it's a trivial change, but nor should it take months to accomplish.

capouch commented 7 years ago

Reading this thread makes my head hurt!!

I hope I could get someone to verify that the behavior I see is "to be expected" following the logic above: You cannot send a body parameter along with the request, but must pass in a variables parameter which is an object that must contain both the body parameter and some value for variables?

That's what it looks like to me, but my brain is pudding.

robgamlin commented 7 years ago

Put in your terms capcouch: If you need 'variables', then specify a 'body' inside the variables object. 'body' is ok outside variables, but will be ignored if a 'variables' object is included.

capouch commented 7 years ago

Was not able to get it to go using body with or without variables. Works only if I omit body but put body content and some value for variables inside variables parm. This seems to be somewhat verified by the first paragraph in the third post by @chadxz above.

It's all good and working that way for me, so the details at this point are academic :-)