How to use superagent to send a FormData object - javascript

I am doing an API request follow by another one to a different server to which I need to pass a file.
Doing the first one is nice and easy. It looks something like this:
if (myFile) {
const data = new FormData()
data.append('myFile', myFile, myFile.name)
myFile = data
}
isomorphicFetch(`${MY_ENDPOINT}`, {
method: 'PATCH',
body: myFile
})
Now, in the server side, I need to pass this into another server. Which I am using superagent for. However I seem to be losing the file in the process. Here is what the code currently looks like:
const fileField = Object.keys(data).pop()
if (fileField === 'myFile') {
res = await request
.patch(`${MY_OTHER_ENDPOINT}`)
.send(data)
.query(query)
}
Take note that data is the body of the previous request, and the FormData object is displayed as an empty object, so I am not sure what to do from here.
On my other server, my file comes back as undefined, however if I do the request straight from the client, it goes through as expected. So how can I forward the FormData object from one server app to the other?

Files are typically posted to servers as part of a Multipart request.
Superagent supports multipart requests like so:
request
.post('/upload')
.attach('image1', 'path/to/felix.jpeg')
.attach('image2', imageBuffer, 'luna.jpeg')
.field('caption', 'My cats')
.then(callback);
Additional information can be found in their documentation:
Superagent Docs - Multipart requests
Specific to your example, you want to make sure that your server is receiving the file and then using the attach function to put the file in your request.

Related

Does there exist a 'good-practice' way to send files without relying on FormData?

I'm currently working on an app with a React Native front-end and Node.js/Express backend. I am perfectly able to upload files using FormData with Content-Type multipart/form-data. The problem I have is that when using FormData, any other data that you wish to send in the body of the request is necessarily converted to a string. This isn't the case when one simply sends a JS object as the body of the request (as long you parse it on the backend of course). I wish to know if there is a good-practice way to send a file/files alongside JSON in a request, without losing the typings of said JSON?
Thanks
Add your data as JSON in a field of the FormData:
const data = {
foo: ["bar", 1]
};
const file = new File(["some content"], "myfile.txt");
const formdata = new FormData();
formdata.append("file", file);
// Send as JSON
formdata.append("data", JSON.stringify(data));
const req = new Request("./", { method: "POST", body: formdata });
// simulate server side getting the response
req.formData().then( (fd) => {
const received_file = fd.get("file");
// parse JSON
const received_data = JSON.parse(fd.get("data"));
console.log({ received_file, received_data });
});
When you insist on sending the image along with other data inside the JSON, then AFAIK your only option is to convert the image to some data type which can be transmitted via JSON.
The most obvious choice would be a string, and what is usually used here is the base64 encoding. It is, however, not very efficient and can cause lag.
What is sometimes done to stay within the JSON domain but still being able to upload images is to create two endpoints. One for the JSON data. One for the image upload in a binary format.

Vue JS how to accept the URLEncoded format

I am trying to accept the URL Encoded format in postman to post some data to the Vue JS app, I am using the below-encoded format, how can I achieve that which npm package should I use?
you can use axios
const axios = require('axios')
const params = new URLSearchParams()
params.append('name', 'Akexorcist')
params.append('age', '28')
params.append('position', 'Android Developer')
params.append('description', 'birthdate=25-12-1989&favourite=coding%20coding%20and%20coding&company=Nextzy%20Technologies&website=http://www.akexorcist.com/')
params.append('awesome', true)
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
axios.post(url, params, config)
.then((result) => {
// Do somthing
})
.catch((err) => {
// Do somthing
})
x-www-form-urlencoded data is sent via HTTP headers
Most HTTP headers are not visible to your front-end JavaScript application. They are only visible to the server responding to the request. You cannot read them directly from JavaScript running in a web browser.
However, there are options...
Change the source; have the POST request changed to a GET and encode the parameters in the URL
A reverse proxy for your application could convert from POST parameters to GET parameters with some additional coding or configuration
Receive the request on your server and feed them into your Vue.js application; use something like php/asp/etc to serve your html instead of static HTML files and embed the posted parameters in the generated HTML page
There may be other options if you are creative, but the simplest is the first - just change the source so it no longer posts data.
I resolved it by adding middleware(server-side code such as .net web API) and then redirected it using query string)

Cloudflare Worker TypeError: One-time-use body

I'm trying to use a Cloudflare Worker to proxy a POST request to another server.
It is throwing a JS exception – by wrapping in a try/catch blog I've established that the error is:
TypeError: A request with a one-time-use body (it was initialized from a stream, not a buffer) encountered a redirect requiring the body to be retransmitted. To avoid this error in the future, construct this request from a buffer-like body initializer.
I would have thought this could be solved by simply copying the Response so that it's unused, like so:
return new Response(response.body, { headers: response.headers })
That's not working. What am I missing about streaming vs buffering here?
addEventListener('fetch', event => {
var url = new URL(event.request.url);
if (url.pathname.startsWith('/blog') || url.pathname === '/blog') {
if (reqType === 'POST') {
event.respondWith(handleBlogPost(event, url));
} else {
handleBlog(event, url);
}
} else {
event.respondWith(fetch(event.request));
}
})
async function handleBlog(event, url) {
var newBlog = "https://foo.com";
var originUrl = url.toString().replace(
'https://www.bar.com/blog', newBlog);
event.respondWith(fetch(originUrl));
}
async function handleBlogPost(event, url) {
try {
var newBlog = "https://foo.com";
var srcUrl = "https://www.bar.com/blog";
const init = {
method: 'POST',
headers: event.request.headers,
body: event.request.body
};
var originUrl = url.toString().replace( srcUrl, newBlog );
const response = await fetch(originUrl, init)
return new Response(response.body, { headers: response.headers })
} catch (err) {
// Display the error stack.
return new Response(err.stack || err)
}
}
A few issues here.
First, the error message is about the request body, not the response body.
By default, Request and Response objects received from the network have streaming bodies -- request.body and response.body both have type ReadableStream. When you forward them on, the body streams through -- chunks are received from the sender and forwarded to the eventual recipient without keeping a copy locally. Because no copies are kept, the stream can only be sent once.
The problem in your case, though, is that after streaming the request body to the origin server, the origin responded with a 301, 302, 307, or 308 redirect. These redirects require that the client re-transmit the exact same request to the new URL (unlike a 303 redirect, which directs the client to send a GET request to the new URL). But, Cloudflare Workers didn't keep a copy of the request body, so it can't send it again!
You'll notice this problem doesn't happen when you do fetch(event.request), even if the request is a POST. The reason is that event.request's redirect property is set to "manual", meaning that fetch() will not attempt to follow redirects automatically. Instead, fetch() in this case returns the 3xx redirect response itself and lets the application deal with it. If you return that response on to the client browser, the browser will take care of actually following the redirect.
However, in your worker, it appears fetch() is trying to follow the redirect automatically, and producing an error. The reason is that you didn't set the redirect property when you constructed your Request object:
const init = {
method: 'POST',
headers: event.request.headers,
body: event.request.body
};
// ...
await fetch(originUrl, init)
Since init.redirect wasn't set, fetch() uses the default behavior, which is the same as redirect = "automatic", i.e. fetch() tries to follow redirects. If you want fetch() to use manual redirect behavior, you could add redirect: "manual" to init. However, it looks like what you're really trying to do here is copy the whole request. In that case, you should just pass event.request in place of the init structure:
// Copy all properties from event.request *except* URL.
await fetch(originUrl, event.request);
This works because a Request has all of the fields that fetch()'s second parameter wants.
What if you want automatic redirects?
If you really do want fetch() to follow the redirect automatically, then you need to make sure that the request body is buffered rather than streamed, so that it can be sent twice. To do this, you will need to read the whole body into a string or ArrayBuffer, then use that, like:
const init = {
method: 'POST',
headers: event.request.headers,
// Buffer whole body so that it can be redirected later.
body: await event.request.arrayBuffer()
};
// ...
await fetch(originUrl, init)
A note on responses
I would have thought this could be solved by simply copying the Response so that it's unused, like so:
return new Response(response.body, { headers: response.headers })
As described above, the error you're seeing is not related to this code, but I wanted to comment on two issues here anyway to help out.
First, this line of code does not copy all properties of the response. For example, you're missing status and statusText. There are also some more-obscure properties that show up in certain situations (e.g. webSocket, a Cloudflare-specific extension to the spec).
Rather than try to list every property, I again recommend simply passing the old Response object itself as the options structure:
new Response(response.body, response)
The second issue is with your comment about copying. This code copies the Response's metadata, but does not copy the body. That is because response.body is a ReadableStream. This code initializes the new Respnose object to contain a reference to the same ReadableStream. Once anything reads from that stream, the stream is consumed for both Response objects.
Usually, this is fine, because usually, you only need one copy of the response. Typically you are just going to send it to the client. However, there are a few unusual cases where you might want to send the response to two different places. One example is when using the Cache API to cache a copy of the response. You could accomplish this by reading the whole Response into memory, like we did with requests above. However, for responses of non-trivial size, that could waste memory and add latency (you would have to wait for the entire response before any of it gets sent to the client).
Instead, what you really want to do in these unusual cases is "tee" the stream so that each chunk that comes in from the network is actually written to two different outputs (like the Unix tee command, which comes from the idea of a T junction in a pipe).
// ONLY use this when there are TWO destinations for the
// response body!
new Response(response.body.tee(), response)
Or, as a shortcut (when you don't need to modify any headers), you can write:
// ONLY use this when there are TWO destinations for the
// response body!
response.clone()
Confusingly, response.clone() does something completely different from new Response(response.body, response). response.clone() tees the response body, but keeps the Headers immutable (if they were immutable on the original). new Response(response.body, response) shares a reference to the same body stream, but clones the headers and makes them mutable. I personally find this pretty confusing, but it's what the Fetch API standard specifies.

get JSON from a Promise

I am using mocha to test a promise that is written in a separate javascript file. I am trying to send data to the promise with a POST request, although I'm not sure what the url should be. Here's what I have so far, using request-promise:
var rp = require('request-promise');
var options = {
method: 'POST',
url: '/algorithm.js',
body: data,
json: true // Automatically stringifies the body to JSON
};
rp(options)
.then(function(body){
count++;
done();
});
The error states that I have an invalid url, although I'm not sure how else to POST to promise inside of a javascript file.
I am trying to send data to the promise with a POST request
You can't do that, at least not directly.
POST requests are for sending data to HTTP servers.
Promises are a JavaScript object for handling asynchronous operations.
These are different things.
algorithm.js needs to either contain code that you can call directly, in which case you should require that code and then call the function.
var algorithm = require("algorithm");
if (algorithm.something()) {
count++;
}
done();
… or it should be server side JavaScript that you need to run an HTTP server for. Once you run the HTTP server, you'll be able to use code like what you wrote in the question, but you'll need to provide an absolute URL since you need to say you are using HTTP and localhost and so on.
var options = {
method: 'POST',
url: 'http://localhost:7878/route/to/algorithm',
body: data,
json: true // Automatically stringifies the body to JSON
};

Receiving submitted POST file Object in server side Javascript

When user sends a multi part form with files/images selected, generally in Meteor or node.js, the server side POST url handler uses this.request or req or request object to detect whether its a POST method or any other and its headers etc but what I don't understand is, where is the actually file located at this request object and how do I retrieve it so that it can be used for image/file upload or certain manipulations at server?
node provides the querystring api to parse strings that look like:
foo=bar&baz=qux&baz=quux&corge
...which is how multi part form data is also sent. Parsing this with that api will return an object:
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
So, you can first detect the method of the request, and if it is POST, you can attach a handler to 'data', get all the data into your own variable, and on 'end' , parse it using querystring:
var qs = require('querystring');
// your request callback function would be something like:
function (request,response){
if(request.method=='POST'){
var body = '';
request.on('data',function(data){
body += data;
//reject requests that have sent too much data (eg 2MB):
if(body.length > 2e6){
// Send HTTP status code for `Request Entity Too Large`:
response.writeHead(413);
response.end();
});
request.on('end', function(){
var form = qs.parse(body);
//use form as object
});
} // end if
} // end handler

Categories

Resources