1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-27 00:32:07 +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:
Gaston Dombiak 2006-01-16 17:34:56 +00:00 committed by gato
parent 86ce253883
commit 15defec50f
3 changed files with 221 additions and 46 deletions

View file

@ -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.
* *

View file

@ -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,8 +919,37 @@ public class XMPPConnection {
private void initReaderAndWriter() throws XMPPException { private void initReaderAndWriter() throws XMPPException {
try { try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); if (!usingCompression) {
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "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(
@ -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();
}
}
} }

View file

@ -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);
} }