├── test ├── pages │ ├── empty-array.jsx │ ├── auth.jsx │ ├── bigfile.jsx │ ├── file.jsx │ ├── locale │ │ └── ru.json │ ├── auth-cached.jsx │ ├── common │ │ └── common.jsx │ ├── template.js │ ├── bigfile-cached.jsx │ ├── hello.jsx │ ├── file-cached.jsx │ ├── http-cached.jsx │ ├── json │ │ ├── album.27.json │ │ └── photo.42.json │ ├── redirect.jsx │ ├── http.jsx │ ├── cookie.jsx │ ├── index.jsx │ ├── result.jsx │ ├── local.jsx │ ├── test.01.jsx │ ├── state.jsx │ └── auths.json ├── config.js ├── dummy │ ├── chunk-json │ ├── chunk-xml │ ├── chunk-txt │ └── dummy.js └── modules │ └── ya.js ├── .gitignore ├── Makefile ├── .jshintrc ├── README.md ├── lib ├── de.js ├── de.logger.js ├── index.js ├── de.log.js ├── de.request.js ├── de.sandbox.js ├── de.response.js ├── de.context.js ├── de.server.js ├── de.common.js ├── de.script.js ├── de.file.js ├── de.http.js ├── de.result.js └── de.block.js ├── descript ├── package.json ├── todo.md └── changelog.md /test/pages/empty-array.jsx: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/pages/auth.jsx: -------------------------------------------------------------------------------- 1 | 'ya:auth()' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | 4 | -------------------------------------------------------------------------------- /test/pages/bigfile.jsx: -------------------------------------------------------------------------------- 1 | 'auths.json' 2 | -------------------------------------------------------------------------------- /test/pages/file.jsx: -------------------------------------------------------------------------------- 1 | de.file('locale/ru.json') 2 | -------------------------------------------------------------------------------- /test/pages/locale/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Привет" 3 | } 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | jshint: 2 | jshint lib/*.js 3 | 4 | .PHONY: jshint 5 | 6 | -------------------------------------------------------------------------------- /test/pages/auth-cached.jsx: -------------------------------------------------------------------------------- 1 | de.call('ya:auth()', { 2 | key: 'auth', 3 | maxage: '+1h' 4 | }) 5 | -------------------------------------------------------------------------------- /test/pages/common/common.jsx: -------------------------------------------------------------------------------- 1 | { 2 | auth: 'ya:auth()', 3 | 4 | locale: '../locale/ru.json' 5 | } 6 | -------------------------------------------------------------------------------- /test/pages/template.js: -------------------------------------------------------------------------------- 1 | function(data) { 2 | return 'Hello, ' + data.username + ''; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /test/pages/bigfile-cached.jsx: -------------------------------------------------------------------------------- 1 | de.file('auths.json', { 2 | key: 'auths.json', 3 | maxage: '+1h' 4 | }) 5 | -------------------------------------------------------------------------------- /test/pages/hello.jsx: -------------------------------------------------------------------------------- 1 | de.block({ 2 | username: 'nop' 3 | }, { 4 | template: 'template.js' 5 | }) 6 | 7 | -------------------------------------------------------------------------------- /test/pages/file-cached.jsx: -------------------------------------------------------------------------------- 1 | de.file('locale/ru.json', { 2 | key: 'locale/ru.json', 3 | maxage: '+1h' 4 | }) 5 | 6 | -------------------------------------------------------------------------------- /test/pages/http-cached.jsx: -------------------------------------------------------------------------------- 1 | de.http('http://www.yandex.ru', { 2 | key: 'yandex', 3 | maxage: '+1h' 4 | }) 5 | 6 | -------------------------------------------------------------------------------- /test/pages/json/album.27.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 27, 3 | "data": { 4 | "title": "Котики" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /test/pages/redirect.jsx: -------------------------------------------------------------------------------- 1 | function(context) { 2 | context.response.setRedirect('http://www.yandex.ru'); 3 | return null; 4 | } 5 | -------------------------------------------------------------------------------- /test/pages/http.jsx: -------------------------------------------------------------------------------- 1 | de.http( 2 | 'http://www.yandex.ru', 3 | { 4 | method: 'POST', 5 | timeout: 3000 6 | } 7 | ) 8 | -------------------------------------------------------------------------------- /test/pages/json/photo.42.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 42, 3 | "album_id": 27, 4 | "data": { 5 | "url": "42.jpg" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqnull": true, 3 | "shadow": true, 4 | "evil": true, 5 | "expr": true, 6 | "loopfunc": true, 7 | "proto": true, 8 | "sub": true 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/pages/cookie.jsx: -------------------------------------------------------------------------------- 1 | function(context) { 2 | var response = context.response; 3 | 4 | response.setCookie('a', 42); 5 | response.setCookie('b', 77); 6 | 7 | return 'Cookies set' 8 | } 9 | -------------------------------------------------------------------------------- /test/pages/index.jsx: -------------------------------------------------------------------------------- 1 | { 2 | common: 'common/common.jsx' +100, 3 | 4 | answer: 42// , 5 | 6 | // yandex: 'http://www.yandex.ru', 7 | 8 | // search: 'http://yandex.ru/yandsearch?' 9 | } 10 | -------------------------------------------------------------------------------- /test/pages/result.jsx: -------------------------------------------------------------------------------- 1 | de.func(function() { 2 | return { 3 | s1: 1, 4 | s2: 2, 5 | s3: 3 6 | }; 7 | }, { 8 | result: { 9 | d1: ".s1", 10 | d2: ".s2", 11 | d3: ".s3" 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | { 2 | port: 2000, 3 | 4 | rootdir: './pages', 5 | 6 | id: 42, 7 | 8 | log: { 9 | level: 'debug' 10 | } 11 | 12 | /* 13 | modules: { 14 | ya: 'modules/ya.js' 15 | }, 16 | 17 | blackbox: { 18 | url: 'blackbox-mimino.yandex.net/blackbox', 19 | domain: 'fotki.yandex.ru' 20 | } 21 | */ 22 | } 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | descript 2 | ======== 3 | 4 | Прокся и агрегатор данных, являющаяся в (каком-то смысле) аналогом `xscript`, 5 | но работающая с данными в формате `json`. 6 | 7 | Подробнее в [вики](https://github.com/pasaran/descript/wiki/syntax). 8 | 9 | ## quick start 10 | 11 | ``` 12 | npm install 13 | ./descript --config test/config.js 14 | # Open http://localhost:2000/hello.jsx in your favorite browser. 15 | ``` 16 | -------------------------------------------------------------------------------- /lib/de.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var de = {}; 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | 9 | module.exports = de; 10 | 11 | // --------------------------------------------------------------------------------------------------------------- // 12 | 13 | -------------------------------------------------------------------------------- /descript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var de = require('./lib/de.js'); 6 | 7 | require('./lib/de.server.js'); 8 | 9 | // --------------------------------------------------------------------------------------------------------------- // 10 | 11 | de.server.init(); 12 | 13 | de.server.start(); 14 | 15 | // --------------------------------------------------------------------------------------------------------------- // 16 | 17 | // vim: set filetype=javascript: 18 | 19 | -------------------------------------------------------------------------------- /test/dummy/chunk-json: -------------------------------------------------------------------------------- 1 | { 2 | "status": { 3 | "value": "VALID", 4 | "id": 0 5 | }, 6 | "error": "OK", 7 | "age": 228005, 8 | "auth": { 9 | "password_verification_age": 228005, 10 | "have_password": true, 11 | "secure": false, 12 | "allow_plain_text": true 13 | }, 14 | "uid": { 15 | "value": "24171229", 16 | "lite": false, 17 | "hosted": false, 18 | "domid": "", 19 | "domain": "", 20 | "mx": "" 21 | }, 22 | "login": "alpha-san", 23 | "karma": { 24 | "value": 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/pages/local.jsx: -------------------------------------------------------------------------------- 1 | de.object( 2 | { 3 | foo: de.value( 4 | 42, 5 | { 6 | after: function(params, context) { 7 | context.state.foo = 42; 8 | } 9 | } 10 | ), 11 | bar: de.value( 12 | 24, 13 | { 14 | after: function(params, context) { 15 | context.state.bar = 24; 16 | } 17 | } 18 | ) 19 | }, 20 | { 21 | result: { 22 | result: '.', 23 | state: 'state' 24 | }, 25 | local: true 26 | } 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /test/dummy/chunk-xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VALID 4 | 5 | OK 6 | 228005 7 | 8 | 228005 9 | true 10 | false 11 | true 12 | 13 | 14 | 24171229 15 | false 16 | false 17 | 18 | 19 | 20 | 21 | alpha-san 22 | 23 | 0 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/de.logger.js: -------------------------------------------------------------------------------- 1 | require('no.colors'); 2 | 3 | function logger(level, msg) { 4 | switch (level) { 5 | case 'info': 6 | console.log(msg.green); 7 | break; 8 | 9 | case 'debug': 10 | console.log(msg); 11 | break; 12 | 13 | case 'error': 14 | console.error(msg.red); 15 | break; 16 | 17 | case 'warn': 18 | console.error(msg.yellow); 19 | break; 20 | } 21 | } 22 | 23 | // --------------------------------------------------------------------------------------------------------------- // 24 | 25 | module.exports = logger; 26 | 27 | // --------------------------------------------------------------------------------------------------------------- // 28 | 29 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de.* 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var de = require('./de.js'); 6 | 7 | require('./de.file.js'); 8 | require('./de.http.js'); 9 | 10 | require('./de.common.js'); 11 | require('./de.block.js'); 12 | require('./de.context.js'); 13 | require('./de.request.js'); 14 | require('./de.response.js'); 15 | require('./de.result.js'); 16 | require('./de.script.js'); 17 | require('./de.server.js'); 18 | 19 | // --------------------------------------------------------------------------------------------------------------- // 20 | 21 | module.exports = de; 22 | 23 | -------------------------------------------------------------------------------- /test/pages/test.01.jsx: -------------------------------------------------------------------------------- 1 | // /test.01.jsx 2 | // /test.01.jsx?skip=yes 3 | 4 | de.block({ 5 | /// photo: de.file('json/photo.{ config.id }.json', { 6 | photo: de.file('json/photo.{ state.photo_id }.json', { 7 | selectBefore: { 8 | photo_id: 42 9 | }, 10 | selectAfter: { 11 | album_id: '.album_id' 12 | }, 13 | guard: '.skip != "yes"' 14 | }) +10, 15 | album: de.file('json/album.{ state.album_id }.json', { 16 | selectAfter: { 17 | album_data: '.data' 18 | } 19 | }) 20 | }, { 21 | result: { 22 | album: '.album', 23 | photo_data: '.photo.data', 24 | state: 'state' 25 | } 26 | /* 27 | result: function(params, context) { 28 | return "hello"; 29 | } 30 | */ 31 | }) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Sergey Nikitin", 4 | "email": "nik.pasaran@gmail.com", 5 | "url": "https://github.com/pasaran" 6 | }, 7 | "name": "descript", 8 | "description": "descript", 9 | "version": "0.0.64", 10 | "homepage": "https://github.com/pasaran/descript", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/pasaran/descript.git" 14 | }, 15 | "dependencies": { 16 | "nopt": "*", 17 | "nommon": "0.0.28", 18 | "no.colors": "0.0.1" 19 | }, 20 | "devDependencies": {}, 21 | "optionalDependencies": {}, 22 | "engines": { 23 | "node": "*" 24 | }, 25 | "bin": "descript", 26 | "main": "lib/index.js", 27 | "files": [ 28 | "lib/index.js", 29 | "lib/de*.js", 30 | "descript" 31 | ] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /lib/de.log.js: -------------------------------------------------------------------------------- 1 | var no = require('nommon'); 2 | 3 | var de = require('./de.js'); 4 | 5 | // --------------------------------------------------------------------------------------------------------------- // 6 | 7 | var LEVELS = { 8 | off: 0, 9 | error: 1, 10 | warn: 2, 11 | info: 3, 12 | debug: 4 13 | }; 14 | 15 | var LEVEL = LEVELS[ de.config.log.level ]; 16 | var LOGGER = de.config.log.logger; 17 | var DATE_FORMAT = de.config.log.dateformat || '[%d.%m.%Y %H:%M:%S.%f]'; 18 | 19 | // --------------------------------------------------------------------------------------------------------------- // 20 | 21 | var format_date = no.date.formatter(DATE_FORMAT); 22 | 23 | de.log = function(level, msg) { 24 | if ( LEVELS[level] <= LEVEL ) { 25 | var timestamp = format_date( new Date() ); 26 | 27 | LOGGER(level, timestamp + ' [' + level + '] ' + msg); 28 | } 29 | }; 30 | 31 | // --------------------------------------------------------------------------------------------------------------- // 32 | 33 | de.log.error = function(msg) { 34 | de.log('error', msg); 35 | }; 36 | 37 | de.log.warn = function(msg) { 38 | de.log('warn', msg); 39 | }; 40 | 41 | de.log.info = function(msg) { 42 | de.log('info', msg); 43 | }; 44 | 45 | de.log.debug = function(msg) { 46 | de.log('debug', msg); 47 | }; 48 | 49 | // --------------------------------------------------------------------------------------------------------------- // 50 | 51 | -------------------------------------------------------------------------------- /lib/de.request.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de.Request 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var url_ = require('url'); 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | 9 | var no = require('nommon'); 10 | var de = require('./de.js'); 11 | 12 | require('./de.common.js'); 13 | 14 | // --------------------------------------------------------------------------------------------------------------- // 15 | 16 | var http_ = require('http'); 17 | 18 | // --------------------------------------------------------------------------------------------------------------- // 19 | 20 | /** 21 | @param {http_.IncomingMessage} request Входящий http-реквест. 22 | @param {Object=} extra_params Дополнительные параметры, например, из post-запроса. 23 | */ 24 | de.Request = function(request, extra_params) { 25 | this.headers = request.headers; 26 | this.cookies = de.parseCookies(this.headers.cookie || ''); 27 | 28 | this.url = url_.parse(request.url, true); 29 | if (extra_params) { 30 | no.extend(this.url.query, extra_params); 31 | } 32 | 33 | this.method = request.method; 34 | }; 35 | 36 | // --------------------------------------------------------------------------------------------------------------- // 37 | 38 | -------------------------------------------------------------------------------- /test/pages/state.jsx: -------------------------------------------------------------------------------- 1 | de.object({ 2 | func: de.func(function() { 3 | return { 4 | idUser: '213948712394', 5 | devices: { 6 | desktop: {os: 'mac'} 7 | }, 8 | emails: [ 9 | 'user@yo.ru', 'user@yo.com' 10 | ] 11 | }; 12 | }, { 13 | state: function(data, context) { 14 | return { 15 | models: { 16 | user: { 17 | id: data.idUser, 18 | emails: data.emails 19 | }, 20 | devices: data.devices 21 | } 22 | } 23 | } 24 | } 25 | ), 26 | jresult: de.func(function() { 27 | return { 28 | lang: 'ru', 29 | settings: { 30 | view: 'tiles', 31 | hasCamera: false, 32 | hasPublished: true 33 | }, 34 | accounts: [ 35 | 'user@mail.ru', 'user@gmail.com' 36 | ] 37 | }; 38 | }, { 39 | state: { 40 | models: { 41 | user: { 42 | language: '.lang', 43 | emails: '.accounts' 44 | }, 45 | settings: '.settings' 46 | } 47 | } 48 | } 49 | ) 50 | }, { 51 | result: function(data, context) { 52 | return context.state; 53 | } 54 | }) -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | Делать 5 | ------ 6 | 7 | * У всех de.Result должно быть свойство datatype. 8 | Выставлять content-type соответственно ему. 9 | 10 | * Сейчас no.de.http не возвращает content-type ответа, 11 | так что сложно автоматически выставить datatype. 12 | 13 | * options.template -- перблочные шаблоны. 14 | Как минимум для yate и каких-нибудь популярных (mustache/handlebars/...). 15 | 16 | * Нужны тесты. 17 | 18 | * Пробрасывать таймуаты между составными блоками и примитивами. 19 | Например, если в объекте одному из блоков сделать таймаут больше дефолтного, 20 | его все равно тормознет дефолтный таймаут объекта. 21 | 22 | Тоже самое про datatype. 23 | 24 | * Использовать path в nopt'е, чтобы сразу резолвить пути, заданные в командной строке. 25 | 26 | * Использовать http.STATUS_CODES http://nodejs.org/api/http.html#http_http_status_codes 27 | 28 | * Сделать метод у de.Script'а для запуска .jsx файла. 29 | 30 | * Подумать, нельзя ли заюзать no.Future как-нибудь. 31 | 32 | * Путь к сокету (из конфига) нужно резолвить. 33 | 34 | Сделано или закрыто 35 | ------------------- 36 | 37 | + Переопределяемый usage(). 38 | 39 | + Унести de.Script.create внутрь конструктора. 40 | 41 | + Перестать хотеть одновременной работы нескольких инстансов de.Script. 42 | Сделать de.Script синглтоном? 43 | 44 | - В основном цикле de.Script'а использовать this._includes вместо _cache. 45 | NOTE: Этого делать нельзя, т.к. в этом случае run зацикливается. 46 | Это разные сущности -- страницы и выполненные файлы .jsx. 47 | 48 | + Унести тело основного цикла de.Script'а в отдельный метод, 49 | чтобы его можно было бы переопределить. 50 | 51 | + В частности нужен заглушечный веб-сервер с набором разных ручек (с задержками и т.д.). 52 | 53 | -------------------------------------------------------------------------------- /lib/de.sandbox.js: -------------------------------------------------------------------------------- 1 | var de = require('./de.js'); 2 | 3 | require('./de.block.js'); 4 | require('./de.log.js'); 5 | 6 | // --------------------------------------------------------------------------------------------------------------- // 7 | 8 | de.sandbox = {}; 9 | 10 | // --------------------------------------------------------------------------------------------------------------- // 11 | 12 | de.sandbox.config = de.config; 13 | 14 | de.sandbox.block = de.Block.compile; 15 | 16 | de.sandbox.http = function(url, options) { 17 | return new de.Block.Http(url, options); 18 | }; 19 | 20 | de.sandbox.file = function(filename, options) { 21 | return new de.Block.File(filename, options); 22 | }; 23 | 24 | de.sandbox.include = function(filename, options) { 25 | return new de.Block.Include(filename, options); 26 | }; 27 | 28 | de.sandbox.call = function(call, options) { 29 | return new de.Block.Call(call, options); 30 | }; 31 | 32 | de.sandbox.array = function(array, options) { 33 | return new de.Block.Array(array, options); 34 | }; 35 | 36 | de.sandbox.object = function(object, options) { 37 | return new de.Block.Object(object, options); 38 | }; 39 | 40 | de.sandbox.value = function(value, options) { 41 | return new de.Block.Value(value, options); 42 | }; 43 | 44 | de.sandbox.func = function(func, options) { 45 | return new de.Block.Function(func, options); 46 | }; 47 | 48 | de.sandbox.expr = function(expr, options) { 49 | return new de.Block.Expr(expr, options); 50 | }; 51 | 52 | de.sandbox.Result = de.Result; 53 | 54 | // --------------------------------------------------------------------------------------------------------------- // 55 | 56 | de.sandbox.log = de.log; 57 | 58 | // --------------------------------------------------------------------------------------------------------------- // 59 | 60 | -------------------------------------------------------------------------------- /test/modules/ya.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // module.ya 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var querystring_ = require('querystring'); 6 | 7 | var de = require('../../lib/de.js'); 8 | var Result = require('../../lib/de.result.js'); 9 | 10 | // --------------------------------------------------------------------------------------------------------------- // 11 | 12 | var ya = {}; 13 | 14 | // --------------------------------------------------------------------------------------------------------------- // 15 | 16 | ya.auth = function(descript, promise, context, params) { 17 | var blackboxConfig = descript.config.blackbox; 18 | var request = context.request; 19 | 20 | var host = blackboxConfig.host; 21 | var path = blackboxConfig.path + '?' + querystring_.stringify({ 22 | 'method': 'sessionid', 23 | 'userip': request.headers['x-real-ip'], 24 | 'sessionid': request.cookies['Session_id'] || '', 25 | 'host': blackboxConfig['domain'], 26 | 'format': 'json' 27 | }); 28 | 29 | de.http.get( 30 | { 31 | 'host': host, 32 | 'path': path, 33 | 'port': 80 34 | } 35 | ) 36 | .then(function(result) { 37 | promise.resolve( new Result.Raw(result, true) ); 38 | }) 39 | .else_(function(error) { 40 | promise.resolve( new Result.Error(error) ); 41 | }); 42 | }; 43 | 44 | // --------------------------------------------------------------------------------------------------------------- // 45 | 46 | module.exports = ya; 47 | 48 | // --------------------------------------------------------------------------------------------------------------- // 49 | 50 | -------------------------------------------------------------------------------- /test/dummy/chunk-txt: -------------------------------------------------------------------------------- 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 | Явление по определению локально растягивает генетический режим, 26 | хотя этот факт нуждается в дальнейшей тщательной экспериментальной проверке. 27 | Конечно, нельзя не принять во внимание тот факт, что воздухосодержание 28 | вызывает ламинарный бур в полном соответствии с законом Дарси. 29 | Впитывание, как следствие уникальности почвообразования в данных условиях, синфазно. 30 | 31 | -------------------------------------------------------------------------------- /lib/de.response.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de.Response 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var de = require('./de.js'); 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | 9 | de.Response = function() { 10 | // FIXME: Кажется тут должен быть массив, а не объект. 11 | // Вполне может быть несколько одинаковых заголовков. 12 | this.headers = {}; 13 | 14 | this.cookies = {}; 15 | 16 | this.status = 200; 17 | }; 18 | 19 | // --------------------------------------------------------------------------------------------------------------- // 20 | 21 | de.Response.prototype.setHeader = function(name, value) { 22 | this.headers[name] = String(value); 23 | }; 24 | 25 | // FIXME: Expires, encoding, ... 26 | de.Response.prototype.setCookie = function(name, value) { 27 | this.cookies[name] = value; 28 | }; 29 | 30 | de.Response.prototype.setStatus = function(status) { 31 | this.status = status; 32 | }; 33 | 34 | de.Response.prototype.setRedirect = function(location) { 35 | this.location = location; 36 | }; 37 | 38 | // --------------------------------------------------------------------------------------------------------------- // 39 | 40 | function escapeHeader(header) { 41 | return header 42 | .replace(/([\uD800-\uDBFF][\uDC00-\uDFFF])+/g, encodeURI) // валидные суррогатные пары 43 | .replace(/[\uD800-\uDFFF]/g, '') // невалидные половинки суррогатных пар 44 | .replace(/[\u0000-\u001F\u007F-\uFFFF]+/g, encodeURI); // всё остальное непечатное 45 | } 46 | 47 | de.Response.prototype.end = function(response, result) { 48 | var headers = this.headers; 49 | for (var header in headers) { 50 | response.setHeader( header, escapeHeader(headers[header]) ); 51 | } 52 | 53 | var cookies = this.cookies; 54 | var cookie = []; 55 | for (var name in cookies) { 56 | cookie.push(escapeHeader(name + '=' + cookies[name])); 57 | } 58 | response.setHeader('Set-Cookie', cookie); // FIXME: Выставлять expire и т.д. 59 | 60 | if (this.location) { 61 | response.statusCode = 302; 62 | response.setHeader('Location', escapeHeader(this.location)); 63 | response.end(); 64 | return; 65 | } 66 | 67 | response.statusCode = this.status; 68 | 69 | if (result) { 70 | response.setHeader( 'Content-Type', result.contentType() ); 71 | 72 | result.write(response); 73 | response.end(); 74 | } 75 | }; 76 | 77 | // --------------------------------------------------------------------------------------------------------------- // 78 | 79 | -------------------------------------------------------------------------------- /lib/de.context.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de.Context 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var no = require('nommon'); 6 | 7 | var de = require('./de.js'); 8 | 9 | require('./de.request.js'); 10 | require('./de.response.js'); 11 | 12 | var http_ = require('http'); 13 | var url_ = require('url'); 14 | var qs_ = require('querystring'); 15 | 16 | // --------------------------------------------------------------------------------------------------------------- // 17 | 18 | var _cid = 0; 19 | 20 | /** 21 | @param {(Object | http_.IncomingMessage)} request 22 | @param {Object=} extra_params 23 | */ 24 | de.Context = function(request, extra_params) { 25 | this.config = de.config; 26 | 27 | if (request instanceof http_.IncomingMessage) { 28 | this.request = new de.Request(request, extra_params); 29 | this.query = this.request.url.query; 30 | } else { 31 | this.request = null; 32 | this.query = request; 33 | } 34 | 35 | this.response = new de.Response(); 36 | 37 | this.state = {}; 38 | this.now = Date.now(); 39 | 40 | this.id = process.pid + '.' + _cid++; 41 | }; 42 | 43 | // Нечестный "клон". 44 | // Нужен для подмены state'а в локальных блоках. 45 | // 46 | de.Context.prototype.clone = function() { 47 | var clone = {}; 48 | clone.__proto__ = this; 49 | 50 | return clone; 51 | }; 52 | 53 | // --------------------------------------------------------------------------------------------------------------- // 54 | 55 | /** 56 | @param {(Object | http_.IncomingMessage)} request 57 | */ 58 | de.Context.create = function(request) { 59 | var promise = new no.Promise(); 60 | 61 | if ( 62 | request.method === 'POST' && 63 | de.mime(request.headers) === 'application/x-www-form-urlencoded' 64 | ) { 65 | var body = ''; 66 | 67 | request.on('data', function(data) { 68 | body += data; 69 | }); 70 | request.on('end', function() { 71 | var extra_params = qs_.parse(body); 72 | 73 | promise.resolve( new de.Context(request, extra_params) ); 74 | }); 75 | } else { 76 | promise.resolve( new de.Context(request) ); 77 | } 78 | 79 | return promise; 80 | }; 81 | 82 | // --------------------------------------------------------------------------------------------------------------- // 83 | 84 | de.Context.prototype.log = function(level, msg) { 85 | de.log(level, '[' + this.id + '] ' + msg); 86 | }; 87 | 88 | de.Context.prototype.error = function(msg) { 89 | this.log('error', msg); 90 | }; 91 | 92 | de.Context.prototype.warn = function(msg) { 93 | this.log('warn', msg); 94 | }; 95 | 96 | de.Context.prototype.info = function(msg) { 97 | this.log('info', msg); 98 | }; 99 | 100 | de.Context.prototype.debug = function(msg) { 101 | this.log('debug', msg); 102 | }; 103 | 104 | de.Context.prototype.log_end = function(level, msg, t1) { 105 | var diff = Date.now() - t1; 106 | if (this.config.log.slow && diff > this.config.log.slow) { 107 | level = 'warn'; 108 | } 109 | this.log( level, msg + ' (' + diff + 'ms)' ); 110 | }; 111 | 112 | // --------------------------------------------------------------------------------------------------------------- // 113 | 114 | -------------------------------------------------------------------------------- /lib/de.server.js: -------------------------------------------------------------------------------- 1 | var os_ = require('os'); 2 | var cluster_ = require('cluster'); 3 | var path_ = require('path'); 4 | var http_ = require('http'); 5 | var fs_ = require('fs'); 6 | 7 | var de = require('./de.js'); 8 | 9 | require('./de.file.js'); 10 | require('./de.script.js'); 11 | require('./de.block.js'); 12 | require('./de.context.js'); 13 | require('./de.result.js'); 14 | 15 | // --------------------------------------------------------------------------------------------------------------- // 16 | 17 | de.server = {}; 18 | 19 | // --------------------------------------------------------------------------------------------------------------- // 20 | 21 | var _server; 22 | 23 | // --------------------------------------------------------------------------------------------------------------- // 24 | 25 | de.server.init = function(config) { 26 | config = de.script.init(config); 27 | 28 | if ( !(config.port || config.socket) || config.help ) { 29 | usage(); 30 | } 31 | }; 32 | 33 | // --------------------------------------------------------------------------------------------------------------- // 34 | 35 | function usage() { 36 | var name = ' ' + path_.basename(require.main.filename); 37 | 38 | console.log('Usage:'); 39 | console.log(name + ' --port 2000 --rootdir test/pages'); 40 | console.log(name + ' --socket descript.sock --rootdir test/pages'); 41 | console.log(name + ' --config test/config.json'); 42 | console.log(); 43 | 44 | process.exit(0); 45 | } 46 | 47 | // --------------------------------------------------------------------------------------------------------------- // 48 | 49 | de.server.start = function() { 50 | var workers = de.config.workers || ( os_.cpus().length - 1 ); 51 | 52 | if (cluster_.isMaster) { 53 | // cluster_.setupMaster({ silent: true }); 54 | 55 | de.log.info('master started. pid = ' + process.pid); 56 | 57 | var fork_worker = function() { 58 | var forked = cluster_.fork(); 59 | de.log.info('process forked. pid = ' + forked.process.pid); 60 | 61 | /* 62 | forked.process.stdout.on('data', function(data) { 63 | process.stdout.write(data); 64 | }); 65 | forked.process.stderr.on('data', function(data) { 66 | process.stderr.write(data); 67 | }); 68 | */ 69 | }; 70 | 71 | // Fork workers. 72 | for (var i = 0; i < workers; i++) { 73 | fork_worker(); 74 | } 75 | 76 | cluster_.on('exit', function(worker, code, signal) { 77 | de.log.error('process died. pid = ' + worker.process.pid + ' code = ' + code + ' signal = ' + signal); 78 | fork_worker(); 79 | }); 80 | 81 | } else { 82 | _server = http_.createServer(function(req, res) { 83 | // Если это post-запрос, то его параметры приходится получать 84 | // асинхронно. Поэтому de.Context.create() возвращает promise. 85 | // 86 | de.Context.create(req).done(function(context) { 87 | de.server.onrequest(req, res, context); 88 | }); 89 | }); 90 | 91 | if (de.config.socket) { 92 | _server.listen(de.config.socket, function() { 93 | // FIXME: Опять забыл, зачем нужна эта строчка. 94 | fs_.chmodSync(this.address(), 0777); 95 | }); 96 | } else { 97 | _server.listen(de.config.port, '::', 'localhost'); 98 | } 99 | 100 | } 101 | }; 102 | 103 | // --------------------------------------------------------------------------------------------------------------- // 104 | 105 | de.server.stop = function() { 106 | if (_server) { 107 | _server.close(); 108 | _server = null; 109 | } 110 | 111 | de.file.unwatch(); 112 | }; 113 | 114 | // --------------------------------------------------------------------------------------------------------------- // 115 | 116 | de.server.route = function(req, res, context) { 117 | var path = context.request.url.pathname || ''; 118 | if ( path.charAt(0) === '/' ) { 119 | path = path.substr(1); 120 | } 121 | path = path || 'index.jsx'; 122 | 123 | return path; 124 | }; 125 | 126 | de.server.onrequest = function(req, res, context) { 127 | var t1 = Date.now(); 128 | 129 | var block = de.server.route(req, res, context); 130 | if ( !(block instanceof de.Block) ) { 131 | block = new de.Block.Include(block); 132 | } 133 | 134 | // FIXME: Добавить сюда ip, headers, честный path из запроса. 135 | block.run(context.query, context) 136 | .done(function(result) { 137 | if (result instanceof de.Result.Error && result.get('id') === 'FILE_OPEN_ERROR') { 138 | res.statusCode = 404; 139 | res.end( result.formatted() ); 140 | return; 141 | } 142 | 143 | context.response.end(res, result); 144 | 145 | }) 146 | .always(function() { 147 | context.log_end('info', '[request ' + JSON.stringify(req.url) + '] ended', t1); 148 | }); 149 | }; 150 | 151 | // --------------------------------------------------------------------------------------------------------------- // 152 | 153 | -------------------------------------------------------------------------------- /lib/de.common.js: -------------------------------------------------------------------------------- 1 | var de = require('./de.js'); 2 | 3 | var no = require('nommon'); 4 | 5 | var path_ = require('path'); 6 | var vm_ = require('vm'); 7 | 8 | // --------------------------------------------------------------------------------------------------------------- // 9 | 10 | de.parseCookies = function(cookie) { 11 | var cookies = {}; 12 | 13 | var parts = cookie.split(';'); 14 | for (var i = 0, l = parts.length; i < l; i++) { 15 | var r = parts[i].match(/^\s*([^=]+)=(.*)$/); 16 | if (r) { 17 | cookies[ r[1] ] = r[2]; 18 | } 19 | } 20 | 21 | return cookies; 22 | }; 23 | 24 | // --------------------------------------------------------------------------------------------------------------- // 25 | 26 | de.duration = function(s) { 27 | if (typeof s === 'number') { 28 | return s; 29 | } 30 | 31 | var parts = s.split(/(\d+)([dhms])/); 32 | var d = 0; 33 | 34 | for (var i = 0, l = parts.length; i < l; i += 3) { 35 | var n = +parts[i + 1]; 36 | 37 | switch (parts[i + 2]) { 38 | case 'd': 39 | d += n * (60 * 60 * 24); 40 | break; 41 | case 'h': 42 | d += n * (60 * 60); 43 | break; 44 | case 'm': 45 | d += n * (60); 46 | break; 47 | case 's': 48 | d += n; 49 | break; 50 | } 51 | } 52 | 53 | return d * 1000; 54 | }; 55 | 56 | // --------------------------------------------------------------------------------------------------------------- // 57 | 58 | de.eval = function(js, namespace, sandbox, filename) { 59 | // FIXME: Таки пострелять в оба вариант. 60 | // Вроде в 0.10.2 разница в пределах погрешности измерения. 61 | // Если это действительно так, то лучше использовать vm. 62 | 63 | var sb = {}; 64 | if (namespace && sandbox) { 65 | sb[namespace] = sandbox; 66 | } 67 | 68 | // Прокидываем в песочницу для удобства: 69 | if (filename) { 70 | var dirname = path_.dirname( path_.resolve(filename) ); 71 | sb.require = function(filename) { 72 | if ( filename.charAt(0) === '.' ) { 73 | return require( path_.resolve(dirname, filename) ); 74 | } else { 75 | return require(filename); 76 | } 77 | }; 78 | } else { 79 | sb.require = require; 80 | } 81 | sb.console = console; 82 | // Array нужен, чтобы правильно работал instanceof Array. 83 | // FIXME: Лучше бы везде заменить на Array.isArray. 84 | // sb.Array = Array; 85 | 86 | return vm_.runInNewContext('(' + js + ')', sb, filename); 87 | /* 88 | */ 89 | 90 | /* 91 | if (namespace) { 92 | return Function( namespace, 'global', '"use strict"; return (' + js + ');' )(sandbox); 93 | } else { 94 | return Function( 'global', '"use strict"; return (' + js + ');' )(); 95 | } 96 | */ 97 | }; 98 | 99 | // --------------------------------------------------------------------------------------------------------------- // 100 | 101 | de.error = function(error) { 102 | return new de.Result.Error(error); 103 | }; 104 | 105 | // --------------------------------------------------------------------------------------------------------------- // 106 | 107 | de.forward = function(src, dest) { 108 | src.pipe(dest); 109 | dest.forward('abort', src); 110 | }; 111 | 112 | // --------------------------------------------------------------------------------------------------------------- // 113 | 114 | de.mime = function(headers) { 115 | var s = headers['content-type'] || ''; 116 | 117 | var i = s.indexOf(';'); 118 | return (i === -1) ? s : s.substr(0, i); 119 | }; 120 | 121 | // --------------------------------------------------------------------------------------------------------------- // 122 | 123 | de.events = no.extend( {}, no.Events ); 124 | 125 | // --------------------------------------------------------------------------------------------------------------- // 126 | 127 | var pid = process.pid; 128 | var gid = 0; 129 | 130 | de.id = function() { 131 | return pid + '.' + gid++; 132 | }; 133 | 134 | // --------------------------------------------------------------------------------------------------------------- // 135 | 136 | /** 137 | * Merges (deeply extends) `dest` object with `src`. At each node 138 | * - concats old and new value if both are arrays, 139 | * - walks deeper only if both values are objects (and not null of course), 140 | * - replaces old value with new if one of them is not an object. 141 | * @param {!Object} dest 142 | * @param {!Object} src 143 | * @return {!Object} 144 | */ 145 | de.mergeObjects = function(dest, src) { 146 | for (var key in src) { 147 | var value = src[key]; 148 | if (Array.isArray(value) && Array.isArray(dest[key])) { 149 | dest[key] = dest[key].concat(value); 150 | } else if (isObjectSimple(dest[key]) && isObjectSimple(value)) { 151 | dest[key] = de.mergeObjects(dest[key], value); 152 | } else { 153 | dest[key] = value; 154 | } 155 | } 156 | return dest; 157 | }; 158 | 159 | var isObjectSimple = function(source) { 160 | return source !== null && typeof source === 'object' && !Array.isArray(source); 161 | }; -------------------------------------------------------------------------------- /lib/de.script.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------- // 2 | // de.script 3 | // --------------------------------------------------------------------------------------------------------------- // 4 | 5 | var de = require('./de.js'); 6 | 7 | require('./de.common.js'); 8 | 9 | var no = require('nommon'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------- // 12 | 13 | var fs_ = require('fs'); 14 | var path_ = require('path'); 15 | 16 | var nopt = require('nopt'); 17 | 18 | // --------------------------------------------------------------------------------------------------------------- // 19 | 20 | de.script = {}; 21 | 22 | // --------------------------------------------------------------------------------------------------------------- // 23 | 24 | de._modules = {}; 25 | 26 | // --------------------------------------------------------------------------------------------------------------- // 27 | 28 | // Конфиг для работы descript'а составляется из трех частей: 29 | // 30 | // * Конфиг из файла. Имя файла либо задано параметром --config, 31 | // либо передано в de.script.init(). 32 | // 33 | // * Объект, переданный в de.script.init(). 34 | // 35 | // * Параметры, переданные в командной строке. 36 | // 37 | de.script.init = function(config) { 38 | // В случае, когда конфиг берется не из файла, 39 | // относительные имена файлов в нем нужно резолвить 40 | // относительно текущей директории. 41 | var cwd = path_.resolve('.'); 42 | 43 | // Читаем все параметры из командной строки. 44 | var args = nopt( 45 | { 46 | port: String, 47 | socket: String, 48 | rootdir: String, 49 | config: String, 50 | help: Boolean, 51 | workers: Number 52 | } 53 | ); 54 | // Удаляем лишние поля, генерируемые nopt'ом. 55 | delete args.argv; 56 | 57 | // Параметры из командной строки. 58 | var config_cmdline = prepare_config(args, cwd); 59 | // Параметры из файла с конфигом. 60 | var config_file; 61 | // Параметры, переданные в de.script.init() в виде объекта. 62 | var config_arg; 63 | 64 | var basedir; 65 | 66 | if (config_cmdline.config || typeof config === 'string') { 67 | // Задано имя файла с конфигом. 68 | var config_filename = config_cmdline.config || config; 69 | 70 | basedir = path_.dirname( path_.resolve(config_filename) ); 71 | 72 | config_file = prepare_config( 73 | // Читаем конфиг из файла. 74 | read_config(config_filename), 75 | // Относительные имена нужно резолвить от места, 76 | // где лежит файл с конфигом. 77 | basedir 78 | ); 79 | } 80 | 81 | if (typeof config === 'object') { 82 | // Объект с конфигом передан в de.script.init(). 83 | basedir = basedir || cwd; 84 | 85 | config_arg = prepare_config(config, basedir); 86 | } 87 | 88 | // Склеиваем все три конфига вместе. 89 | // (на самом деле, один из них всегда undefined). 90 | config = no.extend( {}, config_file, config_arg, config_cmdline ); 91 | 92 | config = default_config(config, basedir); 93 | 94 | // Если ни в одном конфиге не задан rootdir, то 95 | // используем текущую директорию. 96 | config.rootdir = config.rootdir || cwd; 97 | 98 | de.config = config; 99 | 100 | if (config.modules) { 101 | read_modules(config.modules, basedir); 102 | } 103 | 104 | require('./de.log.js'); 105 | require('./de.sandbox.js'); 106 | 107 | // Делаем доступным config в .jsx-файлах. 108 | // de.sandbox.config = config; 109 | 110 | return config; 111 | 112 | }; 113 | 114 | // --------------------------------------------------------------------------------------------------------------- // 115 | 116 | /** 117 | Резолвим относительные имена файлов, загружаем модули и т.д. 118 | 119 | @param {Object} config 120 | @param {string} basedir Директория, относительно которой нужно резолвить относительные пути файлов. 121 | */ 122 | function prepare_config(config, basedir) { 123 | if (!config) { 124 | return; 125 | } 126 | 127 | if (config.socket) { 128 | config.socket = path_.resolve(basedir, config.socket); 129 | } 130 | if (config.rootdir) { 131 | config.rootdir = path_.resolve(basedir, config.rootdir); 132 | } 133 | 134 | return config; 135 | } 136 | 137 | // --------------------------------------------------------------------------------------------------------------- // 138 | 139 | function default_config(config, basedir) { 140 | if (!config) { 141 | return; 142 | } 143 | 144 | if (!config.log) { 145 | config.log = {}; 146 | } 147 | if (!config.log.level) { 148 | config.log.level = 'debug'; 149 | } 150 | if (!config.log.logger) { 151 | config.log.logger = './de.logger.js'; 152 | } else { 153 | config.log.logger = path_.resolve(basedir, config.log.logger); 154 | } 155 | config.log.logger = require(config.log.logger); 156 | 157 | return config; 158 | } 159 | 160 | // --------------------------------------------------------------------------------------------------------------- // 161 | 162 | function read_config(filename) { 163 | var content = fs_.readFileSync(filename, 'utf-8'); 164 | 165 | return de.eval(content); 166 | } 167 | 168 | // --------------------------------------------------------------------------------------------------------------- // 169 | 170 | function read_modules(modules, dirname) { 171 | for (var id in modules) { 172 | var filename = path_.join( dirname, modules[id] ); 173 | 174 | de._modules[id] = require(filename); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/de.file.js: -------------------------------------------------------------------------------- 1 | var no = require('nommon'); 2 | var de = require('./de.js'); 3 | 4 | require('./de.common.js'); 5 | require('./de.result.js'); 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | 9 | var fs_ = require('fs'); 10 | 11 | // --------------------------------------------------------------------------------------------------------------- // 12 | 13 | de.file = {}; 14 | 15 | // --------------------------------------------------------------------------------------------------------------- // 16 | 17 | // Кэш с уже считанными файлами (или файлы, которые в процессе чтения). 18 | // В кэше хранятся promise'ы, в которых хранятся инстансы класса de.Result.File. 19 | var _get_cache = {}; 20 | 21 | // Кэш со считанными и исполненными файлами. 22 | // В кэше хранятся promise'ы, в которых хранится результат выполнения файлов. 23 | var _eval_cache = {}; 24 | 25 | // За какими файлами мы уже следим (чтобы не делать повторный watch). 26 | var _watched = {}; 27 | 28 | // --------------------------------------------------------------------------------------------------------------- // 29 | 30 | de.file.get = function(filename, context) { 31 | var promise = _get_cache[filename]; 32 | 33 | if (!promise) { 34 | promise = _get_cache[filename] = new no.Promise(); 35 | 36 | var rootdir = de.config.rootdir; 37 | // Проверяем, что файл лежит внутри rootdir. 38 | var is_outside = ( filename.substr(0, rootdir.length) !== rootdir ); 39 | if (is_outside) { 40 | var message = 'path \'' + filename + '\' is outside of rootdir'; 41 | context.error('[file.open ' + JSON.stringify(filename) + '] ' + message); 42 | return promise.reject( de.error({ 43 | id: 'FILE_INVALID_PATH', 44 | message: message 45 | }) ); 46 | } 47 | 48 | fs_.readFile(filename, function(error, buffer) { 49 | if (error) { 50 | // Если не удалось считать файл, в следующий раз нужно повторить попытку, 51 | // а не брать из кэша ошибку. 52 | _get_cache[filename] = null; 53 | 54 | // FIXME: Разные коды ошибок в зависимости от. 55 | // Как минимум 404. 56 | context.error('[file.open ' + JSON.stringify(filename) + '] ' + error.message); 57 | promise.reject( de.error({ 58 | id: 'FILE_OPEN_ERROR', 59 | message: error.message 60 | }) ); 61 | } else { 62 | // Содержимое файла закэшировано внутри promise'а. Следим, не изменился ли файл. 63 | de.file.watch('file-changed', filename); 64 | 65 | promise.resolve( new de.Result.File(filename, buffer) ); 66 | } 67 | }); 68 | } 69 | 70 | return promise; 71 | }; 72 | 73 | de.events.on('file-changed', function(e, filename) { 74 | // Файл изменился, выкидываем его из кэша. 75 | _get_cache[filename] = null; 76 | }); 77 | 78 | // --------------------------------------------------------------------------------------------------------------- // 79 | 80 | // Читаем файл и затем исполняем его. 81 | // 82 | // Смысл второго и третьего параметра: 83 | // 84 | // de.file.eval(filename, 'de', { 85 | // file: function() { ... }, 86 | // http: function() { ... }, 87 | // ... 88 | // }, context); 89 | // 90 | // В этом случае в файле можно будет использовать переменную de, 91 | // в частности, методы de.file, de.http, ... 92 | // Т.е. это некий аналог global. 93 | // 94 | de.file.eval = function(filename, namespace, sandbox, context) { 95 | var promise = _eval_cache[filename]; 96 | 97 | if (!promise) { 98 | promise = _eval_cache[filename] = new no.Promise(); 99 | 100 | // FIXME: По идее, эти файлы не нужно кэшировать в _get_cache. 101 | de.file.get(filename, context) 102 | .done(function(/** @type {de.Result.File} */ result) { 103 | var evaled; 104 | 105 | try { 106 | evaled = de.eval(result, namespace, sandbox, filename); 107 | } catch (e) { 108 | context.error('[file.eval ' + JSON.stringify(filename) + '] ' + e.message); 109 | promise.reject( de.error({ 110 | id: 'EVAL_ERROR', 111 | message: e.message 112 | }) ); 113 | } 114 | 115 | de.file.watch('loaded-file-changed', filename); 116 | 117 | promise.resolve(evaled); 118 | }) 119 | .fail(function(error) { 120 | _eval_cache[filename] = null; 121 | 122 | promise.reject(error); 123 | }); 124 | } 125 | 126 | return promise; 127 | }; 128 | 129 | de.events.on('loaded-file-changed', function(e, filename) { 130 | // Файл изменился, выкидываем его из кэша. 131 | _eval_cache[filename] = null; 132 | }); 133 | 134 | // --------------------------------------------------------------------------------------------------------------- // 135 | 136 | de.file.watch = function(type, filename) { 137 | var isWatched = ( _watched[type] || (( _watched[type] = {} )) )[filename]; 138 | 139 | if (!isWatched) { 140 | _watched[type][filename] = true; 141 | 142 | // FIXME: Непонятно, как это будет жить, когда файлов будет много. 143 | fs_.watchFile(filename, function (curr, prev) { 144 | if ( prev.mtime.getTime() !== curr.mtime.getTime() ) { 145 | de.events.trigger(type, filename); 146 | } 147 | }); 148 | } 149 | }; 150 | 151 | // NOTE: Если сделать просто de.file.eval() и не вызвать no.file.de.unwatch(), 152 | // то процесс не завершится никогда. Так как будет висеть слушатель изменений файла. 153 | // 154 | de.file.unwatch = function(type, filename) { 155 | if (type) { 156 | var files = _watched[type]; 157 | 158 | if (filename) { 159 | if ( files && files[filename] ) { 160 | fs_.unwatchFile(filename); 161 | 162 | delete files[filename]; 163 | } 164 | } else { 165 | for (var filename in files) { 166 | fs_.unwatchFile(filename); 167 | } 168 | 169 | _watched[type] = {}; 170 | } 171 | } else { 172 | for (type in _watched) { 173 | de.file.unwatch(type); 174 | } 175 | 176 | _watched = {}; 177 | } 178 | }; 179 | 180 | // --------------------------------------------------------------------------------------------------------------- // 181 | -------------------------------------------------------------------------------- /lib/de.http.js: -------------------------------------------------------------------------------- 1 | var no = require('nommon'); 2 | 3 | var de = require('./de.js'); 4 | require('./de.common.js'); 5 | require('./de.result.js'); 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | 9 | var url_ = require('url'); 10 | var http_ = require('http'); 11 | var https_ = require('https'); 12 | var qs_ = require('querystring'); 13 | 14 | // --------------------------------------------------------------------------------------------------------------- // 15 | 16 | de.http = function(options, params, context) { 17 | var parsed = url_.parse(options.url, true, true); 18 | 19 | parsed = no.extend(parsed, options.http_options); 20 | 21 | if (params) { 22 | parsed.query = no.extend(parsed.query || {}, params); 23 | } 24 | 25 | // в случае пост-запроса и явного указания body в настройках блока, 26 | // нужно передать значение в качестве тела запроса 27 | if ( (options.method === 'post' || options.method === 'put' || options.method === 'patch') && options.body ) { 28 | parsed.query = options.body; 29 | } 30 | 31 | var max_redirects = ( options.max_redirects === undefined ) ? 3 : options.max_redirects; 32 | var only_status = options.only_status; 33 | 34 | parsed.headers = options.headers; 35 | parsed.method = options.method; 36 | 37 | var req; 38 | var promise = new no.Promise(); 39 | 40 | promise.on('abort', function() { 41 | req.abort(); 42 | }); 43 | 44 | var full_path = '[http ' + url_.format(parsed) + '] '; 45 | var http_status = 0; 46 | var received_length = 0; 47 | 48 | var t1 = Date.now(); 49 | promise.done(function() { 50 | context.log_end('info', full_path + http_status + ' ' + received_length + ' ended', t1); 51 | }); 52 | promise.fail(function() { 53 | context.log_end('info', full_path + http_status + ' failed', t1); 54 | }); 55 | 56 | doHttp(parsed, 0); 57 | 58 | return promise; 59 | 60 | function doHttp(options, count) { 61 | var post_data; 62 | if (options.method === 'post' || options.method === 'put' || options.method === 'patch') { 63 | var headers = options.headers || (( options.headers = {} )); 64 | 65 | if ('application/json' === headers['Content-Type']) { 66 | post_data = JSON.stringify(options.query); 67 | } else { 68 | post_data = qs_.stringify(options.query); 69 | } 70 | 71 | if (post_data) { 72 | // FIXME: Если никаких данных не передается, то, кажется, 73 | // эти заголовки и не нужны вовсе? 74 | // 75 | if (!headers['Content-Type']) { 76 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 77 | } 78 | // вычислим значение заголовка Content-Length с учетом 79 | // наличия кириллицы в строке, 80 | // String.prototype.length в данном случае врет 81 | headers['Content-Length'] = Buffer.byteLength(post_data); 82 | } 83 | 84 | } else { 85 | options.path = url_.format({ 86 | pathname: options.pathname, 87 | query: options.query 88 | }); 89 | } 90 | 91 | req = ( (options.protocol === 'https:') ? https_ : http_).request(options, function(res) { 92 | var status = res.statusCode; 93 | 94 | if (only_status) { 95 | // FIXME: Непонятно, нужно ли это? 96 | // Но если там длинная дата, то ждать ее нет смысла. 97 | req.abort(); 98 | 99 | return promise.resolve( new de.Result.Value({ 100 | status: status, 101 | headers: res.headers 102 | }) ); 103 | } 104 | 105 | if (status === 204) { 106 | res.resume(); 107 | return promise.resolve(new de.Result.Value({})); 108 | } 109 | 110 | http_status = status; 111 | 112 | var error; 113 | if (status === 301 || status === 302) { 114 | // TODO: Кэшировать 301 запросы. 115 | 116 | // FIXME: А это нельзя вынести повыше? 117 | res.resume(); 118 | 119 | var location = res.headers['location'] || ''; 120 | 121 | // FIXME: MAX_REDIRECTS. 122 | if (count >= max_redirects) { 123 | context.error('Too many redirects'); 124 | return promise.reject( de.error({ 125 | 'id': 'HTTP_TOO_MANY_REDIRECTS', 126 | 'status': status, 127 | 'location': location 128 | }) ); 129 | } 130 | 131 | location = url_.resolve(options.href, location); 132 | options = url_.parse(location, true, true); 133 | 134 | context.info(full_path + 'redirected to ' + location); 135 | 136 | return doHttp(options, count + 1); 137 | } 138 | 139 | var result = new de.Result.Http(res.headers); 140 | 141 | res.on('data', function(data) { 142 | received_length += data.length; 143 | result.data(data); 144 | }); 145 | res.on('end', function() { 146 | result.end(); 147 | 148 | if (status >= 400 && status <= 599) { 149 | context.error(full_path + 'error ' + status); 150 | 151 | var mime = de.mime( res.headers ); 152 | 153 | var body; 154 | if ( mime === 'application/json' ) { 155 | try { 156 | body = JSON.parse( result.buffer ); 157 | } catch ( e ) { 158 | body = 'JSON.parse: Cannot parse response body'; 159 | } 160 | } 161 | 162 | return promise.reject( de.error({ 163 | 'id': 'HTTP_' + status, 164 | 'message': http_.STATUS_CODES[status], 165 | 'body': body 166 | }) ); 167 | 168 | } else { 169 | promise.resolve(result); 170 | } 171 | }); 172 | res.on('close', function(error) { 173 | context.error(full_path + 'error ' + error.message); 174 | promise.reject( de.error({ 175 | 'id': 'HTTP_CONNECTION_CLOSED', 176 | 'message': error.message 177 | }) ); 178 | }); 179 | }); 180 | 181 | req.on('error', function(error) { 182 | context.error(full_path + 'error ' + error.message); 183 | promise.reject( de.error({ 184 | 'id': 'HTTP_UNKNOWN_ERROR', 185 | 'message': error.message 186 | }) ); 187 | }); 188 | 189 | if (post_data) { 190 | req.write(post_data); 191 | } 192 | req.end(); 193 | } 194 | 195 | }; 196 | 197 | // ----------------------------------------------------------------------------------------------------------------- // 198 | 199 | de.http.get = function(url, params, headers, context) { 200 | return de.http( 201 | { 202 | url: url, 203 | headers: headers, 204 | method: 'get' 205 | }, 206 | params, 207 | context 208 | ); 209 | }; 210 | 211 | de.http.post = function(url, params, headers, context) { 212 | return de.http( 213 | { 214 | url: url, 215 | headers: headers, 216 | method: 'post' 217 | }, 218 | params, 219 | context 220 | ); 221 | }; 222 | 223 | // --------------------------------------------------------------------------------------------------------------- // 224 | -------------------------------------------------------------------------------- /test/dummy/dummy.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Фейковый http-сервер для тестирования http-приложений. 4 | Управляется набором параметров в урле. 5 | 6 | Может возвращать заданную http-ошибку: 7 | 8 | /error=500 9 | 10 | /error=500&message=GOAWAY -- с заданным сообщением об ошибке. 11 | 12 | /error=500&delay=1000 -- вернуть ошибку через секунду, а не сразу. 13 | 14 | Может вернуть содержимое заданного файла: 15 | 16 | /filename=foo.txt 17 | 18 | /filename=foo.txt&delay=1000 -- вернуть весь файл через секунду. 19 | 20 | /filename=foo.txt&delay=1000x5 -- возвращать файл порциями, порций будет 5 штук, интервалы между ними будут в 1 секунду. 21 | 22 | /filename=foo.txt&delay=1000,2000,3000 -- тоже самое, но 3 порции, первая через секунду, вторая еще через 2, третья -- еще через 3. 23 | т.е. весь запрос закончится через 6 секунд. 24 | 25 | Если имя файла не задано, то используется предопределенный контент. 26 | В формате json, txt или xml. Задается параметром datatype. 27 | 28 | / -- возвращает дефолтный json (json -- это дефолтное значение параметра datatype). 29 | 30 | /delay=1000 -- тоже самое, но через секунду. 31 | 32 | /delay=1000&datatype=txt -- тоже самое, но возвращает текстовый контент. 33 | 34 | /delay=1000x5 -- дефолтный json в 5 порций, между порциями задержка в 1 секунду. 35 | 36 | /delay=1000,2000,3000 -- аналогично, но задержки между порциями заданы явно. 37 | 38 | 39 | Как использовать: 40 | 41 | var dummy = require('./dummy.js'); 42 | 43 | var port = 2000; 44 | // Запускает сервер на указанном порту. 45 | var server = dummy(port); 46 | 47 | // И где-нибудь можно остановить сервер. 48 | server.close(); 49 | 50 | 51 | Или же прямо из командной строки: 52 | 53 | node -e 'require("./dummy.js")(2000);' 54 | 55 | */ 56 | 57 | // --------------------------------------------------------------------------------------------------------------- // 58 | 59 | var fs_ = require('fs'); 60 | var http_ = require('http'); 61 | var url_ = require('url'); 62 | var path_ = require('path'); 63 | 64 | // --------------------------------------------------------------------------------------------------------------- // 65 | 66 | var contentTypes = { 67 | json: 'application/json', 68 | xml: 'text/xml', 69 | txt: 'text/plain' 70 | }; 71 | 72 | var fragments = { 73 | json: fs_.readFileSync( path_.resolve(__dirname, './chunk-json'), 'utf-8'), 74 | xml: fs_.readFileSync( path_.resolve(__dirname, './chunk-xml'), 'utf-8'), 75 | txt: fs_.readFileSync( path_.resolve(__dirname, './chunk-txt'), 'utf-8') 76 | }; 77 | 78 | var infos = { 79 | json: { 80 | open: '[', 81 | delim: ',\n', 82 | close: ']' 83 | }, 84 | xml: { 85 | open: '', 86 | delim: '\n', 87 | close: '' 88 | }, 89 | txt: { 90 | open: '', 91 | delim: '\n\n', 92 | close: '' 93 | }, 94 | file: { 95 | open: '', 96 | delim: '', 97 | close: '' 98 | } 99 | }; 100 | 101 | // --------------------------------------------------------------------------------------------------------------- // 102 | 103 | module.exports = function(port) { 104 | 105 | return http_ 106 | .createServer(function(req, res) { 107 | var params = url_.parse(req.url, true, true).query; 108 | 109 | var error = params.error; 110 | if (error) { 111 | var delay = params.delay || 0; 112 | 113 | setTimeout(function() { 114 | res.writeHead( error, { 'Content-Type': 'text/plain' } ); 115 | res.end( params.message || http_.STATUS_CODES[error] || 'Unknown error' ); 116 | }, delay); 117 | 118 | return; 119 | } 120 | 121 | var filename = params.filename; 122 | var datatype = getDatatype(filename, params.datatype); 123 | 124 | var delays = getDelays(params.delay); 125 | var l = delays.length; 126 | 127 | var chunks; 128 | var info; 129 | 130 | if (filename) { 131 | var content = fs_.readFileSync(filename, 'utf-8'); 132 | chunks = splitString(content, l); 133 | info = infos['file']; 134 | } else { 135 | var fragment = fragments[datatype]; 136 | chunks = []; 137 | for (var i = 0; i < l; i++) { 138 | chunks.push(fragment); 139 | } 140 | info = infos[datatype]; 141 | } 142 | 143 | res.writeHead(200, { 'Content-Type': contentTypes[datatype] }); 144 | res.write(info.open); 145 | for (var i = 0; i < l; i++) { 146 | (function(i) { 147 | setTimeout(function() { 148 | if (i) { 149 | res.write(info.delim); 150 | } 151 | res.write( chunks[i] ); 152 | }, delays[i]); 153 | })(i); 154 | } 155 | setTimeout(function() { 156 | res.end(info.close); 157 | }, delays[l - 1]); 158 | }) 159 | .listen(port || 8000, '127.0.0.1'); 160 | 161 | }; 162 | 163 | // --------------------------------------------------------------------------------------------------------------- // 164 | 165 | // Разбиваем строку на n примерно равных кусков. 166 | function splitString(s, n) { 167 | var p = Math.round(s.length / n); 168 | 169 | var chunks = []; 170 | for (var i = 0; i < n - 1; i++) { 171 | chunks.push( s.substr(i * p, p) ); 172 | } 173 | chunks.push( s.substr( (n - 1) * p ) ); 174 | 175 | return chunks; 176 | } 177 | 178 | // --------------------------------------------------------------------------------------------------------------- // 179 | 180 | // Вычисляем тип ответа. Либо по расширению имени файла (если передано), 181 | // либо по параметру datatype. 182 | function getDatatype(filename, datatype) { 183 | if (!filename) { 184 | return datatype || 'json'; 185 | } 186 | 187 | var ext = path_.extname(filename); 188 | 189 | switch (ext) { 190 | case '.json': 191 | return 'json'; 192 | 193 | case '.xml': 194 | return 'xml'; 195 | } 196 | 197 | return 'txt'; 198 | } 199 | 200 | // --------------------------------------------------------------------------------------------------------------- // 201 | 202 | function getDelays(delay) { 203 | if (delay) { 204 | var r; 205 | if (( r = /^(\d+)x(\d+)/.exec(delay) )) { 206 | // Вариант, когда задержки заданы в виде: 1000x5. 207 | // Т.е. 5 интервалов по 1 секунде каждый. 208 | var step = +r[1]; 209 | var n = +r[2]; 210 | 211 | delays = []; 212 | for (var i = 0; i < n; i++) { 213 | delays.push(step); 214 | } 215 | } else { 216 | // Все задержки заданы явно, например: 1000,2000,3000. 217 | // Т.е. первый чанк выдать через секунду, второй через 2 секунды после этого 218 | // и третий еще через 3 секунды после второго. 219 | delays = delay 220 | .split(',') 221 | // Приводим строки к числу. 222 | .map(function(x) { 223 | return +x; 224 | }); 225 | } 226 | } else { 227 | // Дефолтное поведение: отдать все одним чанком без задержек. 228 | delays = [ 0 ]; 229 | } 230 | 231 | // Преобразуем относительные задержки в абсолютные (от начала запроса). 232 | // Т.е. [ 1000, 2000, 3000 ] превратятся в [ 1000, 3000, 6000 ]. 233 | for (var i = 1; i < delays.length; i++) { 234 | delays[i] += delays[i - 1]; 235 | } 236 | 237 | return delays; 238 | } 239 | 240 | // --------------------------------------------------------------------------------------------------------------- // 241 | 242 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## 0.0.64 4 | 5 | * Merged pull request #130. Header value should be a string. 6 | 7 | ## 0.0.63 8 | 9 | * Merged pull request #129. Не падать, если ответ пуст или сформирован некорректно. 10 | * Merged pull request #126. Экранируем HTTP-заголовки в ответе. 11 | * Merged pull request #124. Don't treat `//` specially. 12 | 13 | ## 0.0.62 14 | 15 | * Merged pull request #118. Fix for PUT and body option. 16 | * Fixed #123. Fix for PATCH and body option. 17 | 18 | ## 0.0.61 19 | 20 | * Merged pull request #117. Небольшие улучшения логирования. 21 | 22 | ## 0.0.60 23 | 24 | * Merged pull request #114. Параметр body http-блока, для явного задания тела пост-запроса. 25 | * Merged pull request #108. Фикс: когда блок успевает выполниться до того, как мы установили таймер таймаута в логи пишется ошибка таймаута. 26 | 27 | ## 0.0.59 28 | 29 | * Merged pull request #103. Вычислять значение Content-Length с учетом UTF-8. 30 | 31 | ## 0.0.58 32 | 33 | * Merged pull request #102. fix overwrite dirname in include block. 34 | * Merged pull request #101. support of JSON in post requests. 35 | * Merged pull request #100. clear timeout when block fails. 36 | * Merged pull request #99. https support + http_options support. 37 | 38 | ## 0.0.57 39 | 40 | * Fix для 0.0.56. 41 | 42 | ## 0.0.56 43 | 44 | * Fixed #96. Странное поведение include блока. 45 | 46 | ## 0.0.55 47 | 48 | * Date format support in `de.config`. Merged pull requests #90, #92. 49 | * Fixed #93. Флаг `only_status` возвращает заголовки. Merged pull request #94. 50 | * Merged pull request #95. `options.headers` in http block. 51 | 52 | ## 0.0.54 53 | 54 | * Fixed #84. Local block. 55 | * Merged pull request #83. Log code and signal on fork process exit 56 | * Merged pull request #82. Removed duplicated method 57 | * Merged pull request #80. Debug log found in cache with key message 58 | 59 | ## 0.0.53 60 | 61 | * Merged pull request #78. Декларативная конструкция для гибкой работы со state. 62 | * Merged pull request #76. de.script.init() config support. 63 | * Merged pull request #75. Fixes #73. 64 | * Merged pull request #72. logging http get params. 65 | * Merged pull request #56. Specify filename for running block. 66 | 67 | ## 0.0.52 68 | 69 | * Fix для 0.0.49. Опечатка. 70 | 71 | ## 0.0.51 72 | 73 | * Fix для 0.0.49. Парсим тело ошибки, если там JSON. 74 | 75 | ## 0.0.50 76 | 77 | * Fix для 0.0.49. Приводим буфер к строке. 78 | 79 | ## 0.0.49 80 | 81 | * Fixed #41. Добавляем в http-ошибки тело ответа. 82 | 83 | ## 0.0.48 84 | 85 | * В `de.http.js` ловим все 4xx/5xx ошибки (раньше был фиксированные и неполный список). 86 | 87 | ## 0.0.47 88 | 89 | * Merged pull request #60. 90 | 91 | ## 0.0.46 92 | 93 | * Откатил #57. 94 | 95 | ## 0.0.45 96 | 97 | * Merged pull request #57. 98 | 99 | ## 0.0.44 100 | 101 | * Merged pull request #55. 102 | 103 | ## 0.0.43 104 | 105 | * Merged pull request #54. 106 | 107 | ## 0.0.42 108 | 109 | * Merged pull request #51. 110 | 111 | * `require` должен работать в исполняемых descript'ом файлах. Например, в шаблонах, 112 | в jsx-файлах и т.д. 113 | 114 | * `nommon` версии `0.0.28`. 115 | 116 | ## 0.0.41 117 | 118 | * Merged pull request #50. 119 | 120 | ## 0.0.40 121 | 122 | * `de.server.route` — метод, возвращающий либо строку с путем к jsx-файлу, либо инсанс `de.Block.*`. 123 | 124 | ## 0.0.39 125 | 126 | * Пофикшен баг, когда внутри `de.Result.Value` оказывается `undefined` (например, если в блоке есть `options.result`, 127 | который вычисляется в `undefined`), после чего метод `write()` падает, потому что в поток можно вывести только `Buffer` или `String`. 128 | 129 | ## 0.0.38 130 | 131 | * Разные доработки по логированию. 132 | 133 | * `nommon` версии `0.0.26`. 134 | 135 | ## 0.0.37 136 | 137 | * Merged pull request #48. 138 | 139 | ## 0.0.36 140 | 141 | * Базовое [логирование](https://github.com/pasaran/descript/issues/46). 142 | 143 | В конфиге можно задать уровень логирования и logger: 144 | 145 | log: { 146 | // По-умолчанию: 'debug'. 147 | // Возможные значения: 'off', 'error', 'warn', 'info', 'debug'. 148 | level: 'debug', 149 | 150 | // См. 'lib/de.logger.js' — дефолтный logger. 151 | logger: './my-logger.js' 152 | } 153 | 154 | * `nommon` версии `0.0.24`. 155 | 156 | ## 0.0.35 157 | 158 | * Merged pull request #44. 159 | 160 | ## 0.0.34 161 | 162 | * Merged pull request #42. 163 | 164 | ## 0.0.33 165 | 166 | * В `.jsx`-файлах доступен конфиг: 167 | 168 | de.object({ 169 | // Выводим весь конфиг целиком. 170 | config: de.value(de.config), 171 | 172 | // Выводим отдельное поле конфига 173 | host: de.value(de.config.backend.host) 174 | }) 175 | 176 | 177 | ## 0.0.32 178 | 179 | * Ключик `--cpus` переименован в `--workers`. 180 | Вместо `--cpus 2` нужно использовать `--workers 1`. 181 | Дефолтное значение параметра `--workers` — `require('os').cpus().length - 1`. 182 | 183 | * Изменена сигнатура в `options.after` на `(params, context, result)`. 184 | При этом `result` это инстанс класса `de.Result.*`, а не готовый объект с данными. 185 | Если нужен доступ к какому-то его содержимому нужно использовать метод `object()`: 186 | 187 | after: function(params, context, result) { 188 | var o = result.object(); 189 | 190 | console.log(o.foo.bar); 191 | } 192 | 193 | * Поправлено JS API. Минимально работающий вариант: 194 | 195 | var de = require('descript'); 196 | 197 | de.script.init(); 198 | 199 | de.Block.compile('http://yandex.ru/yandsearch?') 200 | .run({ text: 'descript' }) 201 | .then(function(result) { 202 | console.log( result.string() ); 203 | }); 204 | 205 | В этом примере контекст создается автоматически (при этом и доступа к нему нет). 206 | В `result` находится инстанс `de.Result`. 207 | 208 | Более сложный вариант: 209 | 210 | var http = require('http'); 211 | var de = require('descript'); 212 | 213 | de.script.init(); 214 | 215 | var block = de.Block.compile('http://yandex.ru/yandsearch?'); 216 | 217 | http 218 | .createServer(function(req, res) { 219 | // Создаем (асинхронно) контекст. 220 | de.Context.create(req).done(function(context) { 221 | block 222 | .run({ text: 'descript' }) 223 | .then(function(result) { 224 | // Этот метод выставляет все заголовки и т.д., 225 | // а также выводит результат в выходной поток (res) и закрывает поток. 226 | context.response.end(res, result); 227 | }); 228 | }); 229 | }) 230 | .listen(2000); 231 | 232 | Альтернативный вариант — использовать `de.server.start()` для поднятия сервера автоматически: 233 | 234 | var de = require('descript'); 235 | 236 | de.server.init(); 237 | de.server.start(); 238 | 239 | 240 | * Изменения в `de.Context`: 241 | 242 | * Конструктор `de.Context()` первым параметром принимает или нодовский реквест (`http.IncomingMessage`), 243 | или просто объект с параметрами. 244 | 245 | * Инстанс `de.Context` содержит поля: 246 | 247 | * `request` — может быть null, если в конструктор были переданы просто параметры. 248 | Либо инстанс `de.Request`. 249 | 250 | * `response` — инстанс `de.Response`. 251 | 252 | * `query` — все параметры (включая извлеченные из body запроса при `post`-запросе). 253 | 254 | * `state` — общий стейт для обмена информацией между блоками. 255 | 256 | * `config` — ссылка на `de.config`. 257 | 258 | * `de.Context.create(request)` — метод для создания контекста, возвращает promise, 259 | в который уже приходит готовый контекст. Сделано так потому, что если это `post`-запрос, 260 | то тело запроса (и параметры из него) получаются асинхронно. 261 | 262 | * Изменения в `de.Response`: 263 | 264 | * Больше не содержит ссылку на нодовский `http.ServerResponse`. 265 | 266 | * Появился метод `end(response, result)` — первым параметром принимает `http.ServerResponse`, 267 | второй (опциональный) — `de.Result`. 268 | Метод выставляет заголовки, код ответа, редиректы и т.д. 269 | Если передан `result`, то выводит его в поток (попутно выставляя `content-type`) и закрывает поток. 270 | 271 | * Изменения в `de.Request`: 272 | 273 | * Больше не содержит ссылку на нодовский `http.IncomingMessage`. 274 | 275 | * Имеет поля: 276 | 277 | * `headers` — http-заголовки, пришедшие в реквесте. 278 | 279 | * `cookies` — куки. 280 | 281 | * `url` — объект, получающийся из `require('url').parse(req.url, true, true)`. 282 | (http://nodejs.org/api/url.html#url_url)[http://nodejs.org/api/url.html#url_url]. 283 | 284 | Если это был `POST`-запрос, то в `url.query` будут параметры из тела запроса. 285 | 286 | * `method` — метод (`GET`, `POST`, ...). 287 | 288 | -------------------------------------------------------------------------------- /lib/de.result.js: -------------------------------------------------------------------------------- 1 | var no = require('nommon'); 2 | var de = require('./de.js'); 3 | 4 | var path_ = require('path'); 5 | 6 | 7 | // --------------------------------------------------------------------------------------------------------------- // 8 | // Consts 9 | // --------------------------------------------------------------------------------------------------------------- // 10 | 11 | // Преобразования между расширениями файлов, content-type и data-type. 12 | 13 | var content_type_by_ext = { 14 | '.json': 'application/json', 15 | 16 | '.html': 'text/html', 17 | '.htm': 'text/html', 18 | 19 | '.text': 'text/plain', 20 | '.txt': 'text/plain', 21 | 22 | '.css': 'text/css', 23 | '.js': 'application/javascript', 24 | 25 | '.xml': 'text/xml', 26 | 27 | '.png': 'image/png', 28 | '.jpg': 'image/jpeg', 29 | '.jpeg': 'image/jpeg', 30 | '.gif': 'image/gif' 31 | }; 32 | 33 | var data_type_by_content_type = { 34 | 'text/json': 'json', 35 | 'application/json': 'json', 36 | 37 | 'text/html': 'html', 38 | 39 | 'text/plain': 'text', 40 | 'text/xml': 'text', 41 | 'application/xml': 'text', 42 | 'text/css': 'text', 43 | 'text/javascript': 'text', 44 | 'text/x-javascript': 'text', 45 | 'application/javascript': 'text', 46 | 'application/x-javascript': 'text' 47 | }; 48 | 49 | var content_type_by_data_type = { 50 | 'text': 'text/plain', 51 | 'html': 'text/html', 52 | 'json': 'application/json' 53 | }; 54 | 55 | // --------------------------------------------------------------------------------------------------------------- // 56 | 57 | // В классах de.Result.* хранятся результаты выполнения блоков. 58 | // 59 | // de.Result --- базовый абстрактный класс, от него наследуются все остальные 60 | // (главным образом, для того, чтобы делать instanceof de.Result). 61 | // 62 | // de.Result.File и de.Result.Http возвращаются из de.file и de.http соответственно. 63 | // Сами по себе не используются для вывода, из них всегда создается de.Result.Raw. 64 | // Смысл этого преобразования в том, что при создании de.Result.Raw можно переопределить 65 | // флаги data_type и output_type. Это позволяет кэшировать de.Result.File/Http 66 | // (с тем data_type, который определяется самим контентом) и при выводе менять data_type. 67 | // 68 | // de.Result.Value --- для константных значений (числа, строки, объекты, ...). 69 | // 70 | // de.Result.Error --- для ошибок. 71 | // 72 | // de.Result.Array и de.Result.Object --- композитные объекты, для массивов и объектов соответственно. 73 | // 74 | // Все эти классы должны (как минимум) определить методы: 75 | // 76 | // * string() --- строковое представление объекта 77 | // * object() --- объект, с которым можно работать в js. 78 | // * write() --- записать объект в поток. 79 | // 80 | 81 | // --------------------------------------------------------------------------------------------------------------- // 82 | // de.Result 83 | // --------------------------------------------------------------------------------------------------------------- // 84 | 85 | de.Result = function(result) {}; 86 | 87 | de.Result.prototype.data_type = 'json'; 88 | de.Result.prototype.content_type = 'application/json'; 89 | 90 | de.Result.prototype.string = function() {}; 91 | 92 | de.Result.prototype.object = function() {}; 93 | 94 | de.Result.prototype.write = function(stream) {}; 95 | 96 | de.Result.prototype.formatted = function() { 97 | return JSON.stringify( this.object(), null, 4 ); 98 | }; 99 | 100 | de.Result.prototype.select = function(jpath, vars) { 101 | return no.jpath( jpath, this.object(), vars ); 102 | }; 103 | 104 | de.Result.prototype.contentType = function() { 105 | var content_type = this.content_type; 106 | 107 | if (this.data_type !== 'binary') { 108 | content_type += '; charset=utf-8'; 109 | } 110 | 111 | return content_type; 112 | }; 113 | 114 | // --------------------------------------------------------------------------------------------------------------- // 115 | // de.Result.Value 116 | // --------------------------------------------------------------------------------------------------------------- // 117 | 118 | de.Result.Value = function(result) { 119 | this.result = (result === undefined) ? null : result; 120 | }; 121 | 122 | no.inherit(de.Result.Value, de.Result); 123 | 124 | de.Result.Value.prototype._id = 'value'; 125 | 126 | de.Result.Value.prototype.string = function() { 127 | var s = this._string; 128 | 129 | if (s === undefined) { 130 | s = this._string = JSON.stringify(this.result); 131 | } 132 | 133 | return s; 134 | }; 135 | 136 | de.Result.Value.prototype.object = function() { 137 | return this.result; 138 | }; 139 | 140 | de.Result.Value.prototype.write = function(stream) { 141 | stream.write( this.string() ); 142 | }; 143 | 144 | 145 | // --------------------------------------------------------------------------------------------------------------- // 146 | // de.Result.Raw 147 | // --------------------------------------------------------------------------------------------------------------- // 148 | 149 | // У блока de.Result.Raw есть два флага: data_type и output_type. 150 | // Первый определяется тем, какой контент на самом деле в этом блоке. 151 | // Например: 152 | // 153 | // "hello.txt" 154 | // 155 | // data_type === 'text'. 156 | // 157 | // Но, вот здесь: 158 | // 159 | // { 160 | // hello: "hello.txt" 161 | // } 162 | // 163 | // file-блок нужно выводить не как текстовый файл, но как строку, 164 | // т.е. output_type === 'json' (и по-прежнему data_type === 'text'). 165 | // 166 | // data_type может принимать одно из значений: 'json', 'text', 'html', 'binary', 167 | // а output_type в данный момент может быть 'json' или undefined. 168 | 169 | de.Result.Raw = function(result, data_type, output_type) { 170 | /** @type {Buffer} */ 171 | this.result = result.buffer; 172 | 173 | if (data_type) { 174 | // data_type задан явно в options. 175 | this.data_type = data_type; 176 | this.content_type = content_type_by_data_type[data_type]; 177 | } else { 178 | // Используем data_type и content_type из result'а. 179 | this.data_type = result.data_type; 180 | this.content_type = result.content_type; 181 | } 182 | 183 | // output_type задан явно в options. 184 | if (output_type) { 185 | this.output_type = output_type; 186 | this.content_type = content_type_by_data_type[output_type]; 187 | } 188 | }; 189 | 190 | no.inherit(de.Result.Raw, de.Result); 191 | 192 | de.Result.Raw.prototype._id = 'raw'; 193 | 194 | de.Result.Raw.prototype.string = function() { 195 | var s = this._string; 196 | 197 | if (s === undefined) { 198 | s = this._string = (this.data_type === 'json') ? this.result.toString() : JSON.stringify( this.result.toString() ); 199 | } 200 | 201 | return s; 202 | }; 203 | 204 | de.Result.Raw.prototype.object = function() { 205 | var o = this._object; 206 | 207 | if (o === undefined) { 208 | if (this.data_type === 'json') { 209 | try { 210 | o = JSON.parse( this.string() ); 211 | } catch ( e ) { 212 | o = { error: 'JSON.parse: Cannot parse response body' }; 213 | } 214 | } else { 215 | o = this.string(); 216 | } 217 | 218 | this._object = o; 219 | } 220 | 221 | return o; 222 | }; 223 | 224 | // Значением output_type может быть либо 'json', либо undefined. 225 | // 226 | de.Result.Raw.prototype.write = function(stream, output_type) { 227 | output_type = output_type || this.output_type; 228 | 229 | if (this.data_type === 'json' || output_type !== 'json') { 230 | // Либо это у нас изначально json, 231 | // либо это text или html, который не внутри объекта/массива 232 | // и для которого не задан явно output_type. 233 | // 234 | // Выводим результат как есть. 235 | // 236 | stream.write(this.result); 237 | } else { 238 | // Преобразуем текстовый контент в строку (JSON.stringify). 239 | stream.write( this.string() ); 240 | } 241 | 242 | // FIXME: binary не может быть внутри объекта/массива. 243 | }; 244 | 245 | 246 | // --------------------------------------------------------------------------------------------------------------- // 247 | // de.Result.Array 248 | // --------------------------------------------------------------------------------------------------------------- // 249 | 250 | de.Result.Array = function(result) { 251 | this.result = result; 252 | }; 253 | 254 | no.inherit(de.Result.Array, de.Result); 255 | 256 | de.Result.Array.prototype._id = 'array'; 257 | 258 | de.Result.Array.prototype.string = function() { 259 | var s = this._string; 260 | 261 | if (s === undefined) { 262 | var result = this.result; 263 | 264 | s = '['; 265 | for (var i = 0, l = result.length; i < l; i++) { 266 | if (i) { 267 | s += ','; 268 | } 269 | s += result[i].string(); 270 | } 271 | s += ']'; 272 | 273 | this._string = s; 274 | } 275 | 276 | return s; 277 | }; 278 | 279 | de.Result.Array.prototype.object = function() { 280 | var o = this._object; 281 | 282 | if (!o) { 283 | var result = this.result; 284 | 285 | o = this._object = []; 286 | for (var i = 0, l = result.length; i < l; i++) { 287 | o.push( result[i].object() ); 288 | } 289 | } 290 | 291 | return o; 292 | }; 293 | 294 | de.Result.Array.prototype.write = function(stream) { 295 | stream.write('['); 296 | var result = this.result; 297 | for (var i = 0, l = result.length; i < l; i++) { 298 | if (i) { 299 | stream.write(','); 300 | } 301 | result[i].write(stream, 'json'); 302 | } 303 | stream.write(']'); 304 | }; 305 | 306 | 307 | // --------------------------------------------------------------------------------------------------------------- // 308 | // de.Result.Object 309 | // --------------------------------------------------------------------------------------------------------------- // 310 | 311 | de.Result.Object = function(result) { 312 | this.result = result; 313 | }; 314 | 315 | no.inherit(de.Result.Object, de.Result); 316 | 317 | de.Result.Object.prototype._id = 'object'; 318 | 319 | de.Result.Object.prototype.string = function() { 320 | var s = this._string; 321 | 322 | if (s === undefined) { 323 | var result = this.result; 324 | 325 | s = '{'; 326 | var i = 0; 327 | for (var key in result) { 328 | if (i++) { 329 | s += ','; 330 | } 331 | s += JSON.stringify(key) + ':' + result[key].string(); 332 | } 333 | s += '}'; 334 | 335 | this._string = s; 336 | } 337 | 338 | return s; 339 | }; 340 | 341 | de.Result.Object.prototype.object = function() { 342 | var o = this._object; 343 | 344 | if (!o) { 345 | var result = this.result; 346 | 347 | o = this._object = {}; 348 | for (var key in result) { 349 | o[key] = result[key].object(); 350 | } 351 | } 352 | 353 | return o; 354 | }; 355 | 356 | de.Result.Object.prototype.write = function(stream) { 357 | stream.write('{'); 358 | var i = 0; 359 | var result = this.result; 360 | for (var key in result) { 361 | if (i++) { 362 | stream.write(','); 363 | } 364 | stream.write( JSON.stringify(key) + ':' ); 365 | result[key].write(stream, 'json'); 366 | } 367 | stream.write('}'); 368 | }; 369 | 370 | // --------------------------------------------------------------------------------------------------------------- // 371 | 372 | de.Result.HTML = function(result) { 373 | this.result = result; 374 | }; 375 | 376 | no.inherit(de.Result.HTML, de.Result.Value); 377 | 378 | de.Result.HTML.prototype._id = 'html'; 379 | 380 | de.Result.HTML.prototype.data_type = 'html'; 381 | de.Result.HTML.prototype.content_type = 'text/html'; 382 | 383 | de.Result.HTML.prototype.write = function(stream) { 384 | stream.write(this.result); 385 | }; 386 | 387 | 388 | // --------------------------------------------------------------------------------------------------------------- // 389 | // de.Result.Error 390 | // --------------------------------------------------------------------------------------------------------------- // 391 | 392 | de.Result.Error = function(error) { 393 | this.result = { 394 | 'error': error 395 | }; 396 | }; 397 | 398 | no.inherit(de.Result.Error, de.Result.Value); 399 | 400 | de.Result.Error.prototype._id = 'error'; 401 | 402 | de.Result.Error.prototype.get = function(field) { 403 | return this.result.error[field]; 404 | }; 405 | 406 | 407 | // --------------------------------------------------------------------------------------------------------------- // 408 | // de.Result.File 409 | // --------------------------------------------------------------------------------------------------------------- // 410 | 411 | de.Result.File = function(filename, buffer) { 412 | this.buffer = buffer; 413 | 414 | var ext = path_.extname(filename); 415 | 416 | this.content_type = content_type_by_ext[ext] || 'application/octet-stream'; 417 | this.data_type = data_type_by_content_type[this.content_type] || 'binary'; 418 | }; 419 | 420 | de.Result.File.prototype._id = 'file'; 421 | 422 | de.Result.File.prototype.toString = function() { 423 | return this.buffer.toString(); 424 | }; 425 | 426 | 427 | // --------------------------------------------------------------------------------------------------------------- // 428 | // de.Result.Http 429 | // --------------------------------------------------------------------------------------------------------------- // 430 | 431 | de.Result.Http = function(headers) { 432 | // Здесь будет итоговый буфер с данными (после события 'end'). 433 | this.buffer = null; 434 | 435 | this.content_type = de.mime(headers) || 'application/octet-stream'; 436 | this.data_type = data_type_by_content_type[this.content_type] || 'binary'; 437 | 438 | // Сохраняем все буфера, приходящие в событие 'data'. 439 | this._buffers = []; 440 | // И считаем их суммарную длину. 441 | this._length = 0; 442 | }; 443 | 444 | de.Result.Http.prototype._id = 'http'; 445 | 446 | de.Result.Http.prototype.data = function(data) { 447 | this._buffers.push(data); 448 | this._length += data.length; 449 | }; 450 | 451 | de.Result.Http.prototype.end = function() { 452 | this.buffer = Buffer.concat(this._buffers, this._length); 453 | 454 | this._buffers = null; 455 | }; 456 | 457 | 458 | // --------------------------------------------------------------------------------------------------------------- // 459 | 460 | /* 461 | de.result = function(data) { 462 | if (data && typeof data === 'object') { 463 | if ( Array.isArray(data) ) { 464 | return new de.Result.Array(data); 465 | } else { 466 | return new de.Result.Object(data); 467 | } 468 | } 469 | 470 | return new de.Result.Value(data); 471 | }; 472 | */ 473 | 474 | // --------------------------------------------------------------------------------------------------------------- // 475 | 476 | -------------------------------------------------------------------------------- /lib/de.block.js: -------------------------------------------------------------------------------- 1 | var path_ = require('path'); 2 | 3 | var no = require('nommon'); 4 | 5 | var de = require('./de.js'); 6 | 7 | require('./de.common.js'); 8 | require('./de.file.js'); 9 | require('./de.http.js'); 10 | require('./de.result.js'); 11 | require('./de.context.js'); 12 | 13 | 14 | // --------------------------------------------------------------------------------------------------------------- // 15 | // Vars and consts 16 | // --------------------------------------------------------------------------------------------------------------- // 17 | 18 | var _id = 0; 19 | var _blocks = {}; 20 | 21 | // Кэш результатов выполнения блоков. В кэше хранятся структуры вида: 22 | // 23 | // { 24 | // // Время добавления в кэш. 25 | // timestamp: ..., 26 | // // promise, зарезолвленный инстансом de.Result.*. 27 | // promise: ... 28 | // } 29 | // 30 | var _results = {}; 31 | 32 | // Кэш инклюдов, в кэше хранятся скомпилированные блоки. 33 | var _includes = {}; 34 | 35 | 36 | // --------------------------------------------------------------------------------------------------------------- // 37 | // de.Block 38 | // --------------------------------------------------------------------------------------------------------------- // 39 | 40 | de.Block = function(block, options) {}; 41 | 42 | // --------------------------------------------------------------------------------------------------------------- // 43 | 44 | de.Block.prototype._init = function(options) { 45 | this.priority = this.priority || 0; 46 | 47 | this._block = ''; 48 | 49 | options = options || {}; 50 | 51 | var _options = this.options = {}; 52 | 53 | _options.dirname = options.dirname || de.config.rootdir; 54 | 55 | // options.guard может быть либо функцией вида: 56 | // 57 | // function guard(params, context) { ... } 58 | // 59 | // либо строкой вида: 60 | // 61 | // '.id == 42 && !state.foo' 62 | // 63 | _options.guard = compileBoolean(options.guard); 64 | 65 | // Функция вида: 66 | // 67 | // function before(params, context) { ... } 68 | // 69 | _options.before = options.before || null; 70 | 71 | // Функция вида: 72 | // 73 | // function after(params, context, result) { ... } 74 | // 75 | _options.after = options.after || null; 76 | 77 | // Объект вида: 78 | // 79 | // { 80 | // foo: no.jpath.expr('.foo'), 81 | // ... 82 | // } 83 | // 84 | _options.select = compileObject(options.select); 85 | 86 | // Функция вида: 87 | // 88 | // function(result, context) { ... } 89 | // 90 | // или объект типа jresult: 91 | // 92 | // { 93 | // id: '.id', 94 | // content: '.data' 95 | // } 96 | // 97 | // FIXME: точно нужна проверка, или достаточно написать в документации 98 | // про допустимые форматы? 99 | if (typeof options.state === 'object' || typeof options.state === 'function') { 100 | _options.state = compileExpr(options.state); 101 | } 102 | 103 | // Функция вида: 104 | // 105 | // function(result, context) { ... } 106 | // 107 | // или jpath: 108 | // 109 | // '.foo.bar' 110 | // 111 | // или объект типа jresult: 112 | // 113 | // { 114 | // id: '.id', 115 | // content: '.data' 116 | // } 117 | // 118 | _options.result = compileExpr(options.result); 119 | 120 | // То же, что и в options.result. 121 | _options.params = compileExpr(options.params); 122 | 123 | // Функция с сигнатурой (params, context) или jstring. 124 | _options.key = compileString(options.key); 125 | // Число миллисекунд, на которое нужно закэшировать блок. 126 | _options.maxage = de.duration(options.maxage || 0); 127 | 128 | // Тип результата блока: json, text, ... 129 | // FIXME: Дефолтный data_type? 130 | _options.data_type = options.data_type || ''; 131 | 132 | // Нужно ли преобразовать реальный тип во что-то еще. 133 | // Например, text -> json. 134 | _options.output_type = options.output_type || ''; 135 | 136 | // Таймаут для блока. 137 | _options.timeout = options.timeout || 0; 138 | 139 | // Имя файла с шаблоном, который нужно наложить на результат выполнения блока. 140 | _options.template = compileString(options.template); 141 | 142 | _options.local = options.local; 143 | 144 | return _options; 145 | }; 146 | 147 | function compileBoolean(expr) { 148 | if (expr == null) { return null; } 149 | 150 | return (typeof expr === 'function') ? expr : no.jpath.boolean(expr); 151 | } 152 | 153 | function compileExpr(expr) { 154 | if (expr == null) { return null; } 155 | 156 | return (typeof expr === 'function') ? expr : no.jpath.scalar(expr); 157 | } 158 | 159 | function compileString(str) { 160 | if (str == null) { return null; } 161 | 162 | return (typeof str === 'function') ? str : no.jpath.string(str); 163 | } 164 | 165 | function compileObject(obj) { 166 | if (obj == null) { return null; } 167 | 168 | if (typeof obj === 'function') { return obj; } 169 | 170 | var r = {}; 171 | for (var key in obj) { 172 | r[key] = compileExpr( obj[key] ); 173 | } 174 | return r; 175 | } 176 | 177 | // --------------------------------------------------------------------------------------------------------------- // 178 | 179 | de.Block.prototype.resolveFilename = function(filename) { 180 | return path_.resolve(this.options.dirname, filename); 181 | }; 182 | 183 | // --------------------------------------------------------------------------------------------------------------- // 184 | 185 | de.Block.prototype.valueOf = function() { 186 | var id = this.__valueOf; 187 | if (!id) { 188 | id = this.__valueOf = '@block' + _id++ + '@'; 189 | _blocks[id] = this; 190 | } 191 | 192 | return id; 193 | }; 194 | 195 | // --------------------------------------------------------------------------------------------------------------- // 196 | 197 | de.Block.prototype.log_end = function(context, msg, t1) { 198 | context.log_end('debug', this.debug_id() + ' ' + msg, t1 ); 199 | }; 200 | 201 | de.Block.prototype.debug_id = function() { 202 | return '[block.' + this._id + ' ' + JSON.stringify(this._block) + ']'; 203 | }; 204 | 205 | 206 | de.Block.prototype.run = function(params, context) { 207 | context = context || new de.Context(params); 208 | 209 | var promise = new no.Promise(); 210 | 211 | var options = this.options; 212 | 213 | // Проверяем гвард, если он есть. 214 | if ( options.guard && !options.guard(params, context) ) { 215 | return promise.resolve( new de.Result.Value(null) ); 216 | } 217 | 218 | var that = this; 219 | 220 | var t1 = Date.now(); 221 | promise.done(function() { 222 | that.log_end(context, 'ended', t1); 223 | /* 224 | // См. pull request #57. 225 | promise.done(function(result) { 226 | var result_log = (result && result instanceof de.Result) ? ('[' + JSON.stringify(result.object()) + '] ') : ''; 227 | that.log_end(context, result_log + 'ended', t1); 228 | */ 229 | }); 230 | 231 | if (options.before) { 232 | options.before(params, context); 233 | } 234 | 235 | var running; 236 | 237 | // Смотрим, определен ли ключ для этого блока. 238 | var key; 239 | if (options.key) { 240 | // Вычисляем ключ. 241 | key = options.key(params, context); 242 | 243 | // Смотрим, не закэширован ли блок с таким же ключом. 244 | var cached = _results[key]; 245 | if (cached) { 246 | // Не протух ли еще наш кэш? 247 | if (cached.timestamp + options.maxage > context.now) { 248 | // Нет, берем из кэша promise с результатом. 249 | running = cached.promise; 250 | context.debug('[found in cache key=' + key + ']'); 251 | } else { 252 | // Протух. Выкидываем из кэша. 253 | // FIXME: Может тут таки нужно делать delete. 254 | _results[key] = null; 255 | } 256 | } 257 | } 258 | 259 | if (!running) { 260 | // Если блок все-таки не закэширован, запускаем его. 261 | var _params = (options.params) ? options.params(params, context) : params; 262 | var _context; 263 | if (options.local) { 264 | _context = context.clone(); 265 | _context.state = {}; 266 | } else { 267 | _context = context; 268 | } 269 | var running = this._run(_params, _context); 270 | 271 | // Если определен таймаут для блока. 272 | if (options.timeout) { 273 | var hTimeout = null; 274 | 275 | hTimeout = setTimeout(function() { 276 | // Если через options.timeout ms ничего не случится, кидаем ошибку. 277 | context.error(that.debug_id() + ' timeout'); 278 | running.reject( de.error({ 279 | id: 'TIMEOUT', 280 | message: 'Timeout' // FIXME: Вменяемый текст. 281 | }) ); 282 | 283 | // И отменяем все запросы этого блока. 284 | // Пока что отменяются только http-запросы. 285 | running.trigger('abort'); 286 | 287 | }, options.timeout); 288 | 289 | // Если блок выполнился быстрее, чем таймаут. 290 | running.always(function() { 291 | if (hTimeout) { 292 | // Отменяем setTimeout. 293 | clearTimeout(hTimeout); 294 | hTimeout = null; 295 | } 296 | }); 297 | } 298 | 299 | // Кэша нет, но ключ есть. 300 | if (key) { 301 | // Кэшируем блок на будущее. 302 | // Можно не ждать окончания выполнения блока, т.к. там все равно promise кэшируется. 303 | _results[key] = { 304 | timestamp: context.now, 305 | promise: running 306 | }; 307 | 308 | running.fail(function() { 309 | // Если выполнение привело к ошибке, выкидываем ключ из кэша. 310 | // Чтобы в следующий раз блок выполнился еще раз. 311 | // FIXME: Может лучше использовать delete? 312 | _results[key] = null; 313 | }); 314 | } 315 | } 316 | 317 | var that = this; 318 | 319 | running.done(function(result) { 320 | // Возможность положить что-нибудь в state после выполнения блока. 321 | var select = options.select; 322 | if (select) { 323 | var state = context.state; 324 | 325 | de.log.warn('You are using deprecated `select` section of the block options. It will dissapear soon. Use `state` instead.'); 326 | 327 | var obj = result.object(); 328 | 329 | for (var key in select) { 330 | // FIXME: Сигнатура?! 331 | state[key] = select[key](obj, context); 332 | } 333 | } 334 | 335 | if (options.state) { 336 | var patch = options.state(result.object(), context, params); 337 | context.state = de.mergeObjects(context.state, patch); 338 | } 339 | 340 | if (options.after) { 341 | options.after(params, context, result); 342 | } 343 | 344 | if (options.result) { 345 | result = new de.Result.Value( options.result( result.object(), context, params ) ); 346 | } 347 | 348 | if ( options.template && !(result instanceof de.Result.Error) ) { 349 | var filename = that.resolveFilename( options.template(params, context) ); 350 | 351 | de.file.eval(filename, 'de', de.sandbox, context) 352 | .done(function(template) { 353 | var t1 = Date.now(); 354 | var r = template( result.object() ); 355 | context.log_end('info', '[template ' + JSON.stringify(filename) + '] ended', t1); 356 | 357 | /* 358 | if (data && typeof data === 'object') { 359 | if ( Array.isArray(data) ) { 360 | return new de.Result.Array(data); 361 | } else { 362 | return new de.Result.Object(data); 363 | } 364 | } 365 | */ 366 | if (typeof r === 'string') { 367 | promise.resolve( new de.Result.HTML(r) ); 368 | } else if (r instanceof de.Result) { 369 | promise.resolve(r); 370 | } else { 371 | // FIXME: Что тогда? 372 | } 373 | }) 374 | .fail(function(error) { 375 | promise.resolve(error); 376 | }); 377 | } else { 378 | promise.resolve(result); 379 | } 380 | 381 | }); 382 | 383 | running.fail(function(error) { 384 | promise.resolve(error); 385 | }); 386 | 387 | return promise; 388 | }; 389 | 390 | de.Block.prototype._run = function(params, context) {}; 391 | 392 | // --------------------------------------------------------------------------------------------------------------- // 393 | 394 | de.Block.prototype.params = function(params) { 395 | return new de.Block.Curry(this, params); 396 | }; 397 | 398 | // --------------------------------------------------------------------------------------------------------------- // 399 | 400 | de.Block.compile = function(block, options) { 401 | // options = options || {}; 402 | 403 | var compiled; 404 | var priority; 405 | 406 | switch (typeof block) { 407 | 408 | case 'string': 409 | 410 | var r; 411 | 412 | if (( r = /^https?:\/\//.test(block) )) { // Строка начинается с 'http://' -- это http-блок. 413 | // FIXME: Поддержка https, post, get и т.д. 414 | compiled = new de.Block.Http(block, options); 415 | 416 | } else if (( r = block.match(/^(.*\(\))(\d+)?$/) )) { // Строка оканчивается на '()' -- это call-блок. 417 | compiled = new de.Block.Call(r[1], options); 418 | priority = r[2]; 419 | 420 | } else if (( r = block.match(/^(.*\.jsx)(\d+)?$/) )) { // Строка оканчивается на '.jsx' -- это include-блок. 421 | compiled = new de.Block.Include(r[1], options); 422 | priority = r[2]; 423 | 424 | // FIXME: Уметь задавать список расширений для file-блока через конфиг. 425 | } else if (( r = block.match(/^(.*\.(?:json|txt|html|xml))(\d+)?$/) )) { // Строка оканчивается на '.(json|txt|html|xml)' -- это file-блок. 426 | compiled = new de.Block.File(r[1], options); 427 | priority = r[2]; 428 | 429 | // В предыдущих трех случаях в конце строки может быть число, означающее приоритет. 430 | // Например: 431 | // { 432 | // common: 'common.jsx' + 25, 433 | // ... 434 | // } 435 | // 436 | // В случае http-блока, приоритет нужно задавать так (потому, что число на конце может быть частью урла): 437 | // { 438 | // common: http('http://foo.com/bar') +25, 439 | // ... 440 | // } 441 | // 442 | // Работает это за счет того, что у de.Block переопределен метод valueOf, 443 | // который возвращает уникальную строку вида '@block25@'. 444 | 445 | } else if (( r = block.match(/^(@block\d+@)(\d+)$/) ) || ( r = block.match(/^(\d+)(@block\d+@)$/) )) { 446 | // Строка вида '@block25@45' или '45@block25@', 447 | // где 25 это порядковый номер блока, а 45 -- приоритет. 448 | 449 | var id = r[1]; 450 | priority = r[2]; 451 | 452 | compiled = _blocks[id]; 453 | 454 | // FIXME после фикса include блока в #96 мы делаем eval один раз, а compile - несколько раз. 455 | // Поэтому нам нужно, чтобы eval-енные блоки оставались в _blocks. 456 | // delete _blocks[id]; 457 | } 458 | 459 | break; 460 | 461 | case 'object': 462 | 463 | // NOTE: Тут нельзя использовать (block instanceof Array) потому, что .jsx файлы эвалятся 464 | // в другом контексте и там другой Array. Для справки -- util.isArray примерно в 10 раз медленнее, чем instanceof. 465 | if ( Array.isArray(block) ) { 466 | compiled = new de.Block.Array(block, options); 467 | 468 | } else if ( block && !(block instanceof de.Block) ) { 469 | compiled = new de.Block.Object(block, options); 470 | 471 | } else { 472 | compiled = block; 473 | 474 | } 475 | 476 | break; 477 | 478 | case 'function': 479 | 480 | compiled = new de.Block.Function(block, options); 481 | 482 | break; 483 | 484 | } 485 | 486 | if (!compiled) { 487 | compiled = new de.Block.Value(block, options); 488 | } 489 | 490 | if (priority) { 491 | compiled.priority = +priority; 492 | } 493 | 494 | return compiled; 495 | 496 | }; 497 | 498 | 499 | // --------------------------------------------------------------------------------------------------------------- // 500 | // de.Block.File 501 | // --------------------------------------------------------------------------------------------------------------- // 502 | 503 | de.Block.File = function(filename, options) { 504 | this._init(options); 505 | 506 | this._block = filename; 507 | this.filename = no.jpath.string(filename); 508 | }; 509 | 510 | no.inherit(de.Block.File, de.Block); 511 | 512 | de.Block.File.prototype._id = 'file'; 513 | 514 | // --------------------------------------------------------------------------------------------------------------- // 515 | 516 | de.Block.File.prototype._run = function(params, context) { 517 | var filename = this.resolveFilename( this.filename(params, context) ); 518 | 519 | var options = this.options; 520 | 521 | var promise = new no.Promise(); 522 | 523 | de.file.get(filename, context) 524 | .done(function(result) { 525 | promise.resolve( new de.Result.Raw(result, options.data_type, options.output_type) ); 526 | }) 527 | .fail(function(error) { 528 | promise.resolve(error); 529 | }); 530 | 531 | return promise; 532 | }; 533 | 534 | 535 | // --------------------------------------------------------------------------------------------------------------- // 536 | // de.Block.Http 537 | // --------------------------------------------------------------------------------------------------------------- // 538 | 539 | de.Block.Http = function(url, options) { 540 | options = options || {}; 541 | 542 | var _options = this._init(options); 543 | 544 | // Пробрасывать ли http-заголовки. 545 | // По дефолту -- пробрасывать. 546 | _options.proxy = (options.proxy === undefined) ? true : options.proxy; 547 | 548 | _options.method = ( options.method || 'get' ).toLowerCase(); 549 | 550 | _options.max_redirects = options.max_redirects; 551 | _options.only_status = options.only_status; 552 | _options.headers = options.headers; 553 | _options.http_options = options.http_options || {}; 554 | 555 | // Тело пост-запроса 556 | _options.body = options.body || ''; 557 | 558 | this._block = url; 559 | 560 | var ch = url.slice(-1); 561 | // Если урл заканчивается на '?' или '&', значит в запрос нужно добавить 562 | // параметры из реквеста. 563 | if (ch === '?' || ch === '&') { 564 | _options.extend = true; 565 | url = url.slice(0, -1); 566 | } 567 | 568 | this.url = no.jpath.string(url); 569 | }; 570 | 571 | no.inherit(de.Block.Http, de.Block); 572 | 573 | de.Block.Http.prototype._id = 'http'; 574 | 575 | // --------------------------------------------------------------------------------------------------------------- // 576 | 577 | // Список http-заголовков, которые нужно выкидывать при проксировании. 578 | // Стырено из xscript'а (наверное, они знали, что делали :). 579 | // 580 | var disallow_headers = { 581 | 'host': true, 582 | 'if-modified-since': true, 583 | 'accept-encoding': true, 584 | 'keep-alive': true, 585 | 'connection': true, 586 | 'content-length': true 587 | }; 588 | 589 | de.Block.Http.prototype._run = function(params, context) { 590 | var url = this.url(params, context); 591 | 592 | var options = this.options; 593 | 594 | var query = (options.extend) ? params : null; 595 | 596 | var headers = null; 597 | if (options.proxy && context.request) { 598 | var req_headers = context.request.headers; 599 | 600 | // Копируем все http-заголовки, кроме тех, которые указаны в disallow_headers. 601 | headers = {}; 602 | for (var header in req_headers) { 603 | if (!disallow_headers[header]) { 604 | headers[header] = req_headers[header]; 605 | } 606 | } 607 | } 608 | 609 | if (options.headers) { 610 | headers = no.extend(headers || {}, options.headers); 611 | } 612 | 613 | var promise = new no.Promise(); 614 | 615 | var httpPromise = de.http( 616 | { 617 | url: url, 618 | method: options.method, 619 | headers: headers, 620 | max_redirects: options.max_redirects, 621 | only_status: options.only_status, 622 | http_options: options.http_options, 623 | body: options.body 624 | }, 625 | query, 626 | context 627 | ) 628 | .done(function(result) { 629 | if ( result instanceof de.Result ) { 630 | promise.resolve(result); 631 | } else { 632 | promise.resolve( new de.Result.Raw(result, options.data_type, options.output_type) ); 633 | } 634 | }) 635 | .fail(function(error) { 636 | promise.reject(error); 637 | }); 638 | 639 | promise.forward('abort', httpPromise); 640 | 641 | return promise; 642 | }; 643 | 644 | 645 | // --------------------------------------------------------------------------------------------------------------- // 646 | // de.Block.Call 647 | // --------------------------------------------------------------------------------------------------------------- // 648 | 649 | // FIXME: Нужна ли тут интерполяция строк? 650 | // Типа: 'get{ .method }()' 651 | // 652 | de.Block.Call = function(call, options) { 653 | this._init(options); 654 | 655 | this._block = call; 656 | 657 | var r = call.match(/^(?:(.*?):)?(.*)\(\)$/); 658 | 659 | this.module = de._modules[ r[1] || '' ] || null; 660 | this.method = r[2]; 661 | }; 662 | 663 | no.inherit(de.Block.Call, de.Block); 664 | 665 | de.Block.Call.prototype._id = 'call'; 666 | 667 | // --------------------------------------------------------------------------------------------------------------- // 668 | 669 | de.Block.Call.prototype._run = function(params, context) { 670 | var module = this.module; 671 | var method = this.method; 672 | 673 | if (module) { 674 | var call = module[method] || module; 675 | try { 676 | return call(params, context, method); 677 | } catch (e) { 678 | return no.Promise.resolved( de.error({ 679 | id: 'MODULE_CALL', 680 | message: e.message 681 | }) ); 682 | } 683 | } else { 684 | return no.Promise.resolved( de.error({ 685 | id: 'MODULE_NOT_FOUND', 686 | message: 'Cannot find module "' + this.moduleName + '"' 687 | }) ); 688 | } 689 | }; 690 | 691 | 692 | // --------------------------------------------------------------------------------------------------------------- // 693 | // de.Block.Function 694 | // --------------------------------------------------------------------------------------------------------------- // 695 | 696 | de.Block.Function = function(func, options) { 697 | this._init(options); 698 | 699 | this.func = func; 700 | }; 701 | 702 | no.inherit(de.Block.Function, de.Block); 703 | 704 | de.Block.Function.prototype._id = 'function'; 705 | 706 | de.Block.Function.prototype.log_end = no.nop; 707 | 708 | // --------------------------------------------------------------------------------------------------------------- // 709 | 710 | de.Block.Function.prototype._run = function(params, context) { 711 | var result; 712 | try { 713 | result = this.func(params, context); 714 | } catch (e) { 715 | return no.Promise.resolved( de.error({ 716 | id: 'FUNC_CALL', 717 | message: e.message 718 | }) ); 719 | } 720 | 721 | var block = de.Block.compile( result, { dirname: this.options.dirname } ); 722 | 723 | return block.run(params, context); 724 | }; 725 | 726 | 727 | // --------------------------------------------------------------------------------------------------------------- // 728 | // de.Block.Include 729 | // --------------------------------------------------------------------------------------------------------------- // 730 | 731 | de.Block.Include = function(filename, options) { 732 | this._init(options); 733 | 734 | this._block = filename; 735 | 736 | this.filename = no.jpath.string(filename); 737 | }; 738 | 739 | no.inherit(de.Block.Include, de.Block); 740 | 741 | de.Block.Include.prototype._id = 'include'; 742 | 743 | // --------------------------------------------------------------------------------------------------------------- // 744 | 745 | de.Block.Include.prototype._run = function(params, context) { 746 | var promise = new no.Promise(); 747 | 748 | var filename = this.resolveFilename( this.filename(params, context) ); 749 | 750 | var options = no.extend({}, this.options, {dirname: path_.dirname(filename)}); 751 | 752 | var including = _includes[filename]; 753 | if (!including) { 754 | including = _includes[filename] = new no.Promise(); 755 | 756 | de.file.eval(filename, 'de', de.sandbox, context) 757 | .done(function(include) { 758 | including.resolve(include); 759 | }) 760 | .fail(function(error) { 761 | _includes[filename] = null; 762 | 763 | including.reject(error); 764 | }); 765 | } 766 | 767 | including 768 | .done(function(include) { 769 | var block = de.Block.compile(include, options); 770 | 771 | de.forward( block.run(params, context), promise ); 772 | }) 773 | .fail(function(error) { 774 | promise.resolve(error); 775 | }); 776 | 777 | return promise; 778 | }; 779 | 780 | // FIXME: Получается, что у нас в кэше лежит и исполненный код, 781 | // и созданный из него блок. 782 | // 783 | de.events.on('loaded-file-changed', function(e, filename) { 784 | _includes[filename] = null; 785 | }); 786 | 787 | 788 | // --------------------------------------------------------------------------------------------------------------- // 789 | // de.Block.Value 790 | // --------------------------------------------------------------------------------------------------------------- // 791 | 792 | de.Block.Value = function(value, options) { 793 | this._init(options); 794 | 795 | this.value = new de.Result.Value(value); 796 | }; 797 | 798 | no.inherit(de.Block.Value, de.Block); 799 | 800 | de.Block.Value.prototype._id = 'value'; 801 | 802 | de.Block.Value.prototype.log_end = no.nop; 803 | 804 | // --------------------------------------------------------------------------------------------------------------- // 805 | 806 | de.Block.Value.prototype._run = function(params, context) { 807 | return no.Promise.resolved(this.value); 808 | }; 809 | 810 | 811 | // --------------------------------------------------------------------------------------------------------------- // 812 | // de.Block.Array 813 | // --------------------------------------------------------------------------------------------------------------- // 814 | 815 | function groupItems(items) { 816 | var l = items.length; 817 | if (!l) { 818 | return []; 819 | } 820 | 821 | var sorted = items.sort(function(a, b) { return b.block.priority - a.block.priority; }); 822 | 823 | var groups = []; 824 | var group = []; 825 | 826 | var i = 0; 827 | var item = sorted[0]; 828 | var next; 829 | while (i < l) { 830 | group.push(item); 831 | 832 | i++; 833 | if (i < l) { 834 | next = sorted[i]; 835 | if (item.block.priority !== next.block.priority) { 836 | groups.push(group); 837 | group = []; 838 | } 839 | } else { 840 | groups.push(group); 841 | break; 842 | } 843 | 844 | item = next; 845 | } 846 | 847 | return groups; 848 | } 849 | 850 | de.Block.Array = function(array, options) { 851 | options = this._init(options); 852 | 853 | var items = []; 854 | var item_options = { dirname: options.dirname }; 855 | for (var i = 0, l = array.length; i < l; i++) { 856 | items.push({ 857 | index: i, 858 | block: de.Block.compile(array[i], item_options) 859 | }); 860 | } 861 | 862 | this.groups = groupItems(items); 863 | 864 | }; 865 | 866 | no.inherit(de.Block.Array, de.Block); 867 | 868 | de.Block.Array.prototype._id = 'array'; 869 | 870 | de.Block.Array.prototype.log_end = no.nop; 871 | 872 | // --------------------------------------------------------------------------------------------------------------- // 873 | 874 | de.Block.Array.prototype._run = function(params, context) { 875 | var promise = new no.Promise(); 876 | 877 | var that = this; 878 | 879 | var results = []; 880 | var groups = this.groups; 881 | 882 | var i = 0; 883 | var l = groups.length; 884 | 885 | var workers; 886 | var wait; 887 | 888 | promise.on('abort', function() { 889 | // Останавливаем run(), чтобы он не запускал больше ничего. 890 | i = l; 891 | 892 | promise.resolve( de.error({ 893 | id: 'ERROR_ABORTED' 894 | }) ); 895 | 896 | 897 | if (workers) { 898 | // FIXME: Нужно ли это? 899 | wait.reject(); 900 | 901 | for (var j = 0, m = workers.length; j < m; j++) { 902 | workers[j].trigger('abort'); 903 | } 904 | } 905 | }); 906 | 907 | (function run() { 908 | if (i < l) { 909 | workers = []; 910 | 911 | var group = groups[i]; 912 | for (var j = 0, m = group.length; j < m; j++) { 913 | (function(item) { 914 | var worker = item.block.run(params, context) 915 | .done(function(r) { 916 | results[item.index] = r; 917 | }); 918 | 919 | workers.push(worker); 920 | })( group[j] ); 921 | } 922 | 923 | i++; 924 | 925 | wait = no.Promise.wait(workers).done(run); 926 | 927 | } else { 928 | workers = null; 929 | 930 | promise.resolve( that._getResult(results) ); 931 | } 932 | })(); 933 | 934 | return promise; 935 | }; 936 | 937 | // --------------------------------------------------------------------------------------------------------------- // 938 | 939 | de.Block.Array.prototype._getResult = function(results) { 940 | return new de.Result.Array(results); 941 | }; 942 | 943 | 944 | // --------------------------------------------------------------------------------------------------------------- // 945 | // de.Block.Object 946 | // --------------------------------------------------------------------------------------------------------------- // 947 | 948 | de.Block.Object = function(object, options) { 949 | options = this._init(options); 950 | 951 | var items = []; 952 | var keys = this.keys = []; 953 | 954 | var i = 0; 955 | var item_options = { dirname: options.dirname }; 956 | for (var key in object) { 957 | items.push({ 958 | index: i++, 959 | block: de.Block.compile(object[key], item_options) 960 | }); 961 | keys.push(key); 962 | } 963 | 964 | this.groups = groupItems(items); 965 | }; 966 | 967 | no.inherit(de.Block.Object, de.Block); 968 | 969 | de.Block.Object.prototype._id = 'object'; 970 | 971 | de.Block.Object.prototype.log_end = no.nop; 972 | 973 | // --------------------------------------------------------------------------------------------------------------- // 974 | 975 | de.Block.Object.prototype._run = de.Block.Array.prototype._run; 976 | 977 | de.Block.Object.prototype._getResult = function(results) { 978 | var keys = this.keys; 979 | 980 | var r = {}; 981 | 982 | for (var i = 0, l = results.length; i < l; i++) { 983 | r[ keys[i] ] = results[i]; 984 | } 985 | 986 | return new de.Result.Object(r); 987 | }; 988 | 989 | 990 | /* 991 | // --------------------------------------------------------------------------------------------------------------- // 992 | // de.Block.Page 993 | // --------------------------------------------------------------------------------------------------------------- // 994 | 995 | de.Block.Page = function(page, options) { 996 | this._init(options); 997 | 998 | // FIXME: Вообще может убрать дефолтный таймаут? 999 | this.options.timeout = this.options.timeout || de.config.timeout || 0; 1000 | 1001 | this.page = de.Block.compile(page, options); 1002 | }; 1003 | 1004 | no.inherit(de.Block.Page, de.Block); 1005 | 1006 | de.Block.Page.prototype._id = 'page'; 1007 | 1008 | // --------------------------------------------------------------------------------------------------------------- // 1009 | 1010 | de.Block.Page.prototype._run = function(params, context) { 1011 | return this.page.run(params, context); 1012 | }; 1013 | */ 1014 | 1015 | // --------------------------------------------------------------------------------------------------------------- // 1016 | 1017 | // FIXME: А это вообще где-нибудь используется? 1018 | 1019 | de.Block.Curry = function(block, params) { 1020 | this.block = block; 1021 | this.params = params; 1022 | }; 1023 | 1024 | no.inherit(de.Block.Curry, de.Block); 1025 | 1026 | de.Block.Curry.prototype.run = function(params, context) { 1027 | return this.block.run(this.params, context); 1028 | }; 1029 | 1030 | // --------------------------------------------------------------------------------------------------------------- // 1031 | 1032 | de.Block.Expr = function(expr, options) { 1033 | this._init(options); 1034 | 1035 | this.expr = compileExpr(expr); 1036 | }; 1037 | 1038 | no.inherit(de.Block.Expr, de.Block); 1039 | 1040 | de.Block.Expr.prototype._id = 'expr'; 1041 | 1042 | de.Block.Expr.prototype.log_end = no.nop; 1043 | 1044 | de.Block.Expr.prototype._run = function(params, context) { 1045 | var promise = new no.Promise(); 1046 | 1047 | promise.resolve( new de.Result.Value( this.expr(params, context) ) ); 1048 | 1049 | return promise; 1050 | }; 1051 | 1052 | // --------------------------------------------------------------------------------------------------------------- // 1053 | 1054 | -------------------------------------------------------------------------------- /test/pages/auths.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "status": { 4 | "value": "VALID", 5 | "id": 0 6 | }, 7 | "error": "OK", 8 | "age": 228005, 9 | "auth": { 10 | "password_verification_age": 228005, 11 | "have_password": true, 12 | "secure": false, 13 | "allow_plain_text": true 14 | }, 15 | "uid": { 16 | "value": "24171229", 17 | "lite": false, 18 | "hosted": false, 19 | "domid": "", 20 | "domain": "", 21 | "mx": "" 22 | }, 23 | "login": "alpha-san", 24 | "karma": { 25 | "value": 0 26 | } 27 | }, 28 | { 29 | "status": { 30 | "value": "VALID", 31 | "id": 0 32 | }, 33 | "error": "OK", 34 | "age": 228005, 35 | "auth": { 36 | "password_verification_age": 228005, 37 | "have_password": true, 38 | "secure": false, 39 | "allow_plain_text": true 40 | }, 41 | "uid": { 42 | "value": "24171229", 43 | "lite": false, 44 | "hosted": false, 45 | "domid": "", 46 | "domain": "", 47 | "mx": "" 48 | }, 49 | "login": "alpha-san", 50 | "karma": { 51 | "value": 0 52 | } 53 | }, 54 | { 55 | "status": { 56 | "value": "VALID", 57 | "id": 0 58 | }, 59 | "error": "OK", 60 | "age": 228005, 61 | "auth": { 62 | "password_verification_age": 228005, 63 | "have_password": true, 64 | "secure": false, 65 | "allow_plain_text": true 66 | }, 67 | "uid": { 68 | "value": "24171229", 69 | "lite": false, 70 | "hosted": false, 71 | "domid": "", 72 | "domain": "", 73 | "mx": "" 74 | }, 75 | "login": "alpha-san", 76 | "karma": { 77 | "value": 0 78 | } 79 | }, 80 | { 81 | "status": { 82 | "value": "VALID", 83 | "id": 0 84 | }, 85 | "error": "OK", 86 | "age": 228005, 87 | "auth": { 88 | "password_verification_age": 228005, 89 | "have_password": true, 90 | "secure": false, 91 | "allow_plain_text": true 92 | }, 93 | "uid": { 94 | "value": "24171229", 95 | "lite": false, 96 | "hosted": false, 97 | "domid": "", 98 | "domain": "", 99 | "mx": "" 100 | }, 101 | "login": "alpha-san", 102 | "karma": { 103 | "value": 0 104 | } 105 | }, 106 | { 107 | "status": { 108 | "value": "VALID", 109 | "id": 0 110 | }, 111 | "error": "OK", 112 | "age": 228005, 113 | "auth": { 114 | "password_verification_age": 228005, 115 | "have_password": true, 116 | "secure": false, 117 | "allow_plain_text": true 118 | }, 119 | "uid": { 120 | "value": "24171229", 121 | "lite": false, 122 | "hosted": false, 123 | "domid": "", 124 | "domain": "", 125 | "mx": "" 126 | }, 127 | "login": "alpha-san", 128 | "karma": { 129 | "value": 0 130 | } 131 | }, 132 | { 133 | "status": { 134 | "value": "VALID", 135 | "id": 0 136 | }, 137 | "error": "OK", 138 | "age": 228005, 139 | "auth": { 140 | "password_verification_age": 228005, 141 | "have_password": true, 142 | "secure": false, 143 | "allow_plain_text": true 144 | }, 145 | "uid": { 146 | "value": "24171229", 147 | "lite": false, 148 | "hosted": false, 149 | "domid": "", 150 | "domain": "", 151 | "mx": "" 152 | }, 153 | "login": "alpha-san", 154 | "karma": { 155 | "value": 0 156 | } 157 | }, 158 | { 159 | "status": { 160 | "value": "VALID", 161 | "id": 0 162 | }, 163 | "error": "OK", 164 | "age": 228005, 165 | "auth": { 166 | "password_verification_age": 228005, 167 | "have_password": true, 168 | "secure": false, 169 | "allow_plain_text": true 170 | }, 171 | "uid": { 172 | "value": "24171229", 173 | "lite": false, 174 | "hosted": false, 175 | "domid": "", 176 | "domain": "", 177 | "mx": "" 178 | }, 179 | "login": "alpha-san", 180 | "karma": { 181 | "value": 0 182 | } 183 | }, 184 | { 185 | "status": { 186 | "value": "VALID", 187 | "id": 0 188 | }, 189 | "error": "OK", 190 | "age": 228005, 191 | "auth": { 192 | "password_verification_age": 228005, 193 | "have_password": true, 194 | "secure": false, 195 | "allow_plain_text": true 196 | }, 197 | "uid": { 198 | "value": "24171229", 199 | "lite": false, 200 | "hosted": false, 201 | "domid": "", 202 | "domain": "", 203 | "mx": "" 204 | }, 205 | "login": "alpha-san", 206 | "karma": { 207 | "value": 0 208 | } 209 | }, 210 | { 211 | "status": { 212 | "value": "VALID", 213 | "id": 0 214 | }, 215 | "error": "OK", 216 | "age": 228005, 217 | "auth": { 218 | "password_verification_age": 228005, 219 | "have_password": true, 220 | "secure": false, 221 | "allow_plain_text": true 222 | }, 223 | "uid": { 224 | "value": "24171229", 225 | "lite": false, 226 | "hosted": false, 227 | "domid": "", 228 | "domain": "", 229 | "mx": "" 230 | }, 231 | "login": "alpha-san", 232 | "karma": { 233 | "value": 0 234 | } 235 | }, 236 | { 237 | "status": { 238 | "value": "VALID", 239 | "id": 0 240 | }, 241 | "error": "OK", 242 | "age": 228005, 243 | "auth": { 244 | "password_verification_age": 228005, 245 | "have_password": true, 246 | "secure": false, 247 | "allow_plain_text": true 248 | }, 249 | "uid": { 250 | "value": "24171229", 251 | "lite": false, 252 | "hosted": false, 253 | "domid": "", 254 | "domain": "", 255 | "mx": "" 256 | }, 257 | "login": "alpha-san", 258 | "karma": { 259 | "value": 0 260 | } 261 | }, 262 | { 263 | "status": { 264 | "value": "VALID", 265 | "id": 0 266 | }, 267 | "error": "OK", 268 | "age": 228005, 269 | "auth": { 270 | "password_verification_age": 228005, 271 | "have_password": true, 272 | "secure": false, 273 | "allow_plain_text": true 274 | }, 275 | "uid": { 276 | "value": "24171229", 277 | "lite": false, 278 | "hosted": false, 279 | "domid": "", 280 | "domain": "", 281 | "mx": "" 282 | }, 283 | "login": "alpha-san", 284 | "karma": { 285 | "value": 0 286 | } 287 | }, 288 | { 289 | "status": { 290 | "value": "VALID", 291 | "id": 0 292 | }, 293 | "error": "OK", 294 | "age": 228005, 295 | "auth": { 296 | "password_verification_age": 228005, 297 | "have_password": true, 298 | "secure": false, 299 | "allow_plain_text": true 300 | }, 301 | "uid": { 302 | "value": "24171229", 303 | "lite": false, 304 | "hosted": false, 305 | "domid": "", 306 | "domain": "", 307 | "mx": "" 308 | }, 309 | "login": "alpha-san", 310 | "karma": { 311 | "value": 0 312 | } 313 | }, 314 | { 315 | "status": { 316 | "value": "VALID", 317 | "id": 0 318 | }, 319 | "error": "OK", 320 | "age": 228005, 321 | "auth": { 322 | "password_verification_age": 228005, 323 | "have_password": true, 324 | "secure": false, 325 | "allow_plain_text": true 326 | }, 327 | "uid": { 328 | "value": "24171229", 329 | "lite": false, 330 | "hosted": false, 331 | "domid": "", 332 | "domain": "", 333 | "mx": "" 334 | }, 335 | "login": "alpha-san", 336 | "karma": { 337 | "value": 0 338 | } 339 | }, 340 | { 341 | "status": { 342 | "value": "VALID", 343 | "id": 0 344 | }, 345 | "error": "OK", 346 | "age": 228005, 347 | "auth": { 348 | "password_verification_age": 228005, 349 | "have_password": true, 350 | "secure": false, 351 | "allow_plain_text": true 352 | }, 353 | "uid": { 354 | "value": "24171229", 355 | "lite": false, 356 | "hosted": false, 357 | "domid": "", 358 | "domain": "", 359 | "mx": "" 360 | }, 361 | "login": "alpha-san", 362 | "karma": { 363 | "value": 0 364 | } 365 | }, 366 | { 367 | "status": { 368 | "value": "VALID", 369 | "id": 0 370 | }, 371 | "error": "OK", 372 | "age": 228005, 373 | "auth": { 374 | "password_verification_age": 228005, 375 | "have_password": true, 376 | "secure": false, 377 | "allow_plain_text": true 378 | }, 379 | "uid": { 380 | "value": "24171229", 381 | "lite": false, 382 | "hosted": false, 383 | "domid": "", 384 | "domain": "", 385 | "mx": "" 386 | }, 387 | "login": "alpha-san", 388 | "karma": { 389 | "value": 0 390 | } 391 | }, 392 | { 393 | "status": { 394 | "value": "VALID", 395 | "id": 0 396 | }, 397 | "error": "OK", 398 | "age": 228005, 399 | "auth": { 400 | "password_verification_age": 228005, 401 | "have_password": true, 402 | "secure": false, 403 | "allow_plain_text": true 404 | }, 405 | "uid": { 406 | "value": "24171229", 407 | "lite": false, 408 | "hosted": false, 409 | "domid": "", 410 | "domain": "", 411 | "mx": "" 412 | }, 413 | "login": "alpha-san", 414 | "karma": { 415 | "value": 0 416 | } 417 | }, 418 | { 419 | "status": { 420 | "value": "VALID", 421 | "id": 0 422 | }, 423 | "error": "OK", 424 | "age": 228005, 425 | "auth": { 426 | "password_verification_age": 228005, 427 | "have_password": true, 428 | "secure": false, 429 | "allow_plain_text": true 430 | }, 431 | "uid": { 432 | "value": "24171229", 433 | "lite": false, 434 | "hosted": false, 435 | "domid": "", 436 | "domain": "", 437 | "mx": "" 438 | }, 439 | "login": "alpha-san", 440 | "karma": { 441 | "value": 0 442 | } 443 | }, 444 | { 445 | "status": { 446 | "value": "VALID", 447 | "id": 0 448 | }, 449 | "error": "OK", 450 | "age": 228005, 451 | "auth": { 452 | "password_verification_age": 228005, 453 | "have_password": true, 454 | "secure": false, 455 | "allow_plain_text": true 456 | }, 457 | "uid": { 458 | "value": "24171229", 459 | "lite": false, 460 | "hosted": false, 461 | "domid": "", 462 | "domain": "", 463 | "mx": "" 464 | }, 465 | "login": "alpha-san", 466 | "karma": { 467 | "value": 0 468 | } 469 | }, 470 | { 471 | "status": { 472 | "value": "VALID", 473 | "id": 0 474 | }, 475 | "error": "OK", 476 | "age": 228005, 477 | "auth": { 478 | "password_verification_age": 228005, 479 | "have_password": true, 480 | "secure": false, 481 | "allow_plain_text": true 482 | }, 483 | "uid": { 484 | "value": "24171229", 485 | "lite": false, 486 | "hosted": false, 487 | "domid": "", 488 | "domain": "", 489 | "mx": "" 490 | }, 491 | "login": "alpha-san", 492 | "karma": { 493 | "value": 0 494 | } 495 | }, 496 | { 497 | "status": { 498 | "value": "VALID", 499 | "id": 0 500 | }, 501 | "error": "OK", 502 | "age": 228005, 503 | "auth": { 504 | "password_verification_age": 228005, 505 | "have_password": true, 506 | "secure": false, 507 | "allow_plain_text": true 508 | }, 509 | "uid": { 510 | "value": "24171229", 511 | "lite": false, 512 | "hosted": false, 513 | "domid": "", 514 | "domain": "", 515 | "mx": "" 516 | }, 517 | "login": "alpha-san", 518 | "karma": { 519 | "value": 0 520 | } 521 | }, 522 | { 523 | "status": { 524 | "value": "VALID", 525 | "id": 0 526 | }, 527 | "error": "OK", 528 | "age": 228005, 529 | "auth": { 530 | "password_verification_age": 228005, 531 | "have_password": true, 532 | "secure": false, 533 | "allow_plain_text": true 534 | }, 535 | "uid": { 536 | "value": "24171229", 537 | "lite": false, 538 | "hosted": false, 539 | "domid": "", 540 | "domain": "", 541 | "mx": "" 542 | }, 543 | "login": "alpha-san", 544 | "karma": { 545 | "value": 0 546 | } 547 | }, 548 | { 549 | "status": { 550 | "value": "VALID", 551 | "id": 0 552 | }, 553 | "error": "OK", 554 | "age": 228005, 555 | "auth": { 556 | "password_verification_age": 228005, 557 | "have_password": true, 558 | "secure": false, 559 | "allow_plain_text": true 560 | }, 561 | "uid": { 562 | "value": "24171229", 563 | "lite": false, 564 | "hosted": false, 565 | "domid": "", 566 | "domain": "", 567 | "mx": "" 568 | }, 569 | "login": "alpha-san", 570 | "karma": { 571 | "value": 0 572 | } 573 | }, 574 | { 575 | "status": { 576 | "value": "VALID", 577 | "id": 0 578 | }, 579 | "error": "OK", 580 | "age": 228005, 581 | "auth": { 582 | "password_verification_age": 228005, 583 | "have_password": true, 584 | "secure": false, 585 | "allow_plain_text": true 586 | }, 587 | "uid": { 588 | "value": "24171229", 589 | "lite": false, 590 | "hosted": false, 591 | "domid": "", 592 | "domain": "", 593 | "mx": "" 594 | }, 595 | "login": "alpha-san", 596 | "karma": { 597 | "value": 0 598 | } 599 | }, 600 | { 601 | "status": { 602 | "value": "VALID", 603 | "id": 0 604 | }, 605 | "error": "OK", 606 | "age": 228005, 607 | "auth": { 608 | "password_verification_age": 228005, 609 | "have_password": true, 610 | "secure": false, 611 | "allow_plain_text": true 612 | }, 613 | "uid": { 614 | "value": "24171229", 615 | "lite": false, 616 | "hosted": false, 617 | "domid": "", 618 | "domain": "", 619 | "mx": "" 620 | }, 621 | "login": "alpha-san", 622 | "karma": { 623 | "value": 0 624 | } 625 | }, 626 | { 627 | "status": { 628 | "value": "VALID", 629 | "id": 0 630 | }, 631 | "error": "OK", 632 | "age": 228005, 633 | "auth": { 634 | "password_verification_age": 228005, 635 | "have_password": true, 636 | "secure": false, 637 | "allow_plain_text": true 638 | }, 639 | "uid": { 640 | "value": "24171229", 641 | "lite": false, 642 | "hosted": false, 643 | "domid": "", 644 | "domain": "", 645 | "mx": "" 646 | }, 647 | "login": "alpha-san", 648 | "karma": { 649 | "value": 0 650 | } 651 | }, 652 | { 653 | "status": { 654 | "value": "VALID", 655 | "id": 0 656 | }, 657 | "error": "OK", 658 | "age": 228005, 659 | "auth": { 660 | "password_verification_age": 228005, 661 | "have_password": true, 662 | "secure": false, 663 | "allow_plain_text": true 664 | }, 665 | "uid": { 666 | "value": "24171229", 667 | "lite": false, 668 | "hosted": false, 669 | "domid": "", 670 | "domain": "", 671 | "mx": "" 672 | }, 673 | "login": "alpha-san", 674 | "karma": { 675 | "value": 0 676 | } 677 | }, 678 | { 679 | "status": { 680 | "value": "VALID", 681 | "id": 0 682 | }, 683 | "error": "OK", 684 | "age": 228005, 685 | "auth": { 686 | "password_verification_age": 228005, 687 | "have_password": true, 688 | "secure": false, 689 | "allow_plain_text": true 690 | }, 691 | "uid": { 692 | "value": "24171229", 693 | "lite": false, 694 | "hosted": false, 695 | "domid": "", 696 | "domain": "", 697 | "mx": "" 698 | }, 699 | "login": "alpha-san", 700 | "karma": { 701 | "value": 0 702 | } 703 | }, 704 | { 705 | "status": { 706 | "value": "VALID", 707 | "id": 0 708 | }, 709 | "error": "OK", 710 | "age": 228005, 711 | "auth": { 712 | "password_verification_age": 228005, 713 | "have_password": true, 714 | "secure": false, 715 | "allow_plain_text": true 716 | }, 717 | "uid": { 718 | "value": "24171229", 719 | "lite": false, 720 | "hosted": false, 721 | "domid": "", 722 | "domain": "", 723 | "mx": "" 724 | }, 725 | "login": "alpha-san", 726 | "karma": { 727 | "value": 0 728 | } 729 | }, 730 | { 731 | "status": { 732 | "value": "VALID", 733 | "id": 0 734 | }, 735 | "error": "OK", 736 | "age": 228005, 737 | "auth": { 738 | "password_verification_age": 228005, 739 | "have_password": true, 740 | "secure": false, 741 | "allow_plain_text": true 742 | }, 743 | "uid": { 744 | "value": "24171229", 745 | "lite": false, 746 | "hosted": false, 747 | "domid": "", 748 | "domain": "", 749 | "mx": "" 750 | }, 751 | "login": "alpha-san", 752 | "karma": { 753 | "value": 0 754 | } 755 | }, 756 | { 757 | "status": { 758 | "value": "VALID", 759 | "id": 0 760 | }, 761 | "error": "OK", 762 | "age": 228005, 763 | "auth": { 764 | "password_verification_age": 228005, 765 | "have_password": true, 766 | "secure": false, 767 | "allow_plain_text": true 768 | }, 769 | "uid": { 770 | "value": "24171229", 771 | "lite": false, 772 | "hosted": false, 773 | "domid": "", 774 | "domain": "", 775 | "mx": "" 776 | }, 777 | "login": "alpha-san", 778 | "karma": { 779 | "value": 0 780 | } 781 | }, 782 | { 783 | "status": { 784 | "value": "VALID", 785 | "id": 0 786 | }, 787 | "error": "OK", 788 | "age": 228005, 789 | "auth": { 790 | "password_verification_age": 228005, 791 | "have_password": true, 792 | "secure": false, 793 | "allow_plain_text": true 794 | }, 795 | "uid": { 796 | "value": "24171229", 797 | "lite": false, 798 | "hosted": false, 799 | "domid": "", 800 | "domain": "", 801 | "mx": "" 802 | }, 803 | "login": "alpha-san", 804 | "karma": { 805 | "value": 0 806 | } 807 | }, 808 | { 809 | "status": { 810 | "value": "VALID", 811 | "id": 0 812 | }, 813 | "error": "OK", 814 | "age": 228005, 815 | "auth": { 816 | "password_verification_age": 228005, 817 | "have_password": true, 818 | "secure": false, 819 | "allow_plain_text": true 820 | }, 821 | "uid": { 822 | "value": "24171229", 823 | "lite": false, 824 | "hosted": false, 825 | "domid": "", 826 | "domain": "", 827 | "mx": "" 828 | }, 829 | "login": "alpha-san", 830 | "karma": { 831 | "value": 0 832 | } 833 | }, 834 | { 835 | "status": { 836 | "value": "VALID", 837 | "id": 0 838 | }, 839 | "error": "OK", 840 | "age": 228005, 841 | "auth": { 842 | "password_verification_age": 228005, 843 | "have_password": true, 844 | "secure": false, 845 | "allow_plain_text": true 846 | }, 847 | "uid": { 848 | "value": "24171229", 849 | "lite": false, 850 | "hosted": false, 851 | "domid": "", 852 | "domain": "", 853 | "mx": "" 854 | }, 855 | "login": "alpha-san", 856 | "karma": { 857 | "value": 0 858 | } 859 | }, 860 | { 861 | "status": { 862 | "value": "VALID", 863 | "id": 0 864 | }, 865 | "error": "OK", 866 | "age": 228005, 867 | "auth": { 868 | "password_verification_age": 228005, 869 | "have_password": true, 870 | "secure": false, 871 | "allow_plain_text": true 872 | }, 873 | "uid": { 874 | "value": "24171229", 875 | "lite": false, 876 | "hosted": false, 877 | "domid": "", 878 | "domain": "", 879 | "mx": "" 880 | }, 881 | "login": "alpha-san", 882 | "karma": { 883 | "value": 0 884 | } 885 | }, 886 | { 887 | "status": { 888 | "value": "VALID", 889 | "id": 0 890 | }, 891 | "error": "OK", 892 | "age": 228005, 893 | "auth": { 894 | "password_verification_age": 228005, 895 | "have_password": true, 896 | "secure": false, 897 | "allow_plain_text": true 898 | }, 899 | "uid": { 900 | "value": "24171229", 901 | "lite": false, 902 | "hosted": false, 903 | "domid": "", 904 | "domain": "", 905 | "mx": "" 906 | }, 907 | "login": "alpha-san", 908 | "karma": { 909 | "value": 0 910 | } 911 | }, 912 | { 913 | "status": { 914 | "value": "VALID", 915 | "id": 0 916 | }, 917 | "error": "OK", 918 | "age": 228005, 919 | "auth": { 920 | "password_verification_age": 228005, 921 | "have_password": true, 922 | "secure": false, 923 | "allow_plain_text": true 924 | }, 925 | "uid": { 926 | "value": "24171229", 927 | "lite": false, 928 | "hosted": false, 929 | "domid": "", 930 | "domain": "", 931 | "mx": "" 932 | }, 933 | "login": "alpha-san", 934 | "karma": { 935 | "value": 0 936 | } 937 | }, 938 | { 939 | "status": { 940 | "value": "VALID", 941 | "id": 0 942 | }, 943 | "error": "OK", 944 | "age": 228005, 945 | "auth": { 946 | "password_verification_age": 228005, 947 | "have_password": true, 948 | "secure": false, 949 | "allow_plain_text": true 950 | }, 951 | "uid": { 952 | "value": "24171229", 953 | "lite": false, 954 | "hosted": false, 955 | "domid": "", 956 | "domain": "", 957 | "mx": "" 958 | }, 959 | "login": "alpha-san", 960 | "karma": { 961 | "value": 0 962 | } 963 | }, 964 | { 965 | "status": { 966 | "value": "VALID", 967 | "id": 0 968 | }, 969 | "error": "OK", 970 | "age": 228005, 971 | "auth": { 972 | "password_verification_age": 228005, 973 | "have_password": true, 974 | "secure": false, 975 | "allow_plain_text": true 976 | }, 977 | "uid": { 978 | "value": "24171229", 979 | "lite": false, 980 | "hosted": false, 981 | "domid": "", 982 | "domain": "", 983 | "mx": "" 984 | }, 985 | "login": "alpha-san", 986 | "karma": { 987 | "value": 0 988 | } 989 | }, 990 | { 991 | "status": { 992 | "value": "VALID", 993 | "id": 0 994 | }, 995 | "error": "OK", 996 | "age": 228005, 997 | "auth": { 998 | "password_verification_age": 228005, 999 | "have_password": true, 1000 | "secure": false, 1001 | "allow_plain_text": true 1002 | }, 1003 | "uid": { 1004 | "value": "24171229", 1005 | "lite": false, 1006 | "hosted": false, 1007 | "domid": "", 1008 | "domain": "", 1009 | "mx": "" 1010 | }, 1011 | "login": "alpha-san", 1012 | "karma": { 1013 | "value": 0 1014 | } 1015 | }, 1016 | { 1017 | "status": { 1018 | "value": "VALID", 1019 | "id": 0 1020 | }, 1021 | "error": "OK", 1022 | "age": 228005, 1023 | "auth": { 1024 | "password_verification_age": 228005, 1025 | "have_password": true, 1026 | "secure": false, 1027 | "allow_plain_text": true 1028 | }, 1029 | "uid": { 1030 | "value": "24171229", 1031 | "lite": false, 1032 | "hosted": false, 1033 | "domid": "", 1034 | "domain": "", 1035 | "mx": "" 1036 | }, 1037 | "login": "alpha-san", 1038 | "karma": { 1039 | "value": 0 1040 | } 1041 | }, 1042 | { 1043 | "status": { 1044 | "value": "VALID", 1045 | "id": 0 1046 | }, 1047 | "error": "OK", 1048 | "age": 228005, 1049 | "auth": { 1050 | "password_verification_age": 228005, 1051 | "have_password": true, 1052 | "secure": false, 1053 | "allow_plain_text": true 1054 | }, 1055 | "uid": { 1056 | "value": "24171229", 1057 | "lite": false, 1058 | "hosted": false, 1059 | "domid": "", 1060 | "domain": "", 1061 | "mx": "" 1062 | }, 1063 | "login": "alpha-san", 1064 | "karma": { 1065 | "value": 0 1066 | } 1067 | }, 1068 | { 1069 | "status": { 1070 | "value": "VALID", 1071 | "id": 0 1072 | }, 1073 | "error": "OK", 1074 | "age": 228005, 1075 | "auth": { 1076 | "password_verification_age": 228005, 1077 | "have_password": true, 1078 | "secure": false, 1079 | "allow_plain_text": true 1080 | }, 1081 | "uid": { 1082 | "value": "24171229", 1083 | "lite": false, 1084 | "hosted": false, 1085 | "domid": "", 1086 | "domain": "", 1087 | "mx": "" 1088 | }, 1089 | "login": "alpha-san", 1090 | "karma": { 1091 | "value": 0 1092 | } 1093 | }, 1094 | { 1095 | "status": { 1096 | "value": "VALID", 1097 | "id": 0 1098 | }, 1099 | "error": "OK", 1100 | "age": 228005, 1101 | "auth": { 1102 | "password_verification_age": 228005, 1103 | "have_password": true, 1104 | "secure": false, 1105 | "allow_plain_text": true 1106 | }, 1107 | "uid": { 1108 | "value": "24171229", 1109 | "lite": false, 1110 | "hosted": false, 1111 | "domid": "", 1112 | "domain": "", 1113 | "mx": "" 1114 | }, 1115 | "login": "alpha-san", 1116 | "karma": { 1117 | "value": 0 1118 | } 1119 | }, 1120 | { 1121 | "status": { 1122 | "value": "VALID", 1123 | "id": 0 1124 | }, 1125 | "error": "OK", 1126 | "age": 228005, 1127 | "auth": { 1128 | "password_verification_age": 228005, 1129 | "have_password": true, 1130 | "secure": false, 1131 | "allow_plain_text": true 1132 | }, 1133 | "uid": { 1134 | "value": "24171229", 1135 | "lite": false, 1136 | "hosted": false, 1137 | "domid": "", 1138 | "domain": "", 1139 | "mx": "" 1140 | }, 1141 | "login": "alpha-san", 1142 | "karma": { 1143 | "value": 0 1144 | } 1145 | }, 1146 | { 1147 | "status": { 1148 | "value": "VALID", 1149 | "id": 0 1150 | }, 1151 | "error": "OK", 1152 | "age": 228005, 1153 | "auth": { 1154 | "password_verification_age": 228005, 1155 | "have_password": true, 1156 | "secure": false, 1157 | "allow_plain_text": true 1158 | }, 1159 | "uid": { 1160 | "value": "24171229", 1161 | "lite": false, 1162 | "hosted": false, 1163 | "domid": "", 1164 | "domain": "", 1165 | "mx": "" 1166 | }, 1167 | "login": "alpha-san", 1168 | "karma": { 1169 | "value": 0 1170 | } 1171 | }, 1172 | { 1173 | "status": { 1174 | "value": "VALID", 1175 | "id": 0 1176 | }, 1177 | "error": "OK", 1178 | "age": 228005, 1179 | "auth": { 1180 | "password_verification_age": 228005, 1181 | "have_password": true, 1182 | "secure": false, 1183 | "allow_plain_text": true 1184 | }, 1185 | "uid": { 1186 | "value": "24171229", 1187 | "lite": false, 1188 | "hosted": false, 1189 | "domid": "", 1190 | "domain": "", 1191 | "mx": "" 1192 | }, 1193 | "login": "alpha-san", 1194 | "karma": { 1195 | "value": 0 1196 | } 1197 | }, 1198 | { 1199 | "status": { 1200 | "value": "VALID", 1201 | "id": 0 1202 | }, 1203 | "error": "OK", 1204 | "age": 228005, 1205 | "auth": { 1206 | "password_verification_age": 228005, 1207 | "have_password": true, 1208 | "secure": false, 1209 | "allow_plain_text": true 1210 | }, 1211 | "uid": { 1212 | "value": "24171229", 1213 | "lite": false, 1214 | "hosted": false, 1215 | "domid": "", 1216 | "domain": "", 1217 | "mx": "" 1218 | }, 1219 | "login": "alpha-san", 1220 | "karma": { 1221 | "value": 0 1222 | } 1223 | }, 1224 | { 1225 | "status": { 1226 | "value": "VALID", 1227 | "id": 0 1228 | }, 1229 | "error": "OK", 1230 | "age": 228005, 1231 | "auth": { 1232 | "password_verification_age": 228005, 1233 | "have_password": true, 1234 | "secure": false, 1235 | "allow_plain_text": true 1236 | }, 1237 | "uid": { 1238 | "value": "24171229", 1239 | "lite": false, 1240 | "hosted": false, 1241 | "domid": "", 1242 | "domain": "", 1243 | "mx": "" 1244 | }, 1245 | "login": "alpha-san", 1246 | "karma": { 1247 | "value": 0 1248 | } 1249 | }, 1250 | { 1251 | "status": { 1252 | "value": "VALID", 1253 | "id": 0 1254 | }, 1255 | "error": "OK", 1256 | "age": 228005, 1257 | "auth": { 1258 | "password_verification_age": 228005, 1259 | "have_password": true, 1260 | "secure": false, 1261 | "allow_plain_text": true 1262 | }, 1263 | "uid": { 1264 | "value": "24171229", 1265 | "lite": false, 1266 | "hosted": false, 1267 | "domid": "", 1268 | "domain": "", 1269 | "mx": "" 1270 | }, 1271 | "login": "alpha-san", 1272 | "karma": { 1273 | "value": 0 1274 | } 1275 | }, 1276 | { 1277 | "status": { 1278 | "value": "VALID", 1279 | "id": 0 1280 | }, 1281 | "error": "OK", 1282 | "age": 228005, 1283 | "auth": { 1284 | "password_verification_age": 228005, 1285 | "have_password": true, 1286 | "secure": false, 1287 | "allow_plain_text": true 1288 | }, 1289 | "uid": { 1290 | "value": "24171229", 1291 | "lite": false, 1292 | "hosted": false, 1293 | "domid": "", 1294 | "domain": "", 1295 | "mx": "" 1296 | }, 1297 | "login": "alpha-san", 1298 | "karma": { 1299 | "value": 0 1300 | } 1301 | }, 1302 | { 1303 | "status": { 1304 | "value": "VALID", 1305 | "id": 0 1306 | }, 1307 | "error": "OK", 1308 | "age": 228005, 1309 | "auth": { 1310 | "password_verification_age": 228005, 1311 | "have_password": true, 1312 | "secure": false, 1313 | "allow_plain_text": true 1314 | }, 1315 | "uid": { 1316 | "value": "24171229", 1317 | "lite": false, 1318 | "hosted": false, 1319 | "domid": "", 1320 | "domain": "", 1321 | "mx": "" 1322 | }, 1323 | "login": "alpha-san", 1324 | "karma": { 1325 | "value": 0 1326 | } 1327 | }, 1328 | { 1329 | "status": { 1330 | "value": "VALID", 1331 | "id": 0 1332 | }, 1333 | "error": "OK", 1334 | "age": 228005, 1335 | "auth": { 1336 | "password_verification_age": 228005, 1337 | "have_password": true, 1338 | "secure": false, 1339 | "allow_plain_text": true 1340 | }, 1341 | "uid": { 1342 | "value": "24171229", 1343 | "lite": false, 1344 | "hosted": false, 1345 | "domid": "", 1346 | "domain": "", 1347 | "mx": "" 1348 | }, 1349 | "login": "alpha-san", 1350 | "karma": { 1351 | "value": 0 1352 | } 1353 | }, 1354 | { 1355 | "status": { 1356 | "value": "VALID", 1357 | "id": 0 1358 | }, 1359 | "error": "OK", 1360 | "age": 228005, 1361 | "auth": { 1362 | "password_verification_age": 228005, 1363 | "have_password": true, 1364 | "secure": false, 1365 | "allow_plain_text": true 1366 | }, 1367 | "uid": { 1368 | "value": "24171229", 1369 | "lite": false, 1370 | "hosted": false, 1371 | "domid": "", 1372 | "domain": "", 1373 | "mx": "" 1374 | }, 1375 | "login": "alpha-san", 1376 | "karma": { 1377 | "value": 0 1378 | } 1379 | }, 1380 | { 1381 | "status": { 1382 | "value": "VALID", 1383 | "id": 0 1384 | }, 1385 | "error": "OK", 1386 | "age": 228005, 1387 | "auth": { 1388 | "password_verification_age": 228005, 1389 | "have_password": true, 1390 | "secure": false, 1391 | "allow_plain_text": true 1392 | }, 1393 | "uid": { 1394 | "value": "24171229", 1395 | "lite": false, 1396 | "hosted": false, 1397 | "domid": "", 1398 | "domain": "", 1399 | "mx": "" 1400 | }, 1401 | "login": "alpha-san", 1402 | "karma": { 1403 | "value": 0 1404 | } 1405 | }, 1406 | { 1407 | "status": { 1408 | "value": "VALID", 1409 | "id": 0 1410 | }, 1411 | "error": "OK", 1412 | "age": 228005, 1413 | "auth": { 1414 | "password_verification_age": 228005, 1415 | "have_password": true, 1416 | "secure": false, 1417 | "allow_plain_text": true 1418 | }, 1419 | "uid": { 1420 | "value": "24171229", 1421 | "lite": false, 1422 | "hosted": false, 1423 | "domid": "", 1424 | "domain": "", 1425 | "mx": "" 1426 | }, 1427 | "login": "alpha-san", 1428 | "karma": { 1429 | "value": 0 1430 | } 1431 | }, 1432 | { 1433 | "status": { 1434 | "value": "VALID", 1435 | "id": 0 1436 | }, 1437 | "error": "OK", 1438 | "age": 228005, 1439 | "auth": { 1440 | "password_verification_age": 228005, 1441 | "have_password": true, 1442 | "secure": false, 1443 | "allow_plain_text": true 1444 | }, 1445 | "uid": { 1446 | "value": "24171229", 1447 | "lite": false, 1448 | "hosted": false, 1449 | "domid": "", 1450 | "domain": "", 1451 | "mx": "" 1452 | }, 1453 | "login": "alpha-san", 1454 | "karma": { 1455 | "value": 0 1456 | } 1457 | }, 1458 | { 1459 | "status": { 1460 | "value": "VALID", 1461 | "id": 0 1462 | }, 1463 | "error": "OK", 1464 | "age": 228005, 1465 | "auth": { 1466 | "password_verification_age": 228005, 1467 | "have_password": true, 1468 | "secure": false, 1469 | "allow_plain_text": true 1470 | }, 1471 | "uid": { 1472 | "value": "24171229", 1473 | "lite": false, 1474 | "hosted": false, 1475 | "domid": "", 1476 | "domain": "", 1477 | "mx": "" 1478 | }, 1479 | "login": "alpha-san", 1480 | "karma": { 1481 | "value": 0 1482 | } 1483 | }, 1484 | { 1485 | "status": { 1486 | "value": "VALID", 1487 | "id": 0 1488 | }, 1489 | "error": "OK", 1490 | "age": 228005, 1491 | "auth": { 1492 | "password_verification_age": 228005, 1493 | "have_password": true, 1494 | "secure": false, 1495 | "allow_plain_text": true 1496 | }, 1497 | "uid": { 1498 | "value": "24171229", 1499 | "lite": false, 1500 | "hosted": false, 1501 | "domid": "", 1502 | "domain": "", 1503 | "mx": "" 1504 | }, 1505 | "login": "alpha-san", 1506 | "karma": { 1507 | "value": 0 1508 | } 1509 | }, 1510 | { 1511 | "status": { 1512 | "value": "VALID", 1513 | "id": 0 1514 | }, 1515 | "error": "OK", 1516 | "age": 228005, 1517 | "auth": { 1518 | "password_verification_age": 228005, 1519 | "have_password": true, 1520 | "secure": false, 1521 | "allow_plain_text": true 1522 | }, 1523 | "uid": { 1524 | "value": "24171229", 1525 | "lite": false, 1526 | "hosted": false, 1527 | "domid": "", 1528 | "domain": "", 1529 | "mx": "" 1530 | }, 1531 | "login": "alpha-san", 1532 | "karma": { 1533 | "value": 0 1534 | } 1535 | }, 1536 | { 1537 | "status": { 1538 | "value": "VALID", 1539 | "id": 0 1540 | }, 1541 | "error": "OK", 1542 | "age": 228005, 1543 | "auth": { 1544 | "password_verification_age": 228005, 1545 | "have_password": true, 1546 | "secure": false, 1547 | "allow_plain_text": true 1548 | }, 1549 | "uid": { 1550 | "value": "24171229", 1551 | "lite": false, 1552 | "hosted": false, 1553 | "domid": "", 1554 | "domain": "", 1555 | "mx": "" 1556 | }, 1557 | "login": "alpha-san", 1558 | "karma": { 1559 | "value": 0 1560 | } 1561 | }, 1562 | { 1563 | "status": { 1564 | "value": "VALID", 1565 | "id": 0 1566 | }, 1567 | "error": "OK", 1568 | "age": 228005, 1569 | "auth": { 1570 | "password_verification_age": 228005, 1571 | "have_password": true, 1572 | "secure": false, 1573 | "allow_plain_text": true 1574 | }, 1575 | "uid": { 1576 | "value": "24171229", 1577 | "lite": false, 1578 | "hosted": false, 1579 | "domid": "", 1580 | "domain": "", 1581 | "mx": "" 1582 | }, 1583 | "login": "alpha-san", 1584 | "karma": { 1585 | "value": 0 1586 | } 1587 | }, 1588 | { 1589 | "status": { 1590 | "value": "VALID", 1591 | "id": 0 1592 | }, 1593 | "error": "OK", 1594 | "age": 228005, 1595 | "auth": { 1596 | "password_verification_age": 228005, 1597 | "have_password": true, 1598 | "secure": false, 1599 | "allow_plain_text": true 1600 | }, 1601 | "uid": { 1602 | "value": "24171229", 1603 | "lite": false, 1604 | "hosted": false, 1605 | "domid": "", 1606 | "domain": "", 1607 | "mx": "" 1608 | }, 1609 | "login": "alpha-san", 1610 | "karma": { 1611 | "value": 0 1612 | } 1613 | }, 1614 | { 1615 | "status": { 1616 | "value": "VALID", 1617 | "id": 0 1618 | }, 1619 | "error": "OK", 1620 | "age": 228005, 1621 | "auth": { 1622 | "password_verification_age": 228005, 1623 | "have_password": true, 1624 | "secure": false, 1625 | "allow_plain_text": true 1626 | }, 1627 | "uid": { 1628 | "value": "24171229", 1629 | "lite": false, 1630 | "hosted": false, 1631 | "domid": "", 1632 | "domain": "", 1633 | "mx": "" 1634 | }, 1635 | "login": "alpha-san", 1636 | "karma": { 1637 | "value": 0 1638 | } 1639 | }, 1640 | { 1641 | "status": { 1642 | "value": "VALID", 1643 | "id": 0 1644 | }, 1645 | "error": "OK", 1646 | "age": 228005, 1647 | "auth": { 1648 | "password_verification_age": 228005, 1649 | "have_password": true, 1650 | "secure": false, 1651 | "allow_plain_text": true 1652 | }, 1653 | "uid": { 1654 | "value": "24171229", 1655 | "lite": false, 1656 | "hosted": false, 1657 | "domid": "", 1658 | "domain": "", 1659 | "mx": "" 1660 | }, 1661 | "login": "alpha-san", 1662 | "karma": { 1663 | "value": 0 1664 | } 1665 | }, 1666 | { 1667 | "status": { 1668 | "value": "VALID", 1669 | "id": 0 1670 | }, 1671 | "error": "OK", 1672 | "age": 228005, 1673 | "auth": { 1674 | "password_verification_age": 228005, 1675 | "have_password": true, 1676 | "secure": false, 1677 | "allow_plain_text": true 1678 | }, 1679 | "uid": { 1680 | "value": "24171229", 1681 | "lite": false, 1682 | "hosted": false, 1683 | "domid": "", 1684 | "domain": "", 1685 | "mx": "" 1686 | }, 1687 | "login": "alpha-san", 1688 | "karma": { 1689 | "value": 0 1690 | } 1691 | }, 1692 | { 1693 | "status": { 1694 | "value": "VALID", 1695 | "id": 0 1696 | }, 1697 | "error": "OK", 1698 | "age": 228005, 1699 | "auth": { 1700 | "password_verification_age": 228005, 1701 | "have_password": true, 1702 | "secure": false, 1703 | "allow_plain_text": true 1704 | }, 1705 | "uid": { 1706 | "value": "24171229", 1707 | "lite": false, 1708 | "hosted": false, 1709 | "domid": "", 1710 | "domain": "", 1711 | "mx": "" 1712 | }, 1713 | "login": "alpha-san", 1714 | "karma": { 1715 | "value": 0 1716 | } 1717 | }, 1718 | { 1719 | "status": { 1720 | "value": "VALID", 1721 | "id": 0 1722 | }, 1723 | "error": "OK", 1724 | "age": 228005, 1725 | "auth": { 1726 | "password_verification_age": 228005, 1727 | "have_password": true, 1728 | "secure": false, 1729 | "allow_plain_text": true 1730 | }, 1731 | "uid": { 1732 | "value": "24171229", 1733 | "lite": false, 1734 | "hosted": false, 1735 | "domid": "", 1736 | "domain": "", 1737 | "mx": "" 1738 | }, 1739 | "login": "alpha-san", 1740 | "karma": { 1741 | "value": 0 1742 | } 1743 | }, 1744 | { 1745 | "status": { 1746 | "value": "VALID", 1747 | "id": 0 1748 | }, 1749 | "error": "OK", 1750 | "age": 228005, 1751 | "auth": { 1752 | "password_verification_age": 228005, 1753 | "have_password": true, 1754 | "secure": false, 1755 | "allow_plain_text": true 1756 | }, 1757 | "uid": { 1758 | "value": "24171229", 1759 | "lite": false, 1760 | "hosted": false, 1761 | "domid": "", 1762 | "domain": "", 1763 | "mx": "" 1764 | }, 1765 | "login": "alpha-san", 1766 | "karma": { 1767 | "value": 0 1768 | } 1769 | }, 1770 | { 1771 | "status": { 1772 | "value": "VALID", 1773 | "id": 0 1774 | }, 1775 | "error": "OK", 1776 | "age": 228005, 1777 | "auth": { 1778 | "password_verification_age": 228005, 1779 | "have_password": true, 1780 | "secure": false, 1781 | "allow_plain_text": true 1782 | }, 1783 | "uid": { 1784 | "value": "24171229", 1785 | "lite": false, 1786 | "hosted": false, 1787 | "domid": "", 1788 | "domain": "", 1789 | "mx": "" 1790 | }, 1791 | "login": "alpha-san", 1792 | "karma": { 1793 | "value": 0 1794 | } 1795 | }, 1796 | { 1797 | "status": { 1798 | "value": "VALID", 1799 | "id": 0 1800 | }, 1801 | "error": "OK", 1802 | "age": 228005, 1803 | "auth": { 1804 | "password_verification_age": 228005, 1805 | "have_password": true, 1806 | "secure": false, 1807 | "allow_plain_text": true 1808 | }, 1809 | "uid": { 1810 | "value": "24171229", 1811 | "lite": false, 1812 | "hosted": false, 1813 | "domid": "", 1814 | "domain": "", 1815 | "mx": "" 1816 | }, 1817 | "login": "alpha-san", 1818 | "karma": { 1819 | "value": 0 1820 | } 1821 | }, 1822 | { 1823 | "status": { 1824 | "value": "VALID", 1825 | "id": 0 1826 | }, 1827 | "error": "OK", 1828 | "age": 228005, 1829 | "auth": { 1830 | "password_verification_age": 228005, 1831 | "have_password": true, 1832 | "secure": false, 1833 | "allow_plain_text": true 1834 | }, 1835 | "uid": { 1836 | "value": "24171229", 1837 | "lite": false, 1838 | "hosted": false, 1839 | "domid": "", 1840 | "domain": "", 1841 | "mx": "" 1842 | }, 1843 | "login": "alpha-san", 1844 | "karma": { 1845 | "value": 0 1846 | } 1847 | }, 1848 | { 1849 | "status": { 1850 | "value": "VALID", 1851 | "id": 0 1852 | }, 1853 | "error": "OK", 1854 | "age": 228005, 1855 | "auth": { 1856 | "password_verification_age": 228005, 1857 | "have_password": true, 1858 | "secure": false, 1859 | "allow_plain_text": true 1860 | }, 1861 | "uid": { 1862 | "value": "24171229", 1863 | "lite": false, 1864 | "hosted": false, 1865 | "domid": "", 1866 | "domain": "", 1867 | "mx": "" 1868 | }, 1869 | "login": "alpha-san", 1870 | "karma": { 1871 | "value": 0 1872 | } 1873 | }, 1874 | { 1875 | "status": { 1876 | "value": "VALID", 1877 | "id": 0 1878 | }, 1879 | "error": "OK", 1880 | "age": 228005, 1881 | "auth": { 1882 | "password_verification_age": 228005, 1883 | "have_password": true, 1884 | "secure": false, 1885 | "allow_plain_text": true 1886 | }, 1887 | "uid": { 1888 | "value": "24171229", 1889 | "lite": false, 1890 | "hosted": false, 1891 | "domid": "", 1892 | "domain": "", 1893 | "mx": "" 1894 | }, 1895 | "login": "alpha-san", 1896 | "karma": { 1897 | "value": 0 1898 | } 1899 | }, 1900 | { 1901 | "status": { 1902 | "value": "VALID", 1903 | "id": 0 1904 | }, 1905 | "error": "OK", 1906 | "age": 228005, 1907 | "auth": { 1908 | "password_verification_age": 228005, 1909 | "have_password": true, 1910 | "secure": false, 1911 | "allow_plain_text": true 1912 | }, 1913 | "uid": { 1914 | "value": "24171229", 1915 | "lite": false, 1916 | "hosted": false, 1917 | "domid": "", 1918 | "domain": "", 1919 | "mx": "" 1920 | }, 1921 | "login": "alpha-san", 1922 | "karma": { 1923 | "value": 0 1924 | } 1925 | }, 1926 | { 1927 | "status": { 1928 | "value": "VALID", 1929 | "id": 0 1930 | }, 1931 | "error": "OK", 1932 | "age": 228005, 1933 | "auth": { 1934 | "password_verification_age": 228005, 1935 | "have_password": true, 1936 | "secure": false, 1937 | "allow_plain_text": true 1938 | }, 1939 | "uid": { 1940 | "value": "24171229", 1941 | "lite": false, 1942 | "hosted": false, 1943 | "domid": "", 1944 | "domain": "", 1945 | "mx": "" 1946 | }, 1947 | "login": "alpha-san", 1948 | "karma": { 1949 | "value": 0 1950 | } 1951 | }, 1952 | { 1953 | "status": { 1954 | "value": "VALID", 1955 | "id": 0 1956 | }, 1957 | "error": "OK", 1958 | "age": 228005, 1959 | "auth": { 1960 | "password_verification_age": 228005, 1961 | "have_password": true, 1962 | "secure": false, 1963 | "allow_plain_text": true 1964 | }, 1965 | "uid": { 1966 | "value": "24171229", 1967 | "lite": false, 1968 | "hosted": false, 1969 | "domid": "", 1970 | "domain": "", 1971 | "mx": "" 1972 | }, 1973 | "login": "alpha-san", 1974 | "karma": { 1975 | "value": 0 1976 | } 1977 | }, 1978 | { 1979 | "status": { 1980 | "value": "VALID", 1981 | "id": 0 1982 | }, 1983 | "error": "OK", 1984 | "age": 228005, 1985 | "auth": { 1986 | "password_verification_age": 228005, 1987 | "have_password": true, 1988 | "secure": false, 1989 | "allow_plain_text": true 1990 | }, 1991 | "uid": { 1992 | "value": "24171229", 1993 | "lite": false, 1994 | "hosted": false, 1995 | "domid": "", 1996 | "domain": "", 1997 | "mx": "" 1998 | }, 1999 | "login": "alpha-san", 2000 | "karma": { 2001 | "value": 0 2002 | } 2003 | }, 2004 | { 2005 | "status": { 2006 | "value": "VALID", 2007 | "id": 0 2008 | }, 2009 | "error": "OK", 2010 | "age": 228005, 2011 | "auth": { 2012 | "password_verification_age": 228005, 2013 | "have_password": true, 2014 | "secure": false, 2015 | "allow_plain_text": true 2016 | }, 2017 | "uid": { 2018 | "value": "24171229", 2019 | "lite": false, 2020 | "hosted": false, 2021 | "domid": "", 2022 | "domain": "", 2023 | "mx": "" 2024 | }, 2025 | "login": "alpha-san", 2026 | "karma": { 2027 | "value": 0 2028 | } 2029 | }, 2030 | { 2031 | "status": { 2032 | "value": "VALID", 2033 | "id": 0 2034 | }, 2035 | "error": "OK", 2036 | "age": 228005, 2037 | "auth": { 2038 | "password_verification_age": 228005, 2039 | "have_password": true, 2040 | "secure": false, 2041 | "allow_plain_text": true 2042 | }, 2043 | "uid": { 2044 | "value": "24171229", 2045 | "lite": false, 2046 | "hosted": false, 2047 | "domid": "", 2048 | "domain": "", 2049 | "mx": "" 2050 | }, 2051 | "login": "alpha-san", 2052 | "karma": { 2053 | "value": 0 2054 | } 2055 | }, 2056 | { 2057 | "status": { 2058 | "value": "VALID", 2059 | "id": 0 2060 | }, 2061 | "error": "OK", 2062 | "age": 228005, 2063 | "auth": { 2064 | "password_verification_age": 228005, 2065 | "have_password": true, 2066 | "secure": false, 2067 | "allow_plain_text": true 2068 | }, 2069 | "uid": { 2070 | "value": "24171229", 2071 | "lite": false, 2072 | "hosted": false, 2073 | "domid": "", 2074 | "domain": "", 2075 | "mx": "" 2076 | }, 2077 | "login": "alpha-san", 2078 | "karma": { 2079 | "value": 0 2080 | } 2081 | }, 2082 | { 2083 | "status": { 2084 | "value": "VALID", 2085 | "id": 0 2086 | }, 2087 | "error": "OK", 2088 | "age": 228005, 2089 | "auth": { 2090 | "password_verification_age": 228005, 2091 | "have_password": true, 2092 | "secure": false, 2093 | "allow_plain_text": true 2094 | }, 2095 | "uid": { 2096 | "value": "24171229", 2097 | "lite": false, 2098 | "hosted": false, 2099 | "domid": "", 2100 | "domain": "", 2101 | "mx": "" 2102 | }, 2103 | "login": "alpha-san", 2104 | "karma": { 2105 | "value": 0 2106 | } 2107 | }, 2108 | { 2109 | "status": { 2110 | "value": "VALID", 2111 | "id": 0 2112 | }, 2113 | "error": "OK", 2114 | "age": 228005, 2115 | "auth": { 2116 | "password_verification_age": 228005, 2117 | "have_password": true, 2118 | "secure": false, 2119 | "allow_plain_text": true 2120 | }, 2121 | "uid": { 2122 | "value": "24171229", 2123 | "lite": false, 2124 | "hosted": false, 2125 | "domid": "", 2126 | "domain": "", 2127 | "mx": "" 2128 | }, 2129 | "login": "alpha-san", 2130 | "karma": { 2131 | "value": 0 2132 | } 2133 | }, 2134 | { 2135 | "status": { 2136 | "value": "VALID", 2137 | "id": 0 2138 | }, 2139 | "error": "OK", 2140 | "age": 228005, 2141 | "auth": { 2142 | "password_verification_age": 228005, 2143 | "have_password": true, 2144 | "secure": false, 2145 | "allow_plain_text": true 2146 | }, 2147 | "uid": { 2148 | "value": "24171229", 2149 | "lite": false, 2150 | "hosted": false, 2151 | "domid": "", 2152 | "domain": "", 2153 | "mx": "" 2154 | }, 2155 | "login": "alpha-san", 2156 | "karma": { 2157 | "value": 0 2158 | } 2159 | }, 2160 | { 2161 | "status": { 2162 | "value": "VALID", 2163 | "id": 0 2164 | }, 2165 | "error": "OK", 2166 | "age": 228005, 2167 | "auth": { 2168 | "password_verification_age": 228005, 2169 | "have_password": true, 2170 | "secure": false, 2171 | "allow_plain_text": true 2172 | }, 2173 | "uid": { 2174 | "value": "24171229", 2175 | "lite": false, 2176 | "hosted": false, 2177 | "domid": "", 2178 | "domain": "", 2179 | "mx": "" 2180 | }, 2181 | "login": "alpha-san", 2182 | "karma": { 2183 | "value": 0 2184 | } 2185 | }, 2186 | { 2187 | "status": { 2188 | "value": "VALID", 2189 | "id": 0 2190 | }, 2191 | "error": "OK", 2192 | "age": 228005, 2193 | "auth": { 2194 | "password_verification_age": 228005, 2195 | "have_password": true, 2196 | "secure": false, 2197 | "allow_plain_text": true 2198 | }, 2199 | "uid": { 2200 | "value": "24171229", 2201 | "lite": false, 2202 | "hosted": false, 2203 | "domid": "", 2204 | "domain": "", 2205 | "mx": "" 2206 | }, 2207 | "login": "alpha-san", 2208 | "karma": { 2209 | "value": 0 2210 | } 2211 | }, 2212 | { 2213 | "status": { 2214 | "value": "VALID", 2215 | "id": 0 2216 | }, 2217 | "error": "OK", 2218 | "age": 228005, 2219 | "auth": { 2220 | "password_verification_age": 228005, 2221 | "have_password": true, 2222 | "secure": false, 2223 | "allow_plain_text": true 2224 | }, 2225 | "uid": { 2226 | "value": "24171229", 2227 | "lite": false, 2228 | "hosted": false, 2229 | "domid": "", 2230 | "domain": "", 2231 | "mx": "" 2232 | }, 2233 | "login": "alpha-san", 2234 | "karma": { 2235 | "value": 0 2236 | } 2237 | }, 2238 | { 2239 | "status": { 2240 | "value": "VALID", 2241 | "id": 0 2242 | }, 2243 | "error": "OK", 2244 | "age": 228005, 2245 | "auth": { 2246 | "password_verification_age": 228005, 2247 | "have_password": true, 2248 | "secure": false, 2249 | "allow_plain_text": true 2250 | }, 2251 | "uid": { 2252 | "value": "24171229", 2253 | "lite": false, 2254 | "hosted": false, 2255 | "domid": "", 2256 | "domain": "", 2257 | "mx": "" 2258 | }, 2259 | "login": "alpha-san", 2260 | "karma": { 2261 | "value": 0 2262 | } 2263 | }, 2264 | { 2265 | "status": { 2266 | "value": "VALID", 2267 | "id": 0 2268 | }, 2269 | "error": "OK", 2270 | "age": 228005, 2271 | "auth": { 2272 | "password_verification_age": 228005, 2273 | "have_password": true, 2274 | "secure": false, 2275 | "allow_plain_text": true 2276 | }, 2277 | "uid": { 2278 | "value": "24171229", 2279 | "lite": false, 2280 | "hosted": false, 2281 | "domid": "", 2282 | "domain": "", 2283 | "mx": "" 2284 | }, 2285 | "login": "alpha-san", 2286 | "karma": { 2287 | "value": 0 2288 | } 2289 | }, 2290 | { 2291 | "status": { 2292 | "value": "VALID", 2293 | "id": 0 2294 | }, 2295 | "error": "OK", 2296 | "age": 228005, 2297 | "auth": { 2298 | "password_verification_age": 228005, 2299 | "have_password": true, 2300 | "secure": false, 2301 | "allow_plain_text": true 2302 | }, 2303 | "uid": { 2304 | "value": "24171229", 2305 | "lite": false, 2306 | "hosted": false, 2307 | "domid": "", 2308 | "domain": "", 2309 | "mx": "" 2310 | }, 2311 | "login": "alpha-san", 2312 | "karma": { 2313 | "value": 0 2314 | } 2315 | }, 2316 | { 2317 | "status": { 2318 | "value": "VALID", 2319 | "id": 0 2320 | }, 2321 | "error": "OK", 2322 | "age": 228005, 2323 | "auth": { 2324 | "password_verification_age": 228005, 2325 | "have_password": true, 2326 | "secure": false, 2327 | "allow_plain_text": true 2328 | }, 2329 | "uid": { 2330 | "value": "24171229", 2331 | "lite": false, 2332 | "hosted": false, 2333 | "domid": "", 2334 | "domain": "", 2335 | "mx": "" 2336 | }, 2337 | "login": "alpha-san", 2338 | "karma": { 2339 | "value": 0 2340 | } 2341 | }, 2342 | { 2343 | "status": { 2344 | "value": "VALID", 2345 | "id": 0 2346 | }, 2347 | "error": "OK", 2348 | "age": 228005, 2349 | "auth": { 2350 | "password_verification_age": 228005, 2351 | "have_password": true, 2352 | "secure": false, 2353 | "allow_plain_text": true 2354 | }, 2355 | "uid": { 2356 | "value": "24171229", 2357 | "lite": false, 2358 | "hosted": false, 2359 | "domid": "", 2360 | "domain": "", 2361 | "mx": "" 2362 | }, 2363 | "login": "alpha-san", 2364 | "karma": { 2365 | "value": 0 2366 | } 2367 | }, 2368 | { 2369 | "status": { 2370 | "value": "VALID", 2371 | "id": 0 2372 | }, 2373 | "error": "OK", 2374 | "age": 228005, 2375 | "auth": { 2376 | "password_verification_age": 228005, 2377 | "have_password": true, 2378 | "secure": false, 2379 | "allow_plain_text": true 2380 | }, 2381 | "uid": { 2382 | "value": "24171229", 2383 | "lite": false, 2384 | "hosted": false, 2385 | "domid": "", 2386 | "domain": "", 2387 | "mx": "" 2388 | }, 2389 | "login": "alpha-san", 2390 | "karma": { 2391 | "value": 0 2392 | } 2393 | }, 2394 | { 2395 | "status": { 2396 | "value": "VALID", 2397 | "id": 0 2398 | }, 2399 | "error": "OK", 2400 | "age": 228005, 2401 | "auth": { 2402 | "password_verification_age": 228005, 2403 | "have_password": true, 2404 | "secure": false, 2405 | "allow_plain_text": true 2406 | }, 2407 | "uid": { 2408 | "value": "24171229", 2409 | "lite": false, 2410 | "hosted": false, 2411 | "domid": "", 2412 | "domain": "", 2413 | "mx": "" 2414 | }, 2415 | "login": "alpha-san", 2416 | "karma": { 2417 | "value": 0 2418 | } 2419 | }, 2420 | { 2421 | "status": { 2422 | "value": "VALID", 2423 | "id": 0 2424 | }, 2425 | "error": "OK", 2426 | "age": 228005, 2427 | "auth": { 2428 | "password_verification_age": 228005, 2429 | "have_password": true, 2430 | "secure": false, 2431 | "allow_plain_text": true 2432 | }, 2433 | "uid": { 2434 | "value": "24171229", 2435 | "lite": false, 2436 | "hosted": false, 2437 | "domid": "", 2438 | "domain": "", 2439 | "mx": "" 2440 | }, 2441 | "login": "alpha-san", 2442 | "karma": { 2443 | "value": 0 2444 | } 2445 | }, 2446 | { 2447 | "status": { 2448 | "value": "VALID", 2449 | "id": 0 2450 | }, 2451 | "error": "OK", 2452 | "age": 228005, 2453 | "auth": { 2454 | "password_verification_age": 228005, 2455 | "have_password": true, 2456 | "secure": false, 2457 | "allow_plain_text": true 2458 | }, 2459 | "uid": { 2460 | "value": "24171229", 2461 | "lite": false, 2462 | "hosted": false, 2463 | "domid": "", 2464 | "domain": "", 2465 | "mx": "" 2466 | }, 2467 | "login": "alpha-san", 2468 | "karma": { 2469 | "value": 0 2470 | } 2471 | }, 2472 | { 2473 | "status": { 2474 | "value": "VALID", 2475 | "id": 0 2476 | }, 2477 | "error": "OK", 2478 | "age": 228005, 2479 | "auth": { 2480 | "password_verification_age": 228005, 2481 | "have_password": true, 2482 | "secure": false, 2483 | "allow_plain_text": true 2484 | }, 2485 | "uid": { 2486 | "value": "24171229", 2487 | "lite": false, 2488 | "hosted": false, 2489 | "domid": "", 2490 | "domain": "", 2491 | "mx": "" 2492 | }, 2493 | "login": "alpha-san", 2494 | "karma": { 2495 | "value": 0 2496 | } 2497 | }, 2498 | { 2499 | "status": { 2500 | "value": "VALID", 2501 | "id": 0 2502 | }, 2503 | "error": "OK", 2504 | "age": 228005, 2505 | "auth": { 2506 | "password_verification_age": 228005, 2507 | "have_password": true, 2508 | "secure": false, 2509 | "allow_plain_text": true 2510 | }, 2511 | "uid": { 2512 | "value": "24171229", 2513 | "lite": false, 2514 | "hosted": false, 2515 | "domid": "", 2516 | "domain": "", 2517 | "mx": "" 2518 | }, 2519 | "login": "alpha-san", 2520 | "karma": { 2521 | "value": 0 2522 | } 2523 | }, 2524 | { 2525 | "status": { 2526 | "value": "VALID", 2527 | "id": 0 2528 | }, 2529 | "error": "OK", 2530 | "age": 228005, 2531 | "auth": { 2532 | "password_verification_age": 228005, 2533 | "have_password": true, 2534 | "secure": false, 2535 | "allow_plain_text": true 2536 | }, 2537 | "uid": { 2538 | "value": "24171229", 2539 | "lite": false, 2540 | "hosted": false, 2541 | "domid": "", 2542 | "domain": "", 2543 | "mx": "" 2544 | }, 2545 | "login": "alpha-san", 2546 | "karma": { 2547 | "value": 0 2548 | } 2549 | }, 2550 | { 2551 | "status": { 2552 | "value": "VALID", 2553 | "id": 0 2554 | }, 2555 | "error": "OK", 2556 | "age": 228005, 2557 | "auth": { 2558 | "password_verification_age": 228005, 2559 | "have_password": true, 2560 | "secure": false, 2561 | "allow_plain_text": true 2562 | }, 2563 | "uid": { 2564 | "value": "24171229", 2565 | "lite": false, 2566 | "hosted": false, 2567 | "domid": "", 2568 | "domain": "", 2569 | "mx": "" 2570 | }, 2571 | "login": "alpha-san", 2572 | "karma": { 2573 | "value": 0 2574 | } 2575 | }, 2576 | { 2577 | "status": { 2578 | "value": "VALID", 2579 | "id": 0 2580 | }, 2581 | "error": "OK", 2582 | "age": 228005, 2583 | "auth": { 2584 | "password_verification_age": 228005, 2585 | "have_password": true, 2586 | "secure": false, 2587 | "allow_plain_text": true 2588 | }, 2589 | "uid": { 2590 | "value": "24171229", 2591 | "lite": false, 2592 | "hosted": false, 2593 | "domid": "", 2594 | "domain": "", 2595 | "mx": "" 2596 | }, 2597 | "login": "alpha-san", 2598 | "karma": { 2599 | "value": 0 2600 | } 2601 | } 2602 | ] 2603 | 2604 | --------------------------------------------------------------------------------