Прикрепление файлов к задаче JIRA из 1С

1. TheOldGuard 10 09.06.22 15:17 Сейчас в теме
Всем большой привет!
Занимаюсь разработкой интеграции 1С с JIRA на основе проекта Интеграция между Atlassian Jira и 1С:Предприятие 8.3 используя Jira REST API.
Во время разработки того проекта, у API JIRA не было возможности прикреплять файлы, а сейчас есть JIRA_API/attachments.

При создании запроса через postman на аналогии статьи с confluence все получилось хорошо.
Указал заголовки:
X-Atlassian-Token: nocheck
Произвел Basic аутентификацию

В Body добавил файл с ключом file.
По итогу вернулся ответ 200 с телом, в котором описывается прикрепленный файл.

Решил посмотреть snippet:
POST /rest/api/2/issue/TEKITPL-51/attachments HTTP/1.1
Host: <путь к хосту>
Authorization: Basic <Ba se64 Login:Password>
X-Atlassian-Token: nocheck
Cookie: <Cookie>
Content-Length: 223 
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="text.txt"
Content-Type: <Content-Type header here>

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Показать


После успешного запроса, решил адаптировать запрос под 1С, нашел стати по этой теме:
Обмен с HTTP сервисом файлами по формату multipart/form-data
Загрузка файлов на сервер с использованием HTTP-сервиса 1С (multipart/form-data).
Обмен с HTTP сервисом файлами по формату multipart/form-data

Задача стояла следующая для прикрепленных файлов (БСП) в документе по кнопке отправлять эти файлы в JIRA.
Начал с простого, прикрепил txt файл к документу, чтобы не шаманить в content type.
Покопался в документации БСП, так как сам являюсь начальным программистом на 1С, нашел модуль РаботаСФайлами и написал простенькие функции.
И так что у меня по итогу есть:

Процедура, которая возвращает массив присоединенных файлов:
&НаСервере
Функция ПолучитьДанныеФайловНаСервере()
	
	ПрисоединенныеФайлы = Новый Массив;
	ДанныеФайлов = Новый Массив;
	
	РаботаСФайлами.ЗаполнитьПрисоединенныеФайлыКОбъекту(ВладелецФайла, ПрисоединенныеФайлы);
	Если ПрисоединенныеФайлы.Количество() > 0 Тогда	
		Для Каждого ПрисоединенныйФайл из ПрисоединенныеФайлы Цикл
			ДанныеПрисоединенногоФайла = РаботаСФайлами.ДанныеФайла(ПрисоединенныйФайл);
			ДанныеФайлов.Добавить(ДанныеПрисоединенногоФайла);
		КонецЦикла;	
	КонецЕсли;
	
	Возврат ДанныеФайлов;
	
КонецФункции
Показать


И собственно мой код по отработке этих файлов, о нем поподробнее:
&НаСервере
Процедура ОтработатьПрисоединенныеФайлыНаСервере()
	
	//Здесь я получаю массив с прикрепленными файлами
	ДанныеФайлов = ПолучитьДанныеФайловНаСервере();
	
	Если ДанныеФайлов.Количество() > 0 Тогда
		
		//Проверяю заполнение реквизитов в документе дял интеграции с JIRA
		Отказ = Ложь;
		ПроверитьЗаполнениеОсновныхРеквизитовJIRA(Отказ);
		Если Отказ Тогда
			Возврат;
		КонецЕсли;
		
		//Получаю из собственного РС ОбщиеНастройки параметры для подключения
		//
		//ПараметрыJIRA.Протокол - протокол для соединения
		//ПараметрыJIRA.Сервер - сервер, где у меня равзернута JIRA
		//ПараметрыJIRA.Порт - Порт для подключения по умолчанию для JIRA 8080
		//ПараметрыJIRA.RESTAPI - ссылка на rest API
		//ПараметрыJIRA.КлючJIRA - ключ проекта JIRA
		//ПараметрыJIRA.Пользователь
		//ПараметрыJIRA.Пароль
		//
		ПараметрыJIRA = ПолучитьПараметрыJIRAНаСервере();
		
		//Подключаюсь по HTTP к проекту и получаю Cookie, до этого этим не занимался, думал проблема в этом
		Cookie = ПолучитьКукиJIRA(Отказ, ПараметрыJIRA);
		
		//Начинаю перебирать файлы для отправки по http и прикреплению к задаче
		Для Каждого ПрисоединенныйФайл  из ДанныеФайлов Цикл
			
			//Так как в отличие от Postman файл находится в двоичных данных пришлось почитать rfc7578 и понять, что для передачи таких файлов, чтобы не сохранять их на локалку
			//а у меня все работает на сервере, необходимо пользоваться методом multipart/form-data
			
			//Получаю двоичные данные файла
			ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(ПрисоединенныйФайл.СсылкаНаДвоичныеДанныеФайла);
			
			//Проверяю не пустой ли файл
			Если ДвоичныеДанныеФайла <> Неопределено Тогда	
				
				//Получаю значение для заголовка авторизации base64
				КодированнаяСтрока = ЗашифроватьBase64Сервер(ПараметрыJIRA.Пользователь+":"+ПараметрыJIRA.Пароль);
				Авторизация = "Basic " + КодированнаяСтрока;
				
				//Определяю Boundary
				Boundary = "------------------------------63a4b224c59f";
				
				//Проверяю какой у меня стоит протокол в настройках и при необходимости устанавливаю ssl
				Если ПараметрыJIRA.Протокол="https://" Тогда
					ssl = Новый ЗащищенноеСоединениеOpenSSL(Неопределено, Неопределено);
				Иначе
					ssl = Неопределено;
				КонецЕсли;
				
				//Новое соединение
				Соединение = Новый HTTPСоединение(
				ПараметрыJIRA.Сервер, 	//Сервер
				ПараметрыJIRA.Порт, 	//Порт
				,,,						//Пользователь/Пароль/Прокси
				1000, 					//Таймаут
				ssl 					//Защищенное соединение
				);
				
				//Для отладки создаваемового файла генерирую имя нового файла
				Каталог = КаталогВременныхФайлов();
				ИмяФайла = Строка(Новый УникальныйИдентификатор) + ".txt";
				ИмяВременногоФайла = Каталог + ИмяФайла;
								
				#Область ПотокВерсияПервая
				
				//Создаю новый поток, куда пишу тело запроса
				
				ПотокТело = Новый ПотокВПамяти();
				ЗаписьДанных = Новый ЗаписьДанных(ПотокТело); 
				ЗаписьДанных.ЗаписатьСтроку(Boundary);
				ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename="""+ ПрисоединенныйФайл.ИмяФайла +"""");
				ЗаписьДанных.ЗаписатьСтроку("Content-Type: text/plain; charset=""utf-8""");
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.ЗаписатьСтроку(Boundary);
				ЗаписьДанных.Закрыть();
				
				#КонецОбласти
								
				#Область ПотокВерсияВторая
				
				//Второй вариант создания тела, где я передаю имя поля для записи
								
				ПотокТело = Новый ПотокВПамяти();
				ЗаписьДанных = Новый ЗаписьДанных(ПотокТело); 
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.ЗаписатьСтроку(Boundary);
				ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""field""");
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.ЗаписатьСтроку("attachment");
				ЗаписьДанных.ЗаписатьСтроку(Boundary);
				ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename="""+ ПрисоединенныйФайл.ИмяФайла +"""");
				ЗаписьДанных.ЗаписатьСтроку("Content-Type: text/plain; charset=""utf-8""");
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
				ЗаписьДанных.ЗаписатьСтроку("");
				ЗаписьДанных.ЗаписатьСтроку(Boundary);
				ЗаписьДанных.Закрыть();
				
				#КонецОбласти
				
				//Получаю тело двоичных данных
				ДвоичныеДанныеТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();
				//Записываю файл для отладки
				ДвоичныеДанныеТело.Записать(ИмяВременногоФайла);
				
				
				//Создаю заголовки
				ЗаголовкиHTTP = Новый Соответствие;
				
				//Указываю данные для basic аутентификации, которую определил ранее
				ЗаголовкиHTTP.Вставить("Authorization", Авторизация);
				
				//Для JIRA обязательно указать данный заголовок, так как для метода multipart/form-data действует проверка на XSRF 
				ЗаголовкиHTTP.Вставить("X-Atlassian-Token", "nocheck");
				
				//По идее в rfc сказано, что данный заголовок не обязательный
				ЗаголовкиHTTP.Вставить("Content-Length", XMLСтрока(ДвоичныеДанныеТело.Размер()));
				
				//Указываю boundary
				ЗаголовкиHTTP.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
				
				//Передаю Cookie, опять же это уже я делаю из-за безысходности
				ЗаголовкиHTTP.Вставить("Cookie", Cookie); 												
				
				//Указываю ссылку на API, где "TEKITPL-51" ключ моей задачи, куда собираюсь прикрепить файлы 
				СсылкаНаРесурс = ПараметрыJIRA.RESTAPI + "/issue/TEKITPL-51/attachments";
				
				HTTPЗапрос = Новый HTTPЗапрос(СсылкаНаРесурс,ЗаголовкиHTTP);
				//Устанавливаю тело для запроса
				HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТело);
				
				Попытка
					HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
				Исключение
					ОписаниеОшибки = "Ошибка при выполнении HTTP запроса: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
					ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ОписаниеОшибки);
				КонецПопытки;
				
				//Смотрю что мне вернул запрос
				Сообщить("Нам вернули код: " + HTTPОтвет.КодСостояния);
				Сообщить("Тело результата: " + HTTPОтвет.ПолучитьТелоКакСтроку());
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры
Показать


По итогу выполнения запроса, мне возвращается:
Код ответа: 200
Тело ответа: "[]"
А в задаче JIRA соответственно нечего не прикрепляется

Я предполагаю, что скорее всего дело в data и то, что по сути у меня (data) в кодировке UTF-8 и никак не кодируется. (https://datatracker.ietf.org/doc/html/rfc7578#page-7)

Прошу знающих помочь!
Прикрепленные файлы:
newfile.txt
По теме из базы знаний
Найденные решения
2. Westonline82 09.06.22 16:23 Сейчас в теме
Попробуй разделитель
   //Определяю Boundary
                Boundary = "------------------------------63a4b224c59f";

Заменить на
boundary = СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");

в теле запроса разделитель то же перепиши на, в конце обрати внимание как закрывается разделитель.
 #Область ПотокВерсияПервая
                
                //Создаю новый поток, куда пишу тело запроса
                
                ПотокТело = Новый ПотокВПамяти();
                ЗаписьДанных = Новый ЗаписьДанных(ПотокТело); 
                ЗаписьДанных.ЗаписатьСтроку("--"+Boundary);
                ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename="""+ ПрисоединенныйФайл.ИмяФайла +"""");
                ЗаписьДанных.ЗаписатьСтроку("Content-Type: text/plain; charset=""utf-8""");
                ЗаписьДанных.ЗаписатьСтроку("");
                ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
                ЗаписьДанных.ЗаписатьСтроку("");
                ЗаписьДанных.ЗаписатьСтроку("--"+Boundary+"--"); // вот тут
                ЗаписьДанных.Закрыть();
                
                #КонецОбласти
Показать
TheOldGuard; +1 Ответить
Остальные ответы
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
2. Westonline82 09.06.22 16:23 Сейчас в теме
Попробуй разделитель
   //Определяю Boundary
                Boundary = "------------------------------63a4b224c59f";

Заменить на
boundary = СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");

в теле запроса разделитель то же перепиши на, в конце обрати внимание как закрывается разделитель.
 #Область ПотокВерсияПервая
                
                //Создаю новый поток, куда пишу тело запроса
                
                ПотокТело = Новый ПотокВПамяти();
                ЗаписьДанных = Новый ЗаписьДанных(ПотокТело); 
                ЗаписьДанных.ЗаписатьСтроку("--"+Boundary);
                ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename="""+ ПрисоединенныйФайл.ИмяФайла +"""");
                ЗаписьДанных.ЗаписатьСтроку("Content-Type: text/plain; charset=""utf-8""");
                ЗаписьДанных.ЗаписатьСтроку("");
                ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
                ЗаписьДанных.ЗаписатьСтроку("");
                ЗаписьДанных.ЗаписатьСтроку("--"+Boundary+"--"); // вот тут
                ЗаписьДанных.Закрыть();
                
                #КонецОбласти
Показать
TheOldGuard; +1 Ответить
3. TheOldGuard 10 09.06.22 16:54 Сейчас в теме
(2)
Спасибо огромное и не знаю, чтобы бы без вас делал!
Всю голову уже сломал!!!
4. TheOldGuard 10 09.06.22 17:39 Сейчас в теме
(2)
А почему именно так стало работать, не могли бы рассказать?
5. Westonline82 10.06.22 09:22 Сейчас в теме
(4) На самом деле можно было и твой разделитель использовать, главное что бы тело запроса начиналось с "--"+Разделитель. И заканчивалось "--"+Разделитель+"--". Разделитель это то что ты передал в заголовке
//Указываю boundary
ЗаголовкиHTTP.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
TheOldGuard; +1 Ответить
6. TheOldGuard 10 10.06.22 09:55 Сейчас в теме
(5) Я удивился закрывающим
"--"
так как в rfc7578 и в сниппете postman этого не было.
А есть ли возможность авто определения Content-Type в теле запроса или это уже кем-то реализовано?)
7. Westonline82 10.06.22 09:59 Сейчас в теме
(6) Тут не подскажу, не знаю
Оставьте свое сообщение

Для получения уведомлений об ответах подключите телеграм бот:
Инфостарт бот