Project

General

Profile

Issue with HTTP API script where use MUC as an external component

Igor Khomenko
Added almost 4 years ago

We have next script to modify the admin list of a room

https://projects.tigase.org/boards/17/topics/4593

It works great where MUC component is the internal component:

--comp-name-2=muc
--comp-class-2=tigase.muc.MUCComponent

But we have an issue where we use MUC as the external component

--comp-name-1 = ext
--comp-class-1 = tigase.server.ext.ComponentProtocol
--external = muc.chattest.amazingage.com:muc-secret:listen:5271:152.31.38.17:ReceiverBareJidLB

when we call a script then it takes about 30 seconds to get a response and response is alwasy 500

HTTP/1.1 500 Server Error
Date: Sun, 05 Apr 2015 18:12:47 GMT
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 315
Server: Jetty(9.2.1.v20140609)

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 </title>
</head>
<body>
<h2>HTTP ERROR: 500</h2>
<p>Problem accessing /rest/dialogs/update_room_occupants. Reason:

Powered by Jetty://

and in logs I see next exception:

2015-04-05 18:41:26.642 [qtp1668555951-101]  java_util_logging_Logger$log.call()  SEVERE: exception processing request
java.lang.IllegalArgumentException: The JSON input text should neither be null nor empty.
    at groovy.json.JsonSlurper.parseText(JsonSlurper.java:56)
    at groovy.json.JsonSlurper$parseText.call(Unknown Source)
    at tigase.http.coders.JsonCoder.decode(JsonCoder.groovy:36)
    at tigase.http.coders.Coder$decode.call(Unknown Source)
    at tigase.http.rest.RestServlet.execute(RestServlet.groovy:256)
    at tigase.http.rest.RestServlet$execute.callCurrent(Unknown Source)
    at tigase.http.rest.RestServlet.executeAsync(RestServlet.groovy:186)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:361)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:153)
    at tigase.http.rest.RestServlet$_processRequest_closure3.doCall(RestServlet.groovy:155)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
    at groovy.lang.Closure.call(Closure.java:411)
    at groovy.lang.Closure.call(Closure.java:427)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1326)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1298)
    at org.codehaus.groovy.runtime.dgm$148.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at tigase.http.rest.RestServlet.processRequest(RestServlet.groovy:128)
    at tigase.http.rest.RestServlet$processRequest.callCurrent(Unknown Source)
    at tigase.http.rest.RestServlet.service(RestServlet.groovy:90)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:751)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:566)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:596)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1113)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:498)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1045)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:175)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:98)
    at org.eclipse.jetty.server.Server.handleAsync(Server.java:518)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:321)
    at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:241)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
    at java.lang.Thread.run(Thread.java:745)

What I realised is that by some reason the result package is forwarded to sess-man component, but not http, look at bellow log, it contains a request, a response and an error

2015-04-05 18:12:47.900 [in_3-message-router]  MessageRouter.processPacket()  FINEST:   2. Packet will be processed by: ext@amazingage-1, from=http@amazingage-chat-1/53909c36-c48a-4eb8-a660-48c7b7575c53, to=null, 
DATA=<iq id="b2166a14-2807-45e5-a332-0420aecf0de8" from="5-1@chattest.amazingage.com" to="1_551eb273af353c2fb2000004@muc.chattest.amazingage.com" type="set">
<query xmlns="http://jabber.org/protocol/muc#admin"><item affiliation="outcast" jid="7-1@chattest.amazingage.com"/></query></iq>, SIZE=289, XMLNS=null, PRIORITY=NORMAL, PERMISSION=AUTH, TYPE=set

...

2015-04-05 18:12:47.907 [in_0-message-router]  MessageRouter.processPacket()  FINEST:   2. Packet will be processed by: sess-man@amazingage-chat-1, from=null, to=null, 
DATA=<iq id="b2166a14-2807-45e5-a332-0420aecf0de8" to="5-1@chattest.amazingage.com" xmlns="jabber:client" from="1_551eb273af353c2fb2000004@muc.chattest.amazingage.com" type="result"/>, 
SIZE=184, XMLNS=jabber:client, PRIORITY=NORMAL, PERMISSION=NONE, TYPE=result

...

2015-04-05 18:12:47.932 [in_3-message-router]  MessageRouter.processPacket()  FINEST:   2. Packet will be processed by: ext@amazingage-chat-1, from=sess-man@amazingage-chat-1, 
to=1_551eb273af353c2fb2000004@muc.chattest.amazingage.com, DATA=<iq id="b2166a14-2807-45e5-a332-0420aecf0de8" xmlns="jabber:client" to="1_551eb273af353c2fb2000004@muc.chattest.amazingage.com" 
from="5-1@chattest.amazingage.com" type="error"><error code="503" type="cancel"><service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/><text xml:lang="en" 
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Service not available.</text></error></iq>, SIZE=386, XMLNS=jabber:client, PRIORITY=NORMAL, PERMISSION=NONE, TYPE=error

Do you have any ideas why it happens?


Replies (9)

Added by Igor Khomenko almost 4 years ago

Here is an example of my request

curl -H "Content-Type: application/json" -H "Api-Key: cerwer8f2b2d9aaf7f4256bfc82527112ffc45c4e133c" -i -X POST chattest.amazingage.com:8080/rest/dialogs/update_room_occupants -d '{"room_jid": "6_550168fcefa357015b000002@muc.chattest.amazingage.com", "type": "pull", "user_jids": ["1530071-6@chattest.amazingage.com", "1530072-6@chattest.amazingage.com"], "admin_jid": "19-6@chattest.amazingage.com"}'

Added by Igor Khomenko almost 4 years ago

As I understand the issue is with from and to properties of packet,

they are null when MUC sends a result back, so that's why a MessageRouter routes this packet to sess-man*, not *http component

2015-04-05 18:12:47.907 [in_0-message-router]  MessageRouter.processPacket()  FINEST:   2. Packet will be processed by: sess-man@amazingage-chat-1, from=null, to=null, 
DATA=<iq id="b2166a14-2807-45e5-a332-0420aecf0de8" to="5-1@chattest.amazingage.com" xmlns="jabber:client" from="1_551eb273af353c2fb2000004@muc.chattest.amazingage.com" type="result"/>, 
SIZE=184, XMLNS=jabber:client, PRIORITY=NORMAL, PERMISSION=NONE, TYPE=result
Avatar?id=6023&size=32x32

Added by Artur Hefczyc TigaseTeam almost 4 years ago

Hi Igor,

Generally, the external component logic requires some improvements to make it easier to execute ad-hoc commands on the component. The protocol for connecting external components is actually not well thought trough, hence some problems. We are going to address all problems related to the external components very soon. For now I suggest to deploy components as internal whenever possible.

Added by Igor Khomenko almost 4 years ago

Hi Artur,

I did some more test and found a clear explanation of this issue:

  • The result packet goes to a wrong component. In that case it goes to sess-man, not to http

  • as a result - sendPackets method returns a timeout

So as a workaround we can just ignore a timeout and return a success response to the end user, but this is not ideal and actually bad solution.

We use MUC as an external component because it's possible to cluster it in a such way.

I don't see any other ways how to cluster MUC, it's not possible with internal components

Added by Igor Khomenko almost 4 years ago

Artur,

do you have a chance to quickly look at this issue? why values from and to are null

Or can you give me some possible context or area of this case, which classes to check, so I will be able to trace this on my side as well

thanks

Avatar?id=6023&size=32x32

Added by Artur Hefczyc TigaseTeam almost 4 years ago

Igor,

as I mentioned there are some issues with external component routing and addressing for certain packets and we will work on it, however, due to the limitations in the protocol for connecting external components I cannot promise anything at this point.

As for the clustering MUC another option for you would be to use our Tigase ACS. The ACS module includes support for both MUC and PubSub and is the best solution for clustered systems.

Added by Igor Khomenko over 3 years ago

Just in a case when somebody else has the same issue

We built a small patch for this

class XMPPIOService

1) Added 2 methods:

    private static final String ORIGIN = "origin";
    private static final String ORIGIN_HTTP = "http";
    private boolean isEnabledHTTPFixForExternalMUC = (System.getProperty("external") != null);

    public void addOrigin(Packet packet) {
        if(!isEnabledHTTPFixForExternalMUC){
            return;
        }

        JID from = packet.getPacketFrom();
        JID to = packet.getPacketTo();
        //
        if(from != null && from.getLocalpart() != null && from.getLocalpart().equals(ORIGIN_HTTP)) {
            packet.getElement().addChild(new Element(ORIGIN, new String[]{Packet.FROM_ATT},
                        new String[]{from.toString()}));

        }else if(to != null && to.getLocalpart() != null && to.getLocalpart().equals(ORIGIN_HTTP)){
            packet.getElement().addChild(new Element(ORIGIN, new String[]{Packet.TO_ATT},
                        new String[]{to.toString()}));
        }
    }

    public void readOrigin(Packet packet) {
        if(!isEnabledHTTPFixForExternalMUC){
            return;
        }

        Element origin = packet.getElement().getChild(ORIGIN);
        if (origin != null) {
            String fromStr = origin.getAttributeStaticStr(Packet.FROM_ATT);

            if(fromStr != null) {
                JID fromJid = null;
                try {
                    fromJid = JID.jidInstance(fromStr);
                    packet.setPacketFrom(fromJid);
                } catch (TigaseStringprepException e) {
                    e.printStackTrace();
                }
            } else{
                String toStr = origin.getAttributeStaticStr(Packet.TO_ATT);
                if(toStr != null) {
                    JID toJid = null;
                    try {
                        toJid = JID.jidInstance(toStr);
                        packet.setPacketTo(toJid);
                    } catch (TigaseStringprepException e) {
                        e.printStackTrace();
                    }
                }
            }

            packet.getElement().removeChild(origin);
        }
    }

2) call addOrigin inside addPacketToSend

3) call readOrigin inside addReceivedPacket

Added by Igor Khomenko almost 3 years ago

Hi there,

Looks like this problem is persistent even if use your ACS solution for muc.

for example, we have 2 nodes with ACS and HTTP API enabled.

we make a request to node1, for example, a request to update room members list.

Then node1 forwards this request to node2 because this particular muc room is managed by node2.

But then, a result of this packet doesn't return to node1 HTTP API component. We see some issues with packet's to and from fields, they are wrong.

Added by Wojciech Kapcia TigaseTeam almost 3 years ago

Which Tigase version do you use? Which version of MUC and ACS-MUC? With which strategy?

    (1-9/9)