Writing a local IP proxy with node.js

I recently had the issue where I needed to proxy traffic from my IP address to a different server, in order to perform mobile testing. The setup I have is a "local" server running on my development machine with a local hostname, since my phone can not access this hostname, I decided to use a proxy that allows me to visit the IP address of my development machine on my phone and all traffic would forward to the local domain.

I decided to use Express.JS as it is an easy to use server. Express also has large amount of community contributed middlewares, such as express-http-proxy which fills all of my requirements.

Additionally I use the package "ip" that will discover the local IP address of my development machine.

These dependencies can be installed with:

yarn add express express-http-proxy ip

The proxy server is quite small, and looks as follows:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

const ip = require('ip');
const proxy = require('express-http-proxy');
const app = require('express')();

const port = 8888;
const proxyHostname = process.argv[2];
const proxyURL = `https://${proxyHostname}/`;
const ipAddress = ip.address();
const serverURL = `http://${ipAddress}:${port}/`;

app.use(
  '/',
  proxy(proxyHostname, {
    https: true,

    userResDecorator: function(_proxyRes, proxyResData) {
      let html = proxyResData.toString('utf8');

      html = html.replace(new RegExp(proxyURL, 'g'), serverURL);

      return html;
    }
  })
);

app.listen(port);
console.log(`Proxying all traffic from ${serverURL} to ${proxyURL}`);

Since my local server ("https://my-project.local/") uses HTTPS but with a self generated SSL certificate, the proxy will encounter SSL errors. So the first line of the proxy tells the node process not to crash when this happens.

Next I import ip, proxy and, app from the installed node modules.

Then a few constants are defined to set the port number for the proxy to run on, the server address we want to proxy too, and read the IP address of the development machine. process.argv is used so that you can pass the hostname of the local server to the node process instead of having to hard-code it in the file.

The largest part of this script is then telling express to use the proxy middleware which I configure to proxy all traffic coming in to the server (path /) and proxy it to the local development server. The options passed to proxy tell it that this server uses HTTPS and additionally there is a callback given called userResDecorator.

userResDecorator allows me to intercept the response from the local server before passing it back to the client. In this method what I am doing is replacing all URLs in the returned HTML and rewriting them from the my-project.local to instead use the proxy address. This means all <a> links etc. now point to the correct location.

Finally we tell the express app to listen on our given port and log to the console the address of the proxy that is running.

Now, when I save the script to the file proxy.js run the proxy server with node proxy.js my-project.local, I see a message that my proxy is listening at "http://192.168.1.99:8888/". When I visit this URL on my mobile device, I see my project as if I was visiting from my development machine.

For the full source code, you can find it on GitHub: https://github.com/BlakeSimpson/local-ip-proxy

Serving static files with Server.js

Server.js is a node based server that is based on Express.js but provides you with an even easier interface to write your server with.

As server.js was only recently released, I was trying to find a tutorial on how to serve a directory of static files but could not find one. After reading the documentation, I noticed that since Server.js is built on express, you can use express middlewares out of the box. Since Express already has a express.static() method, I noitced that I could use this to serve static files as I wanted.

The example server is very basic, it simply serves all files in the current directory.

First of all, I import server and express:

const server = require('server');
const express = require('express');

Next I import the modern method from server.utils that allows us to attach Express middleware to our server:

const { modern } = server.utils;

I then call the express.static middleware, and pass it to modern:

const middleware = modern(express.static('./'));

Finally, I start the server and pass the static middleware:

server(middleware);

The final server is only 7 lines long:

const server = require('server');
const express = require('express');
const { modern } = server.utils;

const middleware = modern(express.static('./'));

server(middleware);

You can test this by writing this to a file called server.js and calling it with node server.js. The server will then be running at: http://localhost:3000/.

If you then write an index.html file in the same directory, it will be served when visiting this address in your browser.

For full example code, please visit this repo: https://github.com/BlakeSimpson/serverjs-static-files

React Native: How to truncate text

React Native allows you to automatically truncate text that will not fit within its <View> container.

There is a property numberOfLines that can be passed to the <Text> node. For example:

<View style={styles.container}>
  <Text numberOfLines={1} style={styles.text}>This is a very long text that will overflow on a small device</Text>
</View>

In most cases this is enough for the device to truncate the text, automatically adding ellipsis to the end of the string () after however many lines of text you have specified (In this case, we only want 1 line).

If you however find that you have added the numberOfLines property but the text is still not truncated, as so:

React Native truncation not working

Then make sure your text node styles have the flex: 1 property. For the example above, the StyleSheet would look like:

var styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    padding: 10
  },

  text: {
    flex: 1
  }
});

Which will allow the <Text> node to apply the truncation properly:

React Native truncation working with flex

Login as root with no password on MySQL 5.7.9 with El Capitan (Homebrew)

The MySQL version that will be installed through Homebrew for El Capitan is currently version 5.7.9. This is an upgrade from the 5.6.x versions that brew would install on previous OS X versions.

This new version of MySQL has some security features and syntax changes that may be confusing. Personally after installing through Homebrew I did not know the default password and wanted to set up the server to accept the root user with no password, for my development environment. Read on to see how I achieved this.

Getting a root user with no password

Run the following highlighted steps in the Terminal.

  • Lines prefixed with "$" are a terminal command.
  • Lines prefixed with "mysql >" are entered in the MySQL command line client.

Stop the server

$ mysql.server stop

Also unload the launchctl file, if you have one, to prevent the server restarting:

$ launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

Starting Safe Server

Now that MySQL has stopped running, you can start the mysqld_safe process. We use the --skip-grant-tables option to prevent permission errors when trying to update the password later.

$ mysqld_safe --skip-grant-tables &

Reseting the password: Part 1

Next we will enter the MySQL command line and ask the server to change the remove the password for the "root" user.

$ mysql -u root

mysql> FLUSH PRIVILEGES;
mysql> use mysql;
mysql> UPDATE user SET authentication_string=PASSWORD('') WHERE User = 'root';
mysql> exit

You may notice that in MySQL 5.7.6+ the password field has been changed to authentication_string.

Reseting the password: Part 2

Now that the login password has been set, we can kill the safe server:

$ killall mysqld

Next restart the "real" MySQL server. This will take a few moments.

$ mysql.server start

Now we can log in to MySQL successfully as the root user with no password. Login with the following command (when prompted for the password, don't type anything, just hit enter):

$ mysql -u root -p
Enter password:

Now the process gets confusing. You are logged in as root successfully but try to run use mysql;, you see the following error:

mysql> use mysql;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

Now you must set the password again using the new MySQL 5.7.6 method (ALTER USER):

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '';

Done!

Finally we are done. You can exit the MySQL CLI using the exit command and are back to the terminal. You can try to log in again and access your tables and not experience any errors.

If you wish you can now reload the launchctl plist for MySQL so that the server starts automatically when your computer does.

$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

Fix Homebrew error "/usr/local/bin is not writable" on OS X El Capitan

For some reason since the El Capitan upgrade the /usr/local/bin and /usr/local/share directories are not owned by the current logged in user. This means that Homebrew is not able to access these locations and add the files it needs to install software for you on OS X.

To fix this you will need to use chown in the Terminal application to reclaim permissions for these directories. Open up a terminal and run:

sudo chown -R `whoami`:admin /usr/local/bin

You will be prompted for your password when using the sudo command.

Then run:

sudo chown -R `whoami`:admin /usr/local/share

Now you can rerun your brew install command.

Note: If you have already installed the homebrew package, you will simply need to link it instead. If you see an error similar to:

brew install the_silver_searcher
Warning: the_silver_searcher-0.31.0 already installed, it's just not linked

Then simply run:

brew link the_silver_searcher

Do you run Sophos Antivirus?

I have heard that currently there is a problem with Sophos on El Capitan. When its scans your computer it will touch the /usr/local/ directory and affect the permissions. Sophos are working on a fix for this but currently you may discover permission problems even without a log out or system reboot.

Fix locked OS X volume controls

If your Mac audio controls are locked (greyed out in the control menu and the keyboard buttons don't work), you can open up the Terminal application and run the following command:

sudo killall coreaudiod

You will be prompted for your password and then your audio controls will restart, brining them back to life.

Prevent the Cordova web view from bouncing

When building an iOS project with Cordova the setting that allows web views to bounce will be turned on by default, resulting in an effect while scrolling that can look ugly, like so:

My FIlm List web view bounce

Since Cordova versions 1.5+ the cordova.plist file has been replaced with the config.xml that you find in the root directory of your Cordova project. This is where you set the configuration property to inform iOS to disable web view bouncing.

Instead of editing a plist file in XCode, instead we will add the configuration to the /your-project/config.xml file. Open this file with a text editor and add the line to the end of the <widget> block, before the final </widget>.

<preference name="DisallowOverscroll" value="true" />

Now rebuild the iOS application from the terminal.

cordova build ios

When you next run the application in XCode on a simulator or device, the bouncing problem will be solved.

Sorting a German date format in Tablesorter.js

The German date format is 01.05.2015 as opposed to the UK date format 01/05/2015 which Tablesorter.js comes prebuilt to parse.

To sort by a German date, you have to add a custom parser. Here is one that will work for a date with the German dot notation.

$.tablesorter.addParser({
  id: 'dotDate',
  is: function (content) {
    return /\d{1,2}\.\d{1,2}\.\d{1,4}/.test(content);
  },
  format: function (content) {
    content = content.replace(/\-/|:|\.|\//g,' ').split(' ');
    return $.tablesorter.formatFloat(new Date(content[2], content[1]-1, content[0]).getTime());
  },
  type: 'numeric'
});

Simply add this parser and then tell your tablesorter initializer function which column the German date is in.

$('table').tablesorter({
  headers: {
    0: { sorter: 'dotDate' }
  }
});

If your date only has 2 numbers for the year instead of 4, you can adjust the is method to use the regex /\d{1,2}\.\d{1,2}\.\d{1,2}/.

© Blake Simpson, 2012 – 2017