Высокоанонимные прокси от ipann.net позволят ускорить работу с SEO софтом и социальными сетями.

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

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

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

Итак, перейдем к практике и изучим, как все работает изнутри. Для начала нужно определиться с используемой технологией. Что выбрать - блокировки, ожидания через select или события Windows? В нашей программе, когда пользователь запросит загрузить какой-либо сайт, мы должны будем получить этот сайт от сервера и передать его пользователю. Это отнимет много времени, и при использовании событий другие пользователи в данный момент работать не смогут.

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

Теперь переходим к реализации. Создайте новый проект. На форме нам понадобятся три поля ввода:

• порт, на котором будет работать наш сервер и ожидать подключения со стороны клиента;
• порт внешнего прокси-сервера, если наш должен будет перенаправлять запросы другому серверу. Таким образом, мы сможем строить цепочку из разных прокси-серверов;

• адрес внешнего прокси-сервера.

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

Пример главной формы будущей программы показан на рис. 5.9.

5.5. Создание Proxy-сервера

Рис. 5.9. Форма будущего прокси-сервера

Загрузку сетевой библиотеки WinSock поместим на событие OnCreate для главной формы:

procedure THTTPProxyForm.FormCreate(Sender- TObject). var wData- WSADATA: begin if WSAStartup(MAKEWORDd.l). wData) <> 0 then begin MessageBox(0. "He могу загрузить WinSock'. "Ошибка". 0): exit:

end:
end:

По нажатии кнопки Запуск нужно запустить прокси-сервер. Для этого мы создадим отдельный поток, и в нем будем ожидать подключения от клиента, чтобы не задерживать работу главной формы. Блокировок в программе не будет.

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

procedure THTTPProxyForm.bnStartClick(Sender: TObject). var
st: TServerThread: begin
st := TServerThread.Create(true).
st iLocal Port :" StrToIntDef(edPort.Text. 8088):
st.iExtProxyPort -= StrToIntDef(edExtProxyPort Text. 8080):
st.sExtProxyAddr := edExtProxyAddr.Text:
st.Resume:
end:

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

Теперь посмотрим на реализацию потока сервера. Для его создания выполните команду меню File ► New ► Other (для Delphi 6 и меньшей версии достаточно выбрать File ► New). Перед вами откроется диалоговое окно задания создаваемого элемента. Выберите Thread Object и нажмите ОК. Должно появиться диалоговое окно ввода имени потока. Введите TServerThread и нажмите ОК.

Полный исходный код (только без знакомых нам процедур проверки сообщений TestFuncError и TestWinSockError) вы можете увидеть в листинге 5.10.

Листинг 5.10. Код потока TServerThread

unit ServerThreadUnit.
interface uses
Classes, winsock, windows;
type TServerThread = class(TThread) private {Private declarations} protected
procedure Execute: override: public
i Local Port. i ExtProxyPort:Integer;
sExtProxyAddr:Stri ng:
end:
implementation
uses ClientThreadUnit:

procedure TestWinSockError(S:String): begin // Код этой процедуры нам знаком, поэтому урезан для экономии места

end;

function TestFuncErrordErr:Integer: FuncName:String):Boolean; begin // Код этой функции нам знаком, поэтому урезан для экономии места

end;
procedure TServerThread.Execute;

Листинг 5.10 (продолжение) var sServerListen. stClientSocket: TSOCKET: local-addr- sockaddMn: ct: TCIientThread; begi n

// Создание сокета

SServerListen := socket(AF_INET. SOCK_STREAM, 0);

if SServerListen = INVALID_SOCKET then begin MessageBox(0. 'Ошибка создания сокета'. 'Ошибка'. 0): exit:

end:

// Заполнение структуры адреса localaddr.sin_addr.s_addr := htonl{INADDR_ANY): localaddr.sin_family := AF_INET; localaddr.sin_port := htonsdLocal Port):

// Связывание сокета с локальным адресом

if TestFuncError(bind(sServerListen. localaddr. sizeof(localaddr))
'bind') then exit:

// Запустить прослушивание

if TestFuncError(listen(sServerListen. 4), 'Listen') then exit:

// Цикл обработки подключений от клиента

while true do begin // Принять соединение

stClientSocket := accept(sServerListen, nil. nil): if StClientSocket = INVALID.S0CKET then continue: i

// Создать поток для общения с клиентом ct := TClientThread.Create(true): ct.stClient :" StClientSocket: ct iExtProxyPort := iExtProxyPort: ct sExtProxyAddr := sExtProxyAddr; ct Resume:

end:
end;
end.

В разделе pub! i с добавлены три переменные: i Local Port, iExtProxyPort типа Integer и sExtProxyAddr, имеющая тип String. Мы уже пользовались этими переменными, когда рассматривали код создания потока, здесь же видно, как они объявлены.

Основной код серверного потока расположен в процедуре Execute. Сначала создается сокет и запускается прослушивание подключений на порте, указанном пользователем.

В бесконечном цикле while принимаются входящие соединения. Если получен корректный сокет, то создается еще один поток типа TClientThread. В этом потоке будет происходить непосредственное общение между клиентом и сервером. Поток будет выступать для клиента посредником для доступа к веб-страницам. Код потока TClientThread приведен в листинге 5.11.

Листинг 5.11. Поток, выступающий посредником между клиентом и web

unit ClientThreadUnit.

i nterfасе uses

Classes, winsock. sysutils. windows:
type TClientThread = class(TThread) private {Private declarations} protected
procedure Execute: override;
public
iExtProxyPort: Integer;
sExtProxyAddr: String:
stClient: TSocket:
end:

implementation {TClientThread} function LookupName(name: String): TInAddr; begin // Функция определения имени, которая нам знакома

end.

// Функция отправки строки

procedure SendStr(s: TSocket: str: String):
begin
TempStr := str+#13+#10:
CopyMemory(@sRecvBuff. PCharCTempStr). Length(TempStr));
send(s. sRecvBuff. Length(TempStr). 0);
end;
procedure TClientThread.Execute: var
Buff: array [0..1024] of char: iPort: Integer: sRequest. sHost: String: server_addr: sockaddMn:
продолжение &

Листинг 5.11 (продолжение) sock_server- TSocket: iMode. iSize: Integer-rfds: TFDSET. begin //////////////////////////// // Считывание заголовка //////////////////////////// RecvCstClient. Buff. 1024. 0):

sRequest := String(Buff).

// Нет заголовка if sRequest = '' then begin CIoseSocket(stCl i ent). exit, end:

////////////////////////////

// Определяем адрес сервера и порта

////////////////////////////
sHost := Copy (sRequest. PosCHost: '. sRequest). 255): Delete(sHost. Pos(#13. sHost), 255);
Delete(sHost. 1, 6);
iPort := St rToIntDef( Copy (sHost. PosC:'. sHost)+l. 255). 80): Delete(sHost. PosC:'. sHost). 255):

// Если не найден host, то ошибка

if sHost = '' then.
begin
SendStr(stClient. 'HTTP/1.0 400 Invalid header received
from browser');
CIoseSocket(stCli ent);
exit:
end;

// Если есть внешний "проксик", то перенаправляем на него

if sExtProxyAddr<>"
then
begin
iPort := iExtProxyPort;
sHost := SExtProxyAddr;
end:

sock_server-:= socket(AF_INET. S0CK_STREAM. 0): // Ищем прокси-сервер

server_addr.sin_addr.s_addr := htonl(INADDR_ANY): server_addr sin_family := AF_INET: server_addr.sin_port .= htons(iPort): server_addr.sin_addr := LookupName(sHost); 1

// Соединение с сервером

if connect(sock_server. server_addr. sizeof(server_addr)) =
SOCKETJRROR then
begin
SendStr(stClient. '404 Host Not Found');
exit;
end;
iMode := 1:
setsockopt(sock_server. IPPR0T0_TCP. TCPJODELAY. @iMode.
sizeof (integer)).

// Перенаправляем запрос серверу или другому "проксику" send(sock_server. buff, strlen(buff).0):

// Теперь работаем посредником между клиентом и сервером. // передавая запрошенные данные while true do begin
// Добавляем сокеты в набор для ожидания FD_ZERO(rfds): FD_SET(stClient, rfds): FD_SET(sock_server. rfds);
if (select(0. Orfds. nil. nil. ml) < 0) then exit;
// Если пришел запрос от клиента. // то перенаправляем серверу if (FD_ISSET(stClient. rfds)) then begin
iSize •= recv(stClient. buff, sizeof(buff). 0);
if iSize=-l then break;
Send(sock_server. buff. iSize. 0);
continue:
end:

// Если пришли данные от сервера.

// то перенаправляем клиенту

if (FD_ISSET(sock_server, rfds)) then
begin
iSize := recv(sock_server, buff. sizeof(buff). 0):

// Сервер уже все выслал if iSize = 0 then exit:

Листинг 5.11 (продолжение) SencKstClient. buff. iSize. 0): continue:

end;
end:
CloseSocket(stClі ent): CloseSocket(sock_server):
end: end.

Для прокси-сервера в потоке TClientThread спрятано самое интересное, и сейчас мы это подробно рассмотрим. Единственное, что упущено в этом листинге, - уже знакомая нам функция LookupName.

Итак, когда клиент подключился к прокси-серверу, он посылает стандартный HTTP-запрос на получение какого-либо файла. Мы считываем этот заголовок с помощью функции recv. Заголовок выглядит следующим образом:

GET http://VAAo.vr-onl1ne.rU/HTTP/l.0
Accept: image/gif. image/x-xbitmap. image/jpeg. */*
Accept-Language: ru
User-Agent: Mozilla/4.0 (compatible: MSIE 6.0: windows NT 5.2:
.NET CLR 1.1.4322)
Host: www.vr-online.ru Proxy-Connection- Keep-Alive

Пока не будем вникать в суть запроса и содержащихся здесь команд, для нас сейчас главное - это строка host. В ней находится адрес сервера, с которого нужно получить данные и вернуть клиенту. В данном случае это www.vr-onlin.ru. Адрес известен, осталось выяснить порт. По умолчанию веб-серверы работают на порте 80, но если не так, то адрес веб-сервера будет указан следующим образом: www.vr-online.ru:Port. После двоеточия указывается номер порта, и мы должны учитывать этот нюанс.

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

sHost := Copy(sRequest, Pos('Host: '. sRequest). 255): Delete(sHost. Pos(#13. sHost). 255);
Delete(sHost. 1. 6);

Теперь отделяем номер порта, если он указан:

iPort := StrToIntDef(Copy(sHost. PosC:'. sHost)+l. 255). 80);
Delete(sHost. PosC:'. sHost), 255):

Если порт не указан, то функция StrToIntDef выдаст ошибку и возвратит значение, указанное по умолчанию, то есть 80.

Обязательно проверяем: если переменная sHost после разбора заголовка равна пустой строке, то адрес сервера не указан или указан неверно. В этом случае мы должны вернуть ошибку. Это можно сделать следующим образом:

SendStr(stClі ent. 'HTTP/1.0 400 Invalid header received from browser');

После отправки ошибки закрываем сокет и выходим из потока.

Теперь проверяем: если указан внешний "проксик", то заменяем адрес и порт сервера на параметры внешнего прокси-сервера. В этом случае мы просто должны переслать на этот сервер запрос, полученный от клиента.

В принципе, если есть внешний прокси-сервер, то не было смысла определять адрес сервера. Но я делаю это в любом случае и вам советую, потому что в будущем может понадобиться переадресация. Например, если пользователь запрашивает страницу с сайта www.sex.ru, то наш прокси-сервер определит это и сможет переадресовать запрос на другой сервер. Для этого достаточно поменять значение переменной sHost и немного подкорректировать запрос, который поступил от клиента.

Определив нужные параметры, создаем новый сокет, с помощью которого будет происходить связь с внешним сервером (независимо от того, другой ли это прокси-сервер или непосредственно веб-сервер).

Создав новый сокет и подключившись к серверу, мы сразу же переводим его в асинхронный режим таким образом:

iMode := 1:
setsockopt(sock_server. IPPROTOJCP. TCP_NODELAY. OiMode. sіzeof(integer)):

Функция setsockopt в данном случае схожа по своему назначению с іoctl socket, рассмотренной нами в главе 4. Она переводит сокет в более быстрый режим работы, который позволяет не ожидать и не накапливать данные, а сразу же принимать и пересылать их. Таким образом мы минимизируем задержки, а накапливанием занимается сервер или клиент.

После перевода сокета в нужный режим перенаправляем веб-серверу (или следующему прокси-серверу) запрос от клиента и запускаем бесконечный цикл. Этот цикл и будет посредником. Здесь ожидаются данные от клиента и сервера и пересылаются друг другу. Если пришли пустые данные, то это значит, что соединение завершено, и цикл прерывается. Все открытые сокеты при выходе из процедуры закрываются.

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

Хочу также обратить ваше внимание на то, что в качестве номеров портов в исходном коде по умолчанию установлено значение 8080. Это связано с тем, что моя сеть настроена через прокси-сервер. Если вы используете DialUp, то в качестве порта внешнего "проксика" и адреса нужно указать пустые строки.

Для тестирования примера запустите нашу программу и сервер. Затем откройте Internet Explorer (или другой браузер) и настройте его на использование прокси-сервера. Для этого выполните команду меню Сервис ► Свойства обозревателя, и в появившемся диалоговом окне (рис. 5.10) перейдите на вкладку Подключение.

5.5. Создание Proxy-сервера

Рис. 5.10. Настройка браузера

Нажмите кнопку Настройка сети, и перед вами откроется новое диалоговое окно (рис. 5.11).

5.5. Создание Proxy-сервера

Рис. 5.11. Настройка прокси-сервера

В области Прокси-сервер установите флажок Использовать прокси-сервер и укажите адрес и порт. Если вы запускаете сервер на своем локальном компьютере, то можно указать 127.0.0.1, но я рекомендую указать реальный адрес, чтобы пример точно работал.

ПРИМЕЧАНИЕ ---1Исходный код рассмотренного здесь примера находится на компакт-диске в каталоге 5оигсе5\сп05\Н"ГТРРгоху.

5.4. РОРЗ-клиент на Win API || Оглавление || 5.6. HTTP-клиент


Delphi в шутку и всерьез: что умеют хакеры



Новости за месяц

  • Декабрь
    2021
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31