cancel
Showing results for 
Search instead for 
Did you mean: 

Posting FormData to external REST API

nicorunge
Participant
0 Kudos

Hi CAP Community,

I'm trying to Post a file as FormData to an external rest API. I'm following the documentation here: https://cap.cloud.sap/docs/node.js/services#srv-send

Using fetch it was pretty straight forward and is working fine:

        const imageBuffer = fs.readFileSync('./testfiles/filename.png')

        const form = new FormData()
        form.append('file', imageBuffer, {
            contentType: 'text/plain',
            name: 'file',
            filename: 'filename.png'
        })

        const headers = {
            'Authorization': 'Basic ' + btoa(process.env.LOGIN)
        }

        const url = process.env.URL + '/my/api/path' 

        const response = await fetch(url, {
            method: 'POST',
            headers: headers,
            body: form,
        })

        if (!response.ok) throw new Error(`unexpected response ${response.statusText}`)  
        //hurray, it works

Next I added the api connection information to the package.json:

  "cds": {
    "requires": {
      "myAPI": {
        "kind": "rest",
        "credentials": {
          "url": ...,
          "authentication": "BasicAuthentication",
          "username": ...,
          "password": ...,
          "requestTimeout": 30000
        }
      }
   }
 }

and tried to archive the same using cds.connect and cds.post/cds.send

    this.on('submitFile', async req => {
        const imageBuffer = fs.readFileSync('./testfiles/filename.png')

        const form = new FormData()
        form.append('file', imageBuffer, {
           contentType: 'text/plain',
           name: 'file',
           filename: 'filename.png'
        })

        try {
            const myAPI = await cds.connect.to('myAPI')

            //Does not work
            const response1 = await myAPI.post('/my/api/path', form)

            //Does not work either
            const response2 = await myAPI.send({
               method: 'POST',
               path: '/my/api/path',
               data: form
            })
       } catch (err) {
            console.log("Error message: " + err.message) //Error during request to remote service: Request failed with status code 404
        }
    })

but no success. Does anyone has an idea what I'm doing wrong? Is FormData not supported as payload?

Thanks & Regards
Nico

johannesvogel
Advisor
Advisor
0 Kudos

Hi Nico,

could you try to set the "content-type" header manually with .send?

The default is "application/json" which does not fit your content.

Please also include the error message as it might give a hint what's causing the issue.

Best regards,

Johannes

nicorunge
Participant
0 Kudos

Hi Johannes,

I tried adding the content-type like this:

            
const response2 = await myAPI.tx(req).send({
method: 'POST',
path: '/my/api/path',
data: form,
headers: {
'Content-Type': 'multipart/form-data'
}
})

But it's still returning the timeout message: "Error during request to remote service: Request failed with status code 408"

Here's the full output:

[cds] - POST /browse/submitFile
[cds] - connect to converter > rest {
url: 'XXXXXXXXXXXXXXXXXXX',
requestTimeout: 30000,
authentication: '...',
username: '...',
password: '...'
}
[remote] - Error: Error during request to remote service: Request failed with status code 408
at createError (/workspaces/projects/Converter_CAP/node_modules/axios/lib/core/createError.js:16:15)
at settle (/workspaces/projects/Converter_CAP/node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (/workspaces/projects/Converter_CAP/node_modules/axios/lib/adapters/http.js:269:11)
at IncomingMessage.emit (events.js:412:35)
at endReadableNT (internal/streams/readable.js:1334:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
config: {
url: '/my/api/path',
method: 'post',
proxy: false,
baseURL: 'XXXXXXXXXXXXXXXXXXX',
timeout: 30000,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1
},
request: {
method: 'POST',
url: 'XXXXXXXXXXXXXXXXXXX/my/api/path',
headers: {
Accept: 'application/json,text/plain',
'Content-Type': 'multipart/form-data',
authorization: 'Basic ...',
'accept-language': 'en',
'content-length': 59951,
'User-Agent': 'axios/0.21.4'
}
},
response: {
status: 408,
statusText: 'Request Timeout',
headers: {
date: 'Thu, 06 Jan 2022 10:56:11 GMT',
server: 'Apache/2.4.29 (Ubuntu)',
'content-length': '312',
connection: 'close',
'content-type': 'text/html; charset=iso-8859-1'
},
body: '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n' +
'<html><head>\n' +
'<title>408 Request Timeout</title>\n' +
'</head><body>\n' +
'<h1>Request Timeout</h1>\n' +
'<p>Server timeout waiting for the HTTP request from the client.</p>\n' +
'<hr>\n' +
'<address>Apache/2.4.29 (Ubuntu) Server at XXXXXXXXXXXXXXXXXXX Port 443</address>\n' +
'</body></html>\n'
},
isAxiosError: true,
correlationId: '8c40defc-c917-4b86-9fb5-1d6c223ac59c'
}

best regards
Nico

johannesvogel
Advisor
Advisor
0 Kudos

Hi Nico,

I believe with 6.0.3 we have fixed this issue.

Reason was that the content-length header was calculated wrongly and hence the remote application is waiting for more data to process and finally runs in a timeout.

Best regards,

Johannes

nicorunge
Participant
0 Kudos

Hi johannesvogel,

finally had the time to test and can confirm it works now.

            const response = await myAPI.send({
method: 'POST',
path: apiPath,
data: form,
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'multipart/mixed'
}
})

But this leads me to another question. Is there a way to access the response header? Right now, the response only contains the responded data. But as it is multipart/mixed, I have to parse the response using the returned boundary, which is included in the content-type.

Content-Type: multipart/mixed;boundary=Boundary_1229_117813444_54355453674;charset=UTF-8

Using fetch, I could simply use response.headers.get('content-type'), to get the boundary info. But how to access the headers when using CAP, as response.headers is undefined? Why is it not returning the plain axios response object?

Thanks & regards

Nico

former_member825988
Discoverer
0 Kudos

Hi johannesvogel it seems like the problem with the timeout still exist in version 6.0.3, in our case we solved it calculating the content-length header and passing it to the send.

  var bodyFormData = new FormData();
  bodyFormData.append("var1", var1);
  bodyFormData.append("var2", var2);
  const myAPI = await cds.connect.to(destinationName);
    let response;
    try {
    response = await myAPI.send({
          method: 'POST',
          path: '/someURL',
          data: bodyFormData,
          headers: {
            'Content-Type': 'multipart/form-data',
            'Content-Length': bodyFormData.getLengthSync()
          }
      })
	  }
	  catch (error) {
        throw new Error(
        "MyAPI err: " + error.message
        );
     }

View Entire Topic
johannesvogel
Advisor
Advisor

Hi Nico,

I believe with 6.0.3 we have fixed this issue.

Reason was that the content-length header was calculated wrongly and hence the remote application is waiting for more data to process and finally runs in a timeout.

Best regards,

Johannes

rui_jin
Product and Topic Expert
Product and Topic Expert
0 Kudos

Looks like the issue is not fixed. I'm having the same situation and I plan to send a stream. Feels like there is no way to calculate content length for a stream. I'm using version >7