Archive

Archive for the ‘Программирование’ Category

jQuery: Отмена активных ajax запросов

October 27th, 2011 3 comments

Иногда требуется отменить все активные XHR, либо какую-то часть из них. Для этой нужны я накидал плагин  для jQuery, которые автоматически добавляет все XHR в коллекцию с возможностью указать тег.

 

(function($) {
	$.xhrPool = {};
 
	$.xhrPool.abortAll = function() {
		$.each(this, function(pool, map) {
			$.each(map, function(idx, jqXHR) {
				jqXHR.abort();
				$.xhrPool[pool].splice(idx, 1);
			});
		});
	};
 
	$.xhrPool.abort = function(pool) {
		if (typeof this[pool] != "undefined") {
			$.each(this[pool], function(idx, jqXHR) {
				try {jqXHR.abort();} catch (e) {}
				$.xhrPool[pool].splice(idx, 1);
			});
		}
	};
 
	$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
		var pool = options.xhrpool || "global";
 
		if ( ! $.xhrPool[pool]) {
			$.xhrPool[pool] = [];
		}
 
		$.xhrPool[pool].push(jqXHR);
	});
})(jQuery)

Тег указывается в вызове $.ajax() опцией xhrpool. Без указания этой опции все XHR будут добавляться в $.xhrPool.global. Также доступны 2 метода: $.xhrPool.abortAll() — останавливает все запросы и $.xhrPool.abort(“%pool%”) — останавливает запросы в коллекции с указанным тегом.

Отправка большой формы через Ajax

October 11th, 2011 No comments

Любой кто хотя-бы раз отправить большую форму через Ajax – сталкивался с проблемой, когда в функции отправки приходилось перечислять заново все поля.

Решение проблемы достаточно просто: нам нужна функция которая сама разберет форму.

Первый вариант который приходит в голову:

function parseForm(identifier){
  // Получаем все инпуты содержащиеся в форме
  var inputs = document.getElementById(identifier).getElementsByTagName('input');
  // Инициализируем массив для результатов
  var values = {};
  // Перебираем элементы
  for (var n in inputs){
    values[inputs.n.getAttribute('name')] = inputs.n.getAttribute('value');
  }
}

Но тут есть проблема, в частности с input’ами типа checkbox и radio.

Немного модифицируем функцию что-бы корректно обрабатывать их.

function parseForm(identifier){
  // Получаем все инпуты содержащиеся в форме
  var inputs = document.getElementById(identifier).getElementsByTagName('input');
  // Инициализируем массив для результатов
  var values = {};
  // Перебираем элементы
  for (var n in inputs){
    if(inputs[n].getAttribute('type') == "checkbox"){
      // Если тип - checkbox, то получаем значение checked
      values[inputs[n].getAttribute('name')] = inputs[n].getAttribute('checked');
    }else if(inputs[n].getAttribute('type') == "radio"){
      // Если тип - radio, получаем список всех input с этим именем
      var radios = document.getElementById(identifier).getElementsByName(inputs[n].getAttribute('name'));
      // Перебираем их
      for (var i in radios){
        // Если текущий элемент выделен то ложим в результат его значение
        if(radios[i].getAttribute('checked')){
          values[inputs[n].getAttribute('name')] = radios[i].getAttribute('value');
        }
      }
    }else{
      values[inputs[n].getAttribute('name')] = inputs[n].getAttribute('value');
    }
  }
 
  return values;
}

Вот казалось бы и все, но мы еще забыли про select.
Для него придется добавить в функцию еще часть:

var selects = document.getElementById(identifier).getElementsByTagName("select");
 
for(s in selects){
    var options = selects[s].getElementsByTagName("option");
    // Перебираем их
    for (var i in options){
      // Если текущий элемент выделен то ложим в результат его значение
      if(options[i].getAttribute('selected')){
        values[selects[n].getAttribute('name')] = options[i].getAttribute('value');
      }
    }
}

Реализация этого кода на jQuery останется домашним заданием 🙂

Кроссдоменный AJAX

September 24th, 2011 No comments

Самое простой способ — использование объекта XMLHTTPRequest в современных браузерах(Chrome 6+, Firefox 3.5+, Internet Explorer 8+, Safari 4+) и наличие у сервера заголовка Access-Control-Allow-Origin: *

Все бы хорошо, вот только заметили ли вы в списке Оперу? Нет, она еще не поддерживает XMLHTTPRequest 2. Да и то что IE не поддерживает в 7-ой версии может оказаться для кого-то критичным. Ниже последуют другие способы кроссдоменного ajax’a.

Проксирование

Довольно популярный метод, представляет из себя отправку запроса на серверный скрипт, который получает целевой url и возвращает его в ответе на запрос. Привязка к серверному скрипту делает этот метод недопустимым в некоторых случаях. Да и как минус можно задержку отметить.

 JSONP

JSON Padding или так называемый “JSON с подкладкой” — способ с JSON при котором имя калбэка указывается в аргументах запроса. При этом запрос подгружается через тег script, а в ответе json-объект должен быть заключен в вызов того самого переданного калбэка. Пример:

запрос http://example.com/?q=somevalue&callback=mycallback
ответ  mycallback({"foo": "bar", "a": "x"})

Из минусов возможность использовать только GET-запрос.

Политика безопасности также позволяет загружать css’ники с любых доменов, поэтому аналогично JSONP появился метод CSSHttpRequest. Особой выгоды в нем не вижу, кому интересно могут глянуть на странице проекта.

XHRIframeProxy

Метод основан на том, что ифреймы находясь на разных доменах могут общаться через url hash.

Для использования необходимо 2 ифрейма, первый – клиентский, с тем же url, что и основное окно. Второй – внутри клиентского, с серверным url. Данные отправляем через форму с указанным target=имя фрейма. Затем запускаем ф-цию с интервалом в 1 секунду и ждем пока хэш страницы не изменится – тогда мы получили ответ от сервера. Идентификатор изменяется серверным скриптом через self.parent.location = ‘…’

Стоит иметь ввиду, что в Internet Explorer есть ограничение на кол-во символов в url и составляет  2048 символов. Так что при передачи большого кол-ва данных придется делить на различные запросы. Кроме того необходимо в серверный фрейм передавать url основного окна – серверный фрейм его узнать не сможет.

При использовании https  этот способ корректно работать не будет.

postMessage (HTML 5)

В спецификации HTML 5 был введен метод

otherWindow.postMessage(message, targetOrigin);

в котором otherWindow – ссылка на другой объект window (например ифрейм на этой странице), message – данные которые должны послать, targetOrigin – домен на которой должны отослать данные, либо “*” – для всех доменов. targetOrigin должен соответствовать url объекта window на который посылаются данные.

В окне-приемнике должен быть зарегистрирован обработчик onmessage чтобы получить сообщение. Пример:

if (window.addEventListener){ 
    window.addEventListener(“message”, listener,false);
} else {
    window.attachEvent(“onmessage”, listener);
}

Ф-ция listener получается один параметр – объект event, по свойству origin которого можно узнать домен, с которого отправляли данные.

Работает postMessage в FF3+, IE8+ , Chrome, Safari 5, Opera10+. Стоит отметить что в IE8 нельзя общаться через окна/табы.

Особенности переноса < script >.

September 18th, 2011 No comments

Если перед вами встанет задача изменить положение контейнера (DIV’а, SPAN’а, etc), содержащего внутри себя тег SCRIPT, при помощи JavaScript то вы столкнетесь с такой проблемой: после переноса все содержимое страницы будет заменено на результат работы содержимого этого SCRIPT (в независимости от того подключает ли этот тег внешний скрипт или содержит внутренний).

Есть простое решение, этот скрипт уже отработал и больше запускаться не должен можно просто удалить его перед переносом, например вот так:

var scripts = obj.getElementsByTagName("script");
for (var n in scripts){
  obj.removeChild(scripts[n]);
}
// И уже чистый элемент
target.appendChild(obj);

То же самое на jQuery:

$(obj).find("script").remove();
// И уже чистый элемент
$(obj).appendTo(target);

Если же вы хотите что бы скрипты остались в рабочем состоянии то придется немного извратится:

var scripts = obj.getElementsByTagName("script");
var scripts_src_backup = Array();
var scripts_content_backup = Array();
for (var n in scripts){
  if(scripts[n].src !== undefined){
    scripts_src_backup.push(scripts[n].src);
  }else{
    scripts_content_backup.push(scripts[n].innerHTML);
  }
  obj.removeChild(scripts[n]);
}
// Переносим чистый элемент
target.appendChild(obj);
// Возвращаем скрипты на место
for (var n in scripts_src_backup){
  var new = document.createElement("script");
  new.src = scripts_src_backup[n];
  obj.appendChild(new);
}
delete(scripts_src_backup);
for (var n in scripts_content_backup){
  var new = document.createElement("script");
  new.innerHTML = scripts_content_backup[n];
  obj.appendChild(new);
}
delete(scripts_content_backup);

То же на jQuery:

var scripts_src_backup = Array();
var scripts_content_backup = Array();
$(obj).find("script").each(function(index, element){
  if(element.attr("src") !== undefined){
    scripts_src_backup.push(element.attr("src"));
  }else{
    scripts_content_backup.push(element.html());
  }
}).remove();
 
// Переносим чистый элемент
$(obj).appendTo(target);
 
// Возвращаем скрипты на место
for (var n in scripts_src_backup){
  $('< script type="text/javascript" ></ script >').attr("src", scripts_src_backup[n]).appendTo(obj);
}
delete(scripts_src_backup);
for (var n in scripts_content_backup){
  $('< script type="text/javascript" ></ script >').html(scripts_content_backup[n]).appendTo(obj);
}
delete(scripts_content_backup);

Таким образом элемент нормально переместился и скрипты остались рабочими.

2 слова о серверах

September 16th, 2011 No comments

Сегодня я хочу поделиться технологией создания масштабируемого сервера на основе linux pthreads и epoll.

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

 

Теперь подробнее:

создаём слушающий сокет, причём настоятельно рекомендую использовать опции SO_REUSEADDR и TCP_NODELAY. Да, ещё рекомендую явно указать линжер в нули.

После этого – время создавать потоки. Каждому создаваемому потоку – передаём параметром сокет созданный ранее.

 

pthread_create(&th[t_cnt], &ta,(void*(*)(void *)) server_thread,(void *)&sock)

server_thread – это указатель на функцию потока
sock – это и есть указатель на серверный сокет созданный в начале.

 

Вот как-то так.
Теперь самое главное – функция потока обрабатывающая соединения:

 
void *server_thread(void *fd) {
int *sock = (int *)fd;
int localSock = *sock;

Так преобразовываем поинтер в сокет. Ну, на всякий случай, если кто не знает.

 

struct epoll_event ev, *events;
ep_fd = epoll_create(num_sockets_per_sender);
events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * epoll_room_in_receiver);
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLET;
ev.data.fd = localSock;
epoll_ctl(ep_fd, EPOLL_CTL_ADD,localSock, &ev);

 

Создаём структуру для еполла. Вообще принцип epoll – прост. В структуру записываются сокеты, на которых отслеживаются изменения. Т.е. если в какой-то сокет прошла запись клиентом – ядро (epoll обслуживается ядром Linux) – подаёт сигнал, который вытаскивает программу из ожидания в функции epoll_wait.

Вот как-то так:

while (1) {
int num_fds = epoll_wait(ep_fd, events, num_sockets_per_sender, -1);
.........
};

 

Сиё работает крайне просто, и является абсолютно thread-safe.
Все потоки которые вы создали, заваливаются на epoll_wait, и ждут когда в ep_fd появятся изменения – когда подключится новый клиент, или напишет чего-нибудь кто-то из уже подключенных клиентов.

Ну а дальше – совсем просто. Как только что-то появляется (в количестве в пределах 3 параметра функции epoll_wait) – функция возвращает количество евентов, ну и структуру с сокетами в которых произошли эвенты, и описания происшедших эвентов.

Т.е. например:

if (events.events & (EPOLLHUP | EPOLLERR)) {
fputs("epoll: EPOLLERR", stderr);
close(events.data.fd);
continue;
};

 

В общем-то просто и понятно.
В этом цикле и должна происходить обработка соединений – чтение, запись, закрытие сокетов.

Самое важное в этом процессе – добавлять новые сокеты в структуру epoll.

 

if (events.data.fd == localSock) {
struct sockaddr remote_addr;
socklen_t addr_size = sizeof(remote_addr);
 
int connection = accept(localSock, &remote_addr, &addr_size);
if (connection == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
serr("accept");
}
continue;
};
fcntl(connection, F_SETFL,O_NONBLOCK | fcntl(connection, F_GETFL, 0));
ev.data.fd = connection;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, connection, &ev);
continue;
};

 

Идея проста – если событие произошло на серверном сокете – это означает только одно – появился новый клиент. Т.е. надо принимать коннект и добавлять в структуру epoll для дальнейшего мониторинга.

Ну и двигаясь к концу – пара общих советов, которые вы и так наверняка знаете:
При работе с еполлом – используйте не блокирующиеся сокеты. Это показано выше, но обращу отдельное внимание.

fcntl(connection, F_SETFL,O_NONBLOCK | fcntl(connection, F_GETFL, 0));

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

Мутексы. Мутексы это основа многопоточности. Однако, злоупотребляя мутексами – можно свести на ноль все преимущества многопоточной системы.

Да, два слова о производительности:
Сервер, кусочки кода которого я тут приводил, продемонстрировал примерно вот такие результаты: при 900 обращениях в секунду, полная обработка 1 соединения занимает примерно 27 миллисекунд. Со всей математикой, регекспами, обсчётами, массивами и даже поиском по огромным бинарным деревьям.
Сервер – Dual Quad Core Xeon 2.4Ghz, x64 Debian, 24GB ram.

Из дополнительных параметров компиляции – только -msse3 (только если у вас современный Intel), -O3 (общий набор оптимизации компилятора g++).