001: /* *****************************************************************************
002: * HTTPConnection.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2006 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.connection;
011:
012: import java.io.IOException;
013: import java.io.OutputStream;
014: import java.io.StringReader;
015: import java.util.Collections;
016: import java.util.Date;
017: import java.util.Enumeration;
018: import java.util.Iterator;
019: import java.util.List;
020: import java.util.Properties;
021: import java.util.Random;
022: import java.util.Vector;
023: import javax.servlet.http.HttpSessionBindingListener;
024: import javax.servlet.http.HttpSessionBindingEvent;
025: import javax.servlet.http.HttpServletRequest;
026: import javax.servlet.http.HttpServletResponse;
027: import javax.servlet.ServletOutputStream;
028: import org.apache.log4j.Logger;
029: import org.jdom.Document;
030: import org.jdom.Element;
031: import org.jdom.input.SAXBuilder;
032: import org.jdom.JDOMException;
033:
034: /** Persistent connection for SWF files. */
035: public class HTTPConnection {
036: private static final String CONNECTED = "__LPSCONNECTED";
037: private static final String RECONNECTED = "__LPSRECONNECTED";
038:
039: /** Queue to store events */
040: private List mQueue;
041:
042: /** Username for connection -- this may not be unique */
043: private String mUsername;
044:
045: /** Session id */
046: private String mSID;
047:
048: /** Connection id -- changes with each reconnection */
049: private String mCID;
050:
051: /** Servlet response object */
052: private HttpServletResponse mRes;
053:
054: /** Output stream */
055: private OutputStream mOut;
056:
057: /** Flag to immediately flush message queue */
058: private boolean mDoFlushQueue;
059:
060: /** Flag to pad headers with bytes. Used for IE, which doesn't display
061: * anything until byte 2000. */
062: private boolean mDoPad;
063:
064: /** Heartbeat interval */
065: private long mHeartbeatInterval;
066:
067: /** Heartbeat count */
068: private int mHeartbeatCount;
069:
070: /** Request count */
071: private int mRequestCount;
072:
073: /** Sent count */
074: private int mSentCount;
075:
076: /** Flush count */
077: private int mFlushCount;
078:
079: /** Total number of bytes sent out during application session */
080: private int mTotalNumOfBytes;
081:
082: /** Number of bytes sent out */
083: private int mNumOfBytes;
084:
085: /** String to send out depending on whether it's a connection or
086: * reconnection */
087: private String mConnectedString;
088:
089: private boolean mEmitConnectHeaders = false;
090:
091: /** Version of SWF bytes for connection messages. */
092: private int mSWFVersion;
093:
094: //------------------------------------------------------------
095: // Statics
096: //------------------------------------------------------------
097: private static Logger mLogger = Logger
098: .getLogger(HTTPConnection.class);
099:
100: /** Used to send IE a pad of 2000 spaces. IE doesn't display information
101: * until it receives 2000 bytes or the connection is closed by the web
102: * server. */
103: private static String mPad;
104:
105: /** Static initializer synchronization lock. */
106: private static Object mStaticInitLock = new Object();
107:
108: /** Check for static initialize block. */
109: private static boolean mDoStaticInit = true;
110:
111: /** Check if the class was inited (see doInit()). */
112: private static boolean mDoInit = true;
113:
114: /** Compiled heartbeat SWF bytes. */
115: private static byte[] mSWFHeartbeat;
116:
117: /** Compiled reconnect command SWF bytes. */
118: private static byte[] mSWFDoReconnect;
119:
120: /** Maximum length of a message. */
121: private static int mMaxMessageLen = 2000;
122:
123: /** Content length of connection SWF. */
124: private static int mConnectionLength = 65536;
125:
126: /** Check if disconnect was requested. */
127: private boolean mDoDisconnect = false;
128:
129: /** Flag to check if this connection will be replaced by another
130: * connection. */
131: private boolean mIsReconnect = false;
132:
133: /** Interval to wait after client reconnection command is sent. */
134: private static int mReconnectionWaitInterval = 60000;
135:
136: /** Count to generate UIDs. */
137: private static long mUIDCount = 0;
138:
139: //------------------------------------------------------------
140: // Not clear whether a static initializer needs to be
141: // synchronized, but not taking any chances.
142: //------------------------------------------------------------
143: static {
144: if (mDoStaticInit) {
145: synchronized (mStaticInitLock) {
146: if (mDoStaticInit) {
147: StringBuffer buf = new StringBuffer(2000);
148: for (int i = 0; i < 2000; i++)
149: buf.append(' ');
150: mPad = buf.toString();
151:
152: mSWFHeartbeat = new SwfByte().actionSetElement(
153: getElement(getConnectionInfoXML("__LPSHB",
154: null))).setShowFrame().getBuf();
155:
156: mSWFDoReconnect = new SwfByte().actionSetElement(
157: getElement(getConnectionInfoXML(
158: "__LPSDORECONNECT", null)))
159: .setShowFrame().getBuf();
160:
161: mDoStaticInit = false;
162: }
163: }
164: }
165: }
166:
167: //------------------------------------------------------------
168: // Initialize static properties. This should only get called
169: // once.
170: //------------------------------------------------------------
171: synchronized static public void init(Properties properties) {
172: if (!mDoInit)
173: return;
174:
175: mLogger.debug(
176: /* (non-Javadoc)
177: * @i18n.test
178: * @org-mes="init(properties)"
179: */
180: org.openlaszlo.i18n.LaszloMessages.getMessage(
181: HTTPConnection.class.getName(), "051018-190"));
182:
183: Enumeration propNames = properties.propertyNames();
184: while (propNames.hasMoreElements()) {
185:
186: String key = (String) propNames.nextElement();
187: String val = properties.getProperty(key);
188:
189: try {
190: if (val != null) {
191: if (key.intern() == "maxMessageLen")
192: mMaxMessageLen = Integer.parseInt(val);
193: else if (key.intern() == "connectionLength")
194: mConnectionLength = Integer.parseInt(val);
195: else if (key.intern() == "reconnectionWaitInterval")
196: mReconnectionWaitInterval = Integer
197: .parseInt(val);
198: }
199: } catch (NumberFormatException e) {
200: mLogger.debug(e.getMessage());
201: }
202: }
203:
204: // These are the minimum values.
205: if (mMaxMessageLen < 2000)
206: mMaxMessageLen = 2000;
207: if (mConnectionLength < (5 * mMaxMessageLen))
208: mConnectionLength = 5 * mMaxMessageLen;
209: if (mReconnectionWaitInterval < 10000) // wait a minimum of 10 seconds
210: mReconnectionWaitInterval = 60000; // default 60 seconds
211:
212: mLogger.debug(
213: /* (non-Javadoc)
214: * @i18n.test
215: * @org-mes="maxMessageLen:" + p[0]
216: */
217: org.openlaszlo.i18n.LaszloMessages.getMessage(
218: HTTPConnection.class.getName(), "051018-227",
219: new Object[] { new Integer(mMaxMessageLen) }));
220: mLogger.debug(
221: /* (non-Javadoc)
222: * @i18n.test
223: * @org-mes="connectionLength:" + p[0]
224: */
225: org.openlaszlo.i18n.LaszloMessages.getMessage(
226: HTTPConnection.class.getName(), "051018-235",
227: new Object[] { new Integer(mConnectionLength) }));
228: mDoInit = false;
229: }
230:
231: //------------------------------------------------------------
232: // Constructor
233: //------------------------------------------------------------
234:
235: /**
236: * Generates a unique identifier.
237: * @return hexadecimal unique string identifier.
238: */
239: private synchronized static String generateUID() {
240: return Long.toHexString(System.currentTimeMillis())
241: + Long.toHexString(mUIDCount++);
242: }
243:
244: /**
245: * Constructor.
246: *
247: * @param res http servlet response to use as persistent connection
248: * @param username username associated with connection
249: */
250: public HTTPConnection(HttpServletResponse res, String username,
251: int swfversion) throws IOException {
252: mRes = res;
253: mOut = res.getOutputStream();
254: mUsername = username;
255: mSID = generateUID();
256: mCID = generateUID();
257: mHeartbeatCount = 0;
258: mRequestCount = 0;
259: mSentCount = 0;
260: mFlushCount = 0;
261: mTotalNumOfBytes = 0;
262: mNumOfBytes = 0;
263: mDoDisconnect = false;
264: mSWFVersion = swfversion;
265:
266: // Don't have to make queue thread-safe since synchronization is handled
267: // below.
268: mQueue = new Vector();
269:
270: mConnectedString = CONNECTED;
271:
272: // This is false when attempting a reconnect with client so messages can
273: // be saved.
274: mDoFlushQueue = true;
275:
276: setDoPad(false);
277: setHeartbeatInterval(0);
278: }
279:
280: /**
281: * Will update the request count, heartbeat count, total number of bytes
282: * sent.
283: *
284: * @param res http servlet response to user as persistent connect
285: * @param hc HTTPConnection to copy parameters from
286: */
287: public HTTPConnection(HttpServletResponse res, HTTPConnection hc)
288: throws IOException {
289: mRes = res;
290: mOut = res.getOutputStream();
291: mUsername = hc.mUsername;
292: mSID = hc.mSID;
293: mCID = generateUID();
294: mHeartbeatCount += hc.mHeartbeatCount;
295: mRequestCount += hc.mRequestCount;
296: mSentCount += hc.mSentCount;
297: mFlushCount += hc.mFlushCount;
298: mTotalNumOfBytes += hc.mTotalNumOfBytes;
299: mEmitConnectHeaders = hc.mEmitConnectHeaders;
300: mDoDisconnect = false;
301: mSWFVersion = hc.mSWFVersion;
302:
303: mConnectedString = RECONNECTED;
304:
305: // Easy way to save the queue (or is it too easy? I think this is ok...)
306: mQueue = hc.mQueue;
307:
308: mNumOfBytes = 0;
309:
310: // This is false when attempting a reconnect with client so messages can
311: // be saved.
312: mDoFlushQueue = true;
313:
314: setDoPad(hc.mDoPad);
315: setHeartbeatInterval(hc.mHeartbeatInterval);
316: }
317:
318: //------------------------------------------------------------
319: // Methods
320: //------------------------------------------------------------
321:
322: /**
323: * Queue up event to send.
324: *
325: * @param msg event message
326: * @param doFlushQueue if true, flushes the message event queue
327: * @throws IOException if connection does not exist
328: */
329: synchronized public void send(String msg) throws IOException {
330: mLogger.debug(
331: /* (non-Javadoc)
332: * @i18n.test
333: * @org-mes="send(msg=" + p[0] + ")"
334: */
335: org.openlaszlo.i18n.LaszloMessages.getMessage(
336: HTTPConnection.class.getName(), "051018-353",
337: new Object[] { msg }));
338:
339: if (mDoDisconnect) {
340: mLogger.debug(
341: /* (non-Javadoc)
342: * @i18n.test
343: * @org-mes="connection is already disconnected, not sending"
344: */
345: org.openlaszlo.i18n.LaszloMessages.getMessage(
346: HTTPConnection.class.getName(), "051018-363"));
347: return;
348: }
349:
350: Element element = getElement(msg);
351: if (element == null) {
352: mLogger.debug(
353: /* (non-Javadoc)
354: * @i18n.test
355: * @org-mes="Bad XML for message: " + p[0]
356: */
357: org.openlaszlo.i18n.LaszloMessages.getMessage(
358: HTTPConnection.class.getName(), "051018-376",
359: new Object[] { msg }));
360: return;
361: }
362:
363: byte[] swfBuf = new SwfByte().actionSetElement(element)
364: .setShowFrame().getBuf();
365:
366: if (swfBuf.length > mMaxMessageLen) {
367: String info =
368: /* (non-Javadoc)
369: * @i18n.test
370: * @org-mes="compiled message bytes are too large -- greater than" + p[0] + ")"
371: */
372: org.openlaszlo.i18n.LaszloMessages.getMessage(
373: HTTPConnection.class.getName(), "051018-393",
374: new Object[] { new Integer(mMaxMessageLen) });
375: mLogger.debug(info);
376: throw new IOException(info);
377: }
378:
379: synchronized (mQueue) {
380: mRequestCount++;
381: mQueue.add(swfBuf);
382: }
383:
384: if (mDoFlushQueue) {
385: notify();
386: }
387:
388: }
389:
390: /**
391: * Close connection.
392: */
393: public void disconnect() {
394: disconnect(false);
395: }
396:
397: /**
398: * Close connection.
399: *
400: * @param isReconnect if this connection will be replaced by another
401: * connection.
402: */
403: synchronized public void disconnect(boolean isReconnect) {
404: mLogger.debug("disconnect()");
405: mIsReconnect = isReconnect;
406: mDoDisconnect = true;
407: notify();
408: }
409:
410: /**
411: * Check if connection is to be replaced by another connection.
412: */
413: synchronized public boolean toBeReconnected() {
414: return mIsReconnect;
415: }
416:
417: /**
418: * This call will block and wait for events to handle. Keeps HTTP
419: * connection alive for an asynchronous event.
420: *
421: * @param res servlet response object
422: * @return true if connection was lost to reconnection, else false
423: */
424: public void connect() throws IOException {
425: mLogger.debug("connect()");
426:
427: try {
428: mNumOfBytes += sendHeader();
429: mNumOfBytes += flushMessageQueue();
430:
431: mTotalNumOfBytes += mNumOfBytes;
432:
433: int numOfBytesSent = 0;
434: long waitInterval = mHeartbeatInterval;
435: long reconnectRequestTime = 0; // time we made reconnect request
436:
437: while (true) {
438:
439: mLogger.debug(
440: /* (non-Javadoc)
441: * @i18n.test
442: * @org-mes="[" + p[0] + "," + p[1] + "] " + " request: " + p[2] + " sent: " + p[3] + ", flush: " + p[4] + ", heartbeats: " + p[5] + ", bytes: " + p[6] + " (" + p[7] + "/" + p[8] + ")"
443: */
444: org.openlaszlo.i18n.LaszloMessages.getMessage(
445: HTTPConnection.class.getName(), "051018-471",
446: new Object[] { mUsername, mSID,
447: new Integer(mRequestCount),
448: new Integer(mSentCount),
449: new Integer(mFlushCount),
450: new Integer(mHeartbeatCount),
451: new Integer(mTotalNumOfBytes),
452: new Integer(mNumOfBytes),
453: new Integer(mConnectionLength) }));
454: synchronized (this ) {
455: wait(waitInterval);
456: }
457:
458: if (mDoDisconnect) {
459: mLogger.debug(
460: /* (non-Javadoc)
461: * @i18n.test
462: * @org-mes="disconnecting..."
463: */
464: org.openlaszlo.i18n.LaszloMessages.getMessage(
465: HTTPConnection.class.getName(),
466: "051018-484"));
467: if (reconnectRequestTime != 0) {
468: mLogger.debug(
469: /* (non-Javadoc)
470: * @i18n.test
471: * @org-mes="reconnect time: " + p[0] + "ms"
472: */
473: org.openlaszlo.i18n.LaszloMessages.getMessage(
474: HTTPConnection.class.getName(),
475: "051018-493",
476: new Object[] { new Long(new Date()
477: .getTime()
478: - reconnectRequestTime) }));
479: }
480: return;
481: }
482:
483: // Check to see if we're nearing the limit of bytes sent out
484: if (doReconnect()) {
485:
486: mLogger.debug(
487: /* (non-Javadoc)
488: * @i18n.test
489: * @org-mes="sending reconnect request " + p[0] + "..."
490: */
491: org.openlaszlo.i18n.LaszloMessages.getMessage(
492: HTTPConnection.class.getName(),
493: "051018-508", new Object[] { mUsername }));
494:
495: if (reconnectRequestTime == 0) {
496: // ...give client a few seconds to reconnect...
497: waitInterval = mReconnectionWaitInterval;
498:
499: synchronized (this ) {
500: mDoFlushQueue = false;
501: }
502:
503: mLogger.debug(
504: /* (non-Javadoc)
505: * @i18n.test
506: * @org-mes="...for " + p[0] + " seconds"
507: */
508: org.openlaszlo.i18n.LaszloMessages.getMessage(
509: HTTPConnection.class.getName(),
510: "051018-525", new Object[] { new Long(
511: waitInterval / 1000) }));
512:
513: // ...send reconnect request to client
514: mOut.write(mSWFDoReconnect);
515: mOut.flush();
516:
517: mNumOfBytes += mSWFDoReconnect.length;
518: mTotalNumOfBytes += mSWFDoReconnect.length;
519:
520: reconnectRequestTime = new Date().getTime();
521: continue;
522:
523: }
524:
525: // If our wait is less than the wait interval, keep
526: // waiting...
527: long now = new Date().getTime();
528: long interval = now - reconnectRequestTime;
529: if (interval < waitInterval) {
530: mLogger.debug(
531: /* (non-Javadoc)
532: * @i18n.test
533: * @org-mes="still waiting for reconnect..."
534: */
535: org.openlaszlo.i18n.LaszloMessages.getMessage(
536: HTTPConnection.class.getName(),
537: "051018-551"));
538: continue;
539: }
540:
541: // ...else, really quit.
542: mLogger.debug(
543: /* (non-Javadoc)
544: * @i18n.test
545: * @org-mes="interval was " + p[0] + "; done waiting...goodbye!"
546: */
547: org.openlaszlo.i18n.LaszloMessages.getMessage(
548: HTTPConnection.class.getName(),
549: "051018-563", new Object[] { new Long(
550: interval) }));
551: return;
552: }
553:
554: numOfBytesSent = 0;
555:
556: // If queue is 0, just send heartbeat.
557: if (mQueue.size() == 0) {
558: mHeartbeatCount++;
559: mOut.write(mSWFHeartbeat);
560: mOut.flush();
561: numOfBytesSent = mSWFHeartbeat.length;
562: } else {
563: numOfBytesSent = flushMessageQueue();
564: }
565:
566: mNumOfBytes += numOfBytesSent;
567: mTotalNumOfBytes += numOfBytesSent;
568: }
569: } catch (InterruptedException e) {
570: mLogger.debug(
571: /* (non-Javadoc)
572: * @i18n.test
573: * @org-mes="InterruptedException: " + p[0]
574: */
575: org.openlaszlo.i18n.LaszloMessages.getMessage(
576: HTTPConnection.class.getName(), "051018-590",
577: new Object[] { e.getMessage() }));
578: }
579: }
580:
581: /**
582: * @return true if reconnect needs to happen.
583: */
584: private boolean doReconnect() {
585: return doReconnect(0);
586: }
587:
588: /**
589: * @param n number of potential bytes that will be going out.
590: * @return true if reconnect needs to happen.
591: */
592: private boolean doReconnect(int n) {
593: return (mConnectionLength - (mMaxMessageLen * 2)) <= (mNumOfBytes + n);
594: }
595:
596: /**
597: * Send a connection header and flush any messages in the queue, if any.
598: */
599: private int sendHeader() throws InterruptedException, IOException {
600: mLogger.debug(
601: /* (non-Javadoc)
602: * @i18n.test
603: * @org-mes="sendHeader()"
604: */
605: org.openlaszlo.i18n.LaszloMessages.getMessage(
606: HTTPConnection.class.getName(), "051018-626"));
607:
608: // Content-length used to be commented out because w/Tomcat 3.3
609: // connections seemed open to the servlet container even after browser
610: // close.
611: mRes.setContentLength(mConnectionLength);
612: mRes.setContentType("application/x-shockwave-flash");
613:
614: // This is to keep browsers from doing connection keep-alives.
615: mRes.setHeader("Connection", "close");
616: mRes.setHeader("Keep-Alive", "close");
617:
618: if (mEmitConnectHeaders) {
619: if (mConnectedString == CONNECTED)
620: mRes.setHeader("X-LPS-C", mCID);
621: else if (mConnectedString == RECONNECTED)
622: mRes.setHeader("X-LPS-R", mCID);
623: }
624:
625: mLogger.debug("Sent connected string: " + mConnectedString);
626:
627: String info;
628: if (mConnectedString == CONNECTED) {
629: info = getConnectionInfoXML(mConnectedString, "<cid>"
630: + mCID + "</cid>" + "<sid>" + mSID + "</sid>"
631: + "<usr>" + mUsername + "</usr>");
632: } else {
633: info = getConnectionInfoXML(mConnectedString, "<cid>"
634: + mCID + "</cid>");
635:
636: }
637: SwfByte swf = new SwfByte();
638:
639: swf.setHeader(mSWFVersion);
640: swf.setLength(mConnectionLength);
641: swf.actionSetElement(getElement(info));
642: if (mDoPad)
643: swf.actionSetVariable("pad", mPad);
644: swf.setShowFrame();
645:
646: byte[] buf = swf.getBuf();
647: mOut.write(buf, 0, buf.length);
648: mOut.flush();
649:
650: return buf.length;
651: }
652:
653: /**
654: * Flush out messages in queue for the client.
655: *
656: * @return number of messages sent
657: * @throws IOException if there's a problem with the output stream
658: */
659: private int flushMessageQueue() throws IOException,
660: InterruptedException {
661: mLogger.debug(
662: /* (non-Javadoc)
663: * @i18n.test
664: * @org-mes="flushMessageQueue()"
665: */
666: org.openlaszlo.i18n.LaszloMessages.getMessage(
667: HTTPConnection.class.getName(), "051018-690"));
668:
669: byte[] swfBuf;
670: int numOfBytes = 0;
671: synchronized (mQueue) {
672:
673: Iterator iter = mQueue.iterator();
674: if (!iter.hasNext())
675: return 0;
676:
677: while (iter.hasNext()) {
678: swfBuf = (byte[]) iter.next();
679: mOut.write(swfBuf);
680: numOfBytes += swfBuf.length;
681: ++mSentCount;
682: iter.remove();
683:
684: // If data sent is above the byte limit, reconnect before
685: // flushing the rest of the queue.
686: if (doReconnect(numOfBytes))
687: break;
688: }
689:
690: mOut.flush();
691: ++mFlushCount;
692: }
693:
694: return numOfBytes;
695: }
696:
697: /**
698: * Check if padding is required.
699: */
700: public boolean doPad() {
701: return mDoPad;
702: }
703:
704: /**
705: * Set if you want padding of bytes to headers. Used for browser like IE
706: * that don't display anything until the Nth byte.
707: *
708: * @param doPad flag for padding
709: * @return this object
710: */
711: public HTTPConnection setDoPad(boolean doPad) {
712: mDoPad = doPad;
713: return this ;
714: }
715:
716: /**
717: * Get heartbeat interval in milliseconds.
718: */
719: public long getHeartbeatInterval() {
720: return mHeartbeatInterval;
721: }
722:
723: /**
724: * @return version for SWF bytes.
725: */
726: public int getSWFVersion() {
727: return mSWFVersion;
728: }
729:
730: /**
731: * Set heartbeat in milliseconds.
732: */
733: public HTTPConnection setHeartbeatInterval(long heartbeatInterval) {
734: mHeartbeatInterval = heartbeatInterval;
735: return this ;
736: }
737:
738: /**
739: * Get username.
740: */
741: public String getUsername() {
742: return mUsername;
743: }
744:
745: /**
746: * Get unique id.
747: */
748: public String getCID() {
749: return mCID;
750: }
751:
752: /**
753: */
754: public HTTPConnection setEmitConnectHeaders(
755: boolean emitConnectHeaders) {
756: mEmitConnectHeaders = emitConnectHeaders;
757: return this ;
758: }
759:
760: /**
761: * Turn xml into a JDOM element.
762: *
763: * @param xml xml string
764: * @return JDOM element
765: */
766: private static Element getElement(String xml) {
767: Element el = null;
768: try {
769: el = new SAXBuilder(false).build(new StringReader(xml))
770: .getRootElement();
771: } catch (JDOMException e) {
772: mLogger.debug(e.getMessage());
773: } catch (IOException e) {
774: mLogger.debug(e.getMessage());
775: }
776: return el;
777: }
778:
779: /**
780: * XML message structure used to push server information to the
781: * client. Used for heartbeats, sending connection startup and informing
782: * users someone's been disconnected.
783: *
784: * @param type type of information. This will be received by the client as a
785: * dataset.
786: * @param msg message of type, if any
787: * @return an xml string with connection information
788: */
789: public static String getConnectionInfoXML(String type, String msg) {
790: if (msg == null)
791: return "<resultset s=\"0\"><root dset=\"" + type
792: + "\" /></resultset>";
793: else
794: return "<resultset s=\"0\"><root dset=\"" + type + "\">"
795: + msg + "</root></resultset>";
796: }
797:
798: public static int getMaxMessageLen() {
799: return mMaxMessageLen;
800: }
801:
802: public static int getConnectionLength() {
803: return mConnectionLength;
804: }
805:
806: public static int getReconnectionWaitInterval() {
807: return mReconnectionWaitInterval;
808: }
809:
810: public String toString() {
811: return new StringBuffer().append("<connection").append(
812: " sid=\"").append(mSID).append("\"").append(" cid=\"")
813: .append(mCID).append("\"").append(" user=\"").append(
814: mUsername).append("\"")
815: .append(" heartbeats=\"").append(mHeartbeatCount)
816: .append("\"").append(" flushes=\"").append(mFlushCount)
817: .append("\"").append(" current-bytes=\"").append(
818: mNumOfBytes).append("\"").append(
819: " total-bytes=\"").append(mTotalNumOfBytes)
820: .append("\"").append(" />").toString();
821: }
822:
823: }
|