stevel 2003/01/25 11:12:54 Modified: java/src/org/apache/axis/utils tcpmon.properties tcpmon.java Log: Changes to tcpmon, a mixture of functionality, usability and code layout -user specifiable delay of X milliseconds every bytes, lets you simulate restricted bandwidth links for better client testing -hostname and numeric text edit boxes are charset-restricted; you can't put a char like $ or % in a hostname box, or anything other than 0-9 in a number -explicit import of classes, bracing around all if statements &c. Revision Changes Path 1.5 +7 -0 xml-axis/java/src/org/apache/axis/utils/tcpmon.properties Index: tcpmon.properties =================================================================== RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/utils/tcpmon.properties,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- tcpmon.properties 30 May 2002 23:46:02 -0000 1.4 +++ tcpmon.properties 25 Jan 2003 19:12:54 -0000 1.5 @@ -51,3 +51,10 @@ # NOTE: in xmlFormat00, do not translate "XML" xmlFormat00=XML Format + +#NOTE: this is a SimpleDateFormat format string to declare the layout of date +#and time in the message log. It does need i18n, but not 'translation', per se. +dateformat00=yyyy-MM-dd HH:mm:ss +delay00=Simulate Slow Connection +delay01=Bytes per Pause +delay02=Delay in Milliseconds \ No newline at end of file 1.46 +680 -149 xml-axis/java/src/org/apache/axis/utils/tcpmon.java Index: tcpmon.java =================================================================== RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/utils/tcpmon.java,v retrieving revision 1.45 retrieving revision 1.46 diff -u -r1.45 -r1.46 --- tcpmon.java 17 Jan 2003 04:40:09 -0000 1.45 +++ tcpmon.java 25 Jan 2003 19:12:54 -0000 1.46 @@ -2,7 +2,7 @@ * The Apache Software License, Version 1.1 * * - * Copyright (c) 2001 The Apache Software Foundation. All rights + * Copyright (c) 2001-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -10,7 +10,7 @@ * are met: * * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in @@ -18,7 +18,7 @@ * distribution. * * 3. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: + * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, @@ -26,7 +26,7 @@ * * 4. The names "Axis" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this - * software without prior written permission. For written + * software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", @@ -55,7 +55,26 @@ package org.apache.axis.utils ; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.UIManager; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; @@ -64,7 +83,16 @@ import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; -import java.awt.*; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.PlainDocument; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; @@ -87,26 +115,41 @@ /** + * TCP monitor to log http messages and responses, both SOAP and plain HTTP. * @author Doug Davis ([EMAIL PROTECTED]) + * @author Steve Loughran */ public class tcpmon extends JFrame { private JTabbedPane notebook = null ; - private static int STATE_COLUMN = 0 ; - private static int TIME_COLUMN = 1 ; - private static int INHOST_COLUMN = 2 ; - private static int OUTHOST_COLUMN = 3 ; - private static int REQ_COLUMN = 4 ; + private static final int STATE_COLUMN = 0 ; + private static final int TIME_COLUMN = 1 ; + private static final int INHOST_COLUMN = 2 ; + private static final int OUTHOST_COLUMN = 3 ; + private static final int REQ_COLUMN = 4 ; + + private static final String DEFAULT_HOST="127.0.0.1"; + private static final int DEFAULT_PORT=8080; + + /** + * this is the admin page + */ class AdminPage extends JPanel { public JRadioButton listenerButton, proxyButton ; public JLabel hostLabel, tportLabel; - public JTextField port, host, tport ; + public NumberField port; + public HostnameField host; + public NumberField tport ; public JTabbedPane noteb ; public JCheckBox HTTPProxyBox ; - public JTextField HTTPProxyHost, HTTPProxyPort ; + public HostnameField HTTPProxyHost; + public NumberField HTTPProxyPort ; public JLabel HTTPProxyHostLabel, HTTPProxyPortLabel ; + public JLabel delayTimeLabel, delayBytesLabel; + public NumberField delayTime, delayBytes; + public JCheckBox delayBox; public AdminPage( JTabbedPane notebook, String name ) { JPanel mainPane = null ; @@ -137,7 +180,7 @@ c.anchor = GridBagConstraints.WEST ; c.gridwidth = GridBagConstraints.REMAINDER ; - tmpPanel.add( port = new JTextField(4), c ); + tmpPanel.add( port = new NumberField(4), c ); mainPane.add( tmpPanel, c ); @@ -182,7 +225,9 @@ c.anchor = GridBagConstraints.WEST ; c.gridwidth = GridBagConstraints.REMAINDER ; - mainPane.add( host = new JTextField(30), c ); + host = new HostnameField(30); + mainPane.add( host, c ); + host.setText(DEFAULT_HOST); c.anchor = GridBagConstraints.WEST ; c.gridwidth = 1 ; @@ -191,7 +236,9 @@ c.anchor = GridBagConstraints.WEST ; c.gridwidth = GridBagConstraints.REMAINDER ; - mainPane.add( tport = new JTextField(4), c ); + tport = new NumberField(4); + mainPane.add( tport, c ); + tport.setValue(DEFAULT_PORT); // Act as proxy section /////////////////////////////////////////////////////////////////// @@ -246,7 +293,7 @@ c.anchor = GridBagConstraints.WEST ; c.gridwidth = GridBagConstraints.REMAINDER ; - opts.add( HTTPProxyHost = new JTextField(30), c ); + opts.add( HTTPProxyHost = new HostnameField(30), c ); HTTPProxyHost.setEnabled( false ); c.anchor = GridBagConstraints.WEST ; @@ -256,7 +303,7 @@ c.anchor = GridBagConstraints.WEST ; c.gridwidth = GridBagConstraints.REMAINDER ; - opts.add( HTTPProxyPort = new JTextField(4), c ); + opts.add( HTTPProxyPort = new NumberField(4), c ); HTTPProxyPort.setEnabled( false ); HTTPProxyBox.addActionListener( new ActionListener() { @@ -271,15 +318,15 @@ HTTPProxyPortLabel.setForeground( color ); } } - ; } ); // Set default proxy values... String tmp = System.getProperty( "http.proxyHost" ); - if ( tmp != null && tmp.equals("") ) + if ( tmp != null && tmp.equals("") ) { tmp = null ; + } HTTPProxyBox.setSelected( tmp != null ); HTTPProxyHost.setEnabled( tmp != null ); @@ -291,11 +338,61 @@ HTTPProxyBox.setSelected( true ); HTTPProxyHost.setText( tmp ); tmp = System.getProperty( "http.proxyPort" ); - if ( tmp != null && tmp.equals("") ) tmp = null ; - if ( tmp == null ) tmp = "80" ; + if ( tmp != null && tmp.equals("") ) { + tmp = null ; + } + if ( tmp == null ) { + tmp = "80" ; + } HTTPProxyPort.setText( tmp ); } + //add byte delay fields + opts.add(Box.createRigidArea(new Dimension(1, 10)), c); + c.anchor = GridBagConstraints.WEST; + c.gridwidth = GridBagConstraints.REMAINDER; + final String delaySupport = getMessage("delay00", "Simulate Slow Connection"); + opts.add(delayBox = new JCheckBox(delaySupport), c); + + //bytes per pause + c.anchor = GridBagConstraints.WEST; + c.gridwidth = 1; + delayBytesLabel=new JLabel(getMessage("delay01", "Bytes per Pause")); + opts.add(delayBytesLabel, c); + delayBytesLabel.setForeground(Color.gray); + c.anchor = GridBagConstraints.WEST; + c.gridwidth = GridBagConstraints.REMAINDER; + opts.add(delayBytes = new NumberField(6), c); + delayBytes.setEnabled(false); + + //delay interval + c.anchor = GridBagConstraints.WEST; + c.gridwidth = 1; + delayTimeLabel = new JLabel(getMessage("delay02", "Delay in Milliseconds")); + opts.add(delayTimeLabel, c); + delayTimeLabel.setForeground(Color.gray); + c.anchor = GridBagConstraints.WEST; + c.gridwidth = GridBagConstraints.REMAINDER; + opts.add(delayTime = new NumberField(6), c); + delayTime.setEnabled(false); + + //enabler callback + delayBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + if (delaySupport.equals(event.getActionCommand())) { + boolean b = delayBox.isSelected(); + Color color = b ? Color.black : Color.gray; + + delayBytes.setEnabled(b); + delayTime.setEnabled(b); + delayBytesLabel.setForeground(color); + delayTimeLabel.setForeground(color); + } + } + ; + } + ); + // Spacer ////////////////////////////////////////////////////////////////// mainPane.add( Box.createRigidArea(new Dimension(1, 10)), c ); @@ -317,29 +414,44 @@ if ( add.equals(event.getActionCommand()) ) { String text ; Listener l = null ; - int lPort = Integer.parseInt(port.getText()); + int lPort; + lPort=port.getValue(0); + if(lPort==0) { + //no port, button does nothing + return; + } String tHost = host.getText(); int tPort = 0 ; - - text = tport.getText(); - if ( text != null && !text.equals("") ) - tPort = Integer.parseInt(text); + tPort=tport.getValue(0); + SlowLinkSimulator slowLink=null; + if(delayBox.isSelected()) { + int bytes= delayBytes.getValue(0); + int time = delayTime.getValue(0); + slowLink=new SlowLinkSimulator(bytes,time); + } l = new Listener( noteb, null, lPort, tHost, tPort, - proxyButton.isSelected() ); + proxyButton.isSelected(), slowLink); // Pick-up the HTTP Proxy settings /////////////////////////////////////////////////// text = HTTPProxyHost.getText(); - if ( "".equals(text) ) text = null ; + if ( "".equals(text) ) { + text = null ; + } l.HTTPProxyHost = text ; text = HTTPProxyPort.getText(); - if ( "".equals(text) ) text = null ; - if ( text != null ) + int proxyPort=HTTPProxyPort.getValue(-1); + if(proxyPort!=-1) { l.HTTPProxyPort = Integer.parseInt(text); - + } + //reset the port port.setText(null); + + /* but not, any more, the target port and host + values host.setText(null); tport.setText(null); + */ } } ; @@ -350,8 +462,14 @@ notebook.repaint(); notebook.setSelectedIndex( notebook.getTabCount() - 1 ); } + + } + /** + * wait for incoming connections, spawn a connection thread when + * stuff comes in. + */ class SocketWaiter extends Thread { ServerSocket sSocket = null ; Listener listener ; @@ -372,12 +490,13 @@ for (; ; ) { Socket inSocket = sSocket.accept(); - if ( pleaseStop ) break ; + if ( pleaseStop ) { + break ; + } new Connection( listener, inSocket ); inSocket = null ; } - } - catch ( Exception exp ) { + } catch ( Exception exp ) { if ( !"socket closed".equals(exp.getMessage()) ) { JLabel tmp = new JLabel( exp.toString() ); @@ -389,19 +508,109 @@ } } + /** + * force a halt by connecting to self and then closing the server socket + */ public void halt() { try { pleaseStop = true ; new Socket( "127.0.0.1", port ); - if ( sSocket != null ) sSocket.close(); - } - catch ( Exception e ) { + if ( sSocket != null ) { + sSocket.close(); + } + } catch ( Exception e ) { e.printStackTrace(); } } } + /** + * class to simulate slow connections by slowing down the system + */ + static class SlowLinkSimulator { + private int delayBytes; + private int delayTime; + private int currentBytes; + private int totalBytes; + + /** + * construct + * @param delayBytes bytes per delay; set to 0 for no delay + * @param delayTime delay time per delay in milliseconds + */ + public SlowLinkSimulator(int delayBytes, int delayTime) { + this.delayBytes = delayBytes; + this.delayTime = delayTime; + } + + /** + * construct by copying delay bytes and time, but not current + * count of bytes + * @param that source of data + */ + public SlowLinkSimulator(SlowLinkSimulator that) { + this.delayBytes=that.delayBytes; + this.delayTime=that.delayTime; + } + + /** + * how many bytes have gone past? + * @return + */ + public int getTotalBytes() { + return totalBytes; + } + + /** + * log #of bytes pumped. Will pause when necessary. This method is not + * synchronized + * @param bytes + */ + public void pump(int bytes) { + totalBytes+=bytes; + if(delayBytes==0) { + //when not delaying, we are just a byte counter + return; + } + currentBytes += bytes; + if(currentBytes>delayBytes) { + //we have overshot. lets find out how far + int delaysize=currentBytes/delayBytes; + long delay=delaysize*(long)delayTime; + //move byte counter down to the remainder of bytes + currentBytes=currentBytes%delayBytes; + //now wait + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + ; //ignore the exception + } + } + } + + /** + * get the current byte count + * @return + */ + public int getCurrentBytes() { + return currentBytes; + } + + /** + * set the current byte count + * @param currentBytes + */ + public void setCurrentBytes(int currentBytes) { + this.currentBytes = currentBytes; + } + + } + + /** + * this class handles the pumping of data from the incoming socket to the + * outgoing socket + */ class SocketRR extends Thread { Socket inSocket = null ; Socket outSocket = null ; @@ -414,11 +623,12 @@ int tableIndex = 0 ; String type = null; Connection myConnection = null; + SlowLinkSimulator slowLink; public SocketRR(Connection c, Socket inputSocket, InputStream inputStream, Socket outputSocket, OutputStream outputStream, JTextArea _textArea, boolean format, - TableModel tModel, int index, final String type) { + TableModel tModel, int index, final String type, SlowLinkSimulator slowLink) { inSocket = inputSocket ; in = inputStream ; outSocket = outputSocket ; @@ -429,6 +639,7 @@ tableIndex = index ; this.type = type; myConnection = c; + this.slowLink= slowLink; start(); } @@ -458,42 +669,56 @@ String tmpStr = (String) tmodel.getValueAt(tableIndex, REQ_COLUMN); - if ( !"".equals(tmpStr) ) + if ( !"".equals(tmpStr) ) { reqSaved = tmpStr.length(); + } } - a: + a: for ( ; ; ) { - if ( done ) break; + if ( done ) { + break; + } //try{ //len = in.available(); //}catch(Exception e){len=0;} len = buffer.length ; - // Used to be 1, but if we block it doesn't matter + // Used to be 1, but if we block it doesn't matter // however 1 will break with some servers, including apache - if ( len == 0 ) len = buffer.length; - if ( saved + len > buffer.length) len = buffer.length - saved ; + if ( len == 0 ) { + len = buffer.length; + } + if ( saved + len > buffer.length) { + len = buffer.length - saved ; + } int len1 = 0; while ( len1 == 0 ) { try { len1 = in.read(buffer, saved, len); - } + } catch ( Exception ex ) { - if ( done && saved == 0 ) break a; + if ( done && saved == 0 ) { + break a; + } len1 = -1; break; } } len = len1; - if ( len == -1 && saved == 0 ) break ; - if ( len == -1) done = true; + if ( len == -1 && saved == 0 ) { + break ; + } + if ( len == -1) { + done = true; + } // No matter how we may (or may not) format it, send it // on unformatted - we don't want to mess with how its // sent to the other side, just how its displayed if ( out != null && len > 0 ) { + slowLink.pump(len); out.write( buffer, saved, len ); } @@ -502,8 +727,9 @@ REQ_COLUMN); old = old + new String(buffer, saved, len); - if ( old.length() > 50 ) + if ( old.length() > 50 ) { old = old.substring(0, 50); + } reqSaved = old.length(); @@ -520,7 +746,9 @@ boolean inXML = false ; int bufferLen = saved ; - if ( len != -1 ) bufferLen += len ; + if ( len != -1 ) { + bufferLen += len ; + } i1 = 0 ; i2 = 0 ; saved = 0 ; @@ -537,8 +765,9 @@ inXML = true ; } if ( buffer[i1] == '<' && buffer[i1 + 1] == '/' ) { - if (previousIndent > nextIndent) + if (previousIndent > nextIndent) { thisIndent = nextIndent; + } previousIndent = nextIndent--; inXML = true ; } @@ -547,9 +776,12 @@ inXML = true ; } if ( thisIndent != -1 ) { - if ( thisIndent > 0 ) tmpbuffer[i2++] = (byte) '\n'; - for ( i = tabWidth * thisIndent; i > 0; i-- ) + if ( thisIndent > 0 ) { + tmpbuffer[i2++] = (byte) '\n'; + } + for ( i = tabWidth * thisIndent; i > 0; i-- ) { tmpbuffer[i2++] = (byte) ' '; + } } atMargin = ( buffer[i1] == '\n' || buffer[i1] == '\r'); @@ -561,8 +793,9 @@ textArea.append( new String( tmpbuffer, 0, i2 ) ); // Shift saved bytes to the beginning - for ( i = 0 ; i < saved ; i++ ) + for ( i = 0 ; i < saved ; i++ ) { buffer[i] = buffer[bufferLen - saved + i]; + } } else { textArea.append( new String( buffer, 0, len ) ); @@ -584,18 +817,24 @@ try { if (out != null) { out.flush(); - if (null != outSocket) outSocket.shutdownOutput(); - else out.close(); + if (null != outSocket) { + outSocket.shutdownOutput(); + } else { + out.close(); + } out = null; } - } + } catch (Exception e) { ; } try { if (in != null) { - if (inSocket != null) inSocket.shutdownInput(); - else in.close(); + if (inSocket != null) { + inSocket.shutdownInput(); + } else { + in.close(); + } in = null; } } @@ -608,16 +847,24 @@ public void halt() { try { - if ( inSocket != null ) inSocket.close(); - if ( outSocket != null ) outSocket.close(); + if ( inSocket != null ) { + inSocket.close(); + } + if ( outSocket != null ) { + outSocket.close(); + } inSocket = null ; outSocket = null ; - if ( in != null ) in.close(); - if ( out != null ) out.close(); + if ( in != null ) { + in.close(); + } + if ( out != null ) { + out.close(); + } in = null ; out = null ; done = true; - } + } catch ( Exception e ) { e.printStackTrace(); } @@ -625,6 +872,9 @@ } + /** + * a connection listens to a single current connection + */ class Connection extends Thread { Listener listener ; boolean active ; @@ -644,11 +894,13 @@ String HTTPProxyHost = null ; int HTTPProxyPort = 80 ; + private SlowLinkSimulator slowLink; public Connection(Listener l) { listener = l ; HTTPProxyHost = l.HTTPProxyHost ; HTTPProxyPort = l.HTTPProxyPort ; + slowLink =l.slowLink; } public Connection(Listener l, Socket s ) { @@ -668,33 +920,42 @@ active = true ; HTTPProxyHost = System.getProperty( "http.proxyHost" ); - if ( HTTPProxyHost != null && HTTPProxyHost.equals("") ) + if ( HTTPProxyHost != null && HTTPProxyHost.equals("") ) { HTTPProxyHost = null ; + } if ( HTTPProxyHost != null ) { String tmp = System.getProperty( "http.proxyPort" ); - if ( tmp != null && tmp.equals("") ) tmp = null ; - if ( tmp == null ) HTTPProxyPort = 80 ; - else HTTPProxyPort = Integer.parseInt( tmp ); + if ( tmp != null && tmp.equals("") ) { + tmp = null ; + } + if ( tmp == null ) { + HTTPProxyPort = 80 ; + } else { + HTTPProxyPort = Integer.parseInt( tmp ); + } } - if ( inSocket != null ) + if ( inSocket != null ) { fromHost = (inSocket.getInetAddress()).getHostName(); - else + } else { fromHost = "resend" ; + } + - DateFormat df = new SimpleDateFormat("MM/dd/yy hh:mm:ss aa"); + String dateformat=getMessage("dateformat00", "yyyy-MM-dd HH:mm:ss"); + DateFormat df = new SimpleDateFormat(dateformat); time = df.format( new Date() ); int count = listener.connections.size(); - listener.tableModel.insertRow(count + 1, new Object[] { + listener.tableModel.insertRow(count + 1, new Object[] { getMessage("active00", "Active"), time, fromHost, - listener.hostField.getText(), "" + listener.hostField.getText(), "" } ); listener.connections.add( this ); @@ -729,11 +990,13 @@ InputStream tmpIn2 = null ; OutputStream tmpOut2 = null ; - if ( tmpIn1 == null ) + if ( tmpIn1 == null ) { tmpIn1 = inSocket.getInputStream(); + } - if ( inSocket != null ) + if ( inSocket != null ) { tmpOut1 = inSocket.getOutputStream(); + } String bufferedData = null ; StringBuffer buf = null ; @@ -751,10 +1014,14 @@ int len ; len = tmpIn1.read(b, 0, 1); - if ( len == -1 ) break ; + if ( len == -1 ) { + break ; + } s = new String( b ); buf.append( s ); - if ( b[0] != '\n' ) continue ; + if ( b[0] != '\n' ) { + continue ; + } break ; } @@ -767,16 +1034,22 @@ URL url ; start = bufferedData.indexOf( ' ' ) + 1; - while ( bufferedData.charAt(start) == ' ' ) start++ ; + while ( bufferedData.charAt(start) == ' ' ) { + start++ ; + } end = bufferedData.indexOf( ' ', start ); String urlString = bufferedData.substring( start, end ); - if ( urlString.charAt(0) == '/' ) urlString = urlString.substring(1); + if ( urlString.charAt(0) == '/' ) { + urlString = urlString.substring(1); + } if ( listener.isProxyBox.isSelected() ) { url = new URL( urlString ); targetHost = url.getHost(); targetPort = url.getPort(); - if ( targetPort == -1 ) targetPort = 80 ; + if ( targetPort == -1 ) { + targetPort = 80 ; + } listener.tableModel.setValueAt( targetHost, index + 1, OUTHOST_COLUMN ); @@ -791,15 +1064,15 @@ listener.tableModel.setValueAt( targetHost, index + 1, OUTHOST_COLUMN ); bufferedData = bufferedData.substring( 0, start) + - url.toExternalForm() + - bufferedData.substring( end ); + url.toExternalForm() + + bufferedData.substring( end ); targetHost = HTTPProxyHost ; targetPort = HTTPProxyPort ; } } - } + } else { // // Change Host: header to point to correct host @@ -814,12 +1087,14 @@ int len ; len = tmpIn1.read(b1, 0, 1); - if ( len == -1 ) + if ( len == -1 ) { break ; + } s1 = new String( b1 ); buf.append( s1 ); - if ( b1[0] != '\n' ) + if ( b1[0] != '\n' ) { continue ; + } // we have a complete line String line = buf.toString(); @@ -833,14 +1108,19 @@ break ; } // add it to our headers so far - if (bufferedData == null) + if (bufferedData == null) { bufferedData = line; - else + } else { bufferedData = bufferedData.concat(line); + } // failsafe - if (line.equals("\r\n")) break; - if ("\n".equals(lastLine) && line.equals("\n")) break ; + if (line.equals("\r\n")) { + break; + } + if ("\n".equals(lastLine) && line.equals("\n")) { + break ; + } lastLine = line ; } if ( bufferedData != null ) { @@ -849,16 +1129,20 @@ s1 = bufferedData.substring( 0, idx ); int i = s1.indexOf('\n'); - if ( i > 0 ) s1 = s1.substring(0, i - 1); + if ( i > 0 ) { + s1 = s1.substring(0, i - 1); + } s1 = s1 + " " + - " "; + " "; s1 = s1.substring(0, 51); listener.tableModel.setValueAt( s1, index + 1, REQ_COLUMN ); } } - if ( targetPort == -1 ) targetPort = 80 ; + if ( targetPort == -1 ) { + targetPort = 80 ; + } outSocket = new Socket(targetHost, targetPort ); tmpIn2 = outSocket.getInputStream(); @@ -866,25 +1150,30 @@ if ( bufferedData != null ) { byte[] b = bufferedData.getBytes(); - tmpOut2.write( b ); + slowLink.pump(b.length); } boolean format = listener.xmlFormatBox.isSelected(); + + //this is the channel to the endpoint rr1 = new SocketRR(this, inSocket, tmpIn1, outSocket, tmpOut2, inputText, format, - listener.tableModel, index + 1, "request:" ); + listener.tableModel, index + 1, "request:", slowLink); + //create the response slow link from the inbound slow link + SlowLinkSimulator responseLink = new SlowLinkSimulator(slowLink); + //this is the channel from the endpoint rr2 = new SocketRR( this, outSocket, tmpIn2, inSocket, tmpOut1, outputText, format, - null, 0, "response:" ); + null, 0, "response:", responseLink); while ( rr1 != null || rr2 != null ) { // Only loop as long as the connection to the target // machine is available - once that's gone we can stop. // The old way, loop until both are closed, left us // looping forever since no one closed the 1st one. - // while( !rr2.isDone() ) + // while( !rr2.isDone() ) if (null != rr1 && rr1.isDone()) { if ( index >= 0 && rr2 != null) { listener.tableModel.setValueAt(getMessage("resp00", "Resp"), @@ -933,11 +1222,17 @@ PrintWriter wr = new PrintWriter(st); int index = listener.connections.indexOf( this ); - if ( index >= 0 ) + if ( index >= 0 ) { listener.tableModel.setValueAt( getMessage("error00", "Error"), 1 + index, STATE_COLUMN ); + } e.printStackTrace(wr); wr.close(); - outputText.append( st.toString() ); + if(outputText!=null) { + outputText.append( st.toString() ); + } else { + //something went wrong before we had the output area + System.out.println(st.toString()); + } halt(); } } @@ -948,11 +1243,19 @@ public void halt() { try { - if ( rr1 != null ) rr1.halt(); - if ( rr2 != null ) rr2.halt(); - if ( inSocket != null ) inSocket.close(); + if ( rr1 != null ) { + rr1.halt(); + } + if ( rr2 != null ) { + rr2.halt(); + } + if ( inSocket != null ) { + inSocket.close(); + } inSocket = null ; - if ( outSocket != null ) outSocket.close(); + if ( outSocket != null ) { + outSocket.close(); + } outSocket = null ; } catch ( Exception e ) { @@ -977,6 +1280,9 @@ } + /** + * this is one of the tabbed panels that acts as the actual proxy + */ class Listener extends JPanel { public Socket inputSocket = null ; public Socket outputSocket = null ; @@ -1002,15 +1308,36 @@ public JTabbedPane notebook = null ; public String HTTPProxyHost = null ; public int HTTPProxyPort = 80 ; - - final public Vector connections = new Vector(); - + public int delayBytes = 0; + public int delayTime = 0; + public SlowLinkSimulator slowLink; + + public final Vector connections = new Vector(); + + /** + * create a listener + * @param _notebook + * @param name + * @param listenPort + * @param host + * @param targetPort + * @param isProxy + * @param slowLink optional reference to a slow connection + */ public Listener(JTabbedPane _notebook, String name, int listenPort, String host, int targetPort, - boolean isProxy) { + boolean isProxy, SlowLinkSimulator slowLink) { notebook = _notebook ; - if ( name == null ) name = getMessage("port01", "Port") + " " + listenPort ; - + if ( name == null ) { + name = getMessage("port01", "Port") + " " + listenPort ; + } + //set the slow link to the passed down link + if(slowLink!=null) { + this.slowLink=slowLink; + } else { + //or make up a no-op one. + this.slowLink=new SlowLinkSimulator(0,0); + } this.setLayout( new BorderLayout() ); // 1st component is just a row of labels and 1-line entry fields @@ -1054,8 +1381,12 @@ stopButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if ( getMessage("stop00", "Stop").equals(event.getActionCommand()) ) stop(); - if ( start.equals(event.getActionCommand()) ) start(); + if ( getMessage("stop00", "Stop").equals(event.getActionCommand()) ) { + stop(); + } + if ( start.equals(event.getActionCommand()) ) { + start(); + } } } ); @@ -1090,7 +1421,9 @@ sel.addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { - if (event.getValueIsAdjusting()) return ; + if (event.getValueIsAdjusting()) { + return ; + } ListSelectionModel m = (ListSelectionModel) event.getSource(); int divLoc = outPane.getDividerLocation(); @@ -1138,11 +1471,11 @@ } outPane.setDividerLocation(divLoc); } - } + } ); - tableModel.addRow( new Object[] { - "---", getMessage("mostRecent00", "Most Recent"), "---", "---", "---" - } + tableModel.addRow( new Object[] { + "---", getMessage("mostRecent00", "Most Recent"), "---", "---", "---" + } ); JPanel tablePane = new JPanel(); @@ -1168,7 +1501,9 @@ removeButton.setEnabled( false ); removeButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if ( removeSelected.equals(event.getActionCommand()) ) remove(); + if ( removeSelected.equals(event.getActionCommand()) ) { + remove(); + } } } ); @@ -1176,7 +1511,9 @@ removeAllButton.setEnabled( false ); removeAllButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if ( removeAll.equals(event.getActionCommand()) ) removeAll(); + if ( removeAll.equals(event.getActionCommand()) ) { + removeAll(); + } } } ); @@ -1228,7 +1565,9 @@ saveButton.setEnabled( false ); saveButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if ( save.equals(event.getActionCommand()) ) save(); + if ( save.equals(event.getActionCommand()) ) { + save(); + } } } ); @@ -1236,7 +1575,9 @@ resendButton.setEnabled( false ); resendButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if ( resend.equals(event.getActionCommand()) ) resend(); + if ( resend.equals(event.getActionCommand()) ) { + resend(); + } } } ); @@ -1246,10 +1587,14 @@ if (switchStr.equals(event.getActionCommand()) ) { int v = outPane.getOrientation(); - if ( v == 0 ) // top/bottom + if ( v == 0 ) { + // top/bottom outPane.setOrientation(1); - else // left/right + } + else { + // left/right outPane.setOrientation(0); + } outPane.setDividerLocation(0.5); } } @@ -1258,8 +1603,9 @@ closeButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent event) { - if (close.equals(event.getActionCommand()) ) + if (close.equals(event.getActionCommand()) ) { close(); + } } } ); @@ -1272,7 +1618,7 @@ pane1.setDividerLocation( 150 ); this.add( pane1, BorderLayout.CENTER ); - // + // //////////////////////////////////////////////////////////////////// sel.setSelectionInterval(0, 0); outPane.setDividerLocation( 150 ); @@ -1343,15 +1689,18 @@ for ( int i = top ; i >= bot ; i-- ) { ((Connection) connections.get(i - 1)).remove(); } - if ( bot > connections.size() ) bot = connections.size(); + if ( bot > connections.size() ) { + bot = connections.size(); + } lsm.setSelectionInterval(bot, bot); } public void removeAll() { ListSelectionModel lsm = connectionTable.getSelectionModel(); lsm.clearSelection(); - while ( connections.size() > 0 ) + while ( connections.size() > 0 ) { ((Connection) connections.get(0)).remove(); + } lsm.setSelectionInterval(0, 0); } @@ -1369,7 +1718,7 @@ rc = lsm.getLeadSelectionIndex(); - int n = 0; + int n = 0; for (Iterator i = connections.iterator();i.hasNext();n++) { Connection conn = (Connection)i.next(); if (lsm.isSelectedIndex(n + 1) || @@ -1381,16 +1730,16 @@ "\n" )).getBytes() ); rc = Integer.parseInt( tPortField.getText() ); out.write( (new String(getMessage("targetPort01", "Target Port:") + " " + rc + "\n" )).getBytes() ); - + out.write( (new String("==== " + getMessage("request01", "Request") + " ====\n" )).getBytes() ); out.write( conn.inputText.getText().getBytes() ); - + out.write( (new String("==== " + getMessage("response00", "Response") + " ====\n" )).getBytes() ); out.write( conn.outputText.getText().getBytes() ); out.write("\n==============\n".getBytes()); } } - + out.close(); } catch ( Exception e ) { @@ -1406,7 +1755,9 @@ ListSelectionModel lsm = connectionTable.getSelectionModel(); rc = lsm.getLeadSelectionIndex(); - if ( rc == 0 ) rc = connections.size(); + if ( rc == 0 ) { + rc = connections.size(); + } Connection conn = (Connection) connections.get( rc - 1 ); if ( rc > 0 ) { @@ -1426,10 +1777,13 @@ pos3 = text.indexOf( "\n\n" ); if ( pos3 == -1 ) { pos3 = text.indexOf( "\r\n\r\n" ); - if ( pos3 != -1 ) pos3 = pos3 + 4 ; + if ( pos3 != -1 ) { + pos3 = pos3 + 4 ; + } } - else + else { pos3 += 2 ; + } headers = text.substring( 0, pos3 ); @@ -1474,24 +1828,31 @@ if ( listenPort != 0 ) { Listener l = null ; - if ( targetHost == null ) + if ( targetHost == null ) { l = new Listener( notebook, null, listenPort, - targetHost, targetPort, true ); - else + targetHost, targetPort, true, null); + } else { l = new Listener( notebook, null, listenPort, - targetHost, targetPort, false ); + targetHost, targetPort, false, null); + } notebook.setSelectedIndex( 1 ); l.HTTPProxyHost = System.getProperty( "http.proxyHost" ); - if ( l.HTTPProxyHost != null && l.HTTPProxyHost.equals("") ) + if ( l.HTTPProxyHost != null && l.HTTPProxyHost.equals("") ) { l.HTTPProxyHost = null ; + } if ( l.HTTPProxyHost != null ) { String tmp = System.getProperty( "http.proxyPort" ); - if ( tmp != null && tmp.equals("") ) tmp = null ; - if ( tmp == null ) l.HTTPProxyPort = 80 ; - else l.HTTPProxyPort = Integer.parseInt( tmp ); + if ( tmp != null && tmp.equals("") ) { + tmp = null ; + } + if ( tmp == null ) { + l.HTTPProxyPort = 80 ; + } else { + l.HTTPProxyPort = Integer.parseInt( tmp ); + } } } @@ -1502,11 +1863,11 @@ protected void processWindowEvent(WindowEvent event) { switch ( event.getID() ) { - case WindowEvent.WINDOW_CLOSING: + case WindowEvent.WINDOW_CLOSING: exit(); break ; - default: + default: super.processWindowEvent(event); break ; } @@ -1522,26 +1883,39 @@ public void setOutputHostPort(char hostName, int port) { } + /** + * set up the L&F + */ + private static void setupLookAndFeel(boolean nativeLookAndFeel) throws Exception { + UIManager.setLookAndFeel( + nativeLookAndFeel ? UIManager.getSystemLookAndFeelClassName() + : UIManager.getCrossPlatformLookAndFeelClassName()); + JFrame.setDefaultLookAndFeelDecorated(true); + } + /** + * this is our main method + * @param args + */ public static void main(String[] args) { try { + //switch between swing L&F here + setupLookAndFeel(true); if ( args.length == 3 ) { int p1 = Integer.parseInt( args[0] ); int p2 = Integer.parseInt( args[2] ); - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new tcpmon( p1, args[1], p2 ); } else if ( args.length == 1 ) { int p1 = Integer.parseInt( args[0] ); - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new tcpmon( p1, null, 0 ); } else if ( args.length != 0 ) { - System.err.println( getMessage("usage00", "Usage:") + " tcpmon [listenPort targetHost targetPort]\n"); + System.err.println( getMessage("usage00", "Usage:") + + " tcpmon [listenPort targetHost targetPort]\n"); } else { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new tcpmon(0, null, 0); } } @@ -1562,8 +1936,7 @@ initializeMessages(); } return messages.getString(key); - } - catch (Throwable t) { + } catch (Throwable t) { // If there is any problem whatsoever getting the internationalized // message, return the default. return defaultMsg; @@ -1578,5 +1951,163 @@ private static void initializeMessages() { messages = ResourceBundle.getBundle("org.apache.axis.utils.tcpmon"); } // initializeMessages + + /** + * a text field with a restricted set of characters + */ + static class RestrictedTextField extends JTextField { + protected String validText; + + public RestrictedTextField(String validText) { + setValidText(validText); + } + + public RestrictedTextField(int columns, String validText) { + super(columns); + setValidText(validText); + } + + public RestrictedTextField(String text, String validText) { + super(text); + setValidText(validText); + } + + public RestrictedTextField(String text, int columns, String validText) { + super(text, columns); + setValidText(validText); + } + + private void setValidText(String validText) { + this.validText = validText; + } + + /** + * fascinatingly, this method is called in the super() constructor, + * meaning before we are fully initialized. C++ doesnt actually permit + * such a situation, but java clearly does... + * @return a new document + */ + public Document createDefaultModel() { + return new RestrictedDocument(); + } + + /** + * this class strips out invaid chars + */ + class RestrictedDocument extends PlainDocument { + + + /** + * Constructs a plain text document. A default model using + * <code>GapContent</code> is constructed and set. + */ + public RestrictedDocument() { + + } + + /** + * add a string; only those chars in the valid text list are allowed + * @param offset + * @param string + * @param attributes + * @throws BadLocationException + */ + public void insertString(int offset, String string, AttributeSet attributes) + throws BadLocationException { + + if (string == null) { + return; + } + int len = string.length(); + StringBuffer buffer = new StringBuffer(string.length()); + for (int i = 0; i < len; i++) { + char ch = string.charAt(i); + if (validText.indexOf(ch) >= 0) { + buffer.append(ch); + } + } + super.insertString(offset, new String(buffer), attributes); + } + } //end class NumericDocument + } + + /** + * because we cant use Java1.4's JFormattedTextField, here is + * a class that accepts numbers only + */ + static class NumberField extends RestrictedTextField { + + private static final String VALID_TEXT = "0123456789"; + + /** + * Constructs a new <code>TextField</code>. A default model is created, + * the initial string is <code>null</code>, + * and the number of columns is set to 0. + */ + public NumberField() { + super(VALID_TEXT); + } + + /** + * Constructs a new empty <code>TextField</code> with the specified + * number of columns. + * A default model is created and the initial string is set to + * <code>null</code>. + * + * @param columns the number of columns to use to calculate + * the preferred width; if columns is set to zero, the + * preferred width will be whatever naturally results from + * the component implementation + */ + public NumberField(int columns) { + super(columns, VALID_TEXT); + } + + + /** + * get the int value of a field, any invalid (non int) field returns + * the default + * @param def default value + * @return the field contents + */ + public int getValue(int def) { + int result = def; + String text = getText(); + if (text != null && text.length() != 0) { + try { + result = Integer.parseInt(text); + } catch (NumberFormatException e) { + + } + } + return result; + } + + /** + * set the text to a numeric value + * @param value number to assign + */ + public void setValue(int value) { + setText(Integer.toString(value)); + } + + } //end class NumericTextField + + /** + * hostname fields + */ + static class HostnameField extends RestrictedTextField { + //list of valid chars in a hostname + private static final String VALID_TEXT = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ-."; + + public HostnameField(int columns) { + super(columns, VALID_TEXT); + } + + public HostnameField() { + super(VALID_TEXT); + } + } }