newspaint

Documenting Problems That Were Difficult To Find The Answer To

Monthly Archives: November 2012

Creating a Java Sampler for JMeter

This was written while using JMeter versions 2.7 and 2.8. This article was written due to the difficulty I initially found in creating a Java Sampler for JMeter.

A Java Sampler is an easy way of creating a JMeter test by using native Java code. In fact there are several approaches that can be taken when creating a JMeter Sampler using Java code – they include:

  • BeanShell Samplerpros: very quick and dirty, no need to create your own class or compile anything, cons: very limited in power and flexibility
  • JUnit Samplerpros: can specify multiple test types inside one class, cons: cannot provide variables to test from JMeter script
  • Java Samplerpros: can specify JMeter script variables to class, cons: cannot have more than one test in a class

JUnit Sampler

A JUnit Sampler can be quickly constructed using the following as a template:

package com.wordpress.newspaint.jmeter.junit.demo;

import junit.framework.TestCase;

// test methods MUST BEING WITH THE PREFIX "test"
public class ExampleJUnitTestClass extends TestCase {
    float first = (float)2.0;
    float second = (float)20.0;

    public void testaddition() {
        assertTrue( "Addition Greater Than", ( ( first + second ) > 21.99 ) );
        assertTrue( "Addition Less Than", ( ( first + second ) < 22.01 ) );
    }

    public void testmulti() {
        assertTrue( "Multiplication Greater Than", ( ( first * second ) > 39.99 ) );
        assertTrue( "Multiplication Less Than", ( ( first * second ) < 40.01 ) );
    }
}

This can be compiled and the resulting jar placed in the lib/junit subdirectory of the JMeter distribution.

Tests are very simple and apart from duration and pass/fail results there is little more that can be gained using this method.

Java Sampler

The Java Sampler is a powerful means of testing just about anything. The big advantages are that variables can be taken from the JMeter script (such as thread number, or user name, or whatever you want) and that results can contain a specific error code as well as custom messages and data – more importantly you can control when the timer for the test starts and finishes.

The following can be an example Java Sampler class:

package com.wordpress.newspaint.jmeter.java.demo;

import java.io.Serializable;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;

public class ExampleJavaTestClass extends AbstractJavaSamplerClient implements Serializable {
    private static final long serialVersionUID = 1L;

    // set up default arguments for the JMeter GUI
    @Override
    public Arguments getDefaultParameters() {
        Arguments defaultParameters = new Arguments();
        defaultParameters.addArgument("URL", "http://www.google.com/");
        defaultParameters.addArgument("SEARCHFOR", "newspaint");
        return defaultParameters;
    }

    @Override
    public SampleResult runTest(JavaSamplerContext context) {
        // pull parameters
        String urlString = context.getParameter( "URL" );
        String searchFor = context.getParameter( "SEARCHFOR" );
	
        SampleResult result = new SampleResult();
        result.sampleStart(); // start stopwatch
		
        try {
            java.net.URL url = new java.net.URL(urlString + "?q=" + searchFor);
            java.net.HttpURLConnection connection = (
                java.net.HttpURLConnection
            )url.openConnection(); // have to cast connection
            connection.setRequestMethod("GET");
            connection.connect();

            result.sampleEnd(); // stop stopwatch
            result.setSuccessful( true );
            result.setResponseMessage( "Successfully performed action" );
            result.setResponseCodeOK(); // 200 code
        } catch (Exception e) {
            result.sampleEnd(); // stop stopwatch
            result.setSuccessful( false );
            result.setResponseMessage( "Exception: " + e );

            // get stack trace as a String to return as document data
            java.io.StringWriter stringWriter = new java.io.StringWriter();
            e.printStackTrace( new java.io.PrintWriter( stringWriter ) );
            result.setResponseData( stringWriter.toString() );
            result.setDataType( org.apache.jmeter.samplers.SampleResult.TEXT );
            result.setResponseCode( "500" );
        }

        return result;
    }
}

This can be compiled and the resulting jar placed in the lib/ext subdirectory of the JMeter distribution.

The ability to set variables can be seen from this resulting Sampler screenshot in JMeter:

Java Sampler Demo Showing Variable Input

Java Sampler Demo Showing Variable Input

Those variables can be set to anything – including dynamic variables such as “${__threadNum}”.

The advantage of setting response data is that it will be displayed in the “View Results Tree” listener’s “Response Data” tab – so I find putting the stack trace of any exceptions in there very useful to assist in debugging my tests. Such an example is shown in the next screen shot when attempting to provide an invalid domain name to the above example:

Stack Trace Displayed in Request Data Tab in Results Tree

Jars

I found I needed the following jars to compile this class:

  • ApacheJMeter_core.jar
  • ApacheJMeter_java.jar

Persistent Variables in a Thread

Maybe you want to create several steps/tests. So you create several classes. But how do you share information between tests? Let’s say your first test returned a certain string you wanted to use as the basis for your second test.

JMeter allows you to store variables in the current Thread’s context – so each thread would have its own context.

import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
// store a value
JMeterVariables vars = JMeterContextService.getContext().getVariables();
vars.putObject( "mySpecialValue", "http://www.apache.org/" );
JMeterContextService.getContext().setVariables( vars );
// fetch a value
JMeterVariables vars = JMeterContextService.getContext().getVariables();
String mySpecialValue = (String)vars.getObject( "mySpecialValue" );
// delete a value
JMeterVariables vars = JMeterContextService.getContext().getVariables();
vars.remove( "mySpecialValue" );
JMeterContextService.getContext().setVariables( vars );

Note that you can store anything including classes. But if your thread loops the value will persist – so you must explicitly remove it but setting the value to null if you don’t want to use it any more. Overwriting a value will cause the existing to be freed/destroyed.

You can also use the variables you set in other samplers – such as the HTTP Sampler! If you store a String in, say, “mySpecialValue” then you can refer to it in your other samplers using the JMeter variable notation (in this case ${mySpecialValue}).

BeanShell Interpreter

This is a quick-and-dirty means of adding Java code to your JMeter scripts. But best to read JMeter best practices first.

There is another post about the BeanShell Interpreter at this link.

Converting Russian Encodings to UTF-8 Using Perl

I want to watch TV shows with Russian subtitles. VLC (the video player) on my PC defaults to interpreting subtitle files (e.g. .srt files) as UTF-8.

Some of my subtitle files are not in UTF-8. Sometimes they are encoded as Windows-1251 and in one case they were in UTF-16BE. I’d rather not have to guess and have a script try out all the possible encodings and then write out any non-UTF-8 files to UTF-8.

The following UTF-8 script does this with whatever is on the command line:

#!/usr/bin/perl -w

use utf8; # tell Perl this source code file is utf-8

my @files = ();
foreach ( @ARGV ) {
    push( @files, glob( $_ ) ); # necessary for Windows
}

my @encodings = qw(windows-1251 utf-32 utf-16 utf-16-le utf-16-be); # utf-8 utf8 UTF-8
sub process_file {
    my ( $fname ) = @_;
	
	foreach my $encoding ( @encodings ) {
	    my $fin;
		if ( ! open( $fin, "<$fname" ) ) {
		    warn( "Could not open \"$fname\": $!" );
			last;
		}
		
		if ( ! binmode $fin, ":encoding($encoding)" ) {
		    warn( "Could not set encoding \"$encoding\"" );
			next;
		}
		
		my $data = "";
		eval {
			local $/ = undef;
			$data = <$fin>;
		};
		
		# assume the Russian word for "yes" is somewhere in the file
		next if ( $data !~ m{[Дд][Аа]} );

		print( "  - found encoding $encoding\n" );
		my $fout;
		if ( ! open( $fout, ">$fname.new" ) ) {
		    warn( "Could not write to $fname.new: $!" );
			last;
		}
			
		if ( ! binmode $fout, ":encoding(utf-8)" ) {
		    warn( "Could not set encoding utf-8 for output" );
			last;
		}
			
		print( $fout $data );
		close( $fout );
		last;
	}
}

foreach ( @files ) {
	printf( "Processing file \"$_\"\n" );
	process_file( $_ );
}

You can alternatively use the following script which does not need to be saved as UTF-8:

#!/usr/bin/perl -w

my @files = ();
foreach ( @ARGV ) {
    push( @files, glob( $_ ) ); # necessary for Windows
}

my @encodings = qw(windows-1251 utf-32 utf-16 utf-16-le utf-16-be); # utf-8 utf8 UTF-8
sub process_file {
    my ( $fname ) = @_;
	
	foreach my $encoding ( @encodings ) {
	    my $fin;
		if ( ! open( $fin, "<$fname" ) ) {
		    warn( "Could not open \"$fname\": $!" );
			last;
		}
		
		if ( ! binmode $fin, ":encoding($encoding)" ) {
		    warn( "Could not set encoding \"$encoding\"" );
			next;
		}
		
		my $data = "";
		eval {
			local $/ = undef;
			$data = <$fin>;
		};
		
		# assume the Russian word for "yes" is somewhere in the file
		next if ( $data !~ m{[\x{414}\x{434}][\x{410}\x{430}]} );

		print( "  - found encoding $encoding\n" );
		my $fout;
		if ( ! open( $fout, ">$fname.new" ) ) {
		    warn( "Could not write to $fname.new: $!" );
			last;
		}
			
		if ( ! binmode $fout, ":encoding(utf-8)" ) {
		    warn( "Could not set encoding utf-8 for output" );
			last;
		}
			
		print( $fout $data );
		close( $fout );
		last;
	}
}

foreach ( @files ) {
	printf( "Processing file \"$_\"\n" );
	process_file( $_ );
}

You can run this using the command:

C:\>perl russian-to-utf8.pl *.srt 2>NUL

or

user@myhost# perl russian-to-utf8.pl *.srt >/dev/null

If any file is detected to be Russian and in a non-UTF-8 encoding a new one in UTF-8 will be written with the extension .new appended.

Sharing Port 443 With HTTPS and SSH Using Node.JS

I want to use port 443 for both a HTTPS web server and SSH. This blog post gave the technical details – which is that SSH and HTTPS can be differentiated by the fact that a HTTPS client sends traffic immediately after connecting, whereas SSH clients wait for the SSH banner to be produced by the SSH server.

So I present, here, a TCP proxy that listens on the external IP (whatever your eth0 interface thinks its address is) and then intelligently switches traffic to localhost ports 443 or 22 (configurable) depending on whether it thinks it is HTTPS or SSH.

Note that, because this is a proxy, your SSH and HTTPS logs will not record the source address of who connected to your services – rather the localhost address (127.0.0.1 or ::1) will be shown. See the blog link above for a low-level tool that handles this situation properly.

// import required modules
var net = require('net');

// global variables (defaults)
var debugging = 0;
var internal_ssh_port = 22;
var internal_ssl_port = 443;
var external_port = 443;
var external_address = "0.0.0.0";
var internal_address = "127.0.0.1";
var is_ssh_timeout = 2500; // milliseconds to wait before assuming SSH
var socket_terminate_delay = 500; // milliseconds before destroying end'd sock

// generic function for destroying a socket
function destroySocket( socket, delay, comment ) {
  setTimeout(
    function () {
      try {
        socket.destroy();
      } catch ( err ) {
        if ( debugging ) {
          console.log( '  - error destroying socket "' + comment + '": ' + err );
        }
      }
    },
    delay
  );
}

// generic function for creating a proxied connection
function createSocketProxy( socketServer, type, address, port ) {
  if ( debugging ) {
    console.log( '  > opening ' + type + ' connection to ' + address + ':' + port );
  }

  // create socket and connect to back-end server
  var socketProxy = new net.Socket();
  socketProxy.connect(
    port,
    address,
    function () {
      if ( debugging ) {
        console.log( '  > proxied to ' + type );
      }
    }
  );

  // handle data from the server
  var bytesReceived = 0;
  socketProxy.on(
    'data',
    function ( chunk ) {
      if ( debugging ) {
        bytesReceived += chunk.length;
        console.log( '  < ' + chunk.length + ' ' + type + ' bytes (' + bytesReceived + ' total)' );
      }
      socketServer.write( chunk );
    }
  );

  // handle socket close by the server
  socketProxy.on(
    'end',
    function () {
      if ( debugging ) {
        console.log( '  < ' + type + ' close' );
      }
      var emptyPkt = '';
      socketServer.end( emptyPkt );
      // destroySocket( socketServer, socket_terminate_delay, 'server ' + type );
      socketProxy.end( emptyPkt );
      // destroySocket( socketProxy, socket_terminate_delay, 'proxy ' + type );
      socketProxy = undefined;
    }
  );

  // handle socket error from the server
  socketProxy.on(
    'error',
    function ( err ) {
      if ( debugging ) {
        console.log( '  < ' + type + ' error: ' + err );
      }
      socketServer.end();
      destroySocket( socketServer, socket_terminate_delay, 'server ' + type );
      if ( socketProxy ) {
        socketProxy.destroy(); // sometimes it is undef
      }
      socketProxy = undefined;
    }
  );

  // return the newly created socket
  return( socketProxy );
}

// function for handling connection from client
function socketCallback( socketServer ) {
  var clientSentTraffic = false;
  var socketProxy = undefined;

  if ( debugging ) {
    console.log( "- received connection from " + socketServer.remoteAddress + ":" + socketServer.remotePort );
  }

  // we want to set a timeout here
  setTimeout(
    function() {
      if ( ! clientSentTraffic ) {
        if ( debugging ) {
          console.log( "  > timeout, switching to SSH mode" );
        }
        socketProxy = createSocketProxy(
          socketServer,
          'SSH',
          internal_address,
          internal_ssh_port
        );
      } else {
        if ( debugging ) {
          console.log( "  > ignoring timeout" );
        }
      }
    },
    is_ssh_timeout
  );

  // handle data from client
  socketServer.on(
    'data',
    function( chunk ) {
      console.log( '  > ' + chunk.length + ' bytes' );

      // have we determined yet what kind of connection this is?
      if ( ! socketProxy ) {
        clientSentTraffic = true;

        socketProxy = createSocketProxy(
          socketServer,
          'SSL',
          internal_address,
          internal_ssl_port
        );
      }

      if ( socketProxy )
        socketProxy.write( chunk );
    }
  );

  // handle socket close by client
  socketServer.on(
    'end',
    function() {
      if ( debugging ) {
        console.log( '  > socket close' );
      }

      if ( socketProxy ) {
        socketProxy.end();
        //destroySocket( socketProxy, socket_terminate_delay, 'proxy' );
        socketProxy = undefined;
      }
      socketServer.end();
      //destroySocket( socketServer, socket_terminate_delay, 'server' );
    }
  );

  // handle socket error from client
  socketServer.on(
    'error',
    function( err ) {
      if ( debugging ) {
        console.log( '  > socket error: ' + err );
      }

      if ( socketProxy ) {
        socketProxy.end();
        destroySocket( socketProxy, socket_terminate_delay, 'proxy' );
        socketProxy = undefined;
      }
      if ( socketServer ) {
        socketServer.destroy();
      }
    }
  );
}

function main() {
  // check for any command line arguments
  if ( process.argv.length < 3 ) {
    console.log(
      'ssl/ssh switcher\n' +
      '\n' +
      'Arguments:\n' +
      '  -x <ip> - external interface IP address (default ' + external_address + ')\n' +
      '  -i <ip> - internal interface IP address (default ' + internal_address + ')\n' +
      '  -s <port> - internal SSH port number (default ' + internal_ssh_port + ')\n' +
      '  -w <port> - internal HTTPS port number (default ' + internal_ssl_port + ')\n' +
      '  -p <port> - external port to switch (default ' + external_port + ')\n' +
      '  -d - turn debugging on\n'
    );
    process.exit( 1 );
  }

  for ( var argn = 2; argn < process.argv.length; argn++ ) {
    if ( process.argv[argn] === '-p' ) {
      external_port = parseInt( process.argv[argn + 1] );
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-s' ) {
      internal_ssh_port = parseInt( process.argv[argn + 1] );
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-w' ) {
      internal_ssl_port = parseInt( process.argv[argn + 1] );
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-x' ) {
      external_address = process.argv[argn + 1];
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-i' ) {
      internal_address = process.argv[argn + 1];
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-d' ) {
      debugging = 1;
      continue;
    }
  }

  if ( debugging ) {
    console.log( 'server listening on ' + external_address + ':' + external_port );
  }

  // start TCP server with custom callback function
  var server = net.createServer( socketCallback );
  server.on(
    'error',
    function ( err ) {
      console.log( 'Error with server: ' + err );
    }
  );
  server.listen( external_port, external_address );
}

main();

When run without any command-line arguments you will get the help text:

root@myserver:~# /usr/local/node/bin/node sslsshproxy.js
ssl/ssh switcher

Arguments:
  -x <ip> - external interface IP address (default 0.0.0.0)
  -i <ip> - internal interface IP address (default 127.0.0.1)
  -s <port> - internal SSH port number (default 22)
  -w <port> - internal HTTPS port number (default 443)
  -p <port> - external port to switch (default 443)
  -d - turn debugging on

At a minimum you should specify the -x option with the IP address of your interface. You will find this by running the following command:

root@myserver:~# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr b8:dc:7f:89:2a:a0  
          inet addr:192.168.1.48  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: 2a02:ac8:1:3500::3/64 Scope:Global
          inet6 addr: fe80::bbdd:6fff:fe89:4c1a/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:4223010 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1418451 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:4455158325 (4.4 GB)  TX bytes:466186918 (466.1 MB)
          Interrupt:16 Memory:da000000-da012800 

Then use this in the command:

setsid node sslsshproxy.js -x 192.168.1.48 2>/dev/null >/dev/null &

Using a USA Keyboard for English and Russian in XFCE

I found it difficult to find the instructions I needed for this.

All I wanted was to use my USA standard keyboard with XFCE (I am running Xubuntu 12.04.1) and be able to switch between English (USA) and Russian keyboard layouts.

The following user-mode script, when run, will bind the right-hand-side Windows key to the keyboard switch (so pressing the right Windows key will swap from USA-to-Russian and Russian-to-USA):

setxkbmap \
  -option \
  -option "grp:rwin_toggle" \
  -layout "us,ru" \
  -model "pc105"

It is my understanding that the initial empty -option command-line switch clears all previous option settings. The -option "grp:rwin_toggle" specifies which button swaps the keyboard layouts. The -layout "us,ru" specifies which keyboards to toggle between (I suspect more than 2 are permitted). Finally the -model "pc105" is appropriate for many modern USA keyboards.

Listing Available Toggle Buttons

user@myhost$ grep xkb_symbols /usr/share/X11/xkb/symbols/group
xkb_symbols "menu_toggle" {
xkb_symbols "lwin_toggle" {
xkb_symbols "rwin_toggle" {
xkb_symbols "ctrls_toggle" {
xkb_symbols "alts_toggle" {
xkb_symbols "lshift_toggle" {
...

Я счастлив!

Node.JS HTTP and HTTPS Proxy

Writing a HTTP proxy using Node.JS is easy. There are plenty of examples on how to do this on the web. But things get trickier when you want to also proxy HTTPS requests. This is because http.createServer() doesn’t call the callback when a CONNECT request comes through. Instead you have to explicitly add a separate listener for the “connect” event and handle the socket communications directly. But this is very similar to handling a HTTP proxy request – you set up a socket connection to the target and then proxy communications between the client request socket and the proxied socket.

This proxy application accepts two command line arguments: -d switches on debugging (which you won’t need normally) and -p port selects which TCP port the proxy should listen on. For example:

user@myserver:~# /usr/local/node/bin/node webproxy.js -d -p 8080

Here is the source code:

var http = require('http');
var net = require('net');

var debugging = 0;

var regex_hostport = /^([^:]+)(:([0-9]+))?$/;

function getHostPortFromString( hostString, defaultPort ) {
  var host = hostString;
  var port = defaultPort;

  var result = regex_hostport.exec( hostString );
  if ( result != null ) {
    host = result[1];
    if ( result[2] != null ) {
      port = result[3];
    }
  }

  return( [ host, port ] );
}

// handle a HTTP proxy request
function httpUserRequest( userRequest, userResponse ) {
  if ( debugging ) {
    console.log( '  > request: %s', userRequest.url );
  }

  var httpVersion = userRequest['httpVersion'];
  var hostport = getHostPortFromString( userRequest.headers['host'], 80 );

  // have to extract the path from the requested URL
  var path = userRequest.url;
  result = /^[a-zA-Z]+:\/\/[^\/]+(\/.*)?$/.exec( userRequest.url );
  if ( result ) {
    if ( result[1].length > 0 ) {
      path = result[1];
    } else {
      path = "/";
    }
  }

  var options = {
    'host': hostport[0],
    'port': hostport[1],
    'method': userRequest.method,
    'path': path,
    'agent': userRequest.agent,
    'auth': userRequest.auth,
    'headers': userRequest.headers
  };

  if ( debugging ) {
    console.log( '  > options: %s', JSON.stringify( options, null, 2 ) );
  }

  var proxyRequest = http.request(
    options,
    function ( proxyResponse ) {
      if ( debugging ) {
        console.log( '  > request headers: %s', JSON.stringify( options['headers'], null, 2 ) );
      }

      if ( debugging ) {
        console.log( '  < response %d headers: %s', proxyResponse.statusCode, JSON.stringify( proxyResponse.headers, null, 2 ) );
      }

      userResponse.writeHead(
        proxyResponse.statusCode,
        proxyResponse.headers
      );

      proxyResponse.on(
        'data',
        function (chunk) {
          if ( debugging ) {
            console.log( '  < chunk = %d bytes', chunk.length );
          }
          userResponse.write( chunk );
        }
      );

      proxyResponse.on(
        'end',
        function () {
          if ( debugging ) {
            console.log( '  < END' );
          }
          userResponse.end();
        }
      );
    }
  );

  proxyRequest.on(
    'error',
    function ( error ) {
      userResponse.writeHead( 500 );
      userResponse.write(
        "<h1>500 Error</h1>\r\n" +
        "<p>Error was <pre>" + error + "</pre></p>\r\n" +
        "</body></html>\r\n"
      );
      userResponse.end();
    }
  );

  userRequest.addListener(
    'data',
    function (chunk) {
      if ( debugging ) {
        console.log( '  > chunk = %d bytes', chunk.length );
      }
      proxyRequest.write( chunk );
    }
  );

  userRequest.addListener(
    'end',
    function () {
      proxyRequest.end();
    }
  );
}

function main() {
  var port = 5555; // default port if none on command line

  // check for any command line arguments
  for ( var argn = 2; argn < process.argv.length; argn++ ) {
    if ( process.argv[argn] === '-p' ) {
      port = parseInt( process.argv[argn + 1] );
      argn++;
      continue;
    }

    if ( process.argv[argn] === '-d' ) {
      debugging = 1;
      continue;
    }
  }

  if ( debugging ) {
    console.log( 'server listening on port ' + port );
  }

  // start HTTP server with custom request handler callback function
  var server = http.createServer( httpUserRequest ).listen(port);

  // add handler for HTTPS (which issues a CONNECT to the proxy)
  server.addListener(
    'connect',
    function ( request, socketRequest, bodyhead ) {
      var url = request['url'];
      var httpVersion = request['httpVersion'];

      var hostport = getHostPortFromString( url, 443 );

      if ( debugging )
        console.log( '  = will connect to %s:%s', hostport[0], hostport[1] );

      // set up TCP connection
      var proxySocket = new net.Socket();
      proxySocket.connect(
        parseInt( hostport[1] ), hostport[0],
        function () {
          if ( debugging )
            console.log( '  < connected to %s/%s', hostport[0], hostport[1] );

          if ( debugging )
            console.log( '  > writing head of length %d', bodyhead.length );

          proxySocket.write( bodyhead );

          // tell the caller the connection was successfully established
          socketRequest.write( "HTTP/" + httpVersion + " 200 Connection established\r\n\r\n" );
        }
      );

      proxySocket.on(
        'data',
        function ( chunk ) {
          if ( debugging )
            console.log( '  < data length = %d', chunk.length );

          socketRequest.write( chunk );
        }
      );

      proxySocket.on(
        'end',
        function () {
          if ( debugging )
            console.log( '  < end' );

          socketRequest.end();
        }
      );

      socketRequest.on(
        'data',
        function ( chunk ) {
          if ( debugging )
            console.log( '  > data length = %d', chunk.length );

          proxySocket.write( chunk );
        }
      );

      socketRequest.on(
        'end',
        function () {
          if ( debugging )
            console.log( '  > end' );

          proxySocket.end();
        }
      );

      proxySocket.on(
        'error',
        function ( err ) {
          socketRequest.write( "HTTP/" + httpVersion + " 500 Connection error\r\n\r\n" );
          if ( debugging ) {
            console.log( '  < ERR: %s', err );
          }
          socketRequest.end();
        }
      );

      socketRequest.on(
        'error',
        function ( err ) {
          if ( debugging ) {
            console.log( '  > ERR: %s', err );
          }
          proxySocket.end();
        }
      );
    }
  ); // HTTPS connect listener
}

main();

Postscript

I wrote this proxy and have documented it because I couldn’t find any examples on the Internet for HTTPS proxies. I was trying to connect my web browser to my Node.JS HTTP proxy (which I’d duplicated from code I’d found elsewhere on the web) – but couldn’t see my callback being fired. After delving into the source of http.js that comes with Node.JS and looking at the documentation for the connect event later (it’s obvious when you think about it…) I eventually got myself a working HTTPS proxy (in the same script as the HTTP proxy on the same port).

Skype is a Horrible Application

I find myself somewhat disgusted with Skype in its blatant disregard for user settings. Particularly proxy settings. Skype version 4.0.0.8 for Ubuntu completely ignores whatever proxy settings you assign. It doesn’t even bother trying them. Instead it will do its darndest to sh** all over the network and if it gets half a connection somewhere it will just sit there for minutes until it gives up (without even trying the specified SOCKS or HTTPS proxies you’ve explicitly configured it to use).

I’ve sat with network sniffers on the proxy ports and can see that Skype will not bother trying the specified proxy ports.

Whilst Skype is oriented towards the consumer that doesn’t want to have to deal with the difficulties of punching holes through various firewall types: advanced users (particularly Linux users) should be obeyed.

Microsoft need to reel the Skype team and their arrogance in. Or a decent alternative to Skype needs to be found!

Using tsocks on 64-bit Ubuntu With Skype

The tsocks application can railroad Skype’s TCP connections to your SOCKS proxy server. However – if you have 64-bit Ubuntu and downloaded the Skype 64-bit Ubuntu package you’re in for a nasty surprise: the Skype executable is actually 32-bit and thus the 64-bit tsocks that apt-get installs is incompatible. You’ll get the following message:

user@myserver:~$ tsocks /usr/bin/skype
ERROR: ld.so: object '/usr/lib/libtsocks.so' from LD_PRELOAD cannot be preloaded: ignored.

You can confirm this by doing the following:

user@myserver:~$ file /usr/bin/skype
/usr/bin/skype: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=0xd6667f895e58d47966d6a572b13125e9110ee256, stripped

user@myserver:~$ file /usr/lib/libtsocks.so.1.8
/usr/lib/libtsocks.so.1.8: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x3e0b70e50a4abe5e6d82dde20eaa226d4dc2b1fb, stripped

Annoying as this is the solution is pretty straight-forward. You’ll have to compile your own 32-bit tsocks. Download the tarball from: http://sourceforge.net/projects/tsocks/files/.

If you don’t already have them you’ll need to install the compatibility libraries for building 32-bit on your 64-bit system:

sudo apt-get install libc6-dev-i386

Next untar and run configure on the tsocks source:

CFLAGS=-m32 LFLAGS=-m32 ./configure --prefix=/usr/local/tsocks32

Make:

make && make install

Now you can run Skype:

LD_PRELOAD=/lib/libtsocks.so.1.8 /usr/bin/skype

This will obey the same /etc/tsocks.conf file as your 64-bit tsocks preload library.