Воскресенье, 05.05.2024
Королевство Delphi
Главное меню
Статьи
Наш опрос
Нравится раздел статьи?
Всего ответов: 68
Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Главная » Статьи » Сеть-интернет » Работа с HTTP

Основы HTTP на примере Delphi

О, инет… Как много в этом звуке. А прикинь как в нём много того, о чём ты вообще не в курсах? Нет, я совсем не имею ввиду туеву хучу сайтов, на которых ты не был, я имею ввиду то, что скрыто от тебя… То как твой браузер общается с сервером – ПРОТОКОЛ. Конечно же, полное описание протокола со всеми тонкостями не уместить в одну статью, но примерно раскидав по полочкам некоторые его основы в этой статье, я думаю, потом ты сможешь смело продолжать копать в данном направлении и в конце концов сможешь написать даже свой собственный браузер не на основе компонента TWebBrowser. Для этого нам потребуется совсем немного – установленная Дельфи (я юзаю 6-ю) и немного терпения, чтобы дочитать сей материалец. Дабы не утруждаться всякими тонкостями сокетов, будем юзать TidTCPConnection. Итак начнём с самого начала доверительных отношений сервера и клиента – соединение. Для начала нам надо разделить запрашиваемую страницу и домен, на котором эта страница расположена. Нижеследующий код, собственно, это и производит.

 procedure ParseUrl(RequestString: string);
 var
 Addr: string;
 begin
 Addr:= RequestString;
 if pos('http://',Addr)>0 then
 delete(Addr,1,7);
 if pos('/',Addr)>0 then
 begin
 FHost:=copy(Addr,1,pos('/',Addr)-1);
 FPath:=copy(Addr,pos('/',Addr),length(Addr)-pos('/',Addr)+1);
 end
 else
 begin
 FHost:=Addr;
 FPath:='';
 end;
 end; 

Переменные FHost и FPath объявлены на уровне модуля. Затем я создал у себя такую функцию –

function Connect(Host: string): boolean;

Объявляем на уровне функции переменную phe: PHostEnt; - это структура, в которую будет загоняться инфа о сервере, к которому мы захочем приджойниться, а делается это так –

phe:=gethostbyname(PChar(Host));

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

 if assigned(phe) then
 begin
 end;

Теперь из этой структуры необходимо вытащить ИП-адрес, потому что соединяться всё-равно надо не по имени сайта, а по его ИП-шнику. Для этого объявим ещё две переменных - pa: PChar; - в эту переменную мы вытащим ИП-шник в закодированном виде и соответственно будем его декодировать; FIP: string; - в этой переменной мы будем хранить ИП-адрес. Итого код, который мы вставляем в блок if –

 pa:=phe^.h_addr_list^;
 FIP:=inttostr(ord(pa[0]))+'.'+inttostr(ord(pa[1]))+'.'+inttostr(ord(pa[2]))+'.'+inttostr(ord(pa[3])); 

Главное – не забыть объявить на уровне модуля переменную типа TIdTCPConnection, я у себя назвал её Connection. Также добавим в конце файла ещё такой код перед финальным end’ом –

 initialization
 Connection:=TIdTCPConnection.Create(nil);

 finalization
 FreeAndNil(Connection); 

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

 with Connection do
 begin
 //соединяемся
 if not Binding.HandleAllocated then
 begin
 Binding.AllocateSocket;
 Binding.SetPeer(FIP,80);
 CRes:=Binding.Connect;
 if CRes=-1 then //ошибка соединения
 exit;
 result:=true;
 end;
 end; 

Не стоит забывать, что этот код также должен быть внутри блока if . Тут кстати требуется ещё кой-чего в этой функции – дело в том, что после закрытия соединения, его надо ещё и сбросить, для чего надо выполнить метод ResetConnection у нашего соединения, но загвоздочка в том, что этот метод объявлен как protected. Поэтому нам необходимо написать ещё такую штуку в секцию interface –

 type
 TKRTCPConnection = class(TIdTCPConnection); 

И теперь, в нашей функции перед строкой if not Binding.HandleAllocated then добавляем такой код –

 if ClosedGracefully then
 TKRTCPConnection(Connection).ResetConnection; 

Итого, код функции, которая будет осуществлять соединение, таков –

 function Connect(Host: string): boolean;
 var
 phe: PHostEnt;
 pa: PChar;
 FIP: string;
 CRes: integer;
 begin
 result:=false;
 //получаем ИП-шник
 phe:=gethostbyname(PChar(AHost));
 if assigned(phe) then
 begin
 pa:=phe^.h_addr_list^;
 FIP:=inttostr(ord(pa[0]))+'.'+inttostr(ord(pa[1]))+'.'+inttostr(ord(pa[2]))+'.'+inttostr(ord(pa[3]));
 with Connection do
 begin
 if ClosedGracefully then
 TKRTCPConnection(Connection).ResetConnection;
 //соединяемся
 if not Binding.HandleAllocated then
 begin
 Binding.AllocateSocket;
 Binding.SetPeer(FIP,APort);
 CRes:=Binding.Connect;
 if CRes=-1 then
 exit;
 result:=true;
 end;
 end;
 end;
 end; 

Замечательно – коннект есть, но пока что ещё нет страницы. Что-ж пойдём попросим у сервера нужную нам страницу. Для этих целей, я сделал ещё одну функцию –

function GetHttpOkStatus(RequestString: string): boolean; 

В этой функции сервер должен дать мне разрешение на определённую нужную мне страницу. На уровне функции я объявил переменную RespText: string; в эту переменную я собираюсь сохранять пересылаемые сигналы серверу и ответы от него. Вот тут-то и начинается собственно самое интересное. Начнём чатиться... Ничего смешного в этом нет, мы будем именно чатиться с сервером. Для нас он будет как бы чат-бот. Это конечно смотря с какой стороны посмотреть, но я воспринимаю данный процесс именно так. Итак – просим у сервера нужную нам страницу –

with Connection do
 begin
 RespText:='GET '+RequestString+' HTTP/1.1'+#13#10;
 WriteBuffer(RespText[1],length(RespText));
 end; 

Тут пара маленьких ремарок. Во-первых, весь последующий код относящийся к этой функции я подразумеваю размещённым внутри блока with. А во-вторых, отвечаю на вполне логично возникший вопрос – «А чего-й то за фигня, почему RespText[1], а не RespText?». Всё очень просто – дело в том что надо передать как бы PChar, а у меня string, а у string первый байт является идентификатором её длины, поэтому я даю указатель на первый байт полезной информации, думаю тут вполне возможны какие-либо дополнительные вопросы, но о них в другой раз. Итак, возвращаясь к теме – страницу мы попросили, но теперь мы должны указать домен под которым находится данная страница, потому что, как известно (надеюсь всем читающим), на одном компьютере легко размещаются сотни сайтов, так что шлём ещё такую строку –

RespText:='Host: '+ FHost +#13#10;
 WriteBuffer(RespText[1],length(RespText)); 

С тем что нам надо определились, теперь надо серваку указать то КАК мы это хотим получить, для чего и придумана система заголовков. Сейчас настало их время. В идеале, заголовков очень много и отвечают они за разные параметры, ниже приведён далеко не полный список заголовков –

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

     RespText:=' User-Agent: Samy Krutoy Browser‘+#13#10;
     WriteBuffer(RespText[1],length(RespText));
    

    Более подробно познакомиться с заголовками можно из соответствующих RFC (1945, 2068). Ну а теперь нам осталось просигналить что мы закончили и ждём теперь соответствующих результатов –

     RespText:= #13#10;
     WriteBuffer(RespText[1],length(RespText));
     RespText := ReadLn;
     result:=pos('200',RespText)>0; 
    

    Ключевой момент у нас заключается в последней строке. Дело в том, что всё в порядке у нас при ответе - HTTP/1.1 200 OK или HTTP/1.0 200 OK, ну и понятно почему я ищу собственно цифру 200 – это код состояния дальнейшего общения между клиентом и сервером. Первая цифра кода состояния определяет класс ответа. Последние две цифры не имеют определенной роли в классификации. Имеется 5 значений первой цифры:

    Полный список состояний приводить здесь не имеет смысла, все они описаны в RFC 2068. В итоге, функция GetHttpOkStatus выглядит у нас так –

     function GetHttpOkStatus(RequestString: string): boolean;
     var
     RespText: string;
     begin
     with Connection do
     begin
     RespText:='GET '+RequestString+' HTTP/1.1'+#13#10;
     WriteBuffer(RespText[1],length(RespText));
     RespText:='Host: '+ FHost +#13#10;
     WriteBuffer(RespText[1],length(RespText));
     RespText:='User-Agent: Samy Krutoy Browser '+#13#10;
     WriteBuffer(RespText[1],length(RespText));
     RespText:=#13#10;
     WriteBuffer(RespText[1],length(RespText));
     RespText := ReadLn;
     result:=pos('200',RespText)>0;
     end;
     end; 
    

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

     function GetFromInet(const fileURL, FileName: string): boolean;
     var
     RespText: String;
     memst: TStringStream;
     SL: TStringList;
     begin
     result:=false;
     if Connection.Connected then
     Connection.Disconnect;
     ParseUrl(fileURL);
     if Connect(FHost) then
     try
     if GetHttpOkStatus(FPath) then
     with Connection do
     begin
     memst:=TStringStream.Create('');
     try
     SL:=TStringList.Create;
     try
     ReadStream(memst,-1,true);
     memst.Position:=0;
     SL.Add(memst.DataString);
     SL.SaveToFile(FileName);
     result:=true;
     finally
     FreeAndNil(SL);
     end;
     finally
     FreeAndNil(memst);
     end;
     end;
     finally
     if Connection.Connected then
     Connection.Disconnect;
     end;
     end;
    

    Стоит дополнительно отметить, что данная функция далека от совершенства, ведь есть ещё такие вещи как прокси, авторизация, в конце-концов, обработка и отсечение полученных заголовков, но я смело делаю вывод что направление как таковое указано, а разобраться дальше что делать в тех или иных случаях несложно. Для чего и существует RFC, Delphi help, а также сорцы компонентов. В-общем чувствую что моя совесть чиста и предлагаю вам самим развиваться дальше в данном направлении.

  • Accept-Charset:
  • Accept-Encoding:
  • Accept-Language:
  • From:
  • Referer:
  • User-Agent:
  • Connection:
  • Content-Version:
  • Content-Encoding:
  • Content-Language:
  • Content-Type:
  • Content-Length:
  • Range: bytes=
  • Authorization: Basic
  • Proxy-Authorization: Basic
  • 1xx: Информационные коды - запрос получен, продолжается обработка
  • 2xx: Успешные коды - действие было успешно получено, понято и обработано
  • 3xx: Коды перенаправления - для выполнения запроса должны быть предприняты дальнейшие действия
  • 4xx: Коды ошибок клиента - запрос имеет плохой синтаксис или не может быть выполнен
  • 5xx: Коды ошибок сервера - сервер не в состоянии выполнить допустимый запрос.
  • Получить ссылку на материал

    Категория: Работа с HTTP | Добавил: Барон (14.12.2011)
    Просмотров: 1736 | Теги: HTTP, delphi | Рейтинг: 0.0/0
    [ Пожертвования для сайта ] [ Пожаловаться на материал ]

    Если вам помог материал сайта кликните по оплаченной рекламе размещенной в центре

    Поиск
    Категории раздела
    Web-приложения [6]
    Почта [12]
    Работа с HTTP [4]
    Робота с XML [4]
    Сервер [3]
    Разные [50]
    Королевство Delphi © 2010-2024
    Яндекс цитирования