Записки программиста, обо всем и ни о чем. Но, наверное, больше профессионального.

Показанные сообщения отсортированы по релевантности запросу "node js". Сортировать по дате Показать все сообщения
Показанные сообщения отсортированы по релевантности запросу "node js". Сортировать по дате Показать все сообщения

2012-06-29

Resumable HTTP file upload

Кто о чем, а вшивый о бане. В продолжение этого поста: возобновляемая закачка файлов на веб-сервер с использованием HTML5 FileAPI + Node.js + Socket.io
я проверил работоспособность предлагаемого решения
Заодно и код причесал (см.полный листинг в конце поста)

Кому лень читать многабукаф, заявляю сразу — предлагаемое решение вполне работоспособно. Если нужен веб-сервис по надежной, возобновляемой заливке мега(гига)файлов недорого — берите, пользуйтесь.

Остальные читают дальше :)

В результате получилась как бы инструкция по созданию веб-сервиса по заливке файлов практически любого размера на сервер. На самых современных технологиях :)

Для создания сервиса мне понадобится:

Виртмашина с дебианчиком

Со страницы debian.org/devel/debian-installer
забрал образ debian-wheezy-DI-a1-i386-netinst.iso
и поставил из него систему в виртуалбоксе. Выделил 2 ядра и 512 мегабайт оперативки.

Свежепоставленную систему доточил напильником:
nano .bashrc # для рута и своей учетки

shopt -s histappend
shopt -s cmdhist
export PROMPT_COMMAND='history -a'
export HISTFILESIZE=1000
export HISTSIZE=1000
export HISTCONTROL=ignoreboth:erasedups

# репозитории
nano /etc/apt/sources.list

deb http://mirror.yandex.ru/debian/ testing main contrib non-free
deb-src http://mirror.yandex.ru/debian/ testing main contrib non-free
deb http://security.debian.org/ testing/updates main
deb-src http://security.debian.org/ testing/updates main
deb http://mirror.yandex.ru/debian/ testing-proposed-updates contrib non-free main
deb-src http://mirror.yandex.ru/debian/ testing-proposed-updates contrib non-free main

# доустановить пакеты
aptitude update; aptitude safe-upgrade; aptitude full-upgrade
aptitude install linux-headers-$(uname -r) dkms make gcc g++
aptitude install git-core curl build-essential openssl libssl-dev wget
Сервер к приему софта готов.

Node.js

Веб-сервер Node.js ставится из исходников традиционным образом. Я собрал и запустил ноду по инструкции с сайта разработчиков github.com/joyent/node/wiki/Installing-Node.js-via-package-manager
# mkdir -p /root/build/node && cd $_
# git clone https://github.com/joyent/node.git .
# git checkout v0.6.19
# ./configure --openssl-libpath=/usr/lib/ssl
# make
# make test
# make install
# node -v
Хоть и написано «установка ноды через пакетный менеджер», инфы по установке из исходников там достаточно.
Теперь нужно выйти из рута, можно поиграться с нодой, позапускать хелловорд и прочее.

Socket.io

Это будет модуль к Node.js. Установка модуля проста до безобразия
$ mkdir -p ~/node && cd $_
$ npm install socket.io

# после заливки программ на сервер, запустить тут веб-сервер
# node app.js
Типа, всё, сервер готов. Дальше пишем программу.

Код программы

см.в самом низу. Здесь приведу только сниппеты. Еще раз — код не мой а Габриэля Манрика, я его творение только слегка переписал. CSS & HTML вообще не трогал.
Файлов два: один — это HTML для скармливания браузеру; второй — JS код сервера.

Ключевые места программы, приблизительный ход выполнения
// на клиенте
// по щелчку на кнопке «старт» в сокет уходит событие fileMeta с именем и размером файла;
// на событие «чтение файла ридером» вешается обработчик, который отправляет в сокет событие fileData
//с именем файла и куском считанных бинарных данных
var socket = io.connect(); // connect at the same host / port as your website
...
socket.on('nextChunk', onSocketNextChunk);
socket.on('fileProcessed', onSocketFileProcessed);
...
function onClickStartUpload() {
...
  fReader = new FileReader();
...
  fReader.onload = function(evnt) {
   socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
  }
  socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
…

// на сервере, приняв событие fileMeta, код проверяет наличие файла и открывает его на запись;
// открытие файла отправляет в сокет (в браузер) событие nextChunk
// с номером следующего чанка и числом отработанных процентов
var app = require('http').createServer(httpResponder)
 , io = require('socket.io').listen(app)
...
app.listen(8080); // http://servername:8080/
io.sockets.on('connection', onSocketConnect);
...
function onSocketConnect(socket) {
 socket.on( 'fileMeta', function(data) { onSocketFileMeta(data, socket); } );
 socket.on( 'fileData', function(data) { onSocketFileData(data, socket); } );
}
…
function onSocketFileMeta (data, socket) {
...
  var stat = fs.statSync(fullName);
  if(stat.isFile()) {
   fObj.rcvdBytes = stat.size;
...
 fs.open(fullName, 'a', 0644, function(err, fd) { onFileOpen(err, fd, fName, socket); });
…
function getNextChunk(fObj, socket) {
 var pct = (fObj.rcvdBytes / fObj.fSize) * 100;
 var chunkNum = fObj.rcvdBytes / chunkSize;
 socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });

// на клиенте, обработчик события nextChunk считывает из файла следующий чанк данных,
// после чего обработчик события «чтение файла ридером» отправляет в сокет событие fileData
function onSocketNextChunk (data) {
...
 var blob = getNextBlob(selectedFile, nextByte, chunkSize);
 fReader.readAsBinaryString(blob);
…
function getNextBlob(fileObj, startPos, numBytes) {
 var endPos = startPos + Math.min( numBytes, (fileObj.size - startPos) );
 if(fileObj.webkitSlice)
  return fileObj.webkitSlice(startPos, endPos);

// на сервере, приняв событие fileData, код ветвится на три дорожки:
// в приемном буфере еще есть место — запросить следующий чанк;
// буфер полон — сбросить его на диск и запросить следующий чанк;
// все данные уже здесь — завершить запись в файл, переместить его в репозиторий и отправить уведомление браузеру
function onSocketFileData(data, socket) {
...
 if(fObj.rcvdBytes == fObj.fSize) {
  fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
   onFileWriteDone(err, written, buffer, fName, socket);
  });
 }
 else if(fObj.bytesBuf.length > bufMaxSize) {
  fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
   onFileWriteBuffer(err, written, buffer, fName, socket);
  });
 }
 else {
  getNextChunk(fObj, socket);
 }
…
function onFileWriteBuffer(err, written, buffer, fName, socket) {
 fObj.bytesBuf = '';
...
 getNextChunk(fObj, socket);
}
…
function onFileWriteDone(err, written, buffer, fName, socket) {
...
 fs.close(fObj.fHandle, function(err) {
  fObj.fHandle = '';
...
 });
...
 util.pump(inp, out, function() {
  fs.unlink(tempName, function () {
   socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
  });
 });
}
… и так далее
Все на асинхронных событиях и коллбеках, довольно прикольно.

Запуск и тестирование

После написания файлов надо залить их на сервер
rsync -av "/home/valik/Desktop/node/" "node:node/"
на сервере запустить ноду
$ cd node; mkdir Repo; mkdir Temp; node app.js

После успешного запуска я провел тестирование на предмет — как оно в целом. Выяснилось, что в целом — неплохо. За исключением того, что очень долго выполняется финальная стадия, а именно перенос закачанного файла из папки Temp в папку Repo. Этот кусок кода надо переделать.

В общем, файл размером 8621 мегабайт был передан успешно, со средней скоростью 3.5 мегабайта в секунду. Этот же файл через rsync залился со скоростью 25 мб/сек. Откуда берется такое замедление — неясно, задача почти не дает нагрузки на ресурсы компа.

Браузер (Chrome 19) сожрал 1 гигабайт вирт.памяти, 128 реальной оперативки, 70% процессора (одного ядра). Утечек памяти нет.
Node.js сожрала 140 мегабайт вирт.памяти, 90 реальной, 40% процессора (одного ядра). Утечек памяти нет. В сумме, четырехядерный процессор был занят на 15%.

Файл передавался чанками по 3 мегабайта; серверный буфер 10 мегабайт, после его заполнения буфер сбрасывается на диск.
Финальный сброс буферов и перенос файла из временной папки в репозиторий занял около 3-х минут.

Также я проверил возобновление прерванной закачки. Работает как и ожидалось — ни падение браузера, ни падение сервера не мешает продолжить закачку с того места, где остановились в прошлый раз.

Вывод.
Технология рабочая, рекомендую к использованию.


Полный код аплоадера


index.html
<!DOCTYPE html>
<html>
<head>
 <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 <title>File Uploader</title>
 <script src="/socket.io/socket.io.js"></script>

<script type="text/javascript" charset="utf-8">

window.addEventListener("load", onDocReady);

var socket = io.connect(); // connect at the same host / port as your website
var fReader = '';         // FileReader object
var fileName = '';        // selected file name
var selectedFile = '';     // file object
var mbBytes = 1048576;     // bytes in megabyte
var chunkSize = 3 * mbBytes; // chunk size in bytes

socket.on('nextChunk', onSocketNextChunk);
socket.on('fileProcessed', onSocketFileProcessed);

function onDocReady() {
 if(window.File && window.FileReader) { //These are the necessary HTML5 objects the we are going to use
  document.getElementById('UploadButton').addEventListener('click', onClickStartUpload);
  document.getElementById('FileBox').addEventListener('change', onFileChosen);
 }
 else {
  document.getElementById('UploadArea').innerHTML = "Your Browser Doesn't Support The File API. Please Update Your Browser";
 }
} // function onDocReady()


function onClickStartUpload() {
 if(selectedFile !== '') {
  fReader = new FileReader();
  fileName = document.getElementById('NameBox').value;
  htmlUploading();
  fReader.onload = function(evnt) {
   socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
  }
  socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
 }
 else {
  alert("Please Select A File");
 }
} // function onClickStartUpload()


function onFileChosen(evnt) {
 selectedFile = evnt.target.files[0];
 document.getElementById('NameBox').value = selectedFile.name;
} // function onFileChosen(evnt)


function onSocketNextChunk (data) { // socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });
 var pct = data['Percent'];
 var chunkNum = data['ChunkNum']; // chunk number (0 <= chunkNum < n; n = fSize/chunkSize)
 htmlUpdatePct(pct);
 var nextByte = chunkNum * chunkSize; //The Next Blocks Starting Position
 var blob = getNextBlob(selectedFile, nextByte, chunkSize);
 fReader.readAsBinaryString(blob);
} // function onSocketNextChunk (data)


function getNextBlob(fileObj, startPos, numBytes) {
 var endPos = startPos + Math.min( numBytes, (fileObj.size - startPos) );
 if(fileObj.webkitSlice)
  return fileObj.webkitSlice(startPos, endPos);
 else
  return fileObj.mozSlice(startPos, endPos);
} // function getNextBlob(fileObj, startPos, numBytes)


function htmlUploading() {
 var htmlContent = "<span id='NameArea'>Uploading " + selectedFile.name + " as " + fileName + "</span>";
 htmlContent += '<div id="ProgressContainer"><div id="ProgressBar"></div></div><span id="percent">0%</span>';
 htmlContent += "<span id='Uploaded'> - <span id='MB'>0</span>/" + Math.round(selectedFile.size / mbBytes) + "MB</span>";
 document.getElementById('UploadArea').innerHTML = htmlContent;
} // function htmlUploading()


function htmlUpdatePct(percent) {
 document.getElementById('ProgressBar').style.width = percent + '%';
 document.getElementById('percent').innerHTML = (Math.round(percent * 100) / 100) + '%';
 var mbDone = Math.round(((percent / 100.0) * selectedFile.size) / mbBytes);
 document.getElementById('MB').innerHTML = mbDone;
} // function htmlUpdatePct(percent)


function onSocketFileProcessed (data) { // socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
 var htmlContent = "File Successfully Uploaded!"
 htmlContent += "<img id='Thumb' src='" + data['Preview'] + "' alt='" + fileName + "'><br>";
 htmlContent += "<button type='button' name='Upload' value='' id='Restart' class='Button'>Upload Another</button>";
 document.getElementById('UploadArea').innerHTML = htmlContent;
 document.getElementById('Restart').addEventListener('click', onRefresh);
 document.getElementById('UploadBox').style.width = '270px';
 document.getElementById('UploadBox').style.height = '270px';
 document.getElementById('UploadBox').style.textAlign = 'center';
 document.getElementById('Restart').style.left = '20px';
} // function onSocketFileProcessed (data)


function onRefresh() {
 location.reload(true);
}

</script>

<style type="text/css" media="screen">
body {
 background: #F9F9F9;
 font-family: Calibri;
 font-size: 18px;
}

h2 {
 font-size: 40px;
 margin-top: 6px;
 margin-bottom: 10px;
}

#Thumb {
 max-width: 230px;
 max-height: 130px;
}

#ProgressContainer {
 width: 396px;
 height: 36px;
 background: #F8F8F8;
 margin-top: 14px;
 border: 1px solid #E8E8E8;
 border-top: 1px solid #D8D8D8;

 -webkit-border-radius: 4px;
 -moz-border-radius: 4px;
 border-radius: 4px;
 padding: 2px;
}

#ProgressBar {
 height: 100%;
 width: 0%;

 -webkit-border-radius: 4px;
 -moz-border-radius: 4px;
 border-radius: 4px;
 background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.50, #6b0d6b), to(#4a074a));
}

#UploadBox {
 background: #FFF;
 padding: 20px;
 position: absolute;
 top: 50%;
 left: 50%;
 margin-left: -200px;
 margin-top: -150px;
 height: 200px;
 width: 400px;
 border: 1px solid #DFDFDF;

 -webkit-box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
 -moz-box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
 box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);

 -webkit-border-radius: 11px;
 -moz-border-radius: 11px;
 border-radius: 11px;
}

button.Button {
 font-size: 18px;
 color: #ffffff;
 padding: 8px 30px;
 background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.50, #6b0d6b), to(#4a074a));

 -webkit-border-radius: 5px;
 -moz-border-radius: 5px;
 border-radius: 5px;
 border: 1px solid #5b139e;

 -webkit-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
 -moz-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
 box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 3px rgba(255,255,255,0.4);
 text-shadow: 0px -1px 0px rgba(000,000,000,0.1), 0px 1px 0px rgba(145,035,145,1);
 position: absolute;
 bottom: 20px;
 right: 20px;
 cursor: pointer;
}

button.Button:hover {
 background: -webkit-gradient( linear, left top, left bottom, from(#a50aad), color-stop(0.80, #6b0d6b), to(#a50aad));
 color: #D3D3D3;
}

button.Button:active {
 background: -webkit-gradient( linear, left top, left bottom, from(#4a074a), color-stop(0.80, #6b0d6b), to(#a50aad));
}

input {
 margin-top: 10px;
 margin-bottom: 8px;
}

input[type=text] {
 border: 1px solid #CDCDCD;
 border-top: 1px solid #676767;

 -webkit-border-radius: 3px;
 -moz-border-radius: 3px;
 border-radius: 3px;
 font-size: 18px;
 padding: 2px;
 width: 300px;
 margin-left: 10px;
}

</style>
</head>
<body>
 <div id="UploadBox">
  <h2>Chunked File Uploader</h2>
  <span id='UploadArea'>
   <label for="FileBox">Choose A File: </label><input type="file" id="FileBox"><br>
   <label for="NameBox">Name: </label><input type="text" id="NameBox"><br>
   <button type='button' id='UploadButton' class='Button'>Upload</button>
  </span>
 </div>
</body>
</html>


app.js
//# -*- mode: javascript; coding: utf-8 -*-

var tempDir = 'Temp';
var targetDir = 'Repo';          // mkdir Temp; mkdir Repo; node app.js
var mbBytes = 1048576;           // bytes in megabyte
var chunkSize = 3 * mbBytes;     // chunk size in bytes
var bufMaxSize = 10485760;       // 10 MB

var app = require('http').createServer(httpResponder)
 , io = require('socket.io').listen(app)
 , fs = require('fs')
 , exec = require('child_process').exec
 , util = require('util')
 , filesList = {};

app.listen(8080); // http://servername:8080/
io.sockets.on('connection', onSocketConnect);


function httpResponder(req, res) {
 fs.readFile(__dirname + '/index.html',
  function (err, data) {
   if (err) {
    res.writeHead(500);
    return res.end('Error loading index.html');
   }
   res.writeHead(200);
   res.end(data);
  }
 );
} // function httpResponder(req, res)


function onSocketConnect(socket) {
 socket.on( 'fileMeta', function(data) { onSocketFileMeta(data, socket); } );
 socket.on( 'fileData', function(data) { onSocketFileData(data, socket); } );
} // function onSocketConnect(socket)


function onSocketFileMeta (data, socket) { // start recieve file
 // socket.emit('fileMeta', {'Name' : fileName, 'Size' : selectedFile.size});
 var fName = data['Name'], fSize = data['Size'];
 var fullName = tempDir + '/' + fName;
 var fObj = {  // Create a new Entry in The Files List
  fSize     : fSize, // file size
  bytesBuf  : '',    // data buffer, 10 MB max
  rcvdBytes : 0,     // count of recieved bytes
  fHandle   : ''     // file handle
 }

 try { // if file exist already
  var stat = fs.statSync(fullName);
  if(stat.isFile()) {
   fObj.rcvdBytes = stat.size;
  }
 }
 catch(err) { // It's a New File
  console.log(err);
 }

 filesList[fName] = fObj;
 fs.open(fullName, 'a', 0644, function(err, fd) { onFileOpen(err, fd, fName, socket); });
} // function onSocketFileMeta (data, socket)


function onSocketFileData(data, socket) { // socket.emit('fileData', {'Name' : fileName, 'Data' : evnt.target.result});
 var fName = data['Name'];
 var blob = data['Data']

 var fObj = filesList[fName];
 fObj.rcvdBytes += blob.length;
 fObj.bytesBuf += blob;
 filesList[fName] = fObj;

 if(fObj.rcvdBytes == fObj.fSize) { //If File is Fully Uploaded
  fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
   onFileWriteDone(err, written, buffer, fName, socket);
  });
 } // finish

 else if(fObj.bytesBuf.length > bufMaxSize) { //If the Data Buffer reaches 10MB
  fs.write(fObj.fHandle, fObj.bytesBuf, null, 'Binary', function(err, written, buffer) {
   onFileWriteBuffer(err, written, buffer, fName, socket);
  });
 }
 else {
  getNextChunk(fObj, socket);
 }
} // function onSocketFileData(data, socket)


function getNextChunk(fObj, socket) {
 var pct = (fObj.rcvdBytes / fObj.fSize) * 100;
 var chunkNum = fObj.rcvdBytes / chunkSize;
 socket.emit('nextChunk', { 'ChunkNum' : chunkNum, 'Percent' : pct });
} // function getNextChunk(fObj, socket)


function onFileOpen(err, fd, fName, socket) {
 var fObj = filesList[fName];
 if(err) {
  console.log(err);
 }
 else {
  fObj.fHandle = fd; // We store the file handler so we can write to it later
  filesList[fName] = fObj;
  getNextChunk(fObj, socket);
 }
} // function onFileOpen(err, fd, fName, socket)


function onFileWriteBuffer(err, written, buffer, fName, socket) { // buffer writed
 var fObj = filesList[fName];
 fObj.bytesBuf = ''; // Reset The Buffer
 filesList[fName] = fObj;
 getNextChunk(fObj, socket);
} // function onFileWriteBuffer(err, written, buffer, fName, socket)


function onFileWriteDone(err, written, buffer, fName, socket) {
 var fObj = filesList[fName];
 fs.close(fObj.fHandle, function(err) {
  fObj.fHandle = '';
  filesList[fName] = fObj;
 });
 var tempName = tempDir + '/' + fName;
 var inp = fs.createReadStream(tempName);
 var out = fs.createWriteStream(targetDir + '/' + fName);
 util.pump(inp, out, function() {
  fs.unlink(tempName, function () {
   socket.emit('fileProcessed', {'Preview' : 'thumbnail.jpg'});
//   exec("ffmpeg -i Video/" + Name  + " -ss 01:30 -r 1 -an -vframes 1 -f mjpeg Video/" + Name  + ".jpg", function(err){
//    socket.emit('Done', {'Image' : 'Video/' + Name + '.jpg'});
//   });
  });
 });
} // function onFileWriteDone(err, written, buffer, fName, socket)


2015-06-08

Upload files by chunks

Грузите апельсины бочками.

Есть у меня демка, загрузка файлов чанками на сервер. Суть в том, что раз чанками, то файлы могут быть любого размера, лишь бы место на диске было.
Вообще-то этих поделок у меня уже порядком накопилось, но тут я про одну конкретную расскажу:
HTML5 FileAPI + Node.js + Socket.io

Внезапно, понадобилось вывести аппликуху в люди. Одному хорошему человеку надо было дать возможность передать файл. Файл такого размера, что в электропочту не лезет. Всякие яндекс-диски и прочие дропбоксы этот человек, в силу почтенного возраста, понимать не хочет. Тут я и вспомнил про VCU.

Достал аппликуху с чердака, отряхнул от пыли, засадил за reverse proxy, отягощенный HTTPS и, какая неожиданность, ничего не работает.

Забегая вперед, скажу, что проблем была целая пачка: начиная с нежелания вебсокетов работать через https прокси и заканчивая некоторыми изменениями в реализации FileAPI в разных браузерах.

Проблема первая. Надо заставить вебсокеты работать позади https reverse proxy.
Прокси типа такого:
# /etc/apache2/sites-available/rover-ssl.conf
...
<Location /node/vcu/>
        Order allow,deny
        allow from all
        AuthType Basic
        AuthUserFile /home/valik/.htpasswd
        AuthName "Alwaysglum restricted services"
        Require valid-user
    ProxyPass http://localhost:8080/
</Location>
...

Код в app.js, который прекрасно работает в тепличных лабораторных условиях:
io = require('socket.io').listen(app)
требуется заменить на более изощренный:
io = require('socket.io').listen(app, {'transports': ['polling']})
Почему так и никак иначе, сами догадайтесь.

Теперь второй файл (вся аппликуха состоит из двух файлов), index.html.
Подключение скрипта, меняем вот это:
<script src="/socket.io/socket.io.js"></script>
на более другое:
<script src="socket.io/socket.io.js"></script>
Абсолютные пути для лохов.

И меняем код создания сокета с примитивного
var socket = io.connect();
на более специфичное:
var socket = io.connect(
    'https://alwaysgloom.sytes.net',
    {path: '/node/vcu/socket.io'});

Теперь вебсокеты работать будут.
Осталось решить проблемы совместимости FileAPI в разных браузера.

Если вкратце, то раньше чтение файла браузером с диска было
fileReader.readAsBinaryString(blob);
а стало
fileReader.readAsArrayBuffer(blob);

Что самое удивительное, на сервере в Node.js данные приходят в виде объекта Buffer в обоих случаях! Хотя, казалось бы, очевидно, что должен приходить ArrayBuffer, если клиент засылает ArrayBuffer. Я на этой очевидности потерял пару вечеров, пробуя разные варианты преобразований ArrayBuffer → Buffer.
В итоге, по неясной причине, серверный код править пришлось только в части вызова функций fs.write. Там тоже что-то поломалось.
Конечный вариант использования fs.write выглядит как-то так:
//fs.write(fd, buffer, offset, length[, position], callback)
fs.write(fObj.fHandle, fObj.bytesBuf, 0, fObj.bytesBuf.length, null,
 function(err, written, buffer) { ...

Полностью все тонкости можно рассмотреть тут





original post http://vasnake.blogspot.com/2015/06/upload-files-by-chunks.html

2012-04-14

Real-Time GIS Services

Относительно свежая тема, для ГИС во всяком случае. Веб-приложения реального времени.

Вот, к примеру, карты.яндекс показывающие «пробки» на дорогах. Я код не копал, но мнение имею — там ни разу не реальное время. Изменения в браузер приходят не тогда, когда они проявились на сервере (я уж не говорю — на дороге :), а тогда, когда отработает браузерное событие. Браузер периодически дергает сервер и показывает свежие данные. Это не риалтайм. Риалтаймом это можно было бы назвать, если бы по приходу в БД на сервере описания ДТП, или других обновлений, сервер разослал бы клиентам (браузерам) свежие данные. Хотя, если строго, это тоже не риалтайм. Но в веб-приложениях это называется так.

Так вот, пример ГИС веб-приложения реального времени, сделанного в целях демонстрации и проверки технологий на двух разных платформах (Node.js, Socket.io; ASP.NET MVC, SignalR).

Слово докладчику.

At the 2012 Esri Developer Summit, we gave two talks on building real-time apps – the first used Node.js and Socket.io, and persisted the data to a feature service. The second used ASP.NET MVC and SignalR, and stored the data points in a simple SQL table with x,y values.
...we will show a simple node.js app that uses socket.io to send notifications between clients and the server, as well as sending the point locations to an ArcGIS Server feature service. The desktop client uses the Esri Javascript API, and the mobile version uses Leaflet.js. Both consume ArcGIS Server Map Services.


Чтобы понять в чем фишка, откройте аппликуху в двух разных браузерах и в одном из них добавляйте на карту значки. Вариант на СигналР.

Здорово. Некоторые потребители будут кипятком сцать, ведь раньше они могли такой сервис получать только на тяжелых десктопах. А тут нате, рука на пульсе 24х7 через любой смарт/планшет, был бы доступ в Интернет (почти стихи).

2014-01-08

CoffeeScript

Веб разработчики знают, что такое CoffeeScript, а другим достаточно такого объяснения: это JavaScript с изрядным количеством синтаксического сахара. Многие яваскриптовые конструкции пишутся и, что важнее, читаются легче на кофескрипт. Кто хочет знать больше — велкам http://jashkenas.github.io/coffee-script/

Я же хочу поделиться практическим опытом применения CoffeeScript, на примере переписывания одного из модулей одного из проектов. Проект завязан на dojo, поэтому модуль — суть модуль dojo, что доставляет (одни только скобочки типа «)}))» после блока в сотню строк стоят отдельной песни).

Начну с начала. Основное рабочее место — MS Windows 2003 (да, у меня есть планы переползти на один из юниксов, но пока руки не доходят, не все сразу). Поэтому мне нужен Node.js такой: http://nodejs.org/dist/v0.10.24/node-v0.10.24-x86.msi
Собственно, его и предлагает по умолчанию страница http://nodejs.org/
Качаю, ставлю — easy-peasy. При установке выбрал папку d:\node

Внутри этой папки запускаю cmd.exe, получаю типа консоль.
В консоли запускаю установку кофескрипта
npm install -g coffee-script
Тоже не рокет сайенс.

Внутри рабочего проекта создаю файл build.coffee.cmd с содержимым:
set wd=%~dp0
pushd "%wd%"
coffee -w -c -o c:\mapedit\javascript\vs\obj\ c:\mapedit\javascript\vs\
Его задача — запустить вечный цикл, в котором проверяется состояние файлов *.coffee в папке c:\mapedit\javascript\vs\ и как только любой из них изменился, компилять его, создавая одноименный файл (но с расширением js) в папке c:\mapedit\javascript\vs\obj\
В качестве бонуса компиляции получаю проверку кода на предмет ошибок. Удобно — сохраняешь файл и тут же в окошке рядом видно — ОК или облом.

Остается немного, заменить в коде (другие файлы проекта) подключение модуля с
require("vs/meapp"], function(meapp) {
    try {vsapp.log('try vsapp');} catch(ex) {vsapp = new meapp();}
    vsapp.log(" vsapp ready");
на
require(["vs/obj/meapp"], function(meapp) {
    try {vsapp.log('try vsapp');} catch(ex) {vsapp = new meapp();}
    vsapp.log(" vsapp ready");
и переписать файл vs\meapp.js на аналогичный vs\meapp.coffee

Сейчас вы увидите, чем конкретно coffeescript код лучше кода javascript.

Было (vs\meapp.js):

Стало (vs\meapp.coffee):

А это код JS на выходе компилятора coffee:

Ниже тройной гист с кодом:


Как видно, кроме небольшой экономии на количестве строк, изрядно улучшается читаемость кода за счет применения Python-подобных (Python — the best!) методов — использование отступов вместо скобок (хотя можно продолжать использовать скобки — либерализм), расширенного набора операторов для циклов и условий, и … читайте http://jashkenas.github.io/coffee-script/ там все очень подробно и понятно нарисовано.
А я свою задачу выполнил — показал на примере, как можно переписать dojo-завязанный код на CoffeScript.
Мне понравилось.

Осталось упомянуть про удобство редактирования кода. Тут все не так радужно.
К сожалению, плагин для Eclipse, которому положено ублажать лень разработчика, не рабочий. От слова «совсем». Может быть, когда-нибудь он заработает. Для SciTE поддержки CoffeeScript тоже пока не наблюдается. На сегодня самый удобный способ (под MS Windows) редактирования кофескриптов — это использование Notepad++ с подключенным https://github.com/blakmatrix/CoffeeScript_notepad_UDL
Подключается он проще пареной свеклы: меню Language — Define your language — Import - https://github.com/blakmatrix/CoffeeScript_notepad_UDL/raw/master/CoffeeScript.xml
закрыть окно, перезапустить Нотепад++.


Дополнительно:


original post http://vasnake.blogspot.com/2013/12/coffeescript.html

2014-03-10

Обратно UI

Как справедливо отмечает Роберт Мартин (uncle Bob), тестирование системы через UI тестирует только UI. Если надо тестировать систему — тестируйте через API.
Но пользовательский интерфейс тестировать тоже надо, чем он хуже других подсистем? Многие думают, что он даже важнее всего остального, ведь пользователь взаимодействует именно с UI.

Практикум по тестированию пользовательского интерфейса в браузерах. От Яндекса:

автоматизируем функциональное тестирование веб-интерфейса Яндекс.Почты с помощью Selenium WebDriver и Node.js
…
Помимо Selenium WebDriver существует ещё несколько решений для автоматического тестирования веб-интерфейсов, среди которых Watir, Zombie.js, PhantomJS. Но именно он стал практически стандартом. Во-первых, он имеет хорошую функциональность. А во-вторых, для него есть драйверы подо все распространённые браузеры — в том числе и мобильные — и платформы, чего не скажешь о headless-инструментах (Zombie.js, PhantomJS).

А почему именно Node.js? Потому что все фронтенд-разработчики Яндекс.Почты знают JavaScript, а именно они разрабатывают интерфейс и понимают, где и что в нём меняется от релиза к релизу.

Установка и настройка
...
Первый тест
Для примера, напишем простой тест (test.js):
…





original post http://vasnake.blogspot.com/2014/02/ui.html

2014-03-06

Виртуальная нода

Не знаю, кого как, а лично меня в самый мозжечок поразила вот эта картинка:

На хосте (MS Windows) работает виртмашина VirtualBox и внутри нее работают еще две виртмашины. Ошеломляет такая матрешка.

Конечно, на самом деле все не так загадочно — внутри виртуалбокса крутится Солярка и внутри нее две «зоны» – контейнера виртуализации. Это нормально.

А вообще, пацанам, которые нацелены на промышленное использование облаков, весьма рекомендовано ознакомится со SmartOS:

SmartOS – операционная система, основанная на ядре illumos. Ее основное назначение – быть хост-системой для виртуальных машин. В отличие от большинства операционных систем, SmartOS грузится только с LiveUSB, LiveCD или по сети. При установке системы файлы самой операционной системы не копируются на жесткий диск компьютера. На диске сохраняются только некоторые настройки, а также образы виртуальных машин


Типичные случаи применения SmartOS – системы для ЦОДов, системы для разработки и эксплуатации нагруженного веб-приложения, платформа для создания приватного или публичного облака
…
я решил заранее написать про установку SmartOS и использование Node.js на ней
…
Допустим, вы оказались одним из тех, кому интересно попробовать SmartOS. Как это проще сделать? Я пошел по самому простому пути, который ... позволит воспользоваться готовым образом SmartOS + Node.js. Запускать буду SmartOS в VirtualBox. Понятно, что KVM в виртуалке под VirtualBox не заработает, а остальное – должно заработать на ура



Решено, когда буду делать решение на Node.js, воспользуюсь платформой SmartOS.


original post http://vasnake.blogspot.com/2014/02/blog-post_6836.html

2010-05-26

Node.js

Node.js это фреймворк, или, скорее набор кода, библиотек, позволяющий писать программы на яваскрипте не простые а чУдные. Такие программы обладают параллелизмом основанным на событиях, не на потоках. Асинхронный ввод/вывод, прекрасная поддержка HTTP и прочее. Базируется на гуглевом движке яваскрипта, V8.
Есть люди, которые уже используют его (хотя стабильным назвать ноду язык не повернется) для обработки серверных задач:

Серверная часть, входящие подключения, обслуживаются ТСP-сервером на базе Node.JS, позже, скорее всего, будет добавлена балансировка при помощи HAProxy, так, чтобы запустить несколько экземпляров сервера (по числу ядер), и раскидывать между ними клиентские подключения.

abrdev.com

Кому по душе яваскрипт, это просто подарок. Остальным можно посоветовать
Twisted, к примеру. А вообще это просто прекрасно, когда есть широкий выбор инструментария. Еще бы успевать разбираться со всем этим.

2013-01-23

JavaScript

Оказывается (http://www.tez-tour.com/mem.html?countryId=5732), с напитками и просто водой в Египте серьезные напряги. Воду из крана не то что пить, для чистки зубов и мытья фруктов не рекомендовано использовать. А бутилированная вода только за деньги, даже для All Inclusive. Да и вообще, строго у них там.

Про JavaScript.
Отличная обзорная статья про Яваскрипт
Особенно хороша тем, что плотно наполнена ссылками на разные полезняшки.

Отдельно доставил пост про Рельсы

Люди приуменьшают влияние моды на разработку, но на самом деле не всё так просто. Как говорил Alan Kay (создатель Smalltalk): "Пока есть вещи, развивающиеся быстрее образования, у нас всегда будет поп-культура".

Рельсы — старые и тупые, а Node.js — новый офигеньчик. Но люди ненавидят Node.js из-за чрезмерного «шума». Лично для меня это выглядит забавным, потому что основной шум вокруг Node.js исходит от людей, которые ненавидят Ноду за этот шум.

Меня не удивляют рассказы о том, насколько Нода дерзкая. Когда Рельсы были в таком возрасте, они были не менее дерзкими. На RailsConf 2007 было огромное количество отличных технических докладов, но кроме того там же собралась критическая масса самовлюбленных придурков. Тогда, насколько мог, я старался игнорировать эту самовлюбленность, а теперь советую поступить так же с Нодой. И вот почему


Это перевод вот из этой статьи

Толково и с юмором.

original post http://vasnake.blogspot.com/2013/01/javascript.html

2012-06-22

Javascript FSM


Поскольку мне не подошла готовая библиотека, реализующая подход FSM, я решил делать свой вариант реализации. Простой, ничего лишнего, без блекджека-и-шлюх. В моем варианте реакция на событие определяется однозначно через текущее состояние и само событие. Фактически, строка «currentState_eventName» дает ссылку на функцию для исполнения.

Законченный пример (без HTML кода):
$(document).ready(onDocReady());

function onDocReady() {
 try {
  $(document).stopTime('doWork');
  $(document).everyTime(999, 'doWork', onTimerWork);
 } catch(ex) {
  console.log('Error in function onDocReady: ' + ex.description, true);
 }
} // function onDocReady()


function onTimerWork(i) { // work cycle
 try {
  // app ready?
  if (VSClient.isBusy) return;
  if (VSClient.currentState == '') { 
   VSClient.configure();
   return;
  }

  VSClient.procEvent('timer');

 } catch(ex) {
  $(document).stopTime('doWork');
  VSClient.procEvent('error');
  log('Error in function onTimerWork: ' + ex.description, true);
 }
} // function onTimerWork(i)


function onSelectFile() { // onClick button 'SelectFile...'
 try {
  VSClient.procEvent('selectFile');
 } catch (ex) {
  log('Error in function onSelectFile: ' + ex.description, true);
  VSClient.procEvent('error');
 }
 return true;
} // function onSelectFile()


VSClient = { // Object with app data
 sl: '' // Silverlight object
 , isBusy: false
 , fsmTable: [
  { from: '', event: 'configure', to: 'ready', action: 'configure' }
  , { from: 'ready', event: 'selectFile', to: 'wait4FileName', action: 'selectFile' }
  , { from: 'wait4FileName', event: 'timer', to: 'haveFileName', action: 'getFileName' }
  , { from: '*', event: 'error', to: 'error', action: 'stopOnError' }
 ]
 , currentState: ''
 , fsmArray: {}

 , configure: function() { // on page loaded, init app data
  log('VSClient.configure');
  this.isBusy = false;
  this.sl = $('#silverlightObject')[0].content.vcuSL;
  this.currentState = 'ready';
  for(var n = 0; n < this.fsmTable.length; n++) {
   var itm = this.fsmTable[n];
   this.fsmArray[itm.from + '_' + itm.event] = itm;
  }
  try {
   var res = this.sl.procMessage('test', '');
  } catch (ex) {
   this.sl = '';
   this.currentState = '';
  }
  log('VSClient curr.state: ' + this.currentState);
 } // configure: function(){}
}; // VSClient


VSClient.procEvent = function(evt) {
 var llog = log;
 if (evt == 'timer') llog = function(){;}
 var ind = this.currentState + '_' + evt;
 var itm = this.fsmArray[ind] || this.fsmArray['*_' + evt] || '';
 if (itm == '') {
  llog('VSClient.procEvent, no such transition: ' + ind);
  return;
 }
 llog('VSClient.procEvent, tableIndex: ' + ind);
 this.currentState = itm.to;
 llog('VSClient curr.state: ' + this.currentState);
 if (itm.action == '') {}
 else {
  this[itm.action].call(this);
 }
} // VSClient.procEvent = function(evt)


// { from: 'ready', event: 'selectFile', to: 'wait4FileName', action: this.selectFile }
VSClient.selectFile = function() { // fsm action
 log('VSClient.selectFile');
 var res = this.sl.procMessage('selectFile', '');
 if(res == '' || res.indexOf('fail') == 0) {
  // error
  log('VSClient.selectFile, error: ' + res, true);
 } else {
  // wait for SL, user must pick the file
  $('#inpFileName').val('');
 }
} // VSClient.selectFile = function()


// { from: 'wait4FileName', event: 'timer', to: 'haveFileName', action: this.getFileName }
VSClient.getFileName = function() { // fsm action
// check if user pick the file
 var res = VSClient.sl.procMessage('selectFile', 'getName');
 if(res == '') {
  // file not selected
  this.currentState = 'wait4FileName';
  return;
 }
 log('VSClient.getFileName, res: [' + res + ']');
 log('VSClient curr.state: ' + this.currentState);
 $('#inpFileName').val(res);
} // VSClient.getFileName = function()


// { from: '*', event: 'error', to: 'error', action: this.stopOnError }
VSClient.stopOnError = function() { // fsm action
 log('VSClient.stopOnError');
} // VSClient.stopOnError = function()

По сравнению с тем, что было (нагромождение if-else), как небо и земля. Теперь код понятнее изрядно.


Вообще, это я все терзаю задачу «resumable http chunked file upload». Просто эталонная задача для прокачки скиллов. Вот, к примеру, на хабре попалось:

Если вы когда-либо загружали видеофайл на сайт, то знаете это чувство когда загрузилось 90% и вы случайно обновляете страницу.
В этом учебном руководстве я покажу, как сделать видео загрузчик для сайта, который может возобновить прерванную загрузку, и генерировать обложку после завершения.
...
Чтобы сделать этот загрузчик, сервер должен отслеживать процесс загрузки, что бы была возможность восстановить его при обрыве. Чтобы выполнить эту задачу... с помощью Node.js...
мы будем использовать Socket.io...


Это перевод вот этой статьи. Ужасный перевод, но суть уловить можно. Как совершенно верно заметили в каментах:

Стрёмный перевод.
Но есть два полезных момента в статье: из неё вы узнаете, что Socket.io (посредством WebSocket) умеет передавать двоичные данные, и что файл на клиенте можно разбивать на куски с помощью slice api.

Пойду, попробую решить задачу возобновляемой отгрузки файлов на связке HTML5 FileAPI + Node.js + Socket.io
Надо же проверить, где грабли лежат. А вдруг многогигабайтные файлы невозможно обработать? Или — а нахрена тут Node.js, что, без него нельзя?

2014-12-08

sync and reload

Недавно мне захотелось странного. Чтобы файлы с кодом редактировать на привычном десктопе, а запускать и отлаживать программу на другом хосте, в виртуальной машине. При этом хочется, чтобы изменения, вносимые в код, сразу отражались на работающем приложении.

Приложение — веб-сервис на Node.js, поэтому перезапуск сервиса при изменении в файлах сделать удалось просто. В виртмашине наколдовал:
sudo npm install supervisor -g
supervisor app.js


Осталось разобраться, как автоматом заливать файлы на виртмашину при сохранении того, что я наредактировал в своем любимом текстовом редакторе. Тоже не рокетсаенс. Поскольку в Linux есть inotify, достаточно подобрать один из заточенных под эту систему инструментов. Я выбрал incron.

На десктопе колдунство такое:
su -l
aptitude install incron
nano /etc/incron.allow
valik

su -l valik
incrontab -e
/home/valik/node.uploader/ IN_CREATE,IN_DELETE,IN_CLOSE_WRITE bash /home/valik/scripts/rsync.dev.files.sh

nano rsync.dev.files.sh
#!/usr/bin/env bash
rsync -av /home/valik/node.uploader/ devhost:nodevcu/


Как только в папке /home/valik/node.uploader/ изменится какой-либо файл, incron запускает скрипт rsync.dev.files.sh. Который rsync-ом переписывает свежее в папку devhost:nodevcu/
А там уже супервизор подхватывает эстафету.

Проверил, работает.



original post http://vasnake.blogspot.com/2014/12/sync-and-reload.html

2014-01-15

Коллбеки императивны

Callbacks are imperative, promises are functional: Node’s biggest missed opportunity

Функциональное программирование, JavaScript, Node.JS, callbacks, promises и всё такое. Статья для промывания мозгов:

Если вы когда-либо слышали фразу "программируемая точка с запятой", то вы понимаете, о чём я говорю.
И в лучшем проявлении, функциональное программирование декларативно. В императивном программировании, мы пишем последовательности инструкций, которые сообщают машине, как сделать то, что мы хотим. В функциональом программировании мы описываем зависимости между значениями, которые сообщают машине, что мы хотим вычислить, и машина предлагает последовательности команд, необходимых для этого вычисления.
…
Имея такое определение, я хочу пояснить, что, по моему мнению, является основной ошибкой дизайна, внесённой Node.JS: решение, сделаное в самом начале его развития: решение использовать API на основе коллбеков, вместо "обещаний" (promises).
…
Я надеюсь развеять недопонимание, будто "обещания" это что то, что даёт более аккуратный синтаксис для основанной на коллбеках асинхронной работы. На самом деле, "обещания" позволяют моделировать вашу проблему фундаментально иным способом; они глубже, чем синтаксис, и влияют на то, как вы решаете проблемы на уровне семантики.
…
Эти null-значения, возвращаемые функциями, использующими коллбеки и есть причина того, что программирование с коллбеками сложное: такие функции ничего не возвращают, из за чего их сложно объединять. Функция, которая ничего не возвращает, вызывается только ради её сайд-эффекта, т.к. функция без сайд-эффекта и без возвращаемого значения - это просто чёрная дыра. Так что, программирование с коллбеками императивно по своей сути, речь идёт об упорядочивании выполнения полных сайд-эффектов процедур, вместо отображения входящих данных в выходящие посредством применения функций

Остальное тут:

Кто-то может спросить: зачем это вообще нужно? Обещания какие-то...
Ответ, по моему, достаточно очевиден: продемонстрированные в статье преимущества функционального подхода позволяют писать более лаконичный и простой для понимания код. Следовательно, программист может кодировать решения более сложных проблем с меньшим количеством ошибок.
Эффективность повышается, если использовать «еще более лучшие» инструменты.



original post http://vasnake.blogspot.com/2013/12/blog-post_5832.html

2013-03-06

Meteor runs on Node.js

Node.js продолжает набирать популярность. Нас, гиков, хлебом не корми а дай новую необычную игрушку попробовать. А уж если она многообещающая, то можно не только хлеба не давать а и водой не поить :)

Meteor is a very powerful Javascript framework: this short article demonstrate how to install, configure and create your first application using "Meteor"
…
Meteor uses MongoDB, through Javascript API called "collection"
…
Basically, file are structured in three folders:
client
server
public.
All files in client directory are served only to clients (server doesn't know about them), files in public are resources most of the time, i.e. images. Server directory serves data to server. Everything outside those folders is shared between clients and server (it would be a good place to put scripts or templates to run on both, server and client


Очень заманчивый набор фич у Метеора:
прозрачный обмен данными с БД и обновление страниц; компенсация задержек в канале связи; доставка в браузеры новых версий кода и проч.

По теме

Простенькая геоприкладуха, показывает иконки на карте для тех, кто не побоялся включить в своем браузере определение координат


original post http://vasnake.blogspot.com/2013/03/meteor-runs-on-nodejs.html

2013-05-31

Tornado + Socket.IO

Было дело, я опробовал комбо Node.JS + Socket.IO на своей любимой задаче — аплоад файлов гигабайтных размеров. Как выяснилось, технология вполне рабочая. Но я не очень люблю Javascript. Во всяком случае, Python я люблю гораздо больше, поэтому я не стал делать полноценный аплоадер файлов на JS.

А недавно мне на глаза попалась подборка материалов про Tornado, где первым пунктом идет ссылка

TornadIO2 = Tornado + Socket.IO / Хабрахабр - http://habrahabr.ru/post/133350/


То есть, вполне можно переписать вышеупомянутый пример аплоадера целиком на Python. Кроме клиентской части, что в браузере должна работать.

Upload task / Tornado + Socket.IO = Vuploader.http

Только руки не скоро до этой задачки дойдут, жаль.

original post http://vasnake.blogspot.com/2013/05/tornado-socketio.html

2014-11-18

Квантовые эффекты

Я тут на днях пересобрал одну из версий VCU (Vasnake web Chunked Uploader), ту, которая на HTML5 FileAPI + Node.js + Socket.io.
Обнаружил занятное.

Во первых, от размера чанка скорость передачи файла почти не зависит. Колеблется в диапазоне 5-7 мегабайт в секунду. Свободных ресурсов при этом, хоть залейся.
Во вторых, Chrome browser дает трансфер процентов на 10 больше чем чем Firefox.

И в третьих, самое интересное. Если открыть отладочную консоль (F12) браузера, скорость передачи падает почти в два раза. А Хром еще и крашится с грохотом, если размер чанка больше десяти мегабайт.






original post http://vasnake.blogspot.com/2014/11/blog-post_18.html

2014-12-29

Do you actually need any specialized mapping server at all?

Интересный вопрос — а нужен ли для вашего проекта картографический сервер?
Смысл в том, что создавая некий продукт/сервис, вам нужны пространственные данные/картинки. В такой ситуации многие сразу фигачат какой-нибудь ArcGIS Server или MapServer или еще какой гео-сервер. И присобачивают его к своему проекту. На так ли нужен этот гео-сервер? Может достаточно чего попроще?

As I embark on another Leaflet/HTML/GeoJSON/D3 implementation for a user, I can think of about ten projects that I’ve done over the last dozen or so years that I would love to revisit with this technology mix. These projects shared some characteristics with my current project which made me go down this route this time around.

Product-based workflow – These projects have typically been centered around the delivery of value-added analytical product. Often, the products are only valid for the version of the source data used at the time. Changes to the underlying source data would warrant a re-validation of the analytical product. In other words, “configuration management.”
Frequent, but not constant, product updates – Analytical products are typically generated on a schedule that has an interval that is measured in at least a few days, but usually more. Referring back to item 1, this is usually because the source data isn’t updated frequently enough to warrant constant attention to the product.
Version history – Products often need to be retained for a long period of time, even when superseded. In many of the shops I’ve supported, the previous product serves as the starting point for the next version, so maintaining the history is important to understand the context of the current version of a product.
…




И вот вам пример без гео-сервера

This post describes the construction of a simple, lightweight geospatial data service using Node.JS, PostGIS and Amazon RDS. It is somewhat lengthy and includes a number of code snippets. The post is primarily targeted at users who may be interested in alternative strategies for publishing geospatial data but may not be familiar with the tools discussed here




original post http://vasnake.blogspot.com/2014/12/do-you-actually-need-any-specialized.html

2014-06-24

C10K

Случайно наткнулся на замечательную статью, в которой содержится обзор и оценка разных подходов к вопросу — а как нам обеспечить 10 000 одновременных соединений (сетевых сессий) на одной машине:

Читал, не мог оторваться, просто как детектив какой-то. Масса информации.

А вышел я на эту статью разглядывая вот эти слайды:
Заголовок провокационный, да. Вообще, презентация способна вызвать немало баттхерта у незрелых личностей. Сначала речь идет о реальной асинхронности ввода/вывода в противовес имитируемой через event loop. Потом делается плавный переход от пула реально асинхронных потоков операционной системы (MS Windows) к обработчикам сетевых протоколов, написанных на Python (Python/C API). Изящно.

А чтобы обеспечить thread-safety и не упираться в GIL, PyParallels делает ход конем: проверяет – главный поток выполняется или нет и если нет — работает в обход GIL, перехватывая thread-sensitive вызовы ядра Python.

Побочным неприятным эффектом изящности их решения можно назвать то, что в параллельных контекстах (код, выполняемых в параллельных потоках) память не освобождается. Поэтому они обязаны быть нежадными и короткими.

А есть видео, где автор показывает слайды:

During the fall of 2012, a heated technical discussion regarding asynchronous programming occurred on python-ideas. One of the outcomes of this discussion was Tulip, an asynchronous programming API for Python 3.3, spearheaded by Guido van Rossum. A lesser known outcome was PyParallel: a set of modifications to the CPython interpreter that allows Python code to execute concurrently across multiple cores.
Twisted, Tulip, Gevent, Stackless/greenlets and even node.js are all variations on the same pattern for achieving "asynchronous I/O": non-blocking I/O performed on a single thread. Each framework provides extensive mechanisms for encapsulating computational work via deferreds, coroutines, generators and yield from clauses that can be executed in the future when a file descriptor is ready for reading or writing.
What I found troubling with all these solutions is that so much effort was being invested to encapsulate future computation (to be executed when a file descriptor is ready for reading or writing), without consideration of the fact that execution is still limited to a single core.
PyParallel approaches the problem in a fundamentally different way. Developers will still write code in such a way that they're encapsulating future computation via the provided APIs, however, thanks to some novel CPython interpreter modifications, such code can be run concurrently across all available cores.


PyParallel - How We Removed the GIL and Exploited All Cores (Without Needing to Remove the GIL at all) - Trent Nelson from PyData on Vimeo.


Несколько слайдов, чиста для иллюстрации темы:





original post http://vasnake.blogspot.com/2014/06/c10k.html

2013-07-25

Docker

Контейнерная виртуализация — это очень эффективно. Проблема только одна, внутри контейнера можно пользоваться только теми системными API, которые предоставляет операционная система хоста.

был анонсирован новый интересный Open Source-проект в области легковесной виртуализации для операционной системы GNU/Linux — Docker.
...
Продукт позиционируется как фундамент для создания автоматизированных распределённых систем, таких как масштабируемые инсталляции с веб-инфраструктурой, кластерные инсталляции баз данных или частные PaaS-решения. Исходный код Docker написан на языке программирования Go, распространяется на условиях свободной лицензии Apache License v2 и опубликован на GitHub.


Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
...
Docker is an open-source implementation of the deployment engine which powers dotCloud, a popular Platform-as-a-Service.


Docker is an open-source project to easily create lightweight, portable, self-sufficient containers from any application. The same container that a developer builds and tests on a laptop can run at scale, in production, on VMs, bare metal, OpenStack clusters, public clouds and more.


Контейнерная виртуализация противопоставляется виртуальным машинам — контейнерам наглухо изолированным от хоста и тяжелым. Но и тут есть интересные компромиссы

Для начала расскажу, для чего нужна SmartOS. Если коротко, её предназначение – быть хост-системой для виртуальных машин. Она часто используется как основа для публичных и частных облаков, например, облачных служб Joyent и MITAC. Службой Joyent пользуется LinkedIn: вся его мобильная серверная часть сделана на Node.js, который запущен в облаке Joyent....
Типичные случаи применения SmartOS – системы для ЦОДов, системы для разработки и эксплуатации нагруженного веб-приложения, платформа для создания приватного или публичного облака.
...
В этой статье я рассматриваю только вариант с гостевыми системами под управлением SmartOS. В них реализуется легковесная виртуализация (то же самое, что зоны в Solaris и нечто похожее на клетки во FreeBSD), и, стало быть, для их работы не требуется KVM, который поддерживается только на физических компьютерах.



Даешь контейнеров виртуализации, хороших и разных. И побольше, побольше.

original post http://vasnake.blogspot.com/2013/07/docker.html

Архив блога

Ярлыки

linux (241) python (191) citation (186) web-develop (170) gov.ru (159) video (124) бытовуха (115) sysadm (100) GIS (97) Zope(Plone) (88) бурчалки (84) Book (83) programming (82) грабли (77) Fun (76) development (73) windsurfing (72) Microsoft (64) hiload (62) internet provider (57) opensource (57) security (57) опыт (55) movie (52) Wisdom (51) ML (47) driving (45) hardware (45) language (45) money (42) JS (41) curse (40) bigdata (39) DBMS (38) ArcGIS (34) history (31) PDA (30) howto (30) holyday (29) Google (27) Oracle (27) tourism (27) virtbox (27) health (26) vacation (24) AI (23) Autodesk (23) SQL (23) humor (23) Java (22) knowledge (22) translate (20) CSS (19) cheatsheet (19) hack (19) Apache (16) Klaipeda (15) Manager (15) web-browser (15) Никонов (15) functional programming (14) happiness (14) music (14) todo (14) PHP (13) course (13) scala (13) weapon (13) HTTP. Apache (12) SSH (12) frameworks (12) hero (12) im (12) settings (12) HTML (11) SciTE (11) USA (11) crypto (11) game (11) map (11) HTTPD (9) ODF (9) Photo (9) купи/продай (9) benchmark (8) documentation (8) 3D (7) CS (7) DNS (7) NoSQL (7) cloud (7) django (7) gun (7) matroska (7) telephony (7) Microsoft Office (6) VCS (6) bluetooth (6) pidgin (6) proxy (6) Donald Knuth (5) ETL (5) NVIDIA (5) Palanga (5) REST (5) bash (5) flash (5) keyboard (5) price (5) samba (5) CGI (4) LISP (4) RoR (4) cache (4) car (4) display (4) holywar (4) nginx (4) pistol (4) spark (4) xml (4) Лебедев (4) IDE (3) IE8 (3) J2EE (3) NTFS (3) RDP (3) holiday (3) mount (3) Гоблин (3) кухня (3) урюк (3) AMQP (2) Baltic (2) ERP (2) IE7 (2) NAS (2) Naudoc (2) PDF (2) address (2) air (2) british (2) coffee (2) fitness (2) font (2) ftp (2) fuckup (2) messaging (2) notify (2) sharepoint (2) ssl/tls (2) stardict (2) tests (2) tunnel (2) udev (2) APT (1) CRUD (1) Canyonlands (1) Cyprus (1) DVDShrink (1) Jabber (1) K9Copy (1) Matlab (1) Portugal (1) VBA (1) WD My Book (1) autoit (1) bike (1) cannabis (1) chat (1) concurrent (1) dbf (1) ext4 (1) idioten (1) join (1) krusader (1) license (1) life (1) migration (1) mindmap (1) navitel (1) pneumatic weapon (1) quiz (1) regexp (1) robot (1) science (1) seaside (1) serialization (1) shore (1) spatial (1) tie (1) vim (1) Науру (1) крысы (1) налоги (1) пианино (1)