node-formidable / formidable

The most used, flexible, fast and streaming parser for multipart form data. Supports uploading to serverless environments, AWS S3, Azure, GCP or the filesystem. Used in production.
MIT License
7k stars 680 forks source link

FormidableError: no parser found #870

Closed ajmeese7 closed 1 year ago

ajmeese7 commented 1 year ago

Support plan

Context

What are you trying to achieve or the steps to reproduce?

const formidable = require("formidable");

const parseFormData = (req, { maxFieldsSize, maxFileSize }) => {
    // Occurs regardless of whether I use the commented-out syntax or the active one
    // const form = new formidable.IncomingForm();
    // form.maxFieldsSize = maxFieldsSize;
    // form.maxFileSize = maxFileSize;
    // form.multiple = true;

    const form = formidable({
        multiple: true,
        maxFieldsSize,
        maxFileSize,
    });

    return new Promise((resolve, reject) => {
        form.parse(req, (err, fields, files) => {
            return err ? reject(err) : resolve({ fields, files });
        });
    });
};

What was the result you got?

FormidableError: no parser found, with a code of 1003 and an httpCode of 415.

This then gives the Express error:

RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: 1003

What result did you expect?

I expect to be able to parse the passed file, although I am currently unsure of how to do so. I am working on an inherited project and this seems to have worked before, and researching the error message has not lead to a solution.

Any help would be greatly appreciated!

GrosSacASac commented 1 year ago

How are you sending the form ?

ajmeese7 commented 1 year ago

@GrosSacASac these functions are the parents of the function above:

/**
 * Middleware for handling HTTP requests
 */
const parseFields = (config) => (req, res) => {
    if (["get", "head"].indexOf(req.method.toLowerCase()) !== -1) {
        return Promise.resolve(parseGet(req));
    }

    const json = parseJson(req);
    if (json) {
        return Promise.resolve(json);
    }

    return parseFormData(req, config);
};

/**
 * Creates the middleware
 */
const createMiddleware = (core) => {
    const parse = parseFields(core.config("express"));

    return (req, res, next) =>
        parse(req, res)
            .then(({ fields, files }) => {
                req.fields = fields;
                req.files = files;

                next();
            })
            .catch((error) => {
                core.logger.warn(error);
                req.fields = {};
                req.files = {};

                next(error);
            });
};

const middleware = createMiddleware(core);
GrosSacASac commented 1 year ago

That is not what I asked

ajmeese7 commented 1 year ago

Could you elaborate?

You wanted to know how the form is sent, and it is sent via parseFields in the code above. The middleware variable is used by the Express Router, which sends the form:

const router = express.Router();
router.use(middleware);
GrosSacASac commented 1 year ago

parseFields is server side, how are you sending the form client side

ajmeese7 commented 1 year ago

This is the client-side code that is used:

/**
 * Writes a file.
 *
 * @param {String|VFSFile} path The path to write
 * @param {ArrayBuffer|Blob|String} data The data
 * @param {VFSMethodOptions} [options] Options
 * @returns {Promise<Number>} File size
 */
export const writefile = (adapter, mount) =>
    (path, data, options = {}) => {
        const binary = data instanceof ArrayBuffer || data instanceof Blob
            ? data
            : new Blob([data], { type: "application/octet-stream" });

        return adapter.writefile(pathToObject(path), binary, options, mount);
    };

// NOTE: This is the `adapter` function that is referenced above,
// it is in another file but it is called appropriately. It is in a JSON
// format because it is exported as part of an object
writefile: ({ path }, data, options = {}) => {
        const formData = new FormData();
        formData.append("upload", data);
        formData.append("path", path);
        formData.append("options", options);

        // This is where the server-side code above is called
        return request("writefile", formData, undefined, {
            onProgress: options.onProgress,
            xhr: Boolean(options.onProgress),
        });
    },
GrosSacASac commented 1 year ago

The error means the content type is not recognized amongst the 3 that can be parsed by formidable.

GrosSacASac commented 1 year ago

So I don't know what your 'request' function does but it probably does not set the correct content type header

GrosSacASac commented 1 year ago

Inside parseFormData , what do you get if you console.log(req.headers) ?

oarsoy commented 1 year ago

This issue is just started in my next.js project by self. I think another library cause this bug but I still couldn't find the conflict.

ajmeese7 commented 1 year ago

@oarsoy that was my problem, the issue is the deepmerge library, which merges a FormData object into an empty object, thus losing all the data. Transitioning to the merge-deep library solved my issue, so this repository isn't the underlying cause!

Although I do recommend having some more informative warning when this is the case @GrosSacASac, just from a user's perspective. It took me a LONG time to figure out what was going wrong, only to realize that the issue wasn't even in my code.

GrosSacASac commented 1 year ago

Noted. @oarsoy , what do you get with console.log(req.headers) ?

aderchox commented 1 year ago

@GrosSacASac Can you please check what's wrong in my case? I get the same error and I've realized that as you've said, this is due to content-type: text/plain (among request headers in devtools network section). However, even adding app.use(express.text()) to my express code doesn't resolve it, so I wonder what else I should do?

This is frontend:

  const response = await axios.patch("http://localhost:1233/items/tempid", "Hi Tom", {
    headers: { "Content-Type": "text/plain" },
  });

(Although this is a patch call, I've changed the endpoint to POST too and made a request with text/plain content-type and again got this same error, so is not an issue with patch vs post). For some reasons, in the http://localhost:1233/items/:id endpoint, I both do uploads (using Formidable) and also process text data:

app.patch("/items/:id", (req, res, next) => {
//...
  form.parse(req, (err, fields, files) => {
    console.log({ err }); // <---
    //...
  })
});

[0] { [0] err: FormidableError: no parser found [0] at IncomingForm.writeHeaders (D:\...

I want Formidable to skip/ignore the request when there are no files/fields and request is not a form-data at all. But I don't know how to get it to do that. Maybe I should check the content-type manually? Thanks in advance.

aderchox commented 1 year ago

Yeah it wasn't an issue, I could both check content-type, and also I could just wrap the text in formData too using a field.

1AT1 commented 11 months ago

@oarsoy that was my problem, the issue is the deepmerge library, which merges a FormData object into an empty object, thus losing all the data. Transitioning to the merge-deep library solved my issue, so this repository isn't the underlying cause!

Although I do recommend having some more informative warning when this is the case @GrosSacASac, just from a user's perspective. It took me a LONG time to figure out what was going wrong, only to realize that the issue wasn't even in my code.

how did you transition to the merge-deep library