GCM XMPP - 启动 Web 应用程序服务器时出错

Posted

技术标签:

【中文标题】GCM XMPP - 启动 Web 应用程序服务器时出错【英文标题】:GCM XMPP - Error on start web application server 【发布时间】:2015-10-04 22:30:02 【问题描述】:

我的后端 java 的目标是使用 GCM XMPP 接收移动消息。我的网络应用程序有 spring 4.1.4 和 smack 4.1.4。

Smack 依赖项:

<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-core</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-tcp</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-extensions</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-java7</artifactId>
    <version>4.0.1</version>
</dependency>

XMPP 连接的 Bean:

@Component("CcsClientImpl")
public class CcsClientImpl 

    private static final String GCM_ELEMENT_NAME = "gcm";
    private static final String GCM_NAMESPACE = "google:mobile:data";

    private static XMPPTCPConnection connection;

    /**
     * Indicates whether the connection is in draining state, which means that it
     * will not accept any new downstream messages.
     */
    protected static volatile boolean connectionDraining = false;

    private static final Logger logger = LoggerFactory.getLogger("CcsClientImpl");

    @Value("$sender.id")
    private String mSenderId;

    @Value("$server.api.key")
    private String mServerApiKey;

    @Value("$gcm.xmpp.host")
    private String mHost;

    @Value("$gcm.xmpp.port")
    private int mPort;

    @Value("$gcm.xmpp.debuggable")
    private boolean mDebuggable;

    @Autowired
    private ProcessorFactory processorFactory;

    //@Autowired
    public CcsClientImpl() 

        ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new  ExtensionElementProvider<ExtensionElement>() 
            @Override
            public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException, IOException 
                String json = parser.nextText();
                return new GcmPacketExtension(json);
            
        );

        try 
            connect(mSenderId, mServerApiKey);
         catch (XMPPException ex) 
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
         catch (SmackException ex) 
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
         catch (IOException ex) 
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
        
    

    /**
     * Sends a downstream message to GCM.
     *
     * @return true if the message has been successfully sent.
     */
    public boolean sendDownstreamMessage(String jsonRequest) throws
            NotConnectedException 
        if (!connectionDraining) 
            send(jsonRequest);
            return true;
        
        logger.info("Dropping downstream message since the connection is draining");
        return false;
    

    /**
     * Returns a random message id to uniquely identify a message.
     *
     * <p>Note: This is generated by a pseudo random number generator for
     * illustration purpose, and is not guaranteed to be unique.
     */
    public String nextMessageId() 
        return "m-" + UUID.randomUUID().toString();
    

    /**
     * Sends a packet with contents provided.
     */
    protected void send(String jsonRequest) throws NotConnectedException 
        Stanza request = new GcmPacketExtension(jsonRequest).toPacket();
        connection.sendStanza(request);
    

    /// new: customized version of the standard handleIncomingDateMessage method
    /**
     * Handles an upstream data message from a device application.
     */
    public void handleIncomingDataMessage(CcsMessage msg) 
        if (msg.getPayload().get("action") != null) 
            PayloadProcessor processor = processorFactory.getProcessor(msg.getPayload().get("action"));
            processor.handleMessage(msg);
           
        

    /**
     * Handles an ACK.
     *
     * <p>Logs a INFO message, but subclasses could override it to
     * properly handle ACKs.
     */
    protected void handleAckReceipt(Map<String, Object> jsonObject) 
        String messageId = (String) jsonObject.get("message_id");
        String from = (String) jsonObject.get("from");
        logger.info("handleAckReceipt() from: " + from + ",messageId: " + messageId);
    

    /**
     * Handles a NACK.
     *
     * <p>Logs a INFO message, but subclasses could override it to
     * properly handle NACKs.
     */
    protected void handleNackReceipt(Map<String, Object> jsonObject) 
        String messageId = (String) jsonObject.get("message_id");
        String from = (String) jsonObject.get("from");
        logger.info("handleNackReceipt() from: " + from + ",messageId: " + messageId);
    

    protected void handleControlMessage(Map<String, Object> jsonObject) 
        logger.info("handleControlMessage(): " + jsonObject);
        String controlType = (String) jsonObject.get("control_type");
        if ("CONNECTION_DRAINING".equals(controlType)) 
            connectionDraining = true;
         else 
            logger.info("Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.",
                    controlType);
        
    

    /**
     * Creates a JSON encoded GCM message.
     *
     * @param to RegistrationId of the target device (Required).
     * @param messageId Unique messageId for which CCS sends an
     *         "ack/nack" (Required).
     * @param payload Message content intended for the application. (Optional).
     * @param collapseKey GCM collapse_key parameter (Optional).
     * @param timeToLive GCM time_to_live parameter (Optional).
     * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
     * @return JSON encoded GCM message.
     */
    public static String createJsonMessage(String to, String messageId,
            Map<String, String> payload, String collapseKey, Long timeToLive,
            Boolean delayWhileIdle) 
        Map<String, Object> message = new HashMap<String, Object>();
        message.put("to", to);
        if (collapseKey != null) 
            message.put("collapse_key", collapseKey);
        
        if (timeToLive != null) 
            message.put("time_to_live", timeToLive);
        
        if (delayWhileIdle != null && delayWhileIdle) 
            message.put("delay_while_idle", true);
        
      message.put("message_id", messageId);
      message.put("data", payload);
      return JSONValue.toJSONString(message);
    

    /**
     * Creates a JSON encoded ACK message for an upstream message received
     * from an application.
     *
     * @param to RegistrationId of the device who sent the upstream message.
     * @param messageId messageId of the upstream message to be acknowledged to CCS.
     * @return JSON encoded ack.
     */
    protected static String createJsonAck(String to, String messageId) 
        Map<String, Object> message = new HashMap<String, Object>();
        message.put("message_type", "ack");
        message.put("to", to);
        message.put("message_id", messageId);
        return JSONValue.toJSONString(message);
    

    /**
     * Connects to GCM Cloud Connection Server using the supplied credentials.
     *
     * @param senderId Your GCM project number
     * @param apiKey API Key of your project
     */
    public void connect(String senderId, String serverApiKey)
            throws XMPPException, IOException, SmackException 
        XMPPTCPConnectionConfiguration config =
                XMPPTCPConnectionConfiguration.builder()
                .setServiceName(mHost)
                 .setHost(mHost)
                 .setCompressionEnabled(false)
                 .setPort(mPort)
                 .setConnectTimeout(30000)
                 .setSecurityMode(SecurityMode.disabled)
                 .setSendPresence(false)
                                .setDebuggerEnabled(mDebuggable)
                 .setSocketFactory(SSLSocketFactory.getDefault())
                .build();

        connection = new XMPPTCPConnection(config);

        //disable Roster as I don't think this is supported by GCM
        Roster roster = Roster.getInstanceFor(connection);
        roster.setRosterLoadedAtLogin(false);

        logger.info("Connecting...");
        connection.connect();

        connection.addConnectionListener(new LoggingConnectionListener());

        // Handle incoming packets
        connection.addAsyncStanzaListener(new MyStanzaListener() , new MyStanzaFilter() );

        // Log all outgoing packets
        connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter() );

        connection.login(senderId + "@gcm.googleapis.com" , serverApiKey);

        logger.info("Logged in: " + mSenderId);

    

    private CcsMessage getMessage(Map<String, Object> jsonObject) 
        String from = jsonObject.get("from").toString();

        // PackageName of the application that sent this message.
        String category = jsonObject.get("category").toString();

        // unique id of this message
        String messageId = jsonObject.get("message_id").toString();

        @SuppressWarnings("unchecked")
        Map<String, String> payload = (Map<String, String>) jsonObject.get("data");

        CcsMessage msg = new CcsMessage(from, category, messageId, payload);

        return msg;
    

    private class MyStanzaFilter implements StanzaFilter 

        @Override
        public boolean accept(Stanza arg0) 
            // TODO Auto-generated method stub
            if (arg0.getClass() == Stanza.class) 
                return true;
             else 
                if (arg0.getTo() != null) 
                    if (arg0.getTo().startsWith(mSenderId)) 
                        return true;
                    
                

            

            return false;
        
    

    private class MyStanzaListener implements StanzaListener

        @Override
        public void processPacket(Stanza packet) 
            logger.info("Received: " + packet.toXML());
            Message incomingMessage = (Message) packet;
            GcmPacketExtension gcmPacket =
                    (GcmPacketExtension) incomingMessage.
                    getExtension(GCM_NAMESPACE);
            String json = gcmPacket.getJson();
            try 
                @SuppressWarnings("unchecked")
                Map<String, Object> jsonObject =
                        (Map<String, Object>) JSONValue.
                        parseWithException(json);

                // present for "ack"/"nack", null otherwise
                Object messageType = jsonObject.get("message_type");

                if (messageType == null) 
                    // Normal upstream data message
                    CcsMessage msg = getMessage(jsonObject);

                    handleIncomingDataMessage(msg);

                    // Send ACK to CCS
                    String messageId = (String) jsonObject.get("message_id");
                    String from = (String) jsonObject.get("from");
                    String ack = createJsonAck(from, messageId);
                    send(ack);
                 else if ("ack".equals(messageType.toString())) 
                      // Process Ack
                      handleAckReceipt(jsonObject);
                 else if ("nack".equals(messageType.toString())) 
                      // Process Nack
                      handleNackReceipt(jsonObject);
                 else if ("control".equals(messageType.toString())) 
                      // Process control message
                      handleControlMessage(jsonObject);
                 else 
                      logger.warn("Unrecognized message type (%s)",
                              messageType.toString());
                
             catch (ParseException e) 
                logger.info("Error parsing JSON " + json, e);
             catch (Exception e) 
                logger.info("Failed to process packet", e);
            
        

    

    private class MyStanzaInterceptor implements StanzaListener
    
        @Override
        public void processPacket(Stanza packet) 
            logger.info("Sent: 0", packet.toXML());
        

    


//    public static void main(String[] args) throws Exception 
//        
//        SmackCcsClient ccsClient = new SmackCcsClient();
//
//        ccsClient.connect(YOUR_PROJECT_ID, YOUR_API_KEY);
//        
//        // Send a sample hello downstream message to a device.
//        String messageId = ccsClient.nextMessageId();
//        Map<String, String> payload = new HashMap<String, String>();
//        payload.put("Message", "Ahha, it works!");
//        payload.put("CCS", "Dummy Message");
//        payload.put("EmbeddedMessageId", messageId);
//        String collapseKey = "sample";
//        Long timeToLive = 10000L;
//        String message = createJsonMessage(YOUR_PHONE_REG_ID, messageId, payload,
//                collapseKey, timeToLive, true);
//
//        ccsClient.sendDownstreamMessage(message);
//        logger.info("Message sent.");
//        
//        //crude loop to keep connection open for receiving messages
//        while(true)
//        ;
//    

    /**
     * XMPP Packet Extension for GCM Cloud Connection Server.
     */
    private static final class GcmPacketExtension extends DefaultExtensionElement   

        private final String json;

        public GcmPacketExtension(String json) 
            super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
            this.json = json;
        

        public String getJson() 
            return json;
        

        @Override
        public String toXML() 
            return String.format("<%s xmlns=\"%s\">%s</%s>",
                    GCM_ELEMENT_NAME, GCM_NAMESPACE,
                    StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
        

        public Stanza toPacket() 
            Message message = new Message();
            message.addExtension(this);
            return message;
        
    

    private static final class LoggingConnectionListener
            implements ConnectionListener 

        @Override
        public void connected(XMPPConnection xmppConnection) 
            logger.info("Connected.");
        


        @Override
        public void reconnectionSuccessful() 
            logger.info("Reconnecting..");
        

        @Override
        public void reconnectionFailed(Exception e) 
            logger.info("Reconnection failed.. ", e);
        

        @Override
        public void reconnectingIn(int seconds) 
            logger.info("Reconnecting in %d secs", seconds);
        

        @Override
        public void connectionClosedOnError(Exception e) 
            logger.info("Connection closed on error.");
        

        @Override
        public void connectionClosed() 
            logger.info("Connection closed.");
        

        @Override
        public void authenticated(XMPPConnection arg0, boolean arg1) 
            // TODO Auto-generated method stub

        
    

    @PreDestroy
    public void cleanUp() throws Exception 
        logger.info("Bean do cliente XMPP está sendo destruído...");
        if (connection.isConnected()) 
            logger.info("Conexão GCM XMPP está aberta. Desconectando...");
            connection.disconnect();
            
        


tomcat启动时出现错误:

Caused by: java.lang.NoClassDefFoundError: org/jivesoftware/smack/initializer/SmackAndOsgiInitializer
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2476)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:857)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1282)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at org.jivesoftware.smack.SmackInitialization.loadSmackClass(SmackInitialization.java:213)
    at org.jivesoftware.smack.SmackInitialization.parseClassesToLoad(SmackInitialization.java:193)
    at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:163)
    at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:148)
    at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:116)
    at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.java:96)
    at org.jivesoftware.smack.provider.ProviderManager.<clinit>(ProviderManager.java:121)
    at br.com.soma.service.gcm.xmpp.CcsClientImpl.<init>(CcsClientImpl.java:73)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
    ... 60 more
Caused by: java.lang.ClassNotFoundException: org.jivesoftware.smack.initializer.SmackAndOsgiInitializer
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1313)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
    ... 82 more

有人可以帮助我吗?谢谢!

【问题讨论】:

您是否注意到与所有其他 smack-* 依赖项相比,您为 smack-java7 使用了不同的版本?也许这就是原因。我建议为 Smack 版本字符串定义一个变量,并从 maven 依赖项中引用该变量。 现在可以正常使用了。我只是按照您的建议为 smack 设置了一个变量。谢谢!! 【参考方案1】:

现在可以正常使用了。我只是按照您的建议为 smack 设置了一个变量。谢谢!!

<properties>
   <smack.version>4.1.4</smack.version>
</properties>


<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-core</artifactId>
    <version>$smack.version</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-tcp</artifactId>
    <version>$smack.version</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-extensions</artifactId>
    <version>$smack.version</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-java7</artifactId>
    <version>$smack.version</version>
</dependency>

【讨论】:

以上是关于GCM XMPP - 启动 Web 应用程序服务器时出错的主要内容,如果未能解决你的问题,请参考以下文章

通过 GCM 和 xmpp 输入状态/在线状态

是否可以将 GCM 云连接服务器 (XMPP) 与 Heroku 应用程序一起使用?

使用 HTTP 和 XMPP 协议的 GCM/FCM 推送通知

在带有 HTTP 服务器(不是 CCS/XMPP)的 Android GCM 应用中接收消息

一个 XMPP 连接用于多个 GCM/FCM 应用程序

如何通过xmpp gcm将ack从android应用程序发送到服务器