Skip to content

Задерживающий прокси а-ля xkcd

25/02/2011

Неделю назад в замечательном комиксе xkcd была упомянута интересная тема: прокси, задерживающий запросы к определённым сайтам на 30 секунд, как средство борьбы с прокрастинацией. Естественно, мне захотелось реализовать такую штуку но время для этого нашлось только сейчас.

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

Сделать обычный прокси в Node.js проще простого:

var http = require('http');

http.createServer(function(request, response) {

  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.on('response', function (proxy_response) {
    proxy_response.pipe(response);
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });

  request.pipe(proxy_request);
}).listen(8080);

Здесь я применяю метод pipe, позволяющий соединять два потока. Сначала мы соединяем запрос от пользователя с запросом к удалённому сайту, потом соединяем ответ от сайта с ответом нашему пользователю. Весь прокси-сервер укладывается в несколько строк. Здесь не учтены многие вещи, котоорые должен уметь прокси-сервер — например, перенаправление HTTPS-запросов — но для моих целей хватит и такого.

Задержка для определённых сайтов

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

var oc = function(a) {
  var o = {};
  for(var i=0;i<a.length;i++) {
    o[a[i]]='';
  }
  return o;
}

var delayed_sites_raw = fs.readFileSync('timekillers', 'ascii');

var delayed_sites = oc(delayed_sites_raw.split("\n"));

Функция oc (object converter) преобразует элементы массива в ключи объекта, чтобы можно было делать if ('domain' in delayed_sites) {.

Сами потоки можно задержать несколькими способами. Вообще у потока можно вызвать stream.pause (это остановит генерацию событий, но данные будут накапливатся в буфере) и потом продолжить обработку вызовом stream.resume. Я решил приостанавливать ответ от удалённого сервера — чтобы когда я вызову stream.resume ответ уже был получен и сохранён в буфере.

В результате получился вот такой код:

var oc = function(a) {
  var o = {};
  for(var i=0;i<a.length;i++) {
    o[a[i]]='';
  }
  return o;
}

var http = require('http'),
    url = require('url'),
    fs = require('fs');

var delayed_sites_raw = fs.readFileSync('timekillers', 'ascii');

var delayed_sites = oc(delayed_sites_raw.split("\n"));

console.log('Delayed sites:' + delayed_sites);

http.createServer(function(request, response) {
  var delay = 0;

  if (host in delayed_sites) {
    delay = 30000;
  }

  var proxy = http.createClient(80, request.headers['host'])
  var proxy_request = proxy.request(request.method, request.url, request.headers);
  proxy_request.on('response', function (proxy_response) {
    proxy_response.pause();
    setTimeout(function() {
        proxy_response.resume();
    }, delay);
    proxy_response.pipe(response);
    response.writeHead(proxy_response.statusCode, proxy_response.headers);
  });

  request.pipe(proxy_request);
}).listen(8080);

Для тестов я использовал Firefox. Список доменов читается из файла timekillers, в котором они просто записаны по одному домену на строку:

www.reddit.com
twitter.com
news.ycombinator.com
habrahabr.ru
www.tumblr.com

Если через такой прокси открыть например Яндекс, он откроется сразу же. Но если попытаться зайти на Reddit, ответ от прокси придёт только через 30 секунд (это хорошо видно в консоли FireBug).

Ссылки по теме

3 комментария
  1. limper permalink

    Спасибо! Да, действительно с http прокси никаких проблем.
    А как дела обстоят с https прокси, чтоб не просто https over http, а полноценно, с подменой сертификатов?

    • Подменой сертификатов честно говоря не занимался )

      • limper permalink

        Да, наверное, подмена слишком громко сказано)), скорее генерация самоподписанных сертов на лету. Нашел подобный проект на питоне h**ps://github.com/cortesi/mitmproxy, если онтересно

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: