Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
O
Openfire
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Openfire
Commits
ea7fdc80
Commit
ea7fdc80
authored
Jul 30, 2015
by
Dave Cridland
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #263 from tevans/OF-933
OF-933: Initial WebSocket implementation
parents
c2308294
189773d3
Changes
12
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
962 additions
and
1 deletion
+962
-1
SessionPacketRouter.java
src/java/org/jivesoftware/openfire/SessionPacketRouter.java
+1
-1
changelog.html
src/plugins/websocket/changelog.html
+55
-0
commons-pool2-2.3.jar
src/plugins/websocket/lib/commons-pool2-2.3.jar
+0
-0
logo_large.gif
src/plugins/websocket/logo_large.gif
+0
-0
logo_small.gif
src/plugins/websocket/logo_small.gif
+0
-0
plugin.xml
src/plugins/websocket/plugin.xml
+15
-0
readme.html
src/plugins/websocket/readme.html
+75
-0
StreamManagementPacketRouter.java
...ware/openfire/websocket/StreamManagementPacketRouter.java
+90
-0
WebSocketConnection.java
.../jivesoftware/openfire/websocket/WebSocketConnection.java
+113
-0
WebSocketPlugin.java
.../org/jivesoftware/openfire/websocket/WebSocketPlugin.java
+142
-0
XMPPPPacketReaderFactory.java
...software/openfire/websocket/XMPPPPacketReaderFactory.java
+70
-0
XmppWebSocket.java
...va/org/jivesoftware/openfire/websocket/XmppWebSocket.java
+401
-0
No files found.
src/java/org/jivesoftware/openfire/SessionPacketRouter.java
View file @
ea7fdc80
...
@@ -35,7 +35,7 @@ import java.io.UnsupportedEncodingException;
...
@@ -35,7 +35,7 @@ import java.io.UnsupportedEncodingException;
*/
*/
public
class
SessionPacketRouter
implements
PacketRouter
{
public
class
SessionPacketRouter
implements
PacketRouter
{
pr
ivate
LocalClientSession
session
;
pr
otected
LocalClientSession
session
;
private
PacketRouter
router
;
private
PacketRouter
router
;
private
boolean
skipJIDValidation
=
false
;
private
boolean
skipJIDValidation
=
false
;
...
...
src/plugins/websocket/changelog.html
0 → 100644
View file @
ea7fdc80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>
Openfire WebSocket Plugin Changelog
</title>
<style
type=
"text/css"
>
BODY
{
font-size
:
100%
;
}
BODY
,
TD
,
TH
{
font-family
:
tahoma
,
verdana
,
arial
,
helvetica
,
sans-serif
;
font-size
:
0.8em
;
}
H2
{
font-size
:
10pt
;
font-weight
:
bold
;
padding-left
:
1em
;
}
A
:hover
{
text-decoration
:
none
;
}
H1
{
font-family
:
tahoma
,
arial
,
helvetica
,
sans-serif
;
font-size
:
1.4em
;
font-weight
:
bold
;
border-bottom
:
1px
#ccc
solid
;
padding-bottom
:
2px
;
}
TT
{
font-family
:
courier
new
;
font-weight
:
bold
;
color
:
#060
;
}
PRE
{
font-family
:
courier
new
;
font-size
:
100%
;
}
</style>
</head>
<body>
<h1>
Openfire WebSocket Plugin Changelog
</h1>
<p><b>
1.0
</b>
-- July 28, 2015
</p>
<ul>
<li>
Initial release.
</li>
</ul>
</body>
</html>
\ No newline at end of file
src/plugins/websocket/lib/commons-pool2-2.3.jar
0 → 100644
View file @
ea7fdc80
File added
src/plugins/websocket/logo_large.gif
0 → 100644
View file @
ea7fdc80
1.01 KB
src/plugins/websocket/logo_small.gif
0 → 100644
View file @
ea7fdc80
917 Bytes
src/plugins/websocket/plugin.xml
0 → 100644
View file @
ea7fdc80
<?xml version="1.0" encoding="UTF-8"?>
<!--
Plugin configuration for the WebSocket plugin.
-->
<plugin>
<class>
org.jivesoftware.openfire.websocket.WebSocketPlugin
</class>
<name>
Openfire WebSocket
</name>
<description>
Provides WebSocket support for Openfire.
</description>
<author>
Tom Evans
</author>
<version>
1.0.0
</version>
<date>
07/28/2015
</date>
<url>
https://tools.ietf.org/html/rfc7395
</url>
<minServerVersion>
3.10.0
</minServerVersion>
</plugin>
\ No newline at end of file
src/plugins/websocket/readme.html
0 → 100644
View file @
ea7fdc80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>
Openfire WebSocket Plugin Readme
</title>
<style
type=
"text/css"
>
BODY
{
font-size
:
100%
;
}
BODY
,
TD
,
TH
{
font-family
:
tahoma
,
verdana
,
arial
,
helvetica
,
sans-serif
;
font-size
:
0.8em
;
}
H2
{
font-size
:
10pt
;
font-weight
:
bold
;
}
A
:hover
{
text-decoration
:
none
;
}
H1
{
font-family
:
tahoma
,
arial
,
helvetica
,
sans-serif
;
font-size
:
1.4em
;
font-weight
:
bold
;
border-bottom
:
1px
#ccc
solid
;
padding-bottom
:
2px
;
}
TT
{
font-family
:
courier
new
;
font-weight
:
bold
;
color
:
#060
;
}
PRE
{
font-family
:
courier
new
;
font-size
:
100%
;
}
</style>
</head>
<body>
<h1>
Openfire WebSocket Plugin Readme
</h1>
<h2>
Overview
</h2>
<p>
This plugin extends Openfire to support WebSocket. The implementation follows the XMPP WebSocket subprotocol
(
<a
href=
"https://tools.ietf.org/html/rfc7395"
>
RFC 7395
</a>
) specification, which is a standard extension of the
WebSocket protocol specification (
<a
href=
"https://tools.ietf.org/html/rfc6455"
>
RFC 6455
</a>
).
</p>
<p>
Note that the BOSH (http-bind) capabilities of Openfire must be enabled and correctly configured as a
prerequisite before installing this plugin. The WebSocket servlet is installed within the same context
as the BOSH component, and will reuse the same HTTP/S port(s) when establishing the WebSocket connection.
</p>
<h2>
Installation
</h2>
<p>
Copy websocket.jar into the plugins directory of your Openfire installation. The
plugin will then be automatically deployed. To upgrade to a new version, copy the new
websocket.jar file over the existing file.
</p>
<p>
Upon installation, the WebSocket URI path will be /ws/ on the same server/port as the BOSH
connector. To establish a secure WebSocket, modify the following URL as appropriate:
</p>
<pre>
wss://your.openfire.host:7443/ws/
</pre>
</body>
</html>
src/plugins/websocket/src/java/org/jivesoftware/openfire/websocket/StreamManagementPacketRouter.java
0 → 100644
View file @
ea7fdc80
/**
* Copyright (C) 2015 Tom Evans. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org
.
jivesoftware
.
openfire
.
websocket
;
import
java.io.UnsupportedEncodingException
;
import
org.dom4j.Element
;
import
org.jivesoftware.openfire.SessionPacketRouter
;
import
org.jivesoftware.openfire.multiplex.UnknownStanzaException
;
import
org.jivesoftware.openfire.session.LocalClientSession
;
import
org.jivesoftware.openfire.streammanagement.StreamManager
;
import
org.jivesoftware.util.JiveGlobals
;
/**
* This class extends Openfire's session packet router with the ACK capabilities
* specified by XEP-0198: Stream Management.
*
* NOTE: This class does NOT support the XEP-0198 stream resumption capabilities.
*
* XEP-0198 allows either party (client or server) to send unsolicited ack/answer
* stanzas on a periodic basis. This implementation approximates BOSH ack behavior
* by sending unsolicited <a /> stanzas from the server to the client after a
* configurable number of stanzas have been received from the client.
*
* Setting the system property to "1" would indicate that each client packet should
* be ack'd by the server when stream management is enabled for a particular stream.
* To disable unsolicited server acks, use the default value for system property
* "stream.management.unsolicitedAckFrequency" ("0"). This setting does not affect
* server responses to explicit ack requests from the client.
*/
public
class
StreamManagementPacketRouter
extends
SessionPacketRouter
{
public
static
final
String
SM_UNSOLICITED_ACK_FREQUENCY
=
"stream.management.unsolicitedAckFrequency"
;
static
{
JiveGlobals
.
migrateProperty
(
SM_UNSOLICITED_ACK_FREQUENCY
);
}
private
int
unsolicitedAckFrequency
=
JiveGlobals
.
getIntProperty
(
SM_UNSOLICITED_ACK_FREQUENCY
,
0
);
public
StreamManagementPacketRouter
(
LocalClientSession
session
)
{
super
(
session
);
}
@Override
public
void
route
(
Element
wrappedElement
)
throws
UnsupportedEncodingException
,
UnknownStanzaException
{
String
tag
=
wrappedElement
.
getName
();
if
(
StreamManager
.
NAMESPACE_V3
.
equals
(
wrappedElement
.
getNamespace
().
getStringValue
()))
{
switch
(
tag
)
{
case
"enable"
:
session
.
enableStreamMangement
(
wrappedElement
);
break
;
case
"r"
:
session
.
getStreamManager
().
sendServerAcknowledgement
();
break
;
case
"a"
:
session
.
getStreamManager
().
processClientAcknowledgement
(
wrappedElement
);
break
;
default
:
session
.
getStreamManager
().
sendUnexpectedError
();
}
}
else
{
super
.
route
(
wrappedElement
);
if
(
isUnsolicitedAckExpected
())
{
session
.
getStreamManager
().
sendServerAcknowledgement
();
}
}
}
private
boolean
isUnsolicitedAckExpected
()
{
if
(!
session
.
getStreamManager
().
isEnabled
())
{
return
false
;
}
return
unsolicitedAckFrequency
>
0
&&
session
.
getNumClientPackets
()
%
unsolicitedAckFrequency
==
0
;
}
}
src/plugins/websocket/src/java/org/jivesoftware/openfire/websocket/WebSocketConnection.java
0 → 100644
View file @
ea7fdc80
/**
* Copyright (C) 2015 Tom Evans. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org
.
jivesoftware
.
openfire
.
websocket
;
import
java.net.InetSocketAddress
;
import
org.dom4j.Namespace
;
import
org.jivesoftware.openfire.PacketDeliverer
;
import
org.jivesoftware.openfire.auth.UnauthorizedException
;
import
org.jivesoftware.openfire.net.VirtualConnection
;
import
org.jivesoftware.openfire.nio.OfflinePacketDeliverer
;
import
org.xmpp.packet.Packet
;
import
org.xmpp.packet.StreamError
;
/**
* Following the conventions of the BOSH implementation, this class extends {@link VirtualConnection}
* and delegates the expected XMPP connection behaviors to the corresponding {@link XmppWebSocket}.
*/
public
class
WebSocketConnection
extends
VirtualConnection
{
private
static
final
String
CLIENT_NAMESPACE
=
"jabber:client"
;
private
InetSocketAddress
remotePeer
;
private
XmppWebSocket
socket
;
private
PacketDeliverer
backupDeliverer
;
public
WebSocketConnection
(
XmppWebSocket
socket
,
InetSocketAddress
remotePeer
)
{
this
.
socket
=
socket
;
this
.
remotePeer
=
remotePeer
;
}
@Override
public
void
closeVirtualConnection
()
{
socket
.
closeSession
();
}
@Override
public
byte
[]
getAddress
()
{
return
remotePeer
.
getAddress
().
getAddress
();
}
@Override
public
String
getHostAddress
()
{
return
remotePeer
.
getAddress
().
getHostAddress
();
}
@Override
public
String
getHostName
()
{
return
remotePeer
.
getHostName
();
}
@Override
public
void
systemShutdown
()
{
deliverRawText
(
new
StreamError
(
StreamError
.
Condition
.
system_shutdown
).
toXML
());
close
();
}
@Override
public
void
deliver
(
Packet
packet
)
throws
UnauthorizedException
{
if
(
Namespace
.
NO_NAMESPACE
.
equals
(
packet
.
getElement
().
getNamespace
()))
{
packet
.
getElement
().
add
(
Namespace
.
get
(
CLIENT_NAMESPACE
));
}
if
(
validate
())
{
deliverRawText
(
packet
.
toXML
());
}
else
{
// use fallback delivery mechanism (offline)
getPacketDeliverer
().
deliver
(
packet
);
}
}
@Override
public
void
deliverRawText
(
String
text
)
{
socket
.
deliver
(
text
);
}
@Override
public
boolean
validate
()
{
return
socket
.
isWebSocketOpen
();
}
@Override
public
boolean
isSecure
()
{
return
socket
.
isWebSocketSecure
();
}
@Override
public
PacketDeliverer
getPacketDeliverer
()
{
if
(
backupDeliverer
==
null
)
{
backupDeliverer
=
new
OfflinePacketDeliverer
();
}
return
backupDeliverer
;
}
@Override
public
boolean
isCompressed
()
{
return
XmppWebSocket
.
isCompressionEnabled
();
}
}
src/plugins/websocket/src/java/org/jivesoftware/openfire/websocket/WebSocketPlugin.java
0 → 100644
View file @
ea7fdc80
/**
* Copyright (C) 2015 Tom Evans. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org
.
jivesoftware
.
openfire
.
websocket
;
import
java.io.File
;
import
java.text.MessageFormat
;
import
org.eclipse.jetty.server.handler.ContextHandlerCollection
;
import
org.eclipse.jetty.servlet.ServletContextHandler
;
import
org.eclipse.jetty.servlet.ServletHolder
;
import
org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension
;
import
org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest
;
import
org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse
;
import
org.eclipse.jetty.websocket.servlet.WebSocketCreator
;
import
org.eclipse.jetty.websocket.servlet.WebSocketServlet
;
import
org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
;
import
org.jivesoftware.openfire.SessionManager
;
import
org.jivesoftware.openfire.XMPPServer
;
import
org.jivesoftware.openfire.container.Plugin
;
import
org.jivesoftware.openfire.container.PluginClassLoader
;
import
org.jivesoftware.openfire.container.PluginManager
;
import
org.jivesoftware.openfire.http.HttpBindManager
;
import
org.jivesoftware.openfire.session.ClientSession
;
import
org.jivesoftware.openfire.session.LocalSession
;
import
org.jivesoftware.util.JiveGlobals
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
/**
* This plugin enables XMPP over WebSocket (RFC 7395) for Openfire.
*
* The Jetty WebSocketServlet serves as a base class and enables easy integration into the
* BOSH (http-bind) web context. Each WebSocket request received at the "/ws/" URI will be
* forwarded to this plugin/servlet, which will in turn create a new {@link XmppWebSocket}
* for each new connection.
*/
public
class
WebSocketPlugin
extends
WebSocketServlet
implements
Plugin
{
private
static
final
long
serialVersionUID
=
7281841492829464603L
;
private
static
final
Logger
Log
=
LoggerFactory
.
getLogger
(
WebSocketPlugin
.
class
);
private
ServletContextHandler
contextHandler
;
protected
PluginClassLoader
pluginClassLoader
=
null
;
@Override
public
void
initializePlugin
(
final
PluginManager
manager
,
final
File
pluginDirectory
)
{
if
(
Boolean
.
valueOf
(
JiveGlobals
.
getBooleanProperty
(
HttpBindManager
.
HTTP_BIND_ENABLED
,
true
)))
{
Log
.
info
(
String
.
format
(
"Initializing websocket plugin"
));
try
{
ContextHandlerCollection
contexts
=
HttpBindManager
.
getInstance
().
getContexts
();
contextHandler
=
new
ServletContextHandler
(
contexts
,
"/ws"
,
ServletContextHandler
.
SESSIONS
);
contextHandler
.
addServlet
(
new
ServletHolder
(
this
),
"/*"
);
}
catch
(
Exception
e
)
{
Log
.
error
(
"Failed to start websocket plugin"
,
e
);
}
}
else
{
Log
.
warn
(
"Failed to start websocket plugin; http-bind is disabled"
);
}
}
@Override
public
void
destroyPlugin
()
{
// terminate any active websocket sessions
SessionManager
sm
=
XMPPServer
.
getInstance
().
getSessionManager
();
for
(
ClientSession
session
:
sm
.
getSessions
())
{
if
(
session
instanceof
LocalSession
)
{
Object
ws
=
((
LocalSession
)
session
).
getSessionData
(
"ws"
);
if
(
ws
!=
null
&&
(
Boolean
)
ws
)
{
session
.
close
();
}
}
}
ContextHandlerCollection
contexts
=
HttpBindManager
.
getInstance
().
getContexts
();
contexts
.
removeHandler
(
contextHandler
);
contextHandler
=
null
;
pluginClassLoader
=
null
;
}
@Override
public
void
configure
(
WebSocketServletFactory
factory
)
{
if
(
XmppWebSocket
.
isCompressionEnabled
())
{
factory
.
getExtensionFactory
().
register
(
"permessage-deflate"
,
PerMessageDeflateExtension
.
class
);
}
factory
.
setCreator
(
new
WebSocketCreator
()
{
@Override
public
Object
createWebSocket
(
ServletUpgradeRequest
req
,
ServletUpgradeResponse
resp
)
{
ClassLoader
ccl
=
Thread
.
currentThread
().
getContextClassLoader
();
try
{
ClassLoader
pcl
=
getPluginClassLoader
();
Thread
.
currentThread
().
setContextClassLoader
(
pcl
==
null
?
ccl
:
pcl
);
for
(
String
subprotocol
:
req
.
getSubProtocols
())
{
if
(
"xmpp"
.
equals
(
subprotocol
))
{
resp
.
setAcceptedSubProtocol
(
subprotocol
);
return
new
XmppWebSocket
();
}
}
}
catch
(
Exception
e
)
{
Log
.
warn
(
MessageFormat
.
format
(
"Unable to load websocket factory: {0} ({1})"
,
e
.
getClass
().
getName
(),
e
.
getMessage
()));
}
finally
{
Thread
.
currentThread
().
setContextClassLoader
(
ccl
);
}
Log
.
warn
(
"Failed to create websocket: "
+
req
);
return
null
;
}
});
}
protected
synchronized
PluginClassLoader
getPluginClassLoader
()
{
PluginManager
pm
=
XMPPServer
.
getInstance
().
getPluginManager
();
if
(
pluginClassLoader
==
null
)
{
pluginClassLoader
=
pm
.
getPluginClassloader
(
this
);
}
// report error if plugin is unavailable
if
(
pluginClassLoader
==
null
)
{
Log
.
error
(
"Unable to find class loader for websocket plugin"
);
}
return
pluginClassLoader
;
}
}
src/plugins/websocket/src/java/org/jivesoftware/openfire/websocket/XMPPPPacketReaderFactory.java
0 → 100644
View file @
ea7fdc80
/**
* Copyright (C) 2015 Tom Evans. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org
.
jivesoftware
.
openfire
.
websocket
;
import
org.apache.commons.pool2.BasePooledObjectFactory
;
import
org.apache.commons.pool2.PooledObject
;
import
org.apache.commons.pool2.impl.DefaultPooledObject
;
import
org.dom4j.io.XMPPPacketReader
;
import
org.jivesoftware.openfire.net.MXParser
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.xmlpull.v1.XmlPullParserException
;
import
org.xmlpull.v1.XmlPullParserFactory
;
public
class
XMPPPPacketReaderFactory
extends
BasePooledObjectFactory
<
XMPPPacketReader
>
{
private
static
Logger
Log
=
LoggerFactory
.
getLogger
(
XMPPPPacketReaderFactory
.
class
);
private
static
XmlPullParserFactory
xppFactory
=
null
;
static
{
try
{
xppFactory
=
XmlPullParserFactory
.
newInstance
(
MXParser
.
class
.
getName
(),
null
);
xppFactory
.
setNamespaceAware
(
true
);
}
catch
(
XmlPullParserException
e
)
{
Log
.
error
(
"Error creating a parser factory"
,
e
);
}
}
//-- BasePooledObjectFactory implementation
@Override
public
XMPPPacketReader
create
()
throws
Exception
{
XMPPPacketReader
parser
=
new
XMPPPacketReader
();
parser
.
setXPPFactory
(
xppFactory
);
return
parser
;
}
@Override
public
PooledObject
<
XMPPPacketReader
>
wrap
(
XMPPPacketReader
reader
)
{
return
new
DefaultPooledObject
<
XMPPPacketReader
>(
reader
);
}
@Override
public
boolean
validateObject
(
PooledObject
<
XMPPPacketReader
>
po
)
{
// reset the input for the pooled parser
try
{
po
.
getObject
().
getXPPParser
().
resetInput
();
return
true
;
}
catch
(
XmlPullParserException
xppe
)
{
Log
.
error
(
"Failed to reset pooled parser; evicting from pool"
,
xppe
);
return
false
;
}
}
}
src/plugins/websocket/src/java/org/jivesoftware/openfire/websocket/XmppWebSocket.java
0 → 100644
View file @
ea7fdc80
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment