Модераторы: Snowy, Poseidon, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Как реализовать "ретранслятор" запросов через сокеты? 
:(
    Опции темы
kanaris
Дата 4.12.2009, 16:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



Задача такова. Хочу написать программу, которая посредством TClientSocket и TServerSocket будет пропускать через себя запрос и ответ таким образом:

1) программа висит на каком-нибудь порту (например 888) в режиме сервера (принимает запросы);
2) пользователь в браузере заходит на адрес http://localhost:888/ (т.е. браузер посылает запрос в программу);
3) программа получила запрос от браузера, и отправила его на сайт (например htmlbook.ru), поменяв в запросе строчку "Host: localhost:888" на "Host: htmlbook.ru";
4) дождавшись ответа от htmlbook.ru, программа в точности передает его в браузер.

Т.е. получается, что пишешь в браузере http://localhost:888 и тебе открывается сайт htmlbook.ru. Т.е. выполняется "ретрансляция" запроса. Должно поддерживаться несколько соединений (т.к. браузер открывает сразу несколько запросов с сервером). Как это реализовать?

(прокси не предлагайте, это не совсем то)
PM MAIL   Вверх
kami
Дата 4.12.2009, 22:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Цитата(kanaris @  4.12.2009,  16:08 Найти цитируемый пост)
Как это реализовать?(прокси не предлагайте, это не совсем то)

Совсем то - это маппер. Примеров куча, на порядок проще прокси. А если не подменять адрес - то вообще делается на пол-пинка smile
PM MAIL WWW   Вверх
kanaris
Дата 5.12.2009, 00:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



Попробовал реализовать через TIdMappedPortTCP. Здесь вообще все просто, выполняется тоже самое. Но как подменить адрес в запросе?
PM MAIL   Вверх
kami
Дата 5.12.2009, 03:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Цитата(kanaris @  5.12.2009,  00:04 Найти цитируемый пост)
Но как подменить адрес в запросе?

Вообще - URL это отдельная история - бывают абсолютные, относительные, с "нестандартным" синтаксисом...
Сделал маппер, удовлетворяющий твоим требованиям (код во вложении), но:
1. При вводе в строку адреса "http://localhost:port" только ПЕРВЫЙ запрос (и ответ, соответственно) идет через маппер, все остальное браузер сам начинает получать с этого URL (тестировалось на примере yandex.ru и rambler.ru). Исключение составляют их короткие адреса - ya.ru и r0.ru, и то - далеко не все соединения браузера идут через маппер.
2. При вводе в строку адреса "http://localhost:port" браузер отправляет ОТНОСИТЕЛЬНЫЙ запрос, который выглядит как "GET / HTTP/1.1.........". Оно и понятно - чего ему возиться с полными адресами, когда localhost - он под боком?

Ну и ... в общем, сам посмотришь.

P.S. Код - под D7, в D2009 работать не будет (гарантия 99%).

Присоединённый файл ( Кол-во скачиваний: 16 )
Присоединённый файл  HTTPMapper.zip 4,40 Kb
PM MAIL WWW   Вверх
kanaris
Дата 7.12.2009, 13:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



kami, большое спасибо за программу (все работает, у меня дельфи 7). Но есть одно но: загружается только главная страница, а остальные не грузятся, пишется Not found.
Попытался проснифать трафик между программой и удаленным сервером, понял в чем проблема:

Запрос главной страницы:
Цитата

GET http://hackersoft.ru HTTP/1.1
Host: localhost:99
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 GTB6
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: spylog_test=1
Cache-Control: max-age=0


- здесь, судя по первой строке, веб-сервер понял, о каком хосте идет речь, поэтому строку "Host: localhost:99" проигнорировал.

Запрос других страниц:

Цитата

GET /css/classic_web20.css HTTP/1.1
Host: localhost:99
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 GTB6
Accept: text/css,*/*;q=0.1
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://hackersoft.ru
Cookie: spylog_test=1
Cache-Control: max-age=0


здесь в адресе хост не указан, а в параметре Host стоит значение localhost:99. Сервер не понял, с какого хоста загружать, поэтому грузил по умолчанию, где естественно такого файла нет, на что он выдал ответ:

Цитата

HTTP/1.1 404 Not Found
Date: Mon, 07 Dec 2009 10:17:38 GMT
Server: Apache
Content-Length: 338
Connection: close
Content-Type: text/html; charset=iso-8859-1


Дело в том, что на одном сервере (91.203.4.180) может находиться несколько хостов. Поэтому, чтобы сервер понял, о каком именно хосте идет речь, нужно указывать это в параметре Host. Правильно было бы отсылать такие заголовки:

Запрос главной страницы:

Цитата

GET / HTTP/1.1 (без http://..., т.е. так и должно остаться, не нужно ничего парсить и заменять)
Host: hackersoft.ru (а здесь надо от начала до конца строки заменить Host: localhost:99 на Host: hackersoft.ru)
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 GTB6
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: spylog_test=1
Cache-Control: max-age=0


Запрос других страниц:

Цитата

GET /css/classic_web20.css HTTP/1.1
Host: hackersoft.ru
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 GTB6
Accept: text/css,*/*;q=0.1
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://hackersoft.ru
Cookie: spylog_test=1
Cache-Control: max-age=0



Насколько я понял, все дело в функции TRemoteConnection.TranslateBuffer. В комментариях написано:

Цитата

  {
  Итак, задача:
  структура такова: запрос (с возможным указанием длины тела), ограничивается #13#10#13#10,
  далее - если была указана длина тела запроса - само тело. Ровно то количество байт,
  которое указано в длине, и дальше без перерыва и разделителей пойдет новый заголовок.
  }


Дальше после этого количества байт не идет новый заголовок, а просто заканчивается транзакция (а остальные страницы загружаются через другие соединения). Размер тела в байтах указывается для того, чтобы на стороне клиента было известно, сколько весит страница (файл), чтобы судить, сколько % скачалось.

Цитата

  // Заменяем "втупую" от появления http:// до первого пробела.
  // на самом деле это неправильно, ибо:
  // 1. строка http:// может содержаться и в теле запроса
  // 2. запрос вообще-то имеет сложную структуру, никогда с ней не разбирался,
  //    а работал только с конкретными случаями.
...
      // скорее всего - это относительный адрес. Как парсить его - хз,
      // посему в этом примере будет заменен только первоначальный запрос,
      // а именно GET / HTTP/1.1


На самом деле все гораздо проще, не нужно ничего парсить, нужно просто поменять строку Host: localhost:99 на Host: (хост сервера) (в заголовках, до #13#10#13#10) и тогда все будет работать. А после тела больше не идет никаких заголовков. Напишите пожалуйста код, как заменить эту строку?

p.s. Извините за глупые вопросы, последний раз я кодил на дельфи в 2006-м году, я уже ничего не помню как что делается. Для меня в дельфи будет сложно даже такие простые вещи, как замена подстроки в строке (я сам работаю в php, там намного все проще).

Это сообщение отредактировал(а) kanaris - 7.12.2009, 14:12
PM MAIL   Вверх
Демо
Дата 7.12.2009, 16:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 3
Всего: 50



kanaris, 

Тебя же kami предупредил уже:

Цитата(kami @  5.12.2009,  03:26 Найти цитируемый пост)
1. При вводе в строку адреса "http://localhost:port" только ПЕРВЫЙ запрос (и ответ, соответственно) идет через маппер, все остальное браузер сам начинает получать с этого URL (тестировалось на примере yandex.ru и rambler.ru). Исключение составляют их короткие адреса - ya.ru и r0.ru, и то - далеко не все соединения браузера идут через маппер.2. При вводе в строку адреса "http://localhost:port" браузер отправляет ОТНОСИТЕЛЬНЫЙ запрос, который выглядит как "GET / HTTP/1.1.........". Оно и понятно - чего ему возиться с полными адресами, когда localhost - он под боком?


Соответственно и получаешь эффекты.

Решение - прокси-сервер.


--------------------
    
PM MAIL ICQ Skype   Вверх
kanaris
Дата 7.12.2009, 19:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



Цитата

Тебя же kami предупредил уже:


я увидел smile

Цитата

Решение - прокси-сервер. 


решение уже почти готово (thx to kami), осталось только заменить одну строчку. Я бы сам поискал заменил, но я не понял, откуда берутся данные, как они меняются, и куда они отправляются. Я только понял, что данные передаются в функцию TRemoteConnection.TranslateBuffer параметром var Buffer: string. Не понял, откуда взялись переменные FCurrentBodyLength, FCurrentHeaderLength, еще не понял что делается с переменной Result.
PM MAIL   Вверх
kami
Дата 7.12.2009, 20:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Достаточно подробный анализ.
Цитата(kanaris @  7.12.2009,  13:53 Найти цитируемый пост)
 Поэтому, чтобы сервер понял, о каком именно хосте идет речь, нужно указывать это в параметре Host.

Понятно. Потому я и сказал - работал только с конкретными случаями, когда знал, что на что нужно подменять.
Цитата(kanaris @  7.12.2009,  13:53 Найти цитируемый пост)
Насколько я понял, все дело в функции TRemoteConnection.TranslateBuffer. 

Совершенно верно. Именно в нее приходит полный заголовок запроса, и после изменения отправляется серверу.

Цитата(kanaris @  7.12.2009,  13:53 Найти цитируемый пост)
На самом деле все гораздо проще, не нужно ничего парсить, нужно просто поменять строку Host: localhost:99 на Host: (хост сервера) (в заголовках, до #13#10#13#10) и тогда все будет работать. 

Вот это как раз не проблема. Только яндексу это до одного места smile
Итак, необходимые изменения
1. Слегка переделываем конструктор TRemoteConnection:
Код

constructor TRemoteConnection.Create(OwnerSocket: TCustomWinSocket; Host:string; Port, OldPort:integer);
...// в остальном без изменений, только добавляем одну строчку
  FOldPort:=OldPort;
...

Не забудь переправить объявление и в секции interface, и внести дополнительное поле FOldPort в его private секцию. Не внесешь - Delphi будет ругаться, так что "пропустить" не получится smile
2. Изменения в 
Код

function TRemoteConnection.TranslateBuffer(var Buffer: string): integer;
var
  i:integer;
  j:integer;
begin
  Result:=0;
  i:=FCurrentBodyLength+FCurrentHeaderLength;
  if i<>0 then
    begin
      // еще не передан текущий заголовок с данными
      Result:=min(i, Length(Buffer));
      exit;
    end;
  // все передано, теперь в буфере у нас начинает накапливаться
  // новый заголовок с данными
  i:=Pos(#13#10#13#10, Buffer);
  if i=0 then
    exit; // заголовок прочтен не полностью
//===================================================================
  // да, всё то что было раньше умещается оказывается в одну строчку. главное вовремя 
  // вспомнить про существование таких функций :)
  Buffer:=StringReplace(Buffer, 'localhost:'+intToStr(FOldPort), FHost, [rfReplaceAll, rfIgnoreCase]);
//===================================================================
  // адрес подменен. Пробуем узнать, нет ли тела запроса
  i := Pos('Content-Length:', Buffer);
  if i <> 0 then
    begin
      j := Min(PosEx(#13, Buffer, i), PosEx(' ', Buffer, i));
      inc(i, Length('Content-Length:'));
      FCurrentBodyLength := StrToInt(AnsiUpperCase(Trim(copy(Buffer, i, j - i))));
    end;

  FCurrentHeaderLength:=Pos(#13#10#13#10, Buffer)+Length(#13#10#13#10)-1;
  // Итак, мы сделали все, что нужно - подменили адрес,
  // узнали длину заголовка и тела
  // можем говорить, сколько данных передавать
  Result:=min(Length(Buffer), FCurrentHeaderLength+FCurrentBodyLength);
end;

3. Изменения в серверную часть:
Код

procedure TMapServer.OnConnect(Sender: TObject; Socket: TCustomWinSocket);
.......
  Redir:=TRemoteConnection.Create(Socket, FRedirectHost, FRedirectPort, FPort);

PM MAIL WWW   Вверх
kami
Дата 7.12.2009, 20:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Цитата(kanaris @  7.12.2009,  19:46 Найти цитируемый пост)
но я не понял, откуда берутся данные,

С "локальной" стороны при активации программы создается серверный сокет (модуль uMapper). Когда браузер инициирует соединение с нашим серверным сокетом в процедуре OnConnect уже мы создаем "внешнее" соединение с указанным хостом перенаправления. В этой же процедуре OnConnect внешнему соединению сообщаются координаты локального (для того, чтобы данные, принимаемые из внешнего соединения можно было бы передавать ему), а сокет локального соединения соответственно знает координаты внешнего (Содержится в TCustomWinSocket.Data).

Цитата(kanaris @  7.12.2009,  19:46 Найти цитируемый пост)
 как они меняются,

Сперва в передающий буфер мы накапливаем заголовок (то, что во время тестирования заголовок приходит за одно чтение еще ничего не значит - читать про разбиение и склейку TCP пакетов). Когда TranslateBuffer определяет, что заголовок (из браузера через серверный сокет в TremoteConnection) поступил полностью, то:
1. подменяется Host: бла-бла-бла на нужное значение
2. определяется, не собирается ли передаваться кроме заголовка еще и данные (Content-length). Размер текущего заголовка и размер его данных записывается в соответствующие переменные. Это делается потому, что заголовок и его данные могут не передаться за один прием внешнему серверу (не зря методы Read и Write сокетов - это функции).
Цитата(kanaris @  7.12.2009,  19:46 Найти цитируемый пост)
и куда они отправляются. 

Объяснил уже в п.1
Цитата(kanaris @  7.12.2009,  19:46 Найти цитируемый пост)
не понял что делается с переменной Result.

Если ты про результат TranslateBuffer, то в нем определяется, сколько байт из буфера на передачу можно отправить во внешнее соединение и соответственно - удалить их из буфера. Удаление из буфера на передачу производится непосредственно при отправке, по фактическому количеству переданного.

Забыл добавить - переменные FCurrentBodyLength, FCurrentHeaderLength определяют, сколько данных из текущего заголовка и сопровождаемых им данных осталось передать. Вторая, и более важная задача - определить момент начала нового заголовка (когда и FCurrentBodyLength и FCurrentHeaderLength станут =0)

Это сообщение отредактировал(а) kami - 7.12.2009, 21:00
PM MAIL WWW   Вверх
kanaris
Дата 7.12.2009, 21:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



Теперь работает. Но, с методом GET все нормально, а вот с POST - проблемы. При отправке POST-запроса, выполнение программы обрывается, выскакивает след. ошибка:

user posted image

user posted image

Например, хост в настройках указать example.com, зайти по ссылке http://localhost:99/relax/tests/ и нажать в тесте "Результаты".
Что это может быть?

Upd: В пост-запросе, помимо заголовков, есть еще и тело (т.е. оно не пустое в отличии от метода GET). Может быть с этим связано.

Это сообщение отредактировал(а) kanaris - 16.4.2015, 21:35
PM MAIL   Вверх
kami
Дата 7.12.2009, 21:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Цитата(kanaris @  7.12.2009,  21:01 Найти цитируемый пост)
 В пост-запросе, помимо заголовков, есть еще и тело (т.е. оно не пустое в отличии от метода GET). Может быть с этим связано.

Вообще-то тело обрабатывается в той же функции TranslateBuffer. Да, я немного напортачил там. Но что мешает пройтись отладчиком, тем более что проблема обнаруживается сразу же?

Код

// адрес подменен. Пробуем узнать, нет ли тела запроса
  i := Pos('Content-Length: ', Buffer);
  if i <> 0 then
    begin
      inc(i, Length('Content-Length: '));
      j := Min(PosEx(#13, Buffer, i), PosEx(' ', Buffer, i));
      FCurrentBodyLength := StrToInt(AnsiUpperCase(Trim(copy(Buffer, i, j - i)))); // проблема была здесь из-за неверного 
                 // подсчета символов Content-Length, в результате чего была попытка вычислить StrToInt('')
    end;

PM MAIL WWW   Вверх
kanaris
Дата 9.12.2009, 00:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 70
Регистрация: 16.2.2008

Репутация: нет
Всего: -1



Ничего не понял... Какое значение должно вычислиться для FCurrentBodyLength? Длина тела? Судя по отладке, она равна нулю.

user posted image
PM MAIL   Вверх
kami
Дата 9.12.2009, 01:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 6
Всего: 72



Цитата(kanaris @  9.12.2009,  00:06 Найти цитируемый пост)
Какое значение должно вычислиться для FCurrentBodyLength? Длина тела? Судя по отладке, она равна нулю.

При трассировке Post-запроса выясняется, что неправильно определялись положения начала и конца Content-Length. Из-за этого в FCurrentBodyLength пытались записать StrToInt(пустая_строка), что само собой вызывало исключение. 
В приведенном в предыдущем посте коде это исправлено (правильно вычисляются индексы начала и конца символов, содержащих "длину тела".

Добавлено через 5 минут и 51 секунду
На всякий случай - полный вариант со всеми исправлениями.

Присоединённый файл ( Кол-во скачиваний: 19 )
Присоединённый файл  HTTPMapper.zip 4,30 Kb
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Сети"
Snowy
Poseidon
MetalFan

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делится вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи

Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Snowy, Poseidon, MetalFan.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Сети | Следующая тема »


 




[ Время генерации скрипта: 0.1024 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.