Brooke's Notes

Experience, Record, Share.

Class Inheritance From ES5 to ES6

| Comments

Test Case

For test-driven development, the test case always comes first. The Phone inherits the Node.js EventEmitter: a phone is a customized event emitter, only augmented with its own properties and methods.

A phone has its own name, can powerOn itself. Since it’s an event emitter, it can emit ‘income’ event, and can set the handler by addListener as event emitter.

test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var EventEmitter = require('events').EventEmitter;

//var Phone = require('./phone-es5');
//var Phone = require('./phone-util');
var Phone = require('./phone-es6');   // all implementation should have same interface

var p1 = new Phone('p1');             // class instantiation with parameter
p1.powerOn();                         // invoke instance methodl

console.log(p1 instanceof Phone);     // test ISA object of super class
console.log(p1 instanceof EventEmitter);
console.log(p1);                      // print instance at run-time

var incomeListener = function(number) {
    console.log('incomming call from: ' + number);
};

p1.addListener('income', incomeListener);
p1.income('+4478123456');             // invoke instance method
Phone.vendor('xiaomi');               // static/class method

Expect the following desired output:

1
2
3
4
5
6
7
8
9
10
11
[Phone instance method] p1 is powered on.
true
true
Phone {
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: 20,
  name: 'p1' }
incomming call from: +4478123456
[Phone static method] vendor: xiaomi

Prototype-Based Inheritance in ES5

In fact, javascript object is prototype-based, there isn’t class at all, everything in javascript is object. There’s only Object Extension (extends an exists object to a new object), rather than Class Inheritance (create new subclass that inherits the parent class). To implement Inheritance is to build/extend the appropriate prototype chain. The prototype-based inheritance is more flexible, and it’s easy to emulate traditional textbook or java-like class or inheritance.

phone-es5.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var EventEmitter = require('events').EventEmitter;
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
Phone.prototype = new EventEmitter();
// same as: Phone.prototype = Object.create(EventEmitter.prototype);
Phone.prototype.constructor = Phone;
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

Using Node.js util.inherits

Node.js util module provides syntactical sugar for easily implemention of class-like inheritance.

phone-util.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
util.inherits(Phone, EventEmitter);
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

util.inherits levarages Object.create() of ES5, the inherits line equals to:

1
2
3
4
5
6
7
8
9
Phone.super_ = EventEmitter;
Phone.prototype = Object.create(EventEmitter.prototype, {
  constructor: {
    value: Phone,
    enumerable: false,
    writable: true,
    configurable: true
  }
});

Class Syntax of ES6

From node-v4.0, lots of ES6 features are implemented by default(shipping). Such as block scoping, collections, promises and Arrow Functions. And Class syntax is also fully supported. But, note that class is just another syntactical sugar:

JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance.
The class syntax is not introducing a new object-oriented inheritance model to JavaScript.
JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

With class syntax, class/inheritance can be implemented in a more straightforward manner.

phone-es6.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'
var EventEmitter = require('events').EventEmitter

class Phone extends EventEmitter {
  constructor(name) {
    super()
    this.setMaxListeners(20)
    this.name = name
  }
  powerOn() {
    console.log('[Phone instance method] ' + this.name + ' is powered on.')
  }
  income(number) {
    this.emit('income', number)
  }
  static vendor(name) {
    console.log(`[Phone static method] vendor: ${name}`)
  }
}

module.exports = Phone

Note: ‘use strict’ is required, and we use the template string syntax in static method (class method). And class declaration (as well as let, const, and function in strict mode) cannot be hoisted, unlike var and function.

In summary, with ES6 support in Node.js v4.0, class inheritance has never been more easier. So, let’s embrace ES6.

SSH Port Forwarding

| Comments

Overview

利用SSH端口转发,可以方便实现各类翻墙打洞需求。本文详细介绍SSH端口转发的三种用法,以及其所适用的场景。

Environment

  • L0: localhost behind NAT, with lan ip 192.168.0.100
  • L1: host within same lan of L0, with lan ip 192.168.0.101
  • R0: remote host (cloud vps) with private ip 10.0.0.100
  • R1: remote host (cloud vps) with private ip 10.0.0.101

L0 can ssh (default port 22) to R0 by its public domain name r0.example.com with public key, like this:

1
ssh user@r0.example.com

Forwarding local host (-L)

Usage

Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side.

SSH -L is good for exposing a remote port locally.

增强本地主机的访问远程主机(局域网)能力。

Example

1. Forward L0 to R0

The mongod has the http web service, listening only on localhost:28017. With the following command:

1
ssh -NL 28018:localhost:28017 user@r0.example.com

Then the remote service on 28017 will be accessed on L0’s 28018 port from L0.

2. Forward L0 to R1

Suggest that there’s an API server on R1, listening only on port 10030 of lan (10.0.0.0/24). Because R0 can access R1’s private address of the same lan, so the following command will make R1’s service of port 10030 accessible from L0’s local port 10031.

1
ssh -NL 10031:10.0.0.101:10030 user@r0.example.com

Note, use R1’s private ip 10.0.0.101 instead of localhost.

3. Forward L1 to R1

Say R0 can access R1 through ssh command: ssh secret-user@10.0.0.101, then the following command

1
ssh -NL 192.168.0.100:2222:10.0.0.101:22 user@r0.example.com

will forward L0’s port 2222 to the R1’s port 22, and even make L0 listening within the lan. So from L1, we can access to R1 by this command:

1
ssh -p 2222 secret-user@192.168.0.100

Awesome!

Forwarding remote host (-R)

Usage

Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side.

So the SSH -R can be useful when accessing a box hidden behind a NAT.

增强远端主机的访问本地主机(局域网)的能力。

Example

1. Forward R0 to L0

1
ssh -NR 2222:localhost:22 user@r0.example.com

2. Forward R0 to L1

1
ssh -NR 2222:192.168.0.101:22 user@r0.example.com

3. Forward R1 to L1

Unlike local forwarding, remote forwarding from R1 to L1 is not permitted by default due to security policy, unless we change sshd_config file on R0, with the additional config line:

1
GatewayPorts = yes

then the ultimate tunnel created, with which we can access L1’s port on machine R1. Cool?

Dynamic forwarding (-D)

Usage

Specifies a local “dynamic” application-level port forwarding...and ssh will act as a SOCKS server.

Example

1
ssh -ND 1080 user@r0.example.com

Then, there exists a SOCKS server on L0, and we can use it as a proxy server.

1
curl -x socks5h://localhost:1080 https://www.google.com

Shadowsocks Tutorial

| Comments

Overview

socks5

Socket Secure (SOCKS) is an Internet protocol that routes network packets between a client and server through a proxy server. SOCKS5 additionally provides authentication so only authorized users may access a server.

Practically, a SOCKS server proxies TCP connections to an arbitrary IP address, and provides a means for UDP packets to be forwarded.SOCKS operates at a lower level than HTTP proxying: SOCKS uses a handshake protocol to inform the proxy software about the connection that the client is trying to make, and then acts as transparently as possible. SOCKS proxies can also forward UDP traffic and work in reverse.

shadowsocks

Shadowsocks is a fast tunnel proxy that can help bypass firewalls.

test environment

Remote VPS OS: Ubuntu-14.04 Server VPS on DigitalOcean

Local Client OS: Ubuntu-14.04 Desktop

Installation

install package

1
2
sudo apt-get install python-pip
sudo pip install shadowsocks

remote server

1
ssserver -k PASSWORD -d start

This will run shadowsocks remote server listening at 0.0.0.0:8388

local server

1
sslocal -s VPS -k PASSWORD

This will run shadowsocks local server listening at 127.0.0.1:1080

Client Configuration

Web Browser

Thanks for proxy setting extension like FoxyProxy in Firefox, SwitchyOmega in Chrome, socks proxy tunnel can be easily configured.

Choose socks v5 protocal at localhost:1080, and get it work.

Curl

1
curl -x socks5h://localhost https://www.google.com

Note that we specify protocal as socks5h instead of socks5, which means we use the specified SOCKS5 proxy, and let the proxy resolve the host name, otherwise it may not resolve hostname. And port 1080 is by default.

Nodejs Request

request utility doesn’t support socks proxy by default, so we need socks5-http-client and socks5-https-client to create agent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var request = require('request');
var url = require('url');
var socks5HttpAgent = require('socks5-http-client/lib/Agent');
var socks5HttpsAgent = require('socks5-https-client/lib/Agent');

var urlString = process.argv[2];
var url = url.parse(urlString);
var agentClass = (url.protocol === 'https:') ? socks5HttpsAgent : socks5HttpAgent;
var agentOptions = {
  socksHost: 'localhost', // defaults to localhost
  socksPort: 1080         // defaults to 1080
};
var options = {
  uri: url,
  agentClass: agentClass,
  agentOptions: agentOptions,
  method: 'GET'
};
request(options, function(err, res, body) {
  if (err) console.log(err.message);
  else {
    console.log(res.statusCode);
    console.log(body);
  }
});

Finally, test it.

1
2
node socks-proxy.js http://www.google.com
node socks-proxy.js https://www.google.com

Session With Express and Nginx

| Comments

为什么一定要持久化存储会话信息

如果会话信息仅仅存储在内存…

  • 服务器重启,用户将会登出
  • 无法在多个node进程(或集群环境)中共享会话

所以,会话(无论是HTTP会话还是Web-Socket会话)都需要在服务器端持久化存储在数据库中,如Redis/Mongo。

环境: Express 4.12.2, Nginx 1.8.0

Express添加Redis会话存储

需要的相关库或组件:

1
2
3
4
5
6
7
8
9
10
11
var http = require('http');
var socketio = require('socket.io');
var session = require('express-session');           // 会话支持
var passport = require('passport');                 // 身份认证,依赖express-session
var RedisStore = require('connect-redis')(session); // redis存储,用于持久化会话
var RedisAdapter = require('socket.io-redis');      // 存储web-socket的连接
var mongoose = require('mongoose');
// ...
var app = express();
var server = http.Server(app);
var sio = socketio(server);

配置Redis会话存储HTTP会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var sessionMiddleware = session({
  name: 'XXX:sess',
  secret: 'XXX',
  proxy: true,
  resave: false,
  store: new RedisStore(),
  saveUninitialized: false,
  cookie: {
    //secure: true, // only https
    maxAge: 7*24*60*60*1000 // 1 week
  }
});
// 应用会话存储中间件
app.use(sessionMiddleware);
// passport登录认证
app.use(passport.initialize());
app.use(passport.session());

配置Redis会话存储Web-Socket会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sio.adapter(RedisAdapter({host: 'localhost', port: 6379}));
// 为socket server添加redis存储的中间件
sio.use(function(socket, next) {
  sessionMiddleware(socket.request, socket.request.res, next);
});
// 对未登录的用户禁用socket支持
sio.use(function(socket, next){
  var passport = socket.request.session.passport;
  if (!passport || !passport.user) {
    logger.debug('no socket support without login');
    next(new Error('Authentication error'));
  }
  else next();
});

Nginx配置支持负载均衡

Web-Socket的连接建立需要同一个tcp连接上的多次握手支持,要求比一般的HTTP会话高一些。 所以为实现多进程的共享会话支持,需要配置支持iphash的upstream。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
upstream nodes {
  ip_hash;
  server localhost:8001;
  server localhost:8002;
  server localhost:8003;
  server localhost:8004;
  keepalive 512;
}

server{
  listen 443 ssl;
  server_name example.com;
  location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_pass http://nodes;
  }
}

注意,可以用pm2启动多个node服务进程,但必须以fork mode启动,而不是-i的集群方式启动,负载均衡完全交给nginx即可。 如此:

1
2
3
4
pm2 start -f --name web1 app.js -- 8001
pm2 start -f --name web2 app.js -- 8002
pm2 start -f --name web3 app.js -- 8003
pm2 start -f --name web4 app.js -- 8004

参考资料

Socket.io doc: using multiple nodes

Installing Memcache With PHP

| Comments

install memcached

OS: Ubuntu-14.04 server

PHP: 5.6.8 source build

install memcached with apt-get

1
sudo apt-get install memcached

verification

1
echo "stats settings" | nc localhost 11211

install memcache php extension

download source from pecl

1
wget http://pecl.php.net/get/memcache-2.2.7.tgz

install with pecl

1
/path/to/php/bin/pecl install /path/to/memcache-2.2.7.tgz

There exsists the lib/php/extensions/no-debug-non-zts-XXXXXXX/memcache.so

update the php.ini

add “extension=memcache.so” to php.ini

reload php-fpm server

1
kill -USR2 $(cat /path/to/php/var/run/php-fpm.pid) # no need sudo

Check the php.info page, make sure that memcache section exists

Blog Writing With Octopress 3

| Comments

initialize workspace

1
2
3
4
5
  git clone git@github.com:Pro-YY/pro-yy.github.io.git -b source
  cd pro-yy.github.io/
  git clone git@github.com:Pro-YY/pro-yy.github.io.git -b master _deploy
  apt install ruby-bundler ruby-dev
  bundle install

write post

1
2
  rake new_post["Blog Writing with Octopress 3"]
  vim source/_posts/2015-07-15-blog-writing-with-octopress-3.markdown

preview

1
2
  rake generate
  rake preview

preview at http://localhost:4000

deploy

1
  rake deploy

commit source

1
2
  git commit -asm 'new post'
  git push

update octopress

1
2
3
4
5
  git remote add octopress git://github.com/imathis/octopress.git
  git pull octopress master
  bundle install
  rake update_source
  rake update_style

category list/cloud

plugin from https://github.com/alswl/octopress-category-list