mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 04:22:05 +01:00
Added stream compression support. SMACK-112
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@3306 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
86ce253883
commit
15defec50f
3 changed files with 221 additions and 46 deletions
|
@ -320,10 +320,17 @@ class PacketReader {
|
||||||
resetParser();
|
resetParser();
|
||||||
}
|
}
|
||||||
else if (parser.getName().equals("failure")) {
|
else if (parser.getName().equals("failure")) {
|
||||||
if ("urn:ietf:params:xml:ns:xmpp-tls".equals(parser.getNamespace(null))) {
|
String namespace = parser.getNamespace(null);
|
||||||
|
if ("urn:ietf:params:xml:ns:xmpp-tls".equals(namespace)) {
|
||||||
// TLS negotiation has failed. The server will close the connection
|
// TLS negotiation has failed. The server will close the connection
|
||||||
throw new Exception("TLS negotiation has failed");
|
throw new Exception("TLS negotiation has failed");
|
||||||
}
|
}
|
||||||
|
else if ("http://jabber.org/protocol/compress".equals(namespace)) {
|
||||||
|
// Stream compression has been denied. This is a recoverable
|
||||||
|
// situation. It is still possible to authenticate and
|
||||||
|
// use the connection but using an uncompressed connection
|
||||||
|
connection.streamCompressionDenied();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// SASL authentication has failed. The server may close the connection
|
// SASL authentication has failed. The server may close the connection
|
||||||
// depending on the number of retries
|
// depending on the number of retries
|
||||||
|
@ -347,6 +354,14 @@ class PacketReader {
|
||||||
// will be to bind the resource
|
// will be to bind the resource
|
||||||
connection.getSASLAuthentication().authenticated();
|
connection.getSASLAuthentication().authenticated();
|
||||||
}
|
}
|
||||||
|
else if (parser.getName().equals("compressed")) {
|
||||||
|
// Server confirmed that it's possible to use stream compression. Start
|
||||||
|
// stream compression
|
||||||
|
connection.startStreamCompression();
|
||||||
|
// Reset the state of the parser since a new stream element is going
|
||||||
|
// to be sent by the server
|
||||||
|
resetParser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (eventType == XmlPullParser.END_TAG) {
|
else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (parser.getName().equals("stream")) {
|
if (parser.getName().equals("stream")) {
|
||||||
|
@ -464,6 +479,10 @@ class PacketReader {
|
||||||
// The server supports sessions
|
// The server supports sessions
|
||||||
connection.getSASLAuthentication().sessionsSupported();
|
connection.getSASLAuthentication().sessionsSupported();
|
||||||
}
|
}
|
||||||
|
else if (parser.getName().equals("compression")) {
|
||||||
|
// The server supports stream compression
|
||||||
|
connection.setAvailableCompressionMethods(parseCompressionMethods(parser));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (eventType == XmlPullParser.END_TAG) {
|
else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (parser.getName().equals("features")) {
|
if (parser.getName().equals("features")) {
|
||||||
|
@ -504,6 +523,28 @@ class PacketReader {
|
||||||
return mechanisms;
|
return mechanisms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection parseCompressionMethods(XmlPullParser parser)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
List methods = new ArrayList();
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
int eventType = parser.next();
|
||||||
|
|
||||||
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
String elementName = parser.getName();
|
||||||
|
if (elementName.equals("method")) {
|
||||||
|
methods.add(parser.nextText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (eventType == XmlPullParser.END_TAG) {
|
||||||
|
if (parser.getName().equals("compression")) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an IQ packet.
|
* Parses an IQ packet.
|
||||||
*
|
*
|
||||||
|
|
|
@ -36,6 +36,7 @@ import javax.net.ssl.SSLSocket;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -129,6 +130,15 @@ public class XMPPConnection {
|
||||||
*/
|
*/
|
||||||
Map chats = Collections.synchronizedMap(new HashMap());
|
Map chats = Collections.synchronizedMap(new HashMap());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of available stream compression methods offered by the server.
|
||||||
|
*/
|
||||||
|
private Collection compressionMethods;
|
||||||
|
/**
|
||||||
|
* Flag that indicates if stream compression is actually in use.
|
||||||
|
*/
|
||||||
|
private boolean usingCompression;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
|
* Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
|
||||||
* performed to try to determine the IP address and port corresponding to the
|
* performed to try to determine the IP address and port corresponding to the
|
||||||
|
@ -909,9 +919,38 @@ public class XMPPConnection {
|
||||||
|
|
||||||
private void initReaderAndWriter() throws XMPPException {
|
private void initReaderAndWriter() throws XMPPException {
|
||||||
try {
|
try {
|
||||||
|
if (!usingCompression) {
|
||||||
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
|
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
|
||||||
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
|
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Class zoClass = Class.forName("com.jcraft.jzlib.ZOutputStream");
|
||||||
|
//ZOutputStream out = new ZOutputStream(socket.getOutputStream(), JZlib.Z_BEST_COMPRESSION);
|
||||||
|
Constructor constructor =
|
||||||
|
zoClass.getConstructor(new Class[]{OutputStream.class, Integer.TYPE});
|
||||||
|
Object out = constructor.newInstance(new Object[] {socket.getOutputStream(), new Integer(9)});
|
||||||
|
//out.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
|
||||||
|
Method method = zoClass.getMethod("setFlushMode", new Class[] {Integer.TYPE});
|
||||||
|
method.invoke(out, new Object[] {new Integer(1)});
|
||||||
|
writer = new BufferedWriter(new OutputStreamWriter((OutputStream) out, "UTF-8"));
|
||||||
|
|
||||||
|
Class ziClass = Class.forName("com.jcraft.jzlib.ZInputStream");
|
||||||
|
//ZInputStream in = new ZInputStream(socket.getInputStream());
|
||||||
|
constructor = ziClass.getConstructor(new Class[]{InputStream.class});
|
||||||
|
Object in = constructor.newInstance(new Object[] {socket.getInputStream()});
|
||||||
|
//in.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
|
||||||
|
method = ziClass.getMethod("setFlushMode", new Class[] {Integer.TYPE});
|
||||||
|
method.invoke(in, new Object[] {new Integer(1)});
|
||||||
|
reader = new BufferedReader(new InputStreamReader((InputStream) in, "UTF-8"));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
|
||||||
|
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new XMPPException(
|
throw new XMPPException(
|
||||||
"XMPPError establishing connection with server.",
|
"XMPPError establishing connection with server.",
|
||||||
|
@ -1061,4 +1100,128 @@ public class XMPPConnection {
|
||||||
// Send a new opening stream to the server
|
// Send a new opening stream to the server
|
||||||
packetWriter.openStream();
|
packetWriter.openStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the available stream compression methods offered by the server.
|
||||||
|
*
|
||||||
|
* @param methods compression methods offered by the server.
|
||||||
|
*/
|
||||||
|
void setAvailableCompressionMethods(Collection methods) {
|
||||||
|
compressionMethods = methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the specified compression method was offered by the server.
|
||||||
|
*
|
||||||
|
* @param method the method to check.
|
||||||
|
* @return true if the specified compression method was offered by the server.
|
||||||
|
*/
|
||||||
|
private boolean hasAvailableCompressionMethod(String method) {
|
||||||
|
return compressionMethods != null && compressionMethods.contains(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the server offered stream compression to the client.
|
||||||
|
*
|
||||||
|
* @return true if the server offered stream compression to the client.
|
||||||
|
*/
|
||||||
|
public boolean getServerSupportsCompression() {
|
||||||
|
return compressionMethods != null && !compressionMethods.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if network traffic is being compressed. When using stream compression network
|
||||||
|
* traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow
|
||||||
|
* speed network connection. However, the server will need to use more CPU time in order to
|
||||||
|
* un/compress network data so under high load the server performance might be affected.<p>
|
||||||
|
*
|
||||||
|
* Note: To use stream compression the smackx.jar file has to be present in the classpath.
|
||||||
|
*
|
||||||
|
* @return true if network traffic is being compressed.
|
||||||
|
*/
|
||||||
|
public boolean isUsingCompression() {
|
||||||
|
return usingCompression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts using stream compression that will compress network traffic. Traffic can be
|
||||||
|
* reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network
|
||||||
|
* connection. However, the server and the client will need to use more CPU time in order to
|
||||||
|
* un/compress network data so under high load the server performance might be affected.<p>
|
||||||
|
*
|
||||||
|
* Stream compression has to have been previously offered by the server. Currently only the
|
||||||
|
* zlib method is supported by the client. Stream compression negotiation has to be done
|
||||||
|
* before authentication took place.<p>
|
||||||
|
*
|
||||||
|
* Note: To use stream compression the smackx.jar file has to be present in the classpath.
|
||||||
|
*
|
||||||
|
* @return true if stream compression negotiation was successful.
|
||||||
|
*/
|
||||||
|
public boolean useCompression() {
|
||||||
|
// If stream compression was offered by the server and we want to use
|
||||||
|
// compression then send compression request to the server
|
||||||
|
if (authenticated) {
|
||||||
|
throw new IllegalStateException("Compression should be negotiated before authentication.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class.forName("com.jcraft.jzlib.ZOutputStream");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalStateException("Cannot use compression. Add smackx.jar to the classpath");
|
||||||
|
}
|
||||||
|
if (hasAvailableCompressionMethod("zlib")) {
|
||||||
|
requestStreamCompression();
|
||||||
|
// Wait until compression is being used or a timeout happened
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
this.wait(SmackConfiguration.getPacketReplyTimeout() * 5);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
return usingCompression;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the server that we want to start using stream compression. When using TLS
|
||||||
|
* then negotiation of stream compression can only happen after TLS was negotiated. If TLS
|
||||||
|
* compression is being used the stream compression should not be used.
|
||||||
|
*/
|
||||||
|
private void requestStreamCompression() {
|
||||||
|
try {
|
||||||
|
writer.write("<compress xmlns='http://jabber.org/protocol/compress'>");
|
||||||
|
writer.write("<method>zlib</method></compress>");
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
packetReader.notifyConnectionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start using stream compression since the server has acknowledged stream compression.
|
||||||
|
*/
|
||||||
|
void startStreamCompression() throws Exception {
|
||||||
|
// Secure the plain connection
|
||||||
|
usingCompression = true;
|
||||||
|
// Initialize the reader and writer with the new secured version
|
||||||
|
initReaderAndWriter();
|
||||||
|
|
||||||
|
// Set the new writer to use
|
||||||
|
packetWriter.setWriter(writer);
|
||||||
|
// Send a new opening stream to the server
|
||||||
|
packetWriter.openStream();
|
||||||
|
// Notify that compression is being used
|
||||||
|
synchronized (this) {
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void streamCompressionDenied() {
|
||||||
|
// Notify that compression has been denied
|
||||||
|
synchronized (this) {
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,53 +1,21 @@
|
||||||
/**
|
/**
|
||||||
* $RCSfile$
|
* $RCSfile$
|
||||||
* $Revision$
|
* $Revision$
|
||||||
* $Date$
|
* $Date: $
|
||||||
*
|
*
|
||||||
* Copyright (C) 2002-2003 Jive Software. All rights reserved.
|
* Copyright 2003-2005 Jive Software.
|
||||||
* ====================================================================
|
|
||||||
* The Jive Software License (based on Apache Software License, Version 1.1)
|
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* modification, are permitted provided that the following conditions
|
* you may not use this file except in compliance with the License.
|
||||||
* are met:
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* 1. Redistributions of source code must retain the above copyright
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
*
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* notice, this list of conditions and the following disclaimer in
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* the documentation and/or other materials provided with the
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* distribution.
|
* See the License for the specific language governing permissions and
|
||||||
*
|
* limitations under the License.
|
||||||
* 3. The end-user documentation included with the redistribution,
|
|
||||||
* if any, must include the following acknowledgment:
|
|
||||||
* "This product includes software developed by
|
|
||||||
* Jive Software (http://www.jivesoftware.com)."
|
|
||||||
* Alternately, this acknowledgment may appear in the software itself,
|
|
||||||
* if and wherever such third-party acknowledgments normally appear.
|
|
||||||
*
|
|
||||||
* 4. The names "Smack" and "Jive Software" must not be used to
|
|
||||||
* endorse or promote products derived from this software without
|
|
||||||
* prior written permission. For written permission, please
|
|
||||||
* contact webmaster@jivesoftware.com.
|
|
||||||
*
|
|
||||||
* 5. Products derived from this software may not be called "Smack",
|
|
||||||
* nor may "Smack" appear in their name, without prior written
|
|
||||||
* permission of Jive Software.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
|
|
||||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
||||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
||||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
* SUCH DAMAGE.
|
|
||||||
* ====================================================================
|
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.test;
|
package org.jivesoftware.smack.test;
|
||||||
|
|
||||||
|
@ -242,6 +210,9 @@ public abstract class SmackTestCase extends TestCase {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Boolean.getBoolean("test.compressionEnabled")) {
|
||||||
|
getConnection(i).useCompression();
|
||||||
|
}
|
||||||
// Login with the new test account
|
// Login with the new test account
|
||||||
getConnection(i).login("user" + i, "user" + i);
|
getConnection(i).login("user" + i, "user" + i);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue