Skip to content

[Bug] HTTPError 415 when providing files using POST #1426

@HackXIt

Description

@HackXIt

According to psf/requests#1997 when using files in requests, one is not supposed to provide headers, so that the library will take care of the multipart/form-data with the appropriate content-type.

An example: (we wrote a custom extension just to do the XRAY API import)

        # ...
        files = {'file': (os.path.basename(result_file), open(result_file, 'rb'), 'application/xml')}
        return self.post(api_url, files=files, params=params)

Will result in:
requests.exceptions.HTTPError: 415 Client Error: for url: https://[REDACTED]/rest/raven/1.0/api/import/execution/robot?projectKey=TCHCSIPDEV

This is due to the implementation in rest_client.py:

def request(
        self,
        method="GET",
        path="/",
        data=None,
        json=None,
        flags=None,
        params=None,
        headers=None,
        files=None,
        trailing=None,
        absolute=False,
        advanced_mode=False,
    ):
        """

        :param method:
        :param path:
        :param data:
        :param json:
        :param flags:
        :param params:
        :param headers:
        :param files:
        :param trailing: bool - OPTIONAL: Add trailing slash to url
        :param absolute: bool, OPTIONAL: Do not prefix url, url is absolute
        :param advanced_mode: bool, OPTIONAL: Return the raw response
        :return:
        """
        url = self.url_joiner(None if absolute else self.url, path, trailing)
        params_already_in_url = True if "?" in url else False
        if params or flags:
            if params_already_in_url:
                url += "&"
            else:
                url += "?"
        if params:
            url += urlencode(params or {})
        if flags:
            url += ("&" if params or params_already_in_url else "") + "&".join(flags or [])
        json_dump = None
        if files is None:
            data = None if not data else dumps(data)
            json_dump = None if not json else dumps(json)
        self.log_curl_debug(
            method=method,
            url=url,
            headers=headers,
            data=data if data else json_dump,
        )
        headers = headers or self.default_headers # <------- Always provides headers
        response = self._session.request(
            method=method,
            url=url,
            headers=headers, # <------- Should be None when providing files, so that the requests library handles file upload with appropriate headers
            data=data,
            json=json,
            timeout=self.timeout,
            verify=self.verify_ssl,
            files=files,
            proxies=self.proxies,
            cert=self.cert,
        )
        response.encoding = "utf-8"

        log.debug("HTTP: %s %s -> %s %s", method, path, response.status_code, response.reason)
        log.debug("HTTP: Response text -> %s", response.text)
        if self.advanced_mode or advanced_mode:
            return response

        self.raise_for_status(response)
        return response

If we change this to:

        headers = headers or self.default_headers if files is None else None

Then the request will work, as the multipart/form-data is appropriately handled by the requests library.

This is necessary in APIs like XRAY, where one can import result files, which are not JSON.
But it should also be applicable for JSON files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions