С этой целью, CA-Visual Objects (КаВо) предоставляет нам чудесную библиотеку «Internet». Для работы по протоколу ftp – класс «cFTP», а с http – «cHTTP». Допустим, у меня уже есть сайт, и я не хочу использовать протокол FTP, значит, мне нужен «cHTTP». Документация о работе с интернетом в КаВо столь «подробна» и примеров так много, что прям «нажимай да дуй». Или, как говорят у нас – «без пол литра – не разобраться». :)
Посмотрел в интернете на форумах, и понял, что здесь явно что-то не так. Ведь, не будет же народ упрямо пытаться что-то где-то дорабатывать. Начались эти попытки с австралийского программиста Geoff Schaller-а и продолжились в «IC2» (см.
CA-Visual Objects: Third-Party Products, пункт 20).
На фото: Geoff Schaller
И самое неприятное для начинающих – что до сих пор никто не удосужился «разжевать» эту тему, рассказать «что с чем едят». Короче, «копать от забора и до обеда». И тут нам поможет SDK КаВо.
«Последователей» предупрежу сразу – код в SDK, скорее всего, не совсем соответствует действительности. И это сильно затрудняет понимание вопроса. Т.е., некоторые ошибки, которые там нашёл, абсолютно не проявляли себя, когда вместо SDK-библиотеки подключал стандартную (не SDK) библиотеку. Например, в help-е следующий синтаксис:
CHttp{<cCaption>, <n>} => SELF
Init(<cCaption>, <n>) => SELF
А на самом деле, там три параметра (см. SDK):
METHOD Init( cCaption, n, lStat ) CLASS CHttp
Третий параметр не описан. А он – весьма «подленький». «lStat» фактически отвечает за использование
критических секций.
TRUE – использовать,
FALSE – нет. Автор SDK применил свой механизм работы с секциями и считает, что вы тоже их используете (по умолчанию, lStat =
TRUE). Следовательно, при работе с SDK, если не планируете использовать критические секции, надо писать: cHTTP
{ cCaption
, n
, FALSE }. Или переписать SDK. Самое смешное, в рабочей (не SDK) библиотеке такой проблемы – нет. Ей всё равно, используете вы критические секции или нет.
Если сильно не хочется в SDK работать с критическими секциями, предлагаю субклассирование:
CLASS msHTTP INHERIT cHTTP // Pencil
METHOD Init( cCaption, n, lStat ) CLASS msHTTP
// Description: Этот метод предназначен только для работы с SDK
// Parameters :
// Returns :
// Если критическая секция не инициализирована, использовать её нельзя!!!
IF csWSA.DebugInfo = NULL_PTR
lStat := FALSE
ENDIF
SUPER:Init( cCaption, n, lStat )
RETURN SELF
Где csWSA – глобальная переменная моей критической секции. А у вас может быть другая. Как-то так. Ещё раз напоминаю, в нормальном коде (не для работы с SDK) – это не нужно, более того – противопоказано. Только для работы с SDK!!!
Вернёмся чуть назад. Почему я отказался от идеи использования ftp? Во-первых, лёгких путей мы не ищем, во-вторых, зачем лишний раз где-то «светить» данные подключений, в-третьих, есть стандартные средства для сканирования ftp на предмет наличия файлов, а для http – нет. Хочу, чтобы пользователи получали имена и адрес файлов на основе обработки указанных данных на сайте. Плюс простота проверки настроек в Windows: если в IE открываются интернет-странички, то и обмен файлами тоже будет.
Да уж, как-то я опрометчиво отказался от идеи использования ftp… Дальнейшее исследование показало, что в стандартной библиотеке нет годных средств для работы с файлами по протоколу http. «Орешек знаний твёрд. Но всё же, нам расколоть его поможет…». И хорошая музыка. Что вы сейчас слушаете? А я (прям сейчас) – «RPWL».
План такой:
- Открываем интернет-сессию (передаём данные о нашем прокси и пр.). Здесь мы используем API-функцию InternetOpen().
- Соединяемся с хостом (ftp, http). Это – InternetConnect().
- Выполняем нужные запросы (работаем в сессии с хостом: документы, файлы и пр.). См. InternetOpenUrl().
Ну, а закрывать будем в обратном порядке.
Смотрим, что и как сделано в библиотеке «Internet SDK». И видим, что там вообще не предусмотрен контроль действий. Т.е., очень желательно дописать проверку открытия интернет-сессии и соединения с хостом (сайтом). Ибо, то, что есть – абсолютно нерабочее. Плюс, анализ кода SDK показывает слабую проработку закрытия процессов. А значит, «от греха» стоит эти кусочки тоже переписать (нет гарантий, что в стандартной библиотеке эти вопросы решены лучше).
Здесь надо принять ещё одно волевое решение: как мы будем контролировать, и сообщать причины неполадок. Я пошёл стандартным путём – если Windows считает, что интернет-сессия не открыта – просто сообщать об этом, не уточняя причины. Это дело администратора. Мы, ведь, не пишем диагностическую программу? Там большое поле для деятельности: подножки могут ставить как сам Windows (Firewall), устройства (плата, модем, роутер, кабель), так и провайдер.
CLASS msHTTP INHERIT cHTTP // Pencil
// Description:
DECLARE ACCESS IsOpen // Статус интернета: открыт или нет
ACCESS IsOpen AS LOGIC PASCAL CLASS msHTTP
// Description: Проверка открытия интернета (активное или пассивное подключение)
// Примечание: если пассивное - оно реально может и не работать...
// Parameters : No
// Returns : LOGIC
LOCAL lSuccess AS LOGIC
LOCAL dwConnectionType AS DWORD
IF SELF:hSession == NULL_PTR
lSuccess := FALSE
ELSE
dwConnectionType := INTERNET_CONNECTION_MODEM + INTERNET_CONNECTION_LAN + INTERNET_CONNECTION_PROXY
// INTERNET_CONNECTION_MODEM = 1; // Через модем
// INTERNET_CONNECTION_LAN = 2;
// INTERNET_CONNECTION_PROXY = 4;
/*
; Return :
; 0x40 INTERNET_CONNECTION_CONFIGURED : Local system has a valid connection to the Internet, but it might or might not be currently connected.
; 0x02 INTERNET_CONNECTION_LAN : Local system uses a local area network to connect to the Internet.
; 0x01 INTERNET_CONNECTION_MODEM : Local system uses a modem to connect to the Internet.
; 0x08 INTERNET_CONNECTION_MODEM_BUSY : No longer used.
; 0x20 INTERNET_CONNECTION_OFFLINE : Local system is in offline mode.
; 0x04 INTERNET_CONNECTION_PROXY : Local system uses a proxy server to connect to the Internet.
; 0x10 INTERNET_RAS_INSTALLED : Local system has RAS installed
; or 0 if there is no Internet connection
*/
lSuccess := InternetGetConnectedState( @dwConnectionType, 0 )
ENDIF
RETURN lSuccess
METHOD IsConnected( cHost ) CLASS msHTTP
// Description: Проверка соединения с хостом
// Parameters :
// Returns : LOGIC
LOCAL lSuccess AS LOGIC
IF SELF:hConnect == NULL_PTR
lSuccess := FALSE
ELSE
IF IsNil( cHost )
cHost := SELF:cCurrentUrl
lSuccess := InternetCheckConnection( String2Psz( cHost ), FLAG_ICC_FORCE_CONNECTION, 0 )
ELSEIF IsString( cHost )
cHost := AllTrim( cHost )
IF cHost == NULL_STRING
cHost := SELF:cCurrentUrl
ELSE
IF At2( "//", cHost ) == 0
IF SELF:lFtpRequest
cHost := "ftp://" + cHost
ELSE
cHost := "http://" + cHost
ENDIF
ENDIF
ENDIF
lSuccess := InternetCheckConnection( String2Psz( cHost ), FLAG_ICC_FORCE_CONNECTION, 0 )
ELSE
lSuccess := FALSE
ENDIF
ENDIF
RETURN lSuccess
METHOD CloseRequest( ) CLASS msHTTP
// Description: Закрытие запроса(ов)
// Parameters : No
// Returns : NIL
LOCAL lRet AS LOGIC
lRet := TRUE
IF SELF:hRequest == NULL_PTR
RETURN lRet
ENDIF
// Удаляем запрос в крит. секции
IF SELF:lStatus
lRet := SELF:__DelStatus( SELF:hRequest )
ENDIF
IF lRet .AND. InternetCloseHandle( SELF:hRequest )
SELF:hRequest := NULL_PTR
RETURN TRUE
ENDIF
RETURN FALSE
METHOD CloseRemote( ) CLASS msHTTP // Pencil
// Description: Закрытие соединения с сайтом
// Parameters : No
// Returns : NIL
LOCAL lRet AS LOGIC
// Закрываем запрос
lRet := SELF:CloseRequest( )
IF SELF:hConnect == NULL_PTR
RETURN lRet
ENDIF
// Убираем соединение в крит. секции
IF SELF:lStatus
lRet := SELF:__DelStatus( SELF:hConnect )
ENDIF
IF lRet .AND. InternetCloseHandle( SELF:hConnect )
SELF:hConnect := NULL_PTR
RETURN TRUE
ENDIF
RETURN FALSE
METHOD Close( ) CLASS msHTTP
// Description: Закрытие интернет-сеанса (сессии)
// Parameters : No
// Returns : NIL
// Закрываем соединение
IF .NOT. SELF:CloseRemote()
// !!! Если не закрылось - закрываем, как можем
// Закрываем запрос
IF .NOT. SELF:hRequest == NULL_PTR
IF SELF:lStatus
SELF:__DelStatus( SELF:hRequest )
ENDIF
InternetCloseHandle( SELF:hRequest )
SELF:hRequest := NULL_PTR
ENDIF
// Закрываем соединение
IF .NOT. SELF:hConnect == NULL_PTR
IF SELF:lStatus
SELF:__DelStatus( SELF:hConnect )
ENDIF
InternetCloseHandle( SELF:hConnect )
SELF:hConnect := NULL_PTR
ENDIF
ENDIF
// Закрываем интернет
IF .NOT. SELF:hSession == NULL_PTR
IF SELF:lStatus
SELF:__DelStatus( SELF:hSession )
ENDIF
InternetCloseHandle( SELF:hSession )
SELF:hSession := NULL_PTR
ENDIF
// Чистим переменные
SELF:cResponse := NULL_STRING
SELF:cResponseHeader := NULL_STRING
SELF:cCurDir := NULL_STRING
SELF:cCurrentUrl := NULL_STRING
SELF:cHostAddress := NULL_STRING
SELF:cUserName := NULL_STRING
SELF:cPassWord := NULL_STRING
SELF:cError := NULL_STRING
SELF:cAgent := NULL_STRING
SELF:cProxy := NULL_STRING
SELF:cProxyBypass := NULL_STRING
RETURN NIL
METHOD Axit( ) CLASS msHTTP
// Description:
// Parameters : No
// Returns : NIL
SELF:Close()
RETURN ( SUPER:Axit() )
Как открывать – мы знаем, как закрывать – написали. Осталось научиться «закачивать» файлы. Сколько уже можно издеваться над читателями? Придётся ещё немного попыхтеть, и переписать стандартный метод GetFile(). Подберём ему «костюм» и «туфли»:
METHOD GetFile( cRemoteFile, cNewFile, lFailIfExists ) CLASS msHTTP
// Description: Получение (загрузка) указанного файла
// Parameters :
// Returns : LOGIC
LOCAL lRet AS LOGIC
LOCAL cHead AS STRING
LOCAL nFlags AS DWORD
LOCAL cUrl AS STRING
LOCAL hNewFile AS PTR
LOCAL DIM abTemp[ MAX_SOCKBUFF ] AS BYTE
LOCAL nSize AS DWORD
LOCAL lFile AS LOGIC
Default( @lFailIfExists, FALSE )
Default( @cRemoteFile, NULL_STRING )
Default( @cNewFile, NULL_STRING )
cRemoteFile := AllTrim( cRemoteFile )
cNewFile := AllTrim( cNewFile )
lRet := FALSE
IF cRemoteFile == NULL_STRING
RETURN lRet
ENDIF
IF cNewFile == NULL_STRING
cNewFile := cRemoteFile
ENDIF
lFile := File( cNewFile )
// Если файл есть, а дописывать в него - нельзя
IF lFile .AND. lFailIfExists
RETURN lRet
ENDIF
// Подготовка данных по открытию
IF SELF:hSession == NULL_PTR
IF .NOT. SELF:Open( NIL, SELF:cProxy, SELF:cProxyBypass )
RETURN lRet
ENDIF
ENDIF
cUrl := SELF:__GetUrl( cRemoteFile )
IF cUrl == NULL_STRING
RETURN lRet
ENDIF
cHead := HEADER_ACCEPT
nFlags := INTERNET_FLAG_DONT_CACHE + INTERNET_FLAG_EXISTING_CONNECT
// !!!
SELF:__SetStatusObject()
SELF:hRequest := InternetOpenUrl( SELF:hSession, String2Psz( cUrl ), String2Psz( cHead ), SLen( cHead ), nFlags, SELF:__GetStatusContext() )
IF .NOT. SELF:hRequest == NULL_PTR
SELF:__SetStatus( SELF:hRequest )
// Создаём файл
IF lFile
hNewFile := FxOpen( cNewFile, OF_READWRITE + OF_SHARE_EXCLUSIVE, NULL_STRING )
ELSE
hNewFile := FCreate2( cNewFile, FC_ARCHIVED )
ENDIF
IF .NOT. hNewFile == INVALID_HANDLE_VALUE // F_ERROR
// Читаем и пишем в файл
DO WHILE ( lRet := InternetReadFile( SELF:hRequest, @abTemp[ 1 ], MAX_SOCKBUFF, @nSize ) )
IF nSize = 0
EXIT
ENDIF
FWrite3( hNewFile, @abTemp[ 1 ], nSize )
END DO
// Закрываем файл
FClose( hNewFile )
hNewFile := NULL_PTR
// Если файл плохой - удаляем
IF !lRet
FErase( cNewFile )
ENDIF
ENDIF
// Освобождаем интернет-запрос
SELF:__DelStatus( SELF:hRequest )
IF InternetCloseHandle( SELF:hRequest )
SELF:hRequest := NULL_PTR
ENDIF
ENDIF
SELF:__DelStatusObject()
RETURN lRet
Скажу нетерпеливым испытателям: будьте внимательны. Мой код не идеален и не учитывает многих нюансов. Например, метод получения файлов не учитывает наличия такой возможности, как
URL redirection. Т.е., если пользователь получит некую ссылку, которая на самом деле перенаправит его в другое место, где он получит следующую (возможно, правильную или очередную) – вышеуказанный метод GetFile() не сработает.
Говорят, такую проблему можно решить, просмотрев заголовок (см. метод GetResponseHeader). Ещё раз, будьте внимательны, и всё в ваших руках!
Пример «на закуску»:
oHTTP := msHTTP{ }
IF oHTTP:Open( ) // Открыли интернет-сессию
IF oHTTP:ConnectRemote( "Test.Com" ) // Имя или IP-адрес сайта
cRemFile := "sites/default/files/Test.rar" // Здесь полный путь от "корня" сайта
cNewFile := "D:\Demo.rar"
IF oHTTP:GetFile( cRemFile, cNewFile )
ErrorBox{, "Получилось !"}:Show()
ELSE
ErrorBox{, "Ошибка !"}:Show()
ENDIF
ELSE
ErrorBox{, "Ошибка !"}:Show()
ENDIF
ELSE
ErrorBox{, "Ошибка !"}:Show()
ENDIF
oHTTP:Close()
Удачи!
09.11.2014 г. Карандаш.
Комментарии
Very interesting!
This is a very interesting subject that I would like to know more about. I would greatly appreciate an English translation of this page! I have tried the Google translation but it is very bad.
I will try to make the translation of the text