Это продолжение статей
Использование классов .Net в 1С для новичков
Использование сборок .NET в 1С 7.x b 8.x. Создание внешних Компонент
Продолжение статьи лежит здесь .Net в 1С. На примере использования HTTPClient,AngleSharp.Удобный парсинг сайтов с помощью библиотеки AngleSharp в том числе с авторизацией аля JQuery с использованием CSS селекторов. Динамическая компиляция
Это статья будет полезна не только 7-кам, но и 8-кам. Да, многое умеет HTTPСоединение и HTTPзапрос. Но многого нет.
Например, нет асинхронных запросов, сжатия трафика, отправки составного содержимого и т.д.
В том числе часто можно найти примеры на C# и иногда быстрее их адаптировать с использованием в 1С HTTPClient.
Про HTTPClient можно почитать здесь
https://msdn.microsoft.com/ru-ru/library/windows/apps/xaml/dn440594.aspx
https://msdn.microsoft.com/ru-ru/library/system.net.http.httpclient(v=vs.118).aspx
Итак, начнем.
Клиент=Врап.СоздатьОбъект(HttpClient);
ДанныеРесурса=Клиент.GetStringAsync("https://msdn.microsoft.com/ru-ru/library/hh551745(v=vs.118).aspx").Result;
Сообщить(Врап.ВСтроку(ДанныеРесурса));
Все очень просто. Но это умеет и HTTPСоединение.
Для начала объявим используемые типы при открытии.
врап=новый COMОбъект("NetObjectToIDispatch45");
HttpClient=Врап.ПолучитьТипИзСборки("System.Net.Http.HttpClient","System.Net.Http.dll");
HttpClientHandler = врап.ПолучитьТип("System.Net.Http.HttpClientHandler");
// Контенты для Post
MultipartFormDataContent=Врап.ПолучитьТип("System.Net.Http.MultipartFormDataContent");
StreamContent =Врап.ПолучитьТип("System.Net.Http.StreamContent");
StringContent =Врап.ПолучитьТип("System.Net.Http.StringContent");
ByteArrayContent=Врап.ПолучитьТип("System.Net.Http.ByteArrayContent");
FormUrlEncodedContent =Врап.ПолучитьТип("System.Net.Http.FormUrlEncodedContent");
DecompressionMethods= Врап.ПолучитьТип("System.Net.DecompressionMethods");
ServicePointManager=врап.ПолучитьТип("System.Net.ServicePointManager");
Dictionary=Врап.ПолучитьТип("System.Collections.Generic.Dictionary`2[System.String,System.String]");
StringBuilder=Врап.ПолучитьТип("System.Text.StringBuilder");
String=Врап.ПолучитьТип("System.String");
HttpUtility=Врап.ПолучитьТипИзСборки("System.Web.HttpUtility","System.Web.dll");
// Сборку AngleSharp.dll поместить в каталог программы
//Для использования Scripting Api
КатаогПрограммы=Врап.ПолучитьТип("System.AppDomain").CurrentDomain.BaseDirectory;
ИмяСборкиAngleSharp=Врап.ПолучитьТип("System.IO.Path").Combine(КатаогПрограммы,"AngleSharp.dll");
AngleSharp_Configuration=Врап.ПолучитьТипИзСборки("AngleSharp.Configuration",ИмяСборкиAngleSharp);
IO_File =Врап.ПолучитьТип("System.IO.File");
Encoding=Врап.ПолучитьТип("System.Text.Encoding");
// В релизе нужно отлавливать ошибки
// врап.ВыводитьСообщениеОбОшибке=ложь;
По мере использования я буду пояснять, для чего тот или иной тип.
Начнем с асинхронного программирования. Часто нужно использовать долгие запросы, или нужно одновременно использовать несколько запросов. К сожалению, 1С этого не позволяет. Поэтому в свой разработке я постарался исправить этот недочет.
Выполнитель=Врап.ПолучитьАсинхронныйВыполнитель();
ДобавитьОбработчик Выполнитель.ПриОкончанииВыполненияЗадачи, ПриОкончанииВыполнения;
//Обработчик события выглядит так
Процедура ПриОкончанииВыполнения(Задача,ДанныеКЗадаче)
// Обязательно нужно отлавливать ошибку в 1С
// Иначе она передается в .Net где обрабатывается там
Попытка
Так как задача может завершиться с ошибкой
Мы должны проверить, и если ошибка нужно предпринять какие то действия
Если (Задача.IsFaulted) Тогда // Ошибка выполнения
Сообщить("Ошибка "+Врап.ВСтроку(Задача.Exception));
Сообщить("Данные к задаче "+Врап.ВСтроку(ДанныеКЗадаче));
иначе
Сообщить("=====Выполнена задача ====");
Сообщить("Данные к задаче "+Врап.ВСтроку(ДанныеКЗадаче));
Сообщить(Врап.ВСтроку(Задача.Result));
КонецЕсли;
Исключение
Сообщить("Ошибка в процедуре");
Сообщить(ОписаниеОшибки());
КонецПопытки
КонецПроцедуры
Вызываем задачу так
Клиент=Врап.СоздатьОбъект(HttpClient);
Задача=Клиент.GetStringAsync("https://msdn.microsoft.com/ru-ru/library/hh551745(v=vs.118).aspx");
Выполнитель.Выполнить(задача,ТекущаяДата());
Для тестов я сделал тестовый Вэб сервис на ASP.Net
Для теста множества асинхронных запросов сделал функцию
[HttpGet]
public async Task<string> GetIdAsync(string id)
{
await Task.Delay(1000);
return id;
}
Имитируем задержку в 1 секунду. Например, нам нужно выполнить 100 запросов.
Процедура TestAsyncНажатие(Элемент)
// Вставить содержимое обработчика.
handler = врап.СоздатьОбъект(HttpClientHandler);
Сообщить(ServicePointManager.DefaultConnectionLimit);
ServicePointManager.DefaultConnectionLimit=100;
Клиент = Врап.СоздатьОбъект(HttpClient,handler);
// Использование заголовков не обязательно
// В данном случае это пример их использования
Клиент.DefaultRequestHeaders.Connection.Add("keep-alive");
CacheControl=Врап.СоздатьОбъект("System.Net.Http.Headers.CacheControlHeaderValue");
CacheControl.MaxAge = Врап.ПолучитьТип("System.TimeSpan").Zero;
Клиент.DefaultRequestHeaders.CacheControl = CacheControl;
Клиент.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,*/*");
Клиент.DefaultRequestHeaders.Add("Accept-Language", "ru-Ru");
Клиент.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
uriSources =ПолучитьСтрокуЗапроса("http://localhost.fiddler:40320/api/values/GetIdAsync/");
Клиент.BaseAddress =Врап.СоздатьОбъект("System.Uri",uriSources);
Выполнитель=Врап.ПолучитьАсинхронныйВыполнитель();
ДобавитьОбработчик Выполнитель.ПриОкончанииВыполненияЗадачи, ПриОкончанииВыполнения;
ВыполненоЗадач=0;
МассивОтветов=новый массив;
stopWatch = Врап.СоздатьОбъект("System.Diagnostics.Stopwatch");
stopWatch.Start();
Для сч=1 По 100 Цикл
Задача=Клиент.GetStringAsync(СокрЛП(сч));
Выполнитель.Выполнить(задача,ТекущаяДата());
КонецЦикла;
КонецПроцедуры
Процедура ВывестиВремя(stopWatch,толькоВремя=ложь)
ts = stopWatch.Elapsed;
String=Врап.ПолучитьТип("System.String");
// Format and display the TimeSpan value.
elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10,0);
Сообщить(elapsedTime);
Если толькоВремя Тогда
возврат
КонецЕсли;
Для каждого стр Из МассивОтветов Цикл
сообщить(стр);
КонецЦикла;
КонецПроцедуры
Процедура ПриОкончанииВыполнения(Задача,ДанныеКЗадаче)
// Обязательно нужно отлавливать ошибку в 1С
// Иначе она передается в .Net где обрабатывается там
ВыполненоЗадач=ВыполненоЗадач+1;
Попытка
Если (Задача.IsFaulted) Тогда // Ошибка выполнения
Сообщить("Ошибка "+Врап.ВСтроку(Задача.Exception));
Сообщить("Данные к задаче "+Врап.ВСтроку(ДанныеКЗадаче));
иначе
//Сообщить("=====Выполнена задача ====");
//Сообщить("Данные к задаче "+Врап.ВСтроку(ДанныеКЗадаче));
//Сообщить(Врап.ВСтроку(Задача.Result));
МассивОтветов.Добавить(Задача.Result);
Если ВыполненоЗадач=100 Тогда
ВывестиВремя(stopWatch)
КонецЕсли;
КонецЕсли;
Исключение
Сообщить("Ошибка в процедуре");
Сообщить(ОписаниеОшибки());
КонецПопытки
КонецПроцедуры
Если бы при синхронном выполнении нам понадобилось бы минимум 100 сек, то при асинхронном уходит порядка 1,5 сек.
Отдельно нужно отметить использование ServicePointManager. Более подробно можно посмотреть здесь
Почему одновременно происходит только два соединения с сайтом
Есть еще вариант дождаться всех запросов
лист=Врап.СоздатьОбъект("System.Collections.Generic.List`1[System.Threading.Tasks.Task]");
Для сч=1 по 10 Цикл
Задача=Клиент.GetStringAsync(СокрЛП(сч));
лист.Add(задача);
КонецЦикла;
Task=Врап.ПолучитьТип("System.Threading.Tasks.Task");
массив=лист.ToArray();
Task.WaitAll(массив);
Для каждого задача из лист Цикл
Сообщить(задача.Result);
КонецЦикла
Но здесь нужно учитывать, что например в выполнитель испольует контекс синхронизации, что может приводить к блокировкам https://habrahabr.ru/post/257221/
Проверил асинхронные методы HttpClient не зависят от контекста синхронизации.
Перед тем как перейти к использованию multipart/form-data, покажу примеры отправки Post запросов.
Процедура ЗакрытьРесурс(Ресурс)
Врап.ПолучитьИнтерфейс(Ресурс,"IDisposable").Dispose();
КонецПроцедуры
Функция ПолучитьСтрокуОтвета(стрОриг)
Стр=СтрЗаменить(стрОриг,"""","");
возврат СтрЗаменить(стр,"\r\n",Символы.ПС);
КонецФункции // ПолучитьСтрокуОтвета()
Функция ПолучитьСтрокуЗапроса(uriSources)
Если не ФлИспользоватьФиддлер Тогда
возврат СтрЗаменить(uriSources,".fiddler","");
КонецЕсли;
Возврат uriSources
КонецФункции
Функция ВыполнитьПост(uriSources,Клиент)
// Контент=Врап.СоздатьОбъект("System.Net.Http.StringContent","Тестовая Строка",Encoding.UTF8,"text/plain");
Контент=Врап.СоздатьОбъект("System.Net.Http.StringContent","Тестовая Строка "+uriSources);
резулт=Клиент.PostAsync(uriSources,Контент).Result;
Сообщить("===================================");
Сообщить(резулт.IsSuccessStatusCode);
Сообщить(Врап.Встроку(резулт.StatusCode));
стр=резулт.Content.ReadAsStringAsync().Result;
Сообщить(ПолучитьСтрокуОтвета(стр));
КонецФункции // ВыполнитьПост()
Процедура TestGetНажатие(Элемент)
// Вставить содержимое обработчика.
HttpClient=Врап.ПолучитьТипИзСборки("System.Net.Http.HttpClient","System.Net.Http.dll");
HttpUtility=Врап.ПолучитьТипИзСборки("System.Web.HttpUtility","System.Web.dll");
Попытка
uriSources =ПолучитьСтрокуЗапроса("http://localhost.fiddler:40320/api/values/");
handler = врап.СоздатьОбъект(HttpClientHandler);
cookieContainer = Врап.СоздатьОбъект("System.Net.CookieContainer");
handler.AutomaticDecompression=Врап.OR(DecompressionMethods.GZip,DecompressionMethods.Deflate);
// Используем cookieContainer для задания куков со стороны клиента. Куки со стороны сервера автоматически сохраняются.
cookieContainer.Add(Врап.СоздатьОбъект("System.Net.Cookie","TestCookie", "TruLyaLya", "/", "localhost"));
handler.CookieContainer=cookieContainer;
handler.UseCookies=истина;
Клиент = Врап.СоздатьОбъект(HttpClient,handler);
DefaultRequestHeaders=Клиент.DefaultRequestHeaders;
DefaultRequestHeaders.Connection.Add("keep-alive");
CacheControl=Врап.СоздатьОбъект("System.Net.Http.Headers.CacheControlHeaderValue");
CacheControl.MaxAge = Врап.ПолучитьТип("System.TimeSpan").Zero;
DefaultRequestHeaders.CacheControl = CacheControl;
DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,*/*");
DefaultRequestHeaders.Add("Accept-Language", "ru-Ru");
DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
//Использум BaseAddress, что бы в дальнейшем задавать адрес только ресурса
Клиент.BaseAddress =Врап.СоздатьОбъект("System.Uri",uriSources);
Стр=Клиент.GetStringAsync("GetHeaders").Result;
// В данном случае запрос отправится
//
Сообщить(ПолучитьСтрокуОтвета(стр));
ВыполнитьПост("SendStr",Клиент);
ВыполнитьПост("SendStr2",Клиент);
ЗакрытьРесурс(Клиент);
Исключение
Сообщить(ОписаниеОшибки());
Если Врап.ПоследняяОшибка<>Неопределено Тогда
ПоказатьПредупреждение(,Врап.ПоследняяОшибка.Message);
КонецЕсли;
Врап.ВывестиПоследнююОшибку();
КонецПопытки
КонецПроцедуры
Процедура ПослатьStreamНажатие(Элемент)
// Вставить содержимое обработчика.
uriSources =ПолучитьСтрокуЗапроса("http://localhost.fiddler:40320/api/values/SendStream");
Клиент=Врап.СоздатьОбъект(HttpClient);
// Создаем поток в памяти и записываем в него данные строки в кодировке UTF8
поток=врап.СоздатьОбъект(MemoryStream,Encoding.UTF8.GetBytes("Отсылаемая строка"));
Контент=Врап.СоздатьОбъект(StreamContent,поток);
резулт=Клиент.PostAsync(uriSources,Контент).Result;
Сообщить("===================================");
Сообщить(резулт.IsSuccessStatusCode);
Сообщить(Врап.Встроку(резулт.StatusCode));
стр=резулт.Content.ReadAsStringAsync().Result;
Сообщить(ПолучитьСтрокуОтвета(стр));
ЗакрытьРесурс(Клиент);
КонецПроцедуры
Иногда нужно не только отправить но и получить куки
// Вставить содержимое обработчика.
cookieContainer = Врап.СоздатьОбъект("System.Net.CookieContainer");
handler=Врап.СоздатьОбъект(HttpClientHandler);
handler.AutomaticDecompression=Врап.OR(DecompressionMethods.GZip,DecompressionMethods.Deflate) ;
handler.CookieContainer=cookieContainer;
Клиент = Врап.СоздатьОбъект(HttpClient,handler);
uriSources ="http://www.telerik.com";
Ури=Врап.СоздатьОбъект("System.Uri",uriSources);
Клиент.BaseAddress =Ури;
Стр=Клиент.GetStringAsync("/UpdateCheck.aspx?isBeta=False").Result;
// Пострим все куки присоединенные по этому аресу
Куки = cookieContainer.GetCookies(Ури);
Для каждого кук из Куки Цикл
Сообщить(кук.Name + ": " + кук.Value);
КонецЦикла;
// Можем получить конкретное значение
кук=Куки.get_Item("sid");
Сообщить(кук.Name + ": " + кук.Value);
Отправляемый контекст можно задавать пятью способами
- MultipartFormDataContent,
- StreamContent,
- StringContent,
- ByteArrayContent,
- FormUrlEncodedContent
Выбирайте тот, который удобен. FormUrlEncodedContent это аналог отправики данных Form при Post Submit.
Теперь перейдем к отправке multipart/form-data.
Процедура Multi_PartНажатие(Элемент)
// Вставить содержимое обработчика.
uriSources =ПолучитьСтрокуЗапроса("http://localhost.fiddler:40320");
//uriSources ="http://localhost:40320";
HttpClient=Врап.ПолучитьТипИзСборки("System.Net.Http.HttpClient","System.Net.Http.dll");
MultipartFormDataContent=Врап.ПолучитьТип("System.Net.Http.MultipartFormDataContent");
Клиент = Врап.СоздатьОбъект(HttpClient);
Контент = Врап.СоздатьОбъект(MultipartFormDataContent);
Клиент.BaseAddress =Врап.СоздатьОбъект("System.Uri",uriSources);
// Вариант отправки Ключ-Значение
Значения = Врап.СоздатьОбъект("System.Collections.Generic.Dictionary`2[System.String,System.String]");
Значения.Add("Name", "name");
Значения.Add("id", "id");
// content.Add(new FormUrlEncodedContent(values));
Для каждого КлючЗначение из Значения Цикл
Контент.Add(Врап.СоздатьОбъект("System.Net.Http.StringContent",КлючЗначение.Value),КлючЗначение.Key);
КонецЦикла;
// Вариант отправки двоичных данных из массива
Encoding=Врап.ПолучитьТип("System.Text.Encoding");
СтроковыйКонтент =Врап.СоздатьОбъект("System.Net.Http.ByteArrayContent",Encoding.UTF8.GetBytes("Тестовая строка"));
ContentDisposition=Врап.СоздатьОбъект("System.Net.Http.Headers.ContentDispositionHeaderValue","form-data");
ContentDisposition.FileName ="ПростоСтрока";
ContentDisposition.Name ="attachment";
СтроковыйКонтент.Headers.ContentDisposition = ContentDisposition;
СтроковыйКонтент.Headers.ContentType = Врап.СоздатьОбъект("System.Net.Http.Headers.MediaTypeHeaderValue","text/plain");
Контент.Add(СтроковыйКонтент);
// Вариант отправки двоичных данных из файла
ИмяФайла ="C:/ТестXML";
ПотокФайла =Врап.ПолучитьТип("System.IO.File").OpenRead(ИмяФайла);
ФайловыйКонтент =Врап.СоздатьОбъект("System.Net.Http.StreamContent",ПотокФайла);
ContentDisposition=Врап.СоздатьОбъект("System.Net.Http.Headers.ContentDispositionHeaderValue","form-data");
ContentDisposition.FileName = Врап.ПолучитьТип("System.IO.Path").GetFileName(ИмяФайла);
ФайловыйКонтент.Headers.ContentDisposition = ContentDisposition;
ФайловыйКонтент.Headers.ContentType = Врап.СоздатьОбъект("System.Net.Http.Headers.MediaTypeHeaderValue","application/octet-stream");
Контент.Add(ФайловыйКонтент);
// Вариант отправки двоичных данных из файла но более краткий
ПотокФайла2 =Врап.ПолучитьТип("System.IO.File").OpenRead(ИмяФайла);
ФайловыйКонтент2 =Врап.СоздатьОбъект("System.Net.Http.StreamContent",ПотокФайла2);
Контент.Add(ФайловыйКонтент2,"attachment","TestXml");
requestUri = "api/values/SendFiles";
Результат = Клиент.PostAsync(requestUri, Контент).Result;
стр = Результат.Content.ReadAsStringAsync().Result;
Сообщить(стр);
ЗакрытьРесурс(Клиент);
ЗакрытьРесурс(Контент);
ЗакрытьРесурс(ПотокФайла);
// Вот как выглядит отправляемый запрос
//POST http://localhost:40320/api/values/SendFiles HTTP/1.1
//Content-Type: multipart/form-data; boundary="9f2d525a-7383-46ab-8fc7-419d73486c02"
//Host: localhost:40320
//Content-Length: 811
//Expect: 100-continue
//Connection: Keep-Alive
//--9f2d525a-7383-46ab-8fc7-419d73486c02
//Content-Type: text/plain; charset=utf-8
//Content-Disposition: form-data; name=Name
//name
//--9f2d525a-7383-46ab-8fc7-419d73486c02
//Content-Type: text/plain; charset=utf-8
//Content-Disposition: form-data; name=id
//id
//--9f2d525a-7383-46ab-8fc7-419d73486c02
//Content-Disposition: form-data; filename="=?utf-8?B?0J/RgNC+0YHRgtC+0KHRgtGA0L7QutCw?="; name=attachment
//Content-Type: text/plain
//Тестовая строка
//--9f2d525a-7383-46ab-8fc7-419d73486c02
//Content-Disposition: form-data; filename="=?utf-8?B?0KLQtdGB0YJYTUw=?="
//Content-Type: application/octet-stream
//12345
//--9f2d525a-7383-46ab-8fc7-419d73486c02
//Content-Disposition: form-data; name=attachment; filename=TestXml; filename*=utf-8''TestXml
//12345
//--9f2d525a-7383-46ab-8fc7-419d73486c02--
КонецПроцедуры
Запрос достаточно просто отправить. Вот как это приходится делать на чистом 1С Передача файлов и данных на веб-сервер средствами 1С:Предприятие 8.X методом POST
Стоит отметить использование
handler.AutomaticDecompression=Врап.OR(DecompressionMethods.GZip,DecompressionMethods.Deflate);
который позволяет хорошо сжимать HTML странцы. Например
uriSources ="https://msdn.microsoft.com/en-us/library/system.net.decompressionmethods(v=vs.110).aspx";
handler = врап.СоздатьОбъект(HttpClientHandler);
handler.AutomaticDecompression=Врап.OR(DecompressionMethods.GZip,DecompressionMethods.Deflate) ;
Клиент=Врап.СоздатьОбъект(HttpClient,handler);
Стр=Клиент.GetStringAsync(uriSources).Result;
Сжимает трафик в 5 раз.
Content-Length: 17129 упакованной, против 87 624 неупакованной
К сожалению, объем получился большим. Поэтому продолжение использования HTTPClient и парсинг сайтов выделю в отдельную статью. Так будет проще разбираться.