четверг, 23 ноября 2017 г.

Redux Очень короткая шпаргалка

import {createStore, combineReducers, applyMiddleware} from 'redux';

// Counter

var counterActionType = {
    'UP': 'UP'
};

function couterActionCreator (value) {
    return {
  type: counterActionType.UP,
        value: value
    };
}

function counterReducer (state, action) {
    if (state === undefined) {state = {counter: 0};}
    switch (action.type) {
        case counterActionType.UP: return Object.assign({}, state, {counter: state.counter + action.value});
        default: return state;
     }
}

// Time

var timeActionType = {
    'UPDATE': 'UPDATE'
};

function timeActionCreator (value) {
    return {
        type: timeActionType.UPDATE,
        value: value
    };
}

function timeReducer (state, action) {
    if (state === undefined) {state = {time: new Date()};}
    switch (action.type) {
        case timeActionType.UPDATE: return Object.assign({}, state, {time: new Date(state.time.getTime() + action.value)});
        default: return state;
    }
}

var rootReducer = combineReducers({
    counterObject: counterReducer,
    timeObject: timeReducer
});

var initialState = {
    counterObject: {counter: 2},
    timeObject: {time: new Date()}
};

var store = createStore(rootReducer, initialState, applyMiddleware(/* createLogger(), thunkMiddleware */));

var unsubscribeStore = store.subscribe(function storeChangeListener () {
    console.log(store.getState());
});

console.log(store.getState().counterObject.counter);

store.dispatch(couterActionCreator(1));

console.log(store.getState().counterObject.counter);

unsubscribeStore();

store.dispatch(couterActionCreator(1));

console.log(store.getState().counterObject.counter);

store.dispatch(timeActionCreator(1000000));

console.log(store.getState().timeObject.time);

среда, 22 ноября 2017 г.

React Очень короткая шпаргалка



import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class Time extends Component {
  // Actions
  handleCounterUp () {
    this.setState(Object.assign({}, this.state, {counter: this.state.counter + 1}));
  }
  handleInputChange (event) {
    this.setState(Object.assign({}, this.state, {value: event.target.value.toUpperCase()}));
  }
  handleSubmitForm (event) {
    event.preventDefault();
    alert('Value: ' + this.state.value);
  }
  // First render (only once)
  constructor (props) {
    super(props);
    this.state = {
      time: null,
      counter: 0,
      value: ''
    };
    this.handleCounterUp = this.handleCounterUp.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleSubmitForm = this.handleSubmitForm.bind(this);
  }
  // setState OK
  componentWillMount () {
    this.setState(Object.assign({}, this.state, {time: new Date()}));
  }
  render () {
    return (
      <div>
        {/* Это комментарий */}
        <p>
          <span>пробелы</span>{' '}
          <span>между тэгами</span>{' '}
          <span>на разных строках</span>
        </p>
        <ul>
          {
            [1, 2, 3].map(
              function (value, index) {
                return <li key={index}>{value}</li>;
              }
            )
          }
        </ul>
        <div ref="message">My message</div>
        <div>{this.state.time.toString()}</div>
        <div>{this.props.time.toString()}</div>
        <div>{this.state.counter}</div>
        <div onClick={this.handleCounterUp}>Counter up</div>
        <form onSubmit={this.handleSubmitForm}>
          <input type="text" value={this.state.value} onChange={this.handleInputChange} />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
  componentDidMount () {
    var self = this;
    this.timeoutId = setTimeout(function () {
      self.setState(Object.assign({}, self.state, {time: new Date()}));
    }, 1000);
    // Actions after we updated DOM
    // Here we can use jQuery to manipulate the DOM
    // $('input-id).val('');
    console.log(this.refs.message.innerText);
  }
  // Unmount in the end
  componentWillUnmount () {
    clearTimout(this.timeoutId);
  }
  // Next Props change - setState OK
  componentWillReceiveProps (nextProps) {
    this.setState(Object.assign({}, this.state, {time: nextProps.time}));
  }
  // Props change or only Next State change
  shouldComponentUpdate (nextProps, nextState) {
    return this.state.time.getTime() !== nextProps.time.getTime();
  }
  // No setState
  componentWillUpdate (nextProps, nextState) {
    // Actions before we updated DOM
  }
  // Render and then
  componentDidUpdate (prevProps, prevState) {
    // Actions after we updated DOM
    // Here we can use jQuery to manipulate the DOM
    // $(this._ref.input).val(');
    console.log(this.refs.message.innerText);
  }
}

ReactDOM.render(<Time time={new Date(new Date().getTime() + 100000000)} />, document.getElementById('root'));

пятница, 27 октября 2017 г.

TypeScript Краткая шпаргалка

// Объявление переменных

var a: number = 1;
let b: number = 2;
const c: number = 3;

// Типы переменных

let an: any = 0;
let unusable: void = undefined; // Может иметь только значение undefined или null
let undef: undefined = undefined;
let nul: null = null;
let bool: boolean = true;
let num: number = 1;
let str: string = 'text';
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
let arr3: ReadonlyArray<number> = [1, 2, 3];
let tuple: [number, string] = [1, 'text'];
let matrix: number[][] = [[1, 2], [3, 4]];
let obj: {a?: number} =  {a: 1};
let union: undefined | null | string | number = 'text';

enum Color {Red = 1, Green = 2, Blue = 3}
let enu: Color = Color.Red;

function func (a?: number, b?: string): void {
    return;
}

function error (message: string = 'text'): never {
    throw new Error(message);
}

let [first, second, ...rest] = [1, 2, 3, 4];

let {one, two}: {one: string, two: number} = {
    one: 'text',
    two: 1
};

// Приведение типов переменных

let someValue: any = 'this is a string';
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;

// Объявление функций

function func1 (a?: number, b?: string): void {
    return;
}

const func2 = function  (a?: number, b?: string): void {
    return;
};

const func3 = (a?: number, b?: string): void => {
    return;
};

const func4: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

function func5 (): Fish | Bird {
    // ...
}

// Перегрузка функции

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    if (typeof x == "object") {
        return Math.floor(Math.random() * x.length);
    } else if (typeof x == "number") {
        return {suit: suits[Math.floor(x / 13)], card: x % 13 };
    }
}

// Объявление классов

abstract class Animal {

    abstract makeSound(): void;

    move(): void {
        console.log('roaming the earth...');
    }

}

class A extends B implements BInterface {

    readonly numberOfLegs: number = 8;

    private currentTime: Date;

    protected name: string;

    public h: number;

    public constructor (h: number, public m: number) {
        super(m, h);
    }

    public setTime (d: Date) {
        this.currentTime = d;
    }

    private count = (a: number): void => {
        super.count(a);
        console.log(a);
    }

    private static say (b: string): string {
        return b;
    }

    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName (newName: string) {
        this._fullName = newName;
    }

}

let a: A = new A(10, 20);

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

// Объявление интерфейсов

interface IOne {
     str?: string;
     [index: number]: string;
     func (a: number): number;
}

interface ITwo extends IOne {
    readonly obj: {b: number};
    [propName: string]: any;
    new (hour: number, minute: number);
}

// Объявление дженериков

function identity<T> (arg: T): T {
    return arg;
}

let output = identity<string>('myString');

let myIdentity: <T>(arg: T) => T = identity;

interface GenericIdentityFn {
    <T>(arg: T): T;
}

let myIdentity: GenericIdentityFn = identity;

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
    return x + y;
};

function create<T> (c: {new(): T;}): T {
    return new c();
}

interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

function extend<T, U> (first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

function pluck<T, K extends keyof T> (o: T, names: K[]): T[K][] {
    return names.map(n => o[n]);
}

// Объявление типов

type Name = string;

type NameResolver = () => string;

type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

type Easing = "ease-in" | "ease-out" | "ease-in-out";

type Shape = Square | Rectangle | Circle;

type Container<T> = {
    value: T
};

type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
}

type LinkedList<T> = T & {next: LinkedList<T>};

type Proxy<T> = {
    get(): T;
    set(value: T): void;
}

type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>;
}

function proxify<T>(o: T): Proxify<T> {
   // ... wrap proxies ...
}

let proxyProps = proxify(props);

let personProps: keyof Person; // 'name' | 'age'

// Декларирование типов

type Alias = {num: number}

interface Interface {
    num: number;
}

declare function aliased (arg: Alias): Alias;

declare function interfaced (arg: Interface): Interface;

declare function require (moduleName: string): any;

// Пространство имен

namespace Validation {

    export class ZipCodeValidator implements StringValidator {
        isAcceptable (s: string) {
            return s.length === 5 ;
        }
    }

}

namespace Shapes {
    export namespace Polygons {
        export class Triangle { }
        export class Square { }
    }
}

declare namespace D3 {

    export interface Selectors {
        select: {
            (selector: string): Selection;
            (element: EventTarget): Selection;
        };
    }

    export interface Event {
        x: number;
        y: number;
    }

    export interface Base extends Selectors {
        event: Event;
    }

}

declare var d3: D3.Base;

// Декораторы

function f () {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g () {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

// f(): evaluated
// g(): evaluated
// g(): called
// f(): called

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

// Импорт

import "./my-module.js";

export * from "./ZipCodeValidator";
import * as validator from "./ZipCodeValidator";

import ZipCodeValidator from "./ZipCodeValidator";
import {ZipCodeValidator} from "./ZipCodeValidator";
import {ZipCodeValidator as ZCV} from "./ZipCodeValidator";

// Экспорт

declare let $: JQuery;

export default $;

export default "123";

export default function (s: string) {
    return s.length === 5;
}

export default class ZipCodeValidator {
    isAcceptable (s: string) {
        return s.length === 5;
    }
}

export ZipCodeValidator;

export interface StringValidator {
    isAcceptable (s: string): boolean;
}

export class LettersOnlyValidator implements StringValidator {
    isAcceptable (s: string) {
        return lettersRegexp.test(s);
    }
}

среда, 25 октября 2017 г.

React, Readux, React-Redux, Redux-Thunk краткая шпаргалка

import React from "react";
import {render} from "react-dom";
import {createStore, combineReducers, applyMiddleware} from "redux";
import thunk from "redux-thunk";
import {Provider, connect} from "react-redux";

const couterUpActionCreator = value => {
  return {
    type: "COUNTER_UP",
    value: value
  };
};

const counterDownActionCreator = value => {
  return {
    type: "COUNTER_DOWN",
    value: value
  };
};

const counterDownAsyncActionCreator = value => {
  return dispatch => {
    setTimeout(() => {
      dispatch(counterDownActionCreator(value));
    }, 1000);
  };
};

const countReducer = (state = 0, action) => {
  switch (action.type) {
    case "COUNTER_UP": return state + action.value;
    case "COUNTER_DOWN": return state - action.value;
    default: return state;
  }
};

const counterReducer = combineReducers({
  count: countReducer
});

const rootReducer = combineReducers({
  counter: counterReducer
});

const initialState = {
  counter: {
    count: 0
  }
};

const store = createStore(rootReducer, initialState, applyMiddleware(thunk));

store.subscribe(() => {
  console.log(store.getState());
});

class Counter extends React.Component {
  styles = {
    fontFamily: "sans-serif",
    textAlign: "center"
  }
  constructor (props) {
    super(props);
  }
  componentWillMount () {
    console.log("Component Will Mount");
  }
  render () {
    console.log('Render');
    return (
      <div style={this.styles}>
        <div>{this.props.propsFromApp}</div>
        <br />
        <div onClick={this.handleUpClick}>^</div>
        <div>{this.props.count}</div>
        <div onClick={this.handleDownClick}>v</div>
      </div>
    );
  }
  componentDidMount () {
    console.log("Component Did Mount");
  }
  componentWillReceiveProps (nextProps) {
    console.log("Component Will Recieve Props");
  }
  shouldComponentUpdate (nextProps, nextState) {
    console.log("Should Component Update");
    if (this.props.propsFromApp !== nextProps) {
      return true;
    } else {
      return false;
    }
  }
  componentWillUpdate (nextProps, nextState) {
    console.log("Component Will Update");
  }
  componentDidUpdate (prevProps, prevState) {
    console.log("Component Did Update");
  }
  componentWillUnmount () {
    console.log("Component Wiil Unmount");
  }
  handleUpClick = () => {
    this.props.counterUp();
  }
  handleDownClick = () => {
    this.props.counterDown();
  }
}

const mapStateToProps = state => {
  return {
    count: state.counter.count
  };
};

const mapDispatchToProps = dispatch => {
  return {
    counterUp: () => {
      dispatch(couterUpActionCreator(1));
    },
    counterDown: () => {
      dispatch(counterDownAsyncActionCreator(1));
    }
  };
};

const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      propsFromApp: 0
    };
  }
  render () {
    return (
      <div>
        <div onClick={this.handleClick}>UPDATE STATE AND PROPS</div>
        <ConnectedCounter propsFromApp={this.state.propsFromApp} />
      </div>
    );
  }
  handleClick = () => {
    this.setState(
      Object.assign(
        {},
        this.state,
        {
          propsFromApp: this.state.propsFromApp + 1
        }
      )
    );
  }
}

render(<Provider store={store}><App /></Provider>, document.getElementById("root"));

понедельник, 18 сентября 2017 г.

Node.js Cluster - особенности работы

const cluster = require('cluster')
        , http = require('http')
        , os = require('os');

const cpus = os.cpus().length;

// 3 Уровня создание кластера воркеров

// 1 уровень - уровень прослушивания всех событий для всех воркеров

cluster.on('message', function (worker, message, handle) {
    console.log('All messages: ' + worker.id + ' | ' + message);
});

cluster.on('fork', function (worker) {
    console.log('Worker ' + worker.id + ' forked.');
});

if (cluster.isMaster) {
    // 2 уровень - уровень создания воркеров и уровень прослушивания событий для каждого конкретного воркера
    for (let i = 0; i < cpus; i++) {
        let worker = cluster.fork(); // Создание воркера
        // Навешивание событий на конкретный созданный воркер
        worker.on('listening', (address) => {
            console.log(JSON.stringify(address));
        });
        worker.on('message', function (message) { // Получение сообщения из мастер-процесса кластера
            console.log(message + ' master ' + process.pid);
            worker.send('shutdown'); // Посылка сообщения из воркера в мастер-процесс кластера
            worker.kill(); // Уничтожение текущего активного воркера
        });
    }
    cluster.on('exit', function (worker, code, signal) {
        console.log('Worker ' + worker.id + ' killed.');
    });
} else if (cluster.isWorker) {
    // 3 уровень - уровень кода воркера и посылки сообщений из мастер-процесса кластера текущему активному воркеру
    // Код воркера
    http.createServer(function (request, response) {
        response.writeHead(200, 'OK');
        response.end('HI ' + process.pid);
        process.send('Done: ' + process.pid); // Посылка сообщения из мастер-процесса кластера текущему активному воркеру
    }).listen(8000, '127.0.0.1', function () {
        console.log('Server started ' + process.pid);
    });
    // Получение сообщения из текущего активного воркера мастер-процессом кластера
    process.on('message', function (message) {
        console.log('Message from worker: ' + message);
    });
}

среда, 13 сентября 2017 г.

Node.js UDP / Dgram Server and Client

const dgram  = require('dgram');

// Server

const server = dgram.createSocket('udp4');

server.on('listening', function () {
    console.log('Server listening: ' + JSON.stringify(server.address()));
});

server.on('message', function (message, rinfo) {
    console.log('Server got message: "' + message + '" from: ' + rinfo.address + ':' + rinfo.port);
    server.send(message, 0, rinfo.size, rinfo.port, rinfo.address, function (error) {
        if (error) {throw error;}
        server.close();
    });
});

server.on('close', function () {
    console.log('Server closed');
});

server.on('error', function (error) {
    console.log('Server error: ' + error.stack);
    server.close();
});

server.bind(8080, '127.0.0.1', function () {
    console.log('Datagram server started at 127.0.0.1:8080');
});

// Client

const client = dgram.createSocket('udp4');

client.on('message', function (message, rinfo) {
    console.log('Client got message: "' + message + '" from: ' + rinfo.address + ':' + rinfo.port);
    client.close();
});

client.on('close', function () {
    console.log('Client closed');
});

client.on('error', function (error) {
    console.log('Client error: ' + error.stack);
    client.close();
});

client.send([Buffer.from('Some bytes.'), Buffer.from(' And another bytes.')], 8080, '127.0.0.1', function (error) {
    if (error) {throw error;}
});

вторник, 12 сентября 2017 г.

Node.js TLS Socket connection full example

// https://github.com/digitalbazaar/forge#x509
// npm install node-forge

const tls = require('tls')
        , forge = require('node-forge');

// Generate certificate

const keyPair = forge.pki.rsa.generateKeyPair(2048)
        , cert = forge.pki.createCertificate()
        , now = new Date()
        , oneYear = new Date(new Date(now).setFullYear(now.getFullYear() + 1));

Object.assign(cert, {
      publicKey: keyPair.publicKey
    , serialNumber: '01'
    , validity: {
          notBefore: now
        , notAfter: oneYear
    }
});

cert.sign(keyPair.privateKey, forge.md.sha256.create()); // self signed

const serverPrivateKeyPem = forge.pki.privateKeyToPem(keyPair.privateKey)
        , serverPublicKeyPem = forge.pki.publicKeyToPem(keyPair.publicKey)
        , serverCertificatePem = forge.pki.certificateToPem(cert);

// Server

const serverOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
};

const server = tls.createServer(serverOptions, function (socket) {
    console.log('Insecure connection 2');
    // socket.authorized will be true if the client cert presented validates with our CA
    console.log('Client connected to Server. Client ' + (socket.authorized ? 'authorized' : 'unauthorized'));
    socket.setEncoding('utf8');
    socket.on('data', function (data) {console.log(data.toString());});
    socket.on('end', function () {console.log('END');});
    socket.write('Hello');
    socket.end('World');
});

server.on('connection', function (socket) {
    console.log('Insecure connection 1');
})

server.on('secureConnection', function (socket) {
    // socket.authorized will be true if the client cert presented validates with our CA
    console.log('Secure connection. Client authorized: ' + socket.authorized);
});

server.listen(8000, '127.0.0.1', function () {
    console.log('Server started at https://127.0.0.1:443');
});

// Client

const clientOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
    , rejectUnauthorized: false
};

const client = tls.connect(8000, '127.0.0.1', clientOptions, function () {
    if (client.authorized) {
        console.log('Connection authorized by a Certificate Authority.');

        client.write('GET /hey HTTP/1.1\r\n');
        client.write('\r\n');

        client.write('POST /ho HTTP/1.1\r\n');
        client.write('\r\n');

    } else {
        console.log('Connection not authorized: ' + client.authorizationError);

        client.write('We are not authorized');

    }
});

client.setEncoding('utf8');

client.on('data', function (data) {
    console.log(data.toString());
});

client.on('end', function (data) {
    server.close();
});

client.on('close', function() {
    console.log('SOCKET CLOSED');
});

Node.js Net Socket connection full example

// --------------------------------------------------------------------------
// | Соединение между сокетами net похоже на алгоритм соединения с помощью модуля http.
// --------------------------------------------------------------------------
// | HTTP Client
// --------------------------------------------------------------------------
// | const http = require('http');
// | const server = http.createServer();
// | server.on('request', function (request, response) {
// |     request.on('data', function (data) {
// |         console.log('Request BODY from client: ' + data.toString()); // Вызывается, только, если было послано BODY
// |     });
// |     request.on('end', function () {
// |         console.log('2) Data from client ended');
// |         response.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
// |         response.write('\n\r\ a. Data to client');
// |         response.end('\n\r\ b. Data to client ended');
// |         server.close();
// |     });
// | });
// | server.listen(8080, '127.0.0.1', function () {console.log('1) HTTP server started at 127.0.0.1:8080');});
// --------------------------------------------------------------------------
// | Net Server
// --------------------------------------------------------------------------
// | const net = require('net');
// | const server = net.createServer();
// | server.on('connection', function (socket) {
// |     socket.on('data', function (data) {
// |         console.log('3) Request from client: ' + data.toString());
// |     });
// |     socket.on('end', function () {
// |         console.log('5) Data from client ended');
// |     });
// |     socket.write('\n\r\ a. Data to client');
// |     socket.end('\n\r\ b. Data to client ended');
// |     server.close();
// | });
// | server.listen(8080, '127.0.0.1', function () {console.log('1) Socket server started at 127.0.0.1:8080');});
// --------------------------------------------------------------------------
// | HTTP Client
// --------------------------------------------------------------------------
// | const client = http.request({hostname: '127.0.0.1', port: 8080, path: '/', method: 'GET'});
// | client.on('response', function (response) {
// |     response.on('data', function (data) {console.log('3) Answer from server: ' + data.toString());});
// |     response.on('end', function () {console.log('4) Data from server ended');});
// | });
// | client.write('\n\r\ a. Data to server');
// | client.end('\n\r\ a. Data to server ended');
// --------------------------------------------------------------------------
// | Net Client
// --------------------------------------------------------------------------
// | const client = new net.Socket();
// | client.on('data', function (data) {console.log('2) Answer from server: ' + data.toString());});
// | client.on('end', function () {console.log('4) Data from server ended');});
// | client.on('connect', function () {
// |     client.write('\n\r\ a. Data to server');
// |     client.end('\n\r\ b. Data to server ended');
// | });
// | client.connect(8080, '127.0.0.1');
// --------------------------------------------------------------------------

const net = require('net');

// Server

const server = net.createServer(); // или тоже самое: new net.Server();

server.on('connection', function (socket) {
    socket.setEncoding('utf8');
    console.log('4) Server received connection from client: ' + socket.remoteAddress + ':' + socket.remotePort);
    socket.on('data', function (data) {
        console.log('10) Data from client: ' + data);
    });
    socket.on('end', function () {
        console.log('14) Server disconnected from client: ' + socket.remoteAddress + ':' + socket.remotePort);
        server.close();
    });
    socket.on('close', function () {
        console.log('16) Client closed connection to server.');
    });
    socket.on('error', function (error) {
        console.log('Connection client to server error: ' + error.message);
    });
    socket.write('\n\r a. Hello');
    server.getConnections(function (error, count) {
        if (error) {throw error;}
        socket.write('\n\r b. Total connections: ' + count);
        socket.end('\n\r c. Goodbye');
    });
});

server.on('close', function () {
    console.log('15) All connections to server closed.');
});

server.on('error', function (error) {
    if (error.code === 'EADDRINUSE') {
        console.log('Address in use, retrying...');
        setTimeout(function () {
            server.close();
            server.listen(9000, '127.0.0.1', function () {
                console.log('Socket server restarted at ' + JSON.stringify(server.address()));
            });
        }, 1000);
    }
});

server.on('listening', function () {
    console.log('1) Method server.listen() called.');
    if (server.listening) {
        console.log('2) Socket server is listening now.');
    }
});

server.listen(9000, '127.0.0.1', function () {
    console.log('3) Socket server started at ' + JSON.stringify(server.address()));
});

// Client

const client = new net.Socket(); // net.createConnection(); и net.connect(); вызывают "transmission error"

client.on('connect', function () {
    console.log('5) Client established connection to server.');
    console.log('6) Client socket connected at ' + JSON.stringify(client.address()));
    client.write('\n\r a. First data from client to server', 'utf8', function () {
        console.log('8) Data from client to server sent.');
    });
});

client.on('data', function (data) {
    console.log('9) Answer from server to client: ' + data);
    client.end('\n\r b. Last data from client to server.', 'utf8');
});

client.on('end', function () {
    console.log('11) Client recieved all data from server socket.');
    console.log('12) Client disconnected from server.');
});

client.on('close', function (has_error) {
    if (has_error) {
        console.log('4*) The socket was closed due to a transmission error.');
    } else {
        console.log('13) Client connection to server closed.');
    }
});

client.setTimeout(3000);
client.on('timeout', function () {
    console.log('Client connection timeout');
    client.close(); // или можно: client.destroy();
});

client.on('error', function (error) {
    console.log('3*) Client error: ' + error.message);
});

client.connect(9000, '127.0.0.1', function () {
    if (client.connecting) {
        console.log('Client socket is connecting to server socket');
    }
    console.log('7) Client connected to server.');
});

/*

// Server
1) Method server.listen() called.
2) Socket server is listening now.
3) Socket server started at {"address":"127.0.0.1","family":"IPv4", "port":9000}
4) Server received connection from client.

// Client
5) Client established connection to server.
6) Client socket connected at {"address":"127.0.0.1","family":"IPv4", "port":53977}
7) Client connected to server.
8) Data from client to server sent.
9) Answer from server to client:
 a. Hello
 b. Total connections: 1
 c. Goodbye

 // Server
10) Data from client:
 a. First data from client to server
 b. Last data from client to server.

// Client
11) Client recieved all data from server
12) Client disconnected from server.
13) Client connection to server closed.

 // Server
14) Server disconnected from client.
15) All connections to server closed.

*/

Node.js HTTPS Server with certificate generator

// https://github.com/digitalbazaar/forge#x509
// npm install node-forge
// https://127.0.0.1:443

const https = require('https')
        , forge = require('node-forge');

// Generate certificate

const keyPair = forge.pki.rsa.generateKeyPair(2048)
        , cert = forge.pki.createCertificate()
        , now = new Date()
        , oneYear = new Date(new Date(now).setFullYear(now.getFullYear() + 1));

Object.assign(cert, {
      publicKey: keyPair.publicKey
    , serialNumber: '01'
    , validity: {
          notBefore: now
        , notAfter: oneYear
    }
});

cert.sign(keyPair.privateKey, forge.md.sha256.create()); // self signed

const serverPrivateKeyPem = forge.pki.privateKeyToPem(keyPair.privateKey)
        , serverPublicKeyPem = forge.pki.publicKeyToPem(keyPair.publicKey)
        , serverCertificatePem = forge.pki.certificateToPem(cert);

// Server

const serverOptions = {
      key: serverPrivateKeyPem
    , cert: serverCertificatePem
};

const server = https.createServer(serverOptions, function (request, response) {
    response.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
    response.end('Hello World!');
})

server.listen(443, '127.0.0.1', function () {
    console.log('Server started at https://127.0.0.1:443');
});

// Client

const clientOptions = {
      hostname: '127.0.0.1'
    , port: 443
    , path: '/'
    , method: 'GET'
    , rejectUnauthorized: false // don't check certificate
    , requestCert: true
    , agent: false
};

const client = https.request(clientOptions, function (response) {
    response.on('data', function (data) {
        console.log(data.toString());
    });
    response.on('end', function () {
        server.close();
    });
});

client.on('error', function (error) {
    throw error;
});

client.end();

понедельник, 11 сентября 2017 г.

Node.js Net Socket Server and Client

const net = require('net');

const HOST = '127.0.0.1'
        , PORT = 8080;

// Server

const server = net.createServer();

server.on('connection', function (serverSocket) {
    console.log('Client connected to server at ' + serverSocket.remoteAddress + ':' + serverSocket.remotePort);
    serverSocket.on('data', function (data) {
        console.log('Data from client ' + serverSocket.remoteAddress + ': ' + data);
        serverSocket.write('Client said: ' + data);
    });
    serverSocket.on('close', function () {
        console.log('Client closed connection to server: ' + serverSocket.remoteAddress +' '+ serverSocket.remotePort);
    });
    serverSocket.on('error', function (error) {
        throw error;
    })
});

server.listen(PORT, HOST, function () {
    console.log('Server started at ' + server.address().address + ':' + server.address().port);
});

// Client

const client = new net.Socket();

client.connect(PORT, HOST, function () {
    console.log('Client established connection to: ' + HOST + ':' + PORT);
    client.write('Hello');
});

client.on('data', function (data) {
    console.log('Answer from server: ' + data);
    console.log('Client ready to close connection. ');
    client.destroy(); // kill client and close connection after server's response
});

client.on('close', function () {
    console.log('Client closed his connection.');
});

пятница, 8 сентября 2017 г.

Использование child_process exec(), execFile(), spawn(), fork() в Node.js.

В Node.js модуль child_process предлагает 4 метода для выполнения внешних программ:
- exec()
- execFile()
- spawn()
- fork()

Все 4 метода выполняются асинхронно.

Каждый метод возвращает объект, которые является экземпляром класса ChildProcess.

--------------------------
child_process.exec()
child_process.execFile()
child_process.spawn()
child_process.fork()
--------------------------
              |
 ----------------------
  ChildProcess object
 ----------------------
              |
    -----------------
       ChildProcess
    -----------------
    - close <Event>
    - exit <Event>
    - error <Event>
    -----------------
    - stdin <Stream | String>
    - stdout <Stream | String>
    - stderr <Stream | String>
    - stdio <Array>
    -----------------
              |
 ----------------------
  extends EventEmitter
 ----------------------

Описание методов.

1. child_process.exec()

Что делает?

Создает отдельную оболочку терминала, внутри которой выполняет внешнюю программу и буферизирует возвращаемые в результате её выполнения данные.
Принимает путь до файла, набор необязательных опций, описывающих окружение, в котором будет выполняться внешняя программа и функцию callback, в которую передаётся набор значений о результате выполнения внешней программы.
Возвращает объект класса <ChildProcess>.

По сравнению с execFile() и spawn(), метод exec() не имеет массив аргументов argsArray, в который передаётся в исполняемый файл внешней программы, поскольку метод exec() позволяет выполняться несколько команд подряд внутри оболочки терминала.
Если необходимо в метод exec() передать аргументы, то их просто записывают, как часть команды или пути до исполняемого файла внешней программы.

В каких случаях необходимо использовать метод exec?

Метод exec() используется, когда необходимо выполнить внешнюю команду у получить доступ ко всей функциональности оболочки терминала, такой как pipe, redirect, backgrounding и тому подобное.
Метод exec() выполняет команду в оболочке терминала "/bin/sh" в Linux и "cmd.exe" в Windows.
Использование метода exec() в командной оболочке может нести в себе опасность  shell injection exploit.
Поэтому везде, где возможно, стоит взамен использовать метод execFile(), для которого неправильная передача аргументов приведёт к возникновению ошибке, а не взлому.

Как пользоваться?

const child_process = require('child_process');
child_process.exec('dir /D', {shell: true, encoding: 'utf8'}, function (error, stdout, stderr) {
    if (error) {throw error;}
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
});

Когда внешняя команда запускается в окне оболочке терминала, мы получаем доступ ко всей функциональности оболочки терминала.
В данном примере мы запускаем отдельную подоболочку терминала и выполняем в ней команду 'dir /D'.

Какие передаются аргументы?

child_process.exec(filePathOrCommandsWithArguments, optionsObject, callbackFunction);

- filePathOrCommandsWithArguments <String> - путь до исполняемого файла внешней программы, просто её имя или команда для консоли терминала вместе с набором аргументов для этой программы, отделенных друг от друга пробелами.
Откуда Node.js узнаёт, где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- encoding <String> - кодировка (по умолчанию: 'utf8').
--- shell <String> - внешняя программа будет запущена внутри оболочки терминала "cmd.exe". По умолчанию в качестве терминала используется программа "/bin/sh" на UNIX и "cmd.exe" на Windows. На Windows оболочка терминала должна быть совместима с "cmd.exe".
--- timeout <Number> - время ожидания до начала выполнения внешней программы (по умолчанию: 0).
--- maxBuffer <Number> - максимальный объём данных в байтах, поступающих из внешней программы, допустимый для передачи в stdout или stderr. Если допустимый объём переданных данных будет превышен, то процесс выполнения внешней программы будет убит (по умолчанию: 200 *1024).
--- killSignal <String> - сигнал для убийства процесса выполнения внешней программы (по умолчанию: 'SIGTERM').
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы(смотри setgid(2)).

- callbackFunction <Function> - функция обратного вызова, которая вызывается после завершения выполнения внешней программы, в неё поступаютс следующие значения:
--- error <Error> - аргумент, хранящий в себе сообщение об ошибке в случае её возникновения в процессе запуска внешней программы.
--- stdout <String> | <Buffer> - результат выполнения внешней программы в виде поступивших от неё данных.
--- stderr <String> | <Buffer> - результат ошибки во время выполнения внешней программы в виде поступивших от неё данных об ошибке.

2. child_process.execFile()

Что делает?

Выполняет любой исполняемый файл внешней программы.
Принимает путь до файла, набор необязательных аргументов, набор опций, описывающих окружение, в котором будет выполняться внешняя программа и функцию callback, в которую передаётся набор значений о результате выполнения внешней программы.
Возвращает объект класса <ChildProcess>.

В каких случаях необходимо использовать метод execFile?

Метод execFile() используется, когда необходимо выполнить внешнюю программу и получить только вывод результата её выполнения.
Например, мы можем использовать метод execFile() для запуска программы "ImageMagick", которая конвертирует картинку из формата PNG в формат JPG и нам необходимо только знать завершился ли процесс конвертации успешно или нет.
Не следует использовать метода execFile(), когда внешняя выполняемая программа возвращает огромный объём данных, которые надо обработать в режиме реального времени. Для этого случая используйте метод spawn().

Как пользоваться?

const child_process = require('child_process');
const child = child_process.execFile('node.exe', ['--version'], {cwd: process.cwd(), encoding: 'utf8'}, function (error, stdout, stderr) {
    if (error) {
        console.error('stderr: ' + Buffer.isBuffer(stderr) ? stderr.toString() : stderr);
        throw error;
    }
    console.log('stdout: ' + Buffer.isBuffer(stdout) ? stdout.toString() : stdout);
});

Внешняя программа "node.exe" вызывается с аргументом "--version".
После того, как она завершит своё выполнение, будет вызвана callback функция.
В callback функцию передаются значения "stdout" и "stderr" с результатами выполнения программы "node.exe".
Значения "stdout" и "stderr" могут передаваться в виде буфер или строки.

Какие передаются аргументы?

child_process.execFile(filePath, argsArray, optionsObject, callbackFunction);

- filePath <String> - путь до исполняемого файла внешней программы или просто её имя.
Откуда Node.js узнаёт, где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- encoding <String> - кодировка (по умолчанию: 'utf8').
--- timeout <Number> - время ожидания до начала выполнения внешней программы (по умолчанию: 0).
--- maxBuffer <Number> - максимальный объём данных в байтах, поступающих из внешней программы, допустимый для передачи в stdout или stderr. Если допустимый объём переданных данных будет превышен, то процесс выполнения внешней программы будет убит (по умолчанию: 200 *1024).
--- killSignal <String> - сигнал для убийства процесса выполнения внешней программы (по умолчанию: 'SIGTERM').
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы(смотри setgid(2)).

- callbackFunction <Function> - функция обратного вызова, которая вызывается после завершения выполнения внешней программы, в неё поступаютс следующие значения:
--- error <Error> - аргумент, хранящий в себе сообщение об ошибке в случае её возникновения в процессе запуска внешней программы.
--- stdout <String> | <Buffer> - результат выполнения внешней программы в виде поступивших от неё данных.
--- stderr <String> | <Buffer> - результат ошибки во время выполнения внешней программы в виде поступивших от неё данных об ошибке.

3. child_process.spawn()

Что делает?

Выполняет внешнюю программу в отдельном процессе и возвращает интерфейс ввода-вывода для передачи данных от внешней программы в потоке stream.
Принимает путь до файла, набор необязательных аргументов, набор опций, описывающих окружение, в котором будет выполняться внешняя программа и возвращает объект типа stream, для управления потоком данных, поступающих из внешней программы.
Возвращает объект класса <ChildProcess>.

В каких случаях необходимо использовать метод spawn?

Метод spawn() возвращает объект stream для управления потоком чтения и записи внешней выполняемой программы, поэтому он хорош для выполнения внешней программы, которая возвращает большие объёмы данных.

Как пользоваться?

Пример 1.

const child_process = require('child_process');
const spawnProcess = child_process.spawn('dir', ['/D'], {shell: true});

spawnProcess.on('error', function (error) {throw error;});
spawnProcess.stdout.on('data', function (data) {console.log('stdout: ' + data);});
spawnProcess.stderr.on('data', function (data) {console.log('stderr: ' + data);});
spawnProcess.on('close', function (code, signal) {console.log('Child process exited with code ' + code);});
spawnProcess.on('exit', function (code, signal) {console.log('Child exited with code ' + code);});

spawnProcess.stdin.write('data');
spawnProcess.stdin.end();

console.log(spawnProcess.pid);

Пример 2.

const fs = require('fs');
const child_process = require('child_process');

const spawnImageProcess = child_process.spawn('convert.exe', [
    "-", // use stdin
    "-resize", "640x", // resize width to 640
    "-resize", "x360<", // resize height if it's smaller than 360
    "-gravity", "center", // sets the offset to the center
    "-crop", "640x360+0+0", // crop
    "-" // output to stdout
], {shell: false});

const readImageStream = fs.createReadStream('./path/to/image.jpg');

readImageStream.pipe(spawnImageProcess.stdin);
spawnImageProcess.stdout.pipe(serverResponseStream);

Представим, что у нас есть сервис для преобразования картинок (imagemagick.org).
В данном примере создаётся поток чтения кода картинки из файла, который передается во входной поток внешней программы "convert.exe", которая преобразует картинку.
Затем преобразованные данные картинки передаются в поток ответа от сервера к браузеру.
До тех пор пока процесс spawnImageProcess производит преобразования данных картинки, они передаются в поток ответа от сервера к браузеру и пользователь сайта немедленно начинает получать результат обработки картинки без необходимости ожидать завершения всего процесса сразу.

Какие передаются аргументы?

const spawnProcess = child_process.spawn(filePathOrTerminalCommand, argsArray, optionsObject);

- filePathOrTerminalCommand <String> - путь до исполняемого файла внешней программы, просто её имя или команда для консоли терминала.
Откуда Node.js узнает где искать исполняемый файл внешней программы?
Node.js использует переменную окружения PATH из операционной системы, в которой содержится набор директорий расположения исполняемых программ.
Если путь до внешней программы, которую надо выполнить, содержится в переменной PATH, то она может быть обнаружена без указания абсолютного или относительного пути до неё.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- stdio <Array> | <String> конфигурация stdio для дочернего процесса (смотри options.stdio).
--- detached <Boolean> - заупскать и выполнять ли внешнюю программу независимо от выполнения вызвавшего его родительского процесса Node.js. Особенности выполнения зависят от платформы (смотри options.detached).
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней программы (смотри setgid(2)).
--- shell <Boolean> | <String> - если равно true, то внешняя программа будет запущена внутри оболочки терминала "cmd.exe". По умолчанию в качестве терминала используется программа "/bin/sh" на UNIX и "cmd.exe" на Windows. Другая оболочка терминала, в которой будет выполнена внешняя программа, может быть задана в виде строки. Оболочка терминала должна понимать команду -c switch на UNIX и /s /c на Windows. По умолчанию равно false, что означает не запускать внешнюю программу внутри оболочки терминала.

4. child_process.fork()

Что делает?

Метод fork() является частным случаем метода spawn().
Он используется для создания отдельно Node.js процесса, в котором выполняется файл внешней JavaScript-программы.
Как и метод spawn(), метод fork() возвращает объект класса <ChildProcess>.
Возвращенный объект класса <ChildProcess> из метода fork() имеет дополнительный канал коммуникации для передачи сообщений между родительской программой и дочерним процессом внешней программы.
Таким образом, метод fork() открывате IPC канал, которые позволяет передавать сообщения между двумя программами Node.js:
- со стороны дочернего процесса внешней программы для получения и посыла сообщений могут быть использованы методы process.on('message', function (dataFromParent) {}) и process.send('message to parent').
- со стороны родительского процесса, который запустил внешнюю программу, для получения и посыла сообщений могут быть использованы методы forkedChild.on('message', function (dataFromChild) {}) и forkedChild.send('message to child').
Канал обмена сообщений открыт до тех пор, пока внешняя исполняемая JavaScript-программа не завершит своего выполнения.
Процесс каждой программы имеет свою собственную память и запущенный экземпляр движка V8.

В каких случаях необходимо использовать метод fork?

Поскольку Node.js выполняет все операции в одном потоке, то долго выполняемые операции могут тормозить основной процесс.
Поэтому метод fork() используется, когда необходимо выполнить длительные JavaScript-операции. В этом случае запускается отдельная Node.js программа, которая общается с родительской программой с помощью передачи друг другу сообщений, что позволяет родительской программе быть доступной для выполнения других операций.

Как использовать?

Файл родительской программы parent.js:

const child_process = require('child_process');

const forkedChild = child_process.fork(__dirname + '/child.js', ['build']);

forkedChild.on('message', function (message) {
    console.log('PARENT got message from child: ' + message.foo + ' | '+ message.arg);
});
forkedChild.send({hello: 'world'});

Файл дочерней внешней программы child.js:

process.on('message', function (message) {
    console.log('CHILD got message: ' + message.hello);
    process.exit();
});
process.send({foo: 'bar', arg: process.argv[2]});

Какие передаются аргументы?

child_process.fork(jsFilePath, argsArray, optionsObject);

- jsFilePath <String> - путь до исполняемого файла внешней JavaScript-программы.

- argsArray <Array> - массив аргументов, которые передаются в исполняемый файл внешней JavaScript-программы.

- optionsObject <Object> - набор опций, описывающих окружение, в котором будет выполняться внешняя JavaScript-программа:
--- cwd <String> - текущая рабочая директория, относительно которой будет выполняться файл внешней JavaScript-программы.
--- env <Object> - пары key-value, описывающее рабочее окружение.
--- execPath <String> - путь до исполняемого файла, используемого для создания дочернего процесса внешней JavaScript-программы.
--- execArgv <Array> - массив строк аргументов, которые передаются в исполняемый файл, используемый для создания дочернего процесса внешней JavaScript-программы (по умолчанию: process.execArgv).
--- silent <Boolean> - если равно true, то stdin, stdout, stderr дочерней внешней JavaScript-программы будут связаны (pipe) с родительским процессом, в противном случае они будут наследованы от родительского процесса (смотри опции "pipe" и "inherit" метода child_process.spawn()) (по умолчанию: false).
--- uid <Number> - устанавливает идентификатор пользователя для процесса выполнения внешней JavaScript-программы (смотри setuid(2)).
--- gid <Number> - устанавливает идентификатор группы для процесса выполнения внешней JavaScript-программы(смотри setgid(2)).

среда, 6 сентября 2017 г.

TCP proxy server

client --> proxy --> remote --> proxy --> client

Файл proxy.js

var net = require('net');

var LOCAL_PORT = 8080
    , LOCAL_ADDR = '127.0.0.1'

    , REMOTE_PORT = 80
    , REMOTE_ADDR = 'google.com';

var server = net.createServer(function (socket) {
    socket.on('data', function (data) {
        console.log('------- START -------');
        console.log('<< From client to proxy ', data.toString());
        var serviceSocket = new net.Socket();
        serviceSocket.connect(REMOTE_PORT, REMOTE_ADDR, function () {
            console.log('>> From proxy to remote', data.toString());
            serviceSocket.write(data);
        });
        serviceSocket.on('data', function (data) {
            console.log('<< From remote to proxy', data.toString());
            socket.write(data);
            console.log('>> From proxy to client', data.toString());
        });
        serviceSocket.on('error', function () {throw error;});
    });
});

server.listen(LOCAL_PORT, LOCAL_ADDR, function () {
    console.log('TCP server accepting connection on port: ' + LOCAL_ADDR + ':' + LOCAL_PORT);
});