Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #975 #976

Closed
wants to merge 1 commit into from
Closed

Fix for issue #975 #976

wants to merge 1 commit into from

Conversation

fr33mang
Copy link

@fr33mang fr33mang commented Jun 14, 2019

Fixes #975 .

Changes proposed in this pull request:

  • files added to aiohttp ConnexionRequest class

@fr33mang fr33mang changed the title #975 fix Fix for issue #975 Jun 14, 2019
@hjacobs
Copy link
Contributor

hjacobs commented Jun 15, 2019

Can you add a test?

@fr33mang
Copy link
Author

fr33mang commented Jun 17, 2019

Can you add a test?

Can you help me with that?
The problem is in the aiohttp...

Schema

      summary: 'Test formData with file type, for file upload'
      operationId: fakeapi.aiohttp_handlers.test_formdata_file_upload
      consumes:
        - multipart/form-data
      parameters:
        - in: formData
          name: formData
          type: file
          description: The file to upload.
      responses:
        200:
          description: OK

Requests

resp = yield from app_client.post('/v1.0/aiohttp_test_formData_file_upload',
                        data={'formData': open('file.txt', 'rb')})   
data = FormData()
data.add_field('formData', open('file.txt', 'rb'), filename='file.txt')

resp = yield from app_client.post('/v1.0/aiohttp_test_formData_file_upload',
                        data=data)

Exception

ERROR    connexion.apis.aiohttp_api:aiohttp_api.py:85 Error handling request
Traceback (most recent call last):
  File "/connexion/connexion/apis/aiohttp_api.py", line 62, in problems_middleware
    response = yield from handler(request)
  File "/connexion/.env/lib/python3.7/site-packages/aiohttp/web_middlewares.py", line 110, in impl
    return await handler(request)
  File "/connexion/connexion/decorators/coroutine_wrappers.py", line 19, in wrapper
    connexion_request = yield from connexion_request
  File "/connexion/connexion/apis/aiohttp_api.py", line 282, in get_request
    data = yield from req.post()
  File "/connexion/.env/lib/python3.7/site-packages/aiohttp/web_request.py", line 597, in post
    field = await multipart.next()
  File "/connexion/.env/lib/python3.7/site-packages/aiohttp/multipart.py", line 538, in next
    await self._read_until_first_boundary()
  File "/connexion/.env/lib/python3.7/site-packages/aiohttp/multipart.py", line 604, in _read_until_first_boundary
    % (self._boundary))
ValueError: Could not find starting boundary b'--e85fae5ba3c048148a0736d5a7267cf2'

@prawn-cake
Copy link
Contributor

@vasiliy-garenskikh
It looks like aiohttp related issue (or integration problem), I found some somewhat old issues:
aio-libs/aiohttp#1701
aio-libs/aiohttp#161

@fr33mang
Copy link
Author

fr33mang commented Jun 19, 2019

@vasiliy-garenskikh
It looks like aiohttp related issue (or integration problem), I found some somewhat old issues:
aio-libs/aiohttp#1701
aio-libs/aiohttp#161

The problem was in use this coroutine:
yield from req.post()
after this:
yield from req.read()

But I can't write aiohttp code to imitate HTML file upload form :(

Swagger:

 /aiohttp_test_formData_file_upload:  
    post:
      summary: 'Test formData with file type, for file upload'
      operationId: fakeapi.aiohttp_handlers.test_formdata_file_upload
      consumes:
        - multipart/form-data
      parameters:
        - in: formData  
          name: file_to_upload
          type: file
          required: true 
          description: The file to upload.
      responses:
        200:
          description: OK
file = open('file.txt', 'rb')

    data = FormData()
    data.add_field('file_to_upload',
            open('file.txt', 'rb'),
            filename='file.txt',
            content_type='multipart/form-data')

    with aiohttp.MultipartWriter('mixed') as mp:
        part = mp.append(file, {"Content-type": 'multipart/form-data'})
        part.set_content_disposition('file_to_upload', filename='secret.txt')

        resp = yield from app_client.post('/v1.0/aiohttp_test_formData_file_upload',
                        data=mp)
                        # data={'file_to_upload': (BytesIO(b'file contents'), 'filename.txt')})                        
                        # data=data)

But I get 400 status: file_to_upload field missed.

Do you know rigth way to send "file" html ?

@prawn-cake
Copy link
Contributor

prawn-cake commented Jun 21, 2019

I haven't checked the codebase for a while, but I can assume that aiohttp files integration is missing
https://github.com/zalando/connexion/blob/master/connexion/apis/aiohttp_api.py#L281

Also there are currently few issues regarding multiform data
#954
#935

At first glance, I couldn't indentify where exactly the problem is.

@@ -278,14 +279,17 @@ def get_request(cls, req):
if req.body_exists:
body = yield from req.read()

data = yield from req.post()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this will never work.
If you post the request, whatever that is because important part is to have something in that, so the condition req_body_exists evaluates to True, you are reading the entire payload from aiohttp.web.Request in line 280 (yield from req.read()). Later on you are attempting to read the payload yet again via req.post(). That's why it fails.

What might have be needed is here is to distinguish between multipart requests and ordinary requests.
I assume that multipart requests might be processed in a way that we extract files and normal fields into separate variables and push'em into ConnexionRequest.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!
Indeed, you can't read aiohttp.web.Request payload twice, since it's a StreamReader object and both methods read from it.
Calling different readers based on the content type sounds like a solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prawn-cake Give it a whirl then :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prawn-cake I will give it a try on my own. We're interested in having it fixed as soon as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kornicameister !
Sorry for not being very active here. I'll check the changes

@kornicameister
Copy link
Contributor

@prawn-cake @vasiliy-garenskikh please check comment I left in a code. I cannot really tell if you noticed that as well, based on your conversation. I am just taking this form top of my head after checking aiohttp codebase

@kornicameister
Copy link
Contributor

kornicameister commented Jul 5, 2019

@prawn-cake @vasiliy-garenskikh I have created PR in my fork.
Currently I have achieved only single file upload via form-data.
I will expand the tests and behavior to multiple files as well as normal form data.

PR -> #987

cc @zawisz @hjacobs

@prawn-cake
Copy link
Contributor

Cool. I left some comments there

@hjacobs
Copy link
Contributor

hjacobs commented Apr 25, 2020

I'm closing this PR for now (as it's pretty old, there are no recent updates, and no tests).

@hjacobs hjacobs closed this Apr 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

File uploading with aiohttp (status 500)
4 participants