Jump to content

Проблема с загрузкой файлов по REST API

Featured Replies

Posted

Не совсем понимаю как мне загружать файлы по REST API. Написал скрипт на python для загрузки, но как бы я не пробовал, ничего не получается. Если кто то делал, можете подсказать как именно отправляли файлы?

Вот скрипт, может я в чем то ошибаюсь? Хотя мы с напарником многое перепробовали уже и пока никаких результатов.

Показать контент

Вот часть кода API, ориентировался на него когда писал скрипт.

Показать контент

Ошибку постоянно получаю эту:

Failed to upload file. Error: {
    "errorCode": "1L296\/B",
    "errorMessage": "NO_FILES"
}

Если вывести \IPS\Request::i()->files вместо 'NO_FILES' выводится последний элемент массива files. Пока не понял логику этого

  • MrShandy changed the title to Проблема с загрузкой файлов по REST API
  • Author
  On 06.01.2024 at 13:17, ryancoolround said:

А если использовать форму multipart/form-data?

Ну заголовками управляет aiohttp, моё дело рулить данными

Вот какие он выставляет

Headers:
<CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '1485', 'Content-Type': 'application/x-www-form-urlencoded')>

 

Насильно выставить multipart/form-data не дало результата

Headers:
<CIMultiDictProxy('Host': 'scrapmechanic.ru', 'Accept': 'application/json', 'Content-Type': 'multipart/form-data', 'Accept-Encoding': 'gzip, deflate', 'User-Agent': 'Python/3.10 aiohttp/3.8.4', 'Authorization': 'Basic токен', 'Content-Length': '1485')>
Response JSON:
{'errorCode': '1L296/B', 'errorMessage': 'NO_FILES'}

 

print(params) чего показывает в upload_file?

там же просто всё - 

isset( \IPS\Request::i()->files ) or !\is_array( \IPS\Request::i()->files ) or empty( \IPS\Request::i()->files ) )

Edited by Desti

  • Author
  On 06.01.2024 at 13:30, Desti said:

print(params) чего показывает в upload_file?

Params:
{'category': 5, 'author': 1, 'title': 'archive.zip', 'description': '<p>This is an archive file.</p>', 'hidden': 1, 'files': ['archive.zip']}
Data:
{'archive.zip': [b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00\x00\x00main.pymRM\x8f\xd30\x10\xbdG\xca\x7f\x18\x89\x83\x1b)\x9b\xb44\x9be#\xf5\xb0\x12 q\xe0\x06\x12\x12\xe2\xe0\xd8\x93\xc6\xda\xc46\xb6\x03t\x11\xff\x1d\xdbqKw\x85\xe5\x83g<\xef\xbd\xf9\x12\xb3V\xc6AO-\xb6M\x9e\xe5Yr\x18\xfc\xbe\xa0u6\xb8\x163\xc1\x01\xc8\xe8\x9c\xb6]][f\xa8\x9e\x91\x8dT\nV\x99\xa5\xa6Z\xd4\\\xfd\x94\x93\xa2\xdc\xd6\x83\x98\xd0\x92<s\xea\x11e\xc0\xe1\xb6i\x18\xc5v\x8b\x03\xbd\xc3\xfb\xbb}\xdb\xf48 \xbf\x7f\xd3\xb3]?\xec}\xac\xa6\x86\xce\xd6\x07\xff\xce3\xf0\x870\xea\xf0\xa8\xcc\x89t\xb0{\xbd/\x01^\xc1\x87\xb7 \x97\xb9G\x03j\x007"\x9cc\xa2\x11T\xc1\x8ej\x998\xf4\xfe\xcf\xa0\xff\xe5 d"\xa4\x8b\x1b\x95\xf1t\xcdm\xfb\x7f\xba\x19\xa3\x15\x91B\x1e/\xac\x89\xc0\t7\xa1\xc7\x13j\xd8(~`\xf5$4\x89LQZ\xd2\xf9\x1c\xc9\xd1wHh\'\x94\x0c\xf1\x9fFa\xc1_*!A#\xa2\xba\x02_!\x12\xc7\xda\xc4\xee\xdc\x8fp\x9e\tw\xa04\xca\xcd\x8bd\x88\xe9IqaeJ:\x94ne\xf8\x93g\xfe\x8eH9\x9a\xebF\x7f\xb9y\x88\x9d\x11O4%\xbc\xaeB\xd5\xb7\rJ\xa68n\xe2 \xabd\x14E\xc5q}\x95\x89\xe2\xb3Es\xf3p\xf4R\xa1\xdc\x8f\xa7`G\xb3\xdeU[\x12u\xf3\xcc\xa0\xd5JZ\x84\xc3e\xb5*\xad\xac\xdb\xf8\xe5*c\xbe\xf6\xb0n\xc1\xd7T\xfc\xb7\x128u4y\x0b\xbf\x99\x03\x9cY*\xf5\xd8\xad\xea\xda\x08\xe96\xe4}(x\xd1a\x03\xfd\xd4\xed\xc2\x18Z;,\xd3t\xaa\x88\x87\xe2d\xf1\x05\x80z\x04\x07\xa7\x12j\x9d\t\xbc3F\x99\x8e\x94\xff\x94\x1c\xferE\xa8\xe0/PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00main.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x07Rx\xa3\x1e\xba\xd9\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Y\x00\x00\x00\xe2\x01\x00\x00\x00\x00']}

 

  • Author
  On 06.01.2024 at 13:30, Desti said:

там же просто всё - 

isset( \IPS\Request::i()->files ) or !\is_array( \IPS\Request::i()->files ) or empty( \IPS\Request::i()->files ) )

Ну я видел это, передаю список, но результат ошибка. Как мне запрос то делать надо?

У тебя в запрос улетает ...api/downloads/files?category=1&author=1&title=archive.zip&description=%3Cp%3EThis+is+an+archive+file.%3C/p%3E&hidden=1&files=archive.zip

Т.е. files должен быть массивом, а уходит строка. Как это починить - не знаю, питон не мое.  

  • Author
  On 06.01.2024 at 13:56, Desti said:

У тебя в запрос улетает ...api/downloads/files?category=1&author=1&title=archive.zip&description=%3Cp%3EThis+is+an+archive+file.%3C/p%3E&hidden=1&files=archive.zip

Т.е. files должен быть массивом, а уходит строка. Как это починить - не знаю, питон не мое.  

Ну допустим так, но надо как то туда передать список? Если это вообще возможно передать в параметрах. Мне бы просто понять как отправлять, хотя бы через postman, а там уже разберусь как на питоне написать

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

  • Author
  On 06.01.2024 at 14:34, Desti said:

files - Keys should be filename (e.g. 'file.txt') and values should be file content

Только при этом ниже имеем:

foreach ( array_keys( Request::i()->files ) as $name )
		{
			$fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] );
			
			Db::i()->insert( 'downloads_files_records', array(
				'record_file_id'	=> $file->id,
				'record_type'		=> 'upload',
				'record_location'	=> (string) $fileObject,
				'record_realname'	=> $fileObject->originalFilename,
				'record_size'		=> $fileObject->filesize(),
				'record_time'		=> time(),
			) );
		}

То есть файлы должны быть и в $_POST? Я не знаю php, но $_POST это же data, а не параметры, правильно? 

Ну и опять же, в параметры невозможно передать dict. Это как минимум не логично

Traceback (most recent call last):
  File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 57, in <module>
    loop.run_until_complete(main())
  File "C:\Users\MrSha\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete
    return future.result()
  File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 52, in main
    await upload_file(data_files)
  File "C:\Users\MrSha\Documents\Projects\ICFileUploader\main.py", line 27, in upload_file
    async with session.post(url, params=params, data=data_files) as response:
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client.py", line 1141, in __aenter__
    self._resp = await self._coro
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client.py", line 508, in _request
    req = self._request_class(
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\aiohttp\client_reqrep.py", line 283, in __init__
    url2 = url.with_query(params)
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 1007, in with_query
    new_query = self._get_str_query(*args, **kwargs) or ""
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 968, in _get_str_query
    query = "&".join(self._query_seq_pairs(quoter, query.items()))
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 931, in _query_seq_pairs
    yield quoter(key) + "=" + quoter(cls._query_var(val))
  File "C:\Users\MrSha\.virtualenvs\ICFileUploader-DZz49JbJ\lib\site-packages\yarl\_url.py", line 946, in _query_var
    raise TypeError(
TypeError: Invalid variable type: value should be str, int or float, got {'archive.zip': [b'PK\x03\x04\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00\x00\x00main.pymRM\x8f\xd30\x10\xbdG\xca\x7f\x18\x89\x83\x1b)\x9b\xb44\x9be#\xf5\xb0\x12 q\xe0\x06\x12\x12\xe2\xe0\xd8\x93\xc6\xda\xc46\xb6\x03t\x11\xff\x1d\xdbqKw\x85\xe5\x83g<\xef\xbd\xf9\x12\xb3V\xc6AO-\xb6M\x9e\xe5Yr\x18\xfc\xbe\xa0u6\xb8\x163\xc1\x01\xc8\xe8\x9c\xb6]][f\xa8\x9e\x91\x8dT\nV\x99\xa5\xa6Z\xd4\\\xfd\x94\x93\xa2\xdc\xd6\x83\x98\xd0\x92<s\xea\x11e\xc0\xe1\xb6i\x18\xc5v\x8b\x03\xbd\xc3\xfb\xbb}\xdb\xf48 \xbf\x7f\xd3\xb3]?\xec}\xac\xa6\x86\xce\xd6\x07\xff\xce3\xf0\x870\xea\xf0\xa8\xcc\x89t\xb0{\xbd/\x01^\xc1\x87\xb7 \x97\xb9G\x03j\x007"\x9cc\xa2\x11T\xc1\x8ej\x998\xf4\xfe\xcf\xa0\xff\xe5 d"\xa4\x8b\x1b\x95\xf1t\xcdm\xfb\x7f\xba\x19\xa3\x15\x91B\x1e/\xac\x89\xc0\t7\xa1\xc7\x13j\xd8(~`\xf5$4\x89LQZ\xd2\xf9\x1c\xc9\xd1wHh\'\x94\x0c\xf1\x9fFa\xc1_*!A#\xa2\xba\x02_!\x12\xc7\xda\xc4\xee\xdc\x8fp\x9e\tw\xa04\xca\xcd\x8bd\x88\xe9IqaeJ:\x94ne\xf8\x93g\xfe\x8eH9\x9a\xebF\x7f\xb9y\x88\x9d\x11O4%\xbc\xaeB\xd5\xb7\rJ\xa68n\xe2 \xabd\x14E\xc5q}\x95\x89\xe2\xb3Es\xf3p\xf4R\xa1\xdc\x8f\xa7`G\xb3\xdeU[\x12u\xf3\xcc\xa0\xd5JZ\x84\xc3e\xb5*\xad\xac\xdb\xf8\xe5*c\xbe\xf6\xb0n\xc1\xd7T\xfc\xb7\x128u4y\x0b\xbf\x99\x03\x9cY*\xf5\xd8\xad\xea\xda\x08\xe96\xe4}(x\xd1a\x03\xfd\xd4\xed\xc2\x18Z;,\xd3t\xaa\x88\x87\xe2d\xf1\x05\x80z\x04\x07\xa7\x12j\x9d\t\xbc3F\x99\x8e\x94\xff\x94\x1c\xferE\xa8\xe0/PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x1d`\xf3V2\xc6\np\xbd\x01\x00\x00*\x03\x00\x00\x07\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00main.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x9d\x82\xe6\x88\x1f\xba\xd9\x01\x07Rx\xa3\x1e\xba\xd9\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Y\x00\x00\x00\xe2\x01\x00\x00\x00\x00']} of type <class 'dict'>

 

  • Author
  On 06.01.2024 at 16:00, Desti said:

image.thumb.png.ced5ae042d0d265a2c834d0680f221db.png

Осталось только превратить содержимое файла в строку. 

Окей, это работает

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

  On 06.01.2024 at 16:06, MrShandy said:

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

@Desti, проверил, текст отправляется без проблем. Байты нет, ошибка нет файлов

Текст:
 

Показать контент

Для байтов:
 

Показать контент

Забавно на самом деле

  • Author
  On 06.01.2024 at 16:15, Desti said:

{'files[archive.zip]': b'PK\x03.... это не строка

Ну а как я должен отправить не текстовую информацию?)

Не, можно впринципе сделать ход конем и отправлять base64, а на сервере это обратно. Вопрос в адекватности таких действий

  • Author

Можно закрывать тему, решил 🙂

Просто заменил строку

$fileObject = \IPS\File::create( 'downloads_Files', $name, $_POST['files'][ $name ] );

на

$fileObject = \IPS\File::create( 'downloads_Files', $name, base64_decode($_POST['files'][ $name ]) );

На стороне клиента (в моём случае приложения на Python) так:

"files[archive.zip]": base64.b64encode(file_content.read()).decode('utf-8')

 

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

  • Author
  On 06.01.2024 at 16:46, Desti said:

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

Какое API такое и решение) Ну если они не смогли даже нормально задокументировать всё, как я должен по другому действовать?

Вариант рабочий, проблем не должен создать. Если найду решение лучше - обязательно напишу здесь, но пока будет так

  • Author

Да, без проблем

import asyncio
import base64
import aiohttp

url = "https://example.com/api/downloads/files"
token = "токен"
params = {
    "category": 5,
    "author": 1,
    "title": "archive.zip",
    "description": "<p>This is an archive file.</p>",
    "hidden": 1,
}
headers = {"Accept": "application/json"}


async def upload_file(data_files: dict):
    auth = aiohttp.BasicAuth(token, "")
    async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
        print("Params:", params)
        print("Data:", data_files)
        async with session.post(url, params=params, data=data_files) as response:
            print("Headers:", response.request_info.headers)
            print("Response JSON:", await response.json())
            print("Response text:", await response.text())
            print("Response status:", response.status)
            print("Response content-type:", response.content_type)
            print("=" * 10)
            if response.ok:
                print("File uploaded successfully.")
            else:
                print("Failed to upload file. Error:", await response.text())


async def main():
    with open("archive.zip", "rb") as file_content:
        data_files = {
            "files[archive.zip]": base64.b64encode(file_content.read()).decode("utf-8")
        }
    await upload_file(data_files)


loop = asyncio.new_event_loop()
loop.run_until_complete(main())

Думаю кому будет надо разберется где что менять :)

Если API поддерживает стандартный метод multipart/form-data, рекомендуется использовать его для более эффективной передачи файлов. Ниже представлен пример использования aiohttp.FormData для достижения этой цели:

import asyncio
import aiohttp

url = "https://example.com/api/downloads/files"
token = "токен"
params = {
    "category": 5,
    "author": 1,
    "title": "archive.zip",
    "description": "<p>This is an archive file.</p>",
    "hidden": 1,
}
headers = {"Accept": "application/json"}


async def upload_file(data_files: dict):
    auth = aiohttp.BasicAuth(token, "")
    async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
        form_data = aiohttp.FormData()
        form_data.add_fields(**params)
        for file_name, file_content in data_files.items():
            form_data.add_field('files', file_content, filename=file_name)
        
        async with session.post(url, data=form_data) as response:
            print("Headers:", response.request_info.headers)
            print("Response JSON:", await response.json())
            print("Response text:", await response.text())
            print("Response status:", response.status)
            print("Response content-type:", response.content_type)
            print("=" * 10)
            if response.ok:
                print("File uploaded successfully.")
            else:
                print("Failed to upload file. Error:", await response.text())


async def main():
    with open("archive.zip", "rb") as file_content:
        data_files = {
            "archive.zip": file_content
        }
    await upload_file(data_files)


loop = asyncio.new_event_loop()
loop.run_until_complete(main())

В этом примере файл добавляется в форму с использованием add_field, и aiohttp самостоятельно управляет кодированием multipart/form-data. Ваши параметры и заголовки добавляются также, как и в предыдущем варианте. Этот метод более эффективен для передачи больших файлов

Если так

import asyncio
import aiohttp

url = "https://example.com/api/downloads/files"
token = "токен"
params = {
    "category": 5,
    "author": 1,
    "title": "archive.zip",
    "description": "<p>This is an archive file.</p>",
    "hidden": 1,
}
headers = {"Accept": "application/json"}


async def upload_file(data_files: dict):
    auth = aiohttp.BasicAuth(token, "")
    async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
        form_data = aiohttp.FormData()
        for key, value in params.items():
            form_data.add_field(key, str(value))
        for file_name, file_content in data_files.items():
            form_data.add_field('files', file_content, filename=file_name)

        async with session.post(url, data=form_data) as response:
            print("Headers:", response.request_info.headers)
            print("Response JSON:", await response.json())
            print("Response text:", await response.text())
            print("Response status:", response.status)
            print("Response content-type:", response.content_type)
            print("=" * 10)
            if response.ok:
                print("File uploaded successfully.")
            else:
                print("Failed to upload file. Error:", await response.text())


async def main():
    with open("archive.zip", "rb") as file_content:
        data_files = {
            "archive.zip": file_content
        }
    await upload_file(data_files)


loop = asyncio.new_event_loop()
loop.run_until_complete(main())

тоже не айс

File "C:\Python312\Lib\site-packages\aiohttp\payload.py", line 377, in size                                                       return os.fstat(self._value.fileno()).st_size - self._value.tell()                                                                                   ^^^^^^^^^^^^^^^^^^^^                                                                                                 
ValueError: I/O operation on closed file    

                                                                                                                                                                   

Edited by Desti

import asyncio
import aiohttp

url = "https://example.com/api/downloads/files"
token = "токен"
params = {
    "category": 5,
    "author": 1,
    "title": "archive.zip",
    "description": "<p>This is an archive file.</p>",
    "hidden": 1,
}
headers = {"Accept": "application/json"}


async def upload_file(file_path: str):
    auth = aiohttp.BasicAuth(token, "")
    async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
        with open(file_path, "rb") as file_content:
            form_data = aiohttp.FormData()
            for key, value in params.items():
                form_data.add_field(key, str(value))
            form_data.add_field('files', file_content, filename=file_path)

            async with session.post(url, data=form_data) as response:
                print("Headers:", response.request_info.headers)
                print("Response JSON:", await response.json())
                print("Response text:", await response.text())
                print("Response status:", response.status)
                print("Response content-type:", response.content_type)
                print("=" * 10)
                if response.ok:
                    print("File uploaded successfully.")
                else:
                    print("Failed to upload file. Error:", await response.text())


async def main():
    file_path = "archive.zip"
    await upload_file(file_path)


loop = asyncio.new_event_loop()
loop.run_until_complete(main())
  • Author

Совсем какой то треш пошел, не проще ли открыть файл, прочитать данные и спокойно закрыть? Зачем его открытым то держать пока запрос выполняется? 

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Ответить в этой теме...

Последние посетители 0

  • No registered users viewing this page.