A Node.js module for parsing form data, especially file uploads.
Se você tiver qualquer tipo de pergunta sobre como fazer, por favor leia o Contributing
Guia e Código de Conduta
documentos.
Para relatórios de bugs e solicitações de recursos, crie uma
issue ou ping @tunnckoCore / @3a1FcBx0
no Twitter.
Este projeto é semanticamente versionado e está disponível como parte da Assinatura Tidelift para nível profissional garantias, suporte aprimorado e segurança. Saiba mais.
Os mantenedores do formidable
e milhares de outros pacotes estão trabalhando
com Tidelift para fornecer suporte comercial e manutenção para o Open Source
dependências que você usa para construir seus aplicativos. Economize tempo, reduza riscos e
melhorar a integridade do código, enquanto paga aos mantenedores das dependências exatas que você
usar.
Verifique VERSION NOTES para obter mais informações sobre os planos v1, v2 e v3, NPM dist-tags e branches.
Este módulo foi inicialmente desenvolvido por @felixge para Transloadit, um serviço focado em upload e codificação de imagens e vídeos. Foi testado em batalha contra centenas de GBs de uploads de arquivos de uma grande variedade de clientes e é considerado pronto para produção e é usado na produção por anos.
Atualmente, somos poucos mantenedores tentando lidar com isso. :) Mais contribuidores são sempre bem-vindos! ❤️ Pule issue #412 que está fechado, mas se você estiver interessado, podemos discuti-lo e adicioná-lo após regras estritas, como ativar o Two-Factor Auth em suas contas npm e GitHub.
- Rápido (~ 900-2500 mb/seg) e analisador multiparte de streaming
- Gravar uploads de arquivos automaticamente no disco (opcional, consulte
options.fileWriteStreamHandler
) - API de plug-ins - permitindo analisadores e plug-ins personalizados
- Baixo consumo de memória
- Tratamento de erros gracioso
- Cobertura de teste muito alta
Este projeto requer Node.js >= 10.13
. Instale-o usando
yarn ou npm.
Nós altamente
recomendamos usar o Yarn quando pensar em contribuir para este projeto.
Este é um pacote de baixo nível e, se você estiver usando uma estrutura de alto nível, pode já estar incluído. Verifique os exemplos abaixo e a pasta examples/.
# v2
npm install formidable
npm install formidable@v2
# v3
npm install formidable@v3
Nota: Em um futuro próximo, a v3 será publicada na dist-tag latest
do NPM.
Versões futuras não prontas serão publicadas nas dist-tags *-next
para a versão correspondente.
Para mais exemplos veja o diretório examples/
.
Analisar um upload de arquivo de entrada, com o
Módulo http
integrado do Node.js.
import http from 'node:http';
import formidable, {errors as formidableErrors} from 'formidable';
const server = http.createServer((req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// analisar um upload de arquivo
const form = formidable({});
form.parse(req, (err, fields, files) => {
if (err) {
// exemplo para verificar um erro muito específico
if (err.code === formidableErrors.maxFieldsExceeded) {
}
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
res.end(String(err));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
});
return;
}
// mostrar um formulário de upload de arquivo
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<h2>With Node.js <code>"http"</code> module</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="multipleFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
server.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...');
});
Existem várias variantes para fazer isso, mas o Formidable só precisa do Node.js Request stream, então algo como o exemplo a seguir deve funcionar bem, sem nenhum middleware Express.js de terceiros.
Ou tente o examples/with-express.js
import express from 'express';
import formidable from 'formidable';
const app = express();
app.get('/', (req, res) => {
res.send(`
<h2>With <code>"express"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="someExpressFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
app.post('/api/upload', (req, res, next) => {
const form = formidable({});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.json({ fields, files });
});
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
Claro, com Koa v1, v2 ou future v3 as coisas
sao muito parecidas. Você pode usar formidable
manualmente como mostrado abaixo ou através
do pacote koa-better-body que é
usando formidable
sob o capô e suporte a mais recursos e diferentes
corpos de solicitação, verifique sua documentação para mais informações.
Nota: este exemplo está assumindo Koa v2. Esteja ciente de que você deve passar ctx.req
que é a solicitação do Node.js e NÃO o ctx.request
que é a solicitação do Koa
objeto - há uma diferença.
import Koa from 'Koa';
import formidable from 'formidable';
const app = new Koa();
app.on('error', (err) => {
console.error('server error', err);
});
app.use(async (ctx, next) => {
if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') {
const form = formidable({});
// não muito elegante, mas é por enquanto se você não quiser usar `koa-better-body`
// ou outros middlewares.
await new Promise((resolve, reject) => {
form.parse(ctx.req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
ctx.set('Content-Type', 'application/json');
ctx.status = 200;
ctx.state = { fields, files };
ctx.body = JSON.stringify(ctx.state, null, 2);
resolve();
});
});
await next();
return;
}
// mostrar um formulário de upload de arquivo
ctx.set('Content-Type', 'text/html');
ctx.status = 200;
ctx.body = `
<h2>With <code>"koa"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="koaFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`;
});
app.use((ctx) => {
console.log('The next middleware is called');
console.log('Results:', ctx.state);
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
O benchmark é bastante antigo, da antiga base de código. Mas talvez seja bem verdade. Anteriormente, os números giravam em torno de ~ 500 mb/s. Atualmente com a mudança para o novo Node.js Streams API, é mais rápido. Você pode ver claramente as diferenças entre as versões do Node.
Observação: um benchmarking muito melhor pode e deve ser feito no futuro.
Benchmark realizado em 8 GB de RAM, Xeon X3440 (2,53 GHz, 4 núcleos, 8 threads)
~/github/node-formidable master
❯ nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js
⬢ Node 8
1261.08 mb/sec
⬢ Node 10
1113.04 mb/sec
⬢ Node 12
2107.00 mb/sec
⬢ Node 13
2566.42 mb/sec
Todos os mostrados são equivalentes.
Por favor, passe options
para a função/construtor, não atribuindo
eles para a instância form
import formidable from 'formidable';
const form = formidable(options);
Veja seus padrões em src/Formidable.js DEFAULT_OPTIONS
(a constante DEFAULT_OPTIONS
).
-
options.encoding
{string} - padrão'utf-8'
; define a codificação para campos de formulário de entrada, -
options.uploadDir
{string} - padrãoos.tmpdir()
; o diretório para colocar os uploads de arquivos. Você pode movê-los mais tarde usandofs.rename()
. -
options.keepExtensions
{boolean} - padrãofalse
; incluir as extensões dos arquivos originais ou não -
options.allowEmptyFiles
{boolean} - padrãofalse
; permitir upload de arquivos vazios -
options.minFileSize
{number} - padrão1
(1byte); o tamanho mínimo do arquivo carregado. -
options.maxFiles
{number} - padrãoInfinity
; limitar a quantidade de arquivos carregados, defina Infinity para ilimitado -
options.maxFileSize
{number} - padrão200 * 1024 * 1024
(200mb); limitar o tamanho de cada arquivo carregado. -
options.maxTotalFileSize
{number} - padrãooptions.maxFileSize
; limitar o tamanho do lote de arquivos carregados. -
options.maxFields
{number} - padrão1000
; limite o número de campos, defina Infinity para ilimitado -
options.maxFieldsSize
{number} - padrão20 * 1024 * 1024
(20mb); limitar a quantidade de memória que todos os campos juntos (exceto arquivos) podem alocar em bytes. -
options.hashAlgorithm
{string | false} - padrãofalse
; incluir checksums calculados para arquivos recebidos, defina isso para algum algoritmo de hash, consulte crypto.createHash para algoritmos disponíveis -
options.fileWriteStreamHandler
{function} - padrãonull
, que por padrão grava no sistema de arquivos da máquina host cada arquivo analisado; A função deve retornar uma instância de um fluxo gravável que receberá os dados do arquivo carregado. Com esta opção, você pode ter qualquer comportamento personalizado em relação a onde os dados do arquivo carregado serão transmitidos. Se você deseja gravar o arquivo carregado em outros tipos de armazenamento em nuvem (AWS S3, armazenamento de blob do Azure, armazenamento em nuvem do Google) ou armazenamento de arquivo privado, esta é a opção que você está procurando. Quando esta opção é definida, o comportamento padrão de gravar o arquivo no sistema de arquivos da máquina host é perdido. -
options.filename
{function} - padrãoundefined
Use-o para controlar newFilename. Deve retornar uma string. Será associado a options.uploadDir. -
options.filter
{function} - função padrão que sempre retorna verdadeiro. Use-o para filtrar arquivos antes de serem carregados. Deve retornar um booleano.
onde a parte pode ser decomposta como
const { originalFilename, mimetype} = part;
Observação: Se este tamanho de campos combinados, ou tamanho de algum arquivo for excedido, um
O evento 'error'
é disparado.
// A quantidade de bytes recebidos para este formulário até agora.
form.bytesReceived;
// O número esperado de bytes neste formulário.
form.bytesExpected;
Observação: use uma variável externa para cancelar todos os uploads no primeiro erro
const options = {
filter: function ({name, originalFilename, mimetype}) {
// manter apenas imagens
return mimetype && mimetype.includes("image");
}
};
Analisa uma request
do Node.js recebida contendo dados de formulário. Se callback
for
fornecido, todos os campos e arquivos são coletados e passados para o retorno de chamada.
const form = formidable({ uploadDir: __dirname });
form.parse(req, (err, fields, files) => {
console.log('fields:', fields);
console.log('files:', files);
});
Você pode substituir esse método se estiver interessado em acessar diretamente o
fluxo de várias partes. Fazer isso desativará qualquer processamento de eventos 'field'
/ 'file'
que ocorreria de outra forma, tornando você totalmente responsável por lidar com o processamento.
Sobre uploadDir
, dada a seguinte estrutura de diretório
project-name
├── src
│ └── server.js
│
└── uploads
└── image.jpg
__dirname
seria o mesmo diretório que o próprio arquivo de origem (src)
`${__dirname}/../uploads`
para colocar arquivos em uploads.
Omitir __dirname
tornaria o caminho relativo ao diretório de trabalho atual. Isso seria o mesmo se server.js fosse iniciado a partir de src, mas não de project-name.
null
usará o padrão que é os.tmpdir()
Nota: Se o diretório não existir, os arquivos carregados são silenciosamente descartados. Para ter certeza de que existe:
import {createNecessaryDirectoriesSync} from "filesac";
const uploadPath = `${__dirname}/../uploads`;
createNecessaryDirectoriesSync(`${uploadPath}/x`);
No exemplo abaixo, escutamos alguns eventos e os direcionamos para o ouvinte data
, para
que você possa fazer o que quiser lá, com base em se é antes do arquivo ser emitido, o valor do
cabeçalho, o nome do cabeçalho, no campo , em arquivo e etc.
Ou a outra maneira poderia ser apenas substituir o form.onPart
como é mostrado um pouco
mais tarde.
form.once('error', console.error);
form.on('fileBegin', (formname, file) => {
form.emit('data', { name: 'fileBegin', formname, value: file });
});
form.on('file', (formname, file) => {
form.emit('data', { name: 'file', formname, value: file });
});
form.on('field', (fieldName, fieldValue) => {
form.emit('data', { name: 'field', key: fieldName, value: fieldValue });
});
form.once('end', () => {
console.log('Done!');
});
// Se você quiser personalizar o que quiser...
form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => {
if (name === 'partBegin') {
}
if (name === 'partData') {
}
if (name === 'headerField') {
}
if (name === 'headerValue') {
}
if (name === 'headerEnd') {
}
if (name === 'headersEnd') {
}
if (name === 'field') {
console.log('field name:', key);
console.log('field value:', value);
}
if (name === 'file') {
console.log('file:', formname, value);
}
if (name === 'fileBegin') {
console.log('fileBegin:', formname, value);
}
});
Um método que permite estender a biblioteca Formidable. Por padrão, incluímos 4 plug-ins, que são essencialmente adaptadores para conectar os diferentes analisadores integrados.
Os plugins adicionados por este método estão sempre ativados.
Consulte src/plugins/ para uma visão mais detalhada dos plug-ins padrão.
O parâmetro plugin
tem essa assinatura:
function(formidable: Formidable, options: Options): void;
A arquitetura é simples. O plugin
é uma função que é passada com a instância Formidable (o form
nos exemplos README) e as opções.
Observação: o contexto this
da função do plug-in também é a mesma instância.
const form = formidable({ keepExtensions: true });
form.use((self, options) => {
// self === this === form
console.log('woohoo, custom plugin');
// faça suas coisas; verifique `src/plugins` para inspiração
});
form.parse(req, (error, fields, files) => {
console.log('done!');
});
Importante observar, é que dentro do plugin this.options
, self.options
e
options
PODEM ou NÃO ser iguais. A melhor prática geral é sempre usar o
this
, para que você possa testar seu plugin mais tarde de forma independente e mais fácil.
Se você quiser desabilitar alguns recursos de análise do Formidable, você pode desabilitar
o plugin que corresponde ao analisador. Por exemplo, se você deseja desabilitar a análise de
várias partes (para que o src/parsers/Multipart.js
que é usado em src/plugins/multipart.js), então
você pode removê-lo do options.enabledPlugins
, assim
import formidable, {octetstream, querystring, json} from "formidable";
const form = formidable({
hashAlgorithm: 'sha1',
enabledPlugins: [octetstream, querystring, json],
});
Esteja ciente de que a ordem PODE ser importante também. Os nomes correspondem 1:1 a arquivos na pasta src/plugins/.
Solicitações pull para novos plug-ins integrados PODEM ser aceitas - por exemplo, analisador de
querystring mais avançado. Adicione seu plugin como um novo arquivo na pasta src/plugins/
(em letras minúsculas) e
siga como os outros plugins são feitos.
Se você quiser usar Formidable para manipular apenas algumas partes para você, você pode fazer alguma coisa similar. ou ver #387 para inspiração, você pode, por exemplo, validar o tipo mime.
const form = formidable();
form.onPart = (part) => {
part.on('data', (buffer) => {
// faça o que quiser aqui
});
};
Por exemplo, force Formidable a ser usado apenas em "partes" que não sejam de arquivo (ou seja, html Campos)
const form = formidable();
form.onPart = function (part) {
// deixe formidável lidar apenas com partes não arquivadas
if (part.originalFilename === '' || !part.mimetype) {
// usado internamente, por favor, não substitua!
form._handlePart(part);
}
};
export interface File {
// O tamanho do arquivo enviado em bytes.
// Se o arquivo ainda estiver sendo carregado (veja o evento `'fileBegin'`),
// esta propriedade diz quantos bytes do arquivo já foram gravados no disco.
file.size: number;
// O caminho em que este arquivo está sendo gravado. Você pode modificar isso no evento `'fileBegin'`
// caso você esteja insatisfeito com a forma como o formidable gera um caminho temporário para seus arquivos.
file.filepath: string;
// O nome que este arquivo tinha de acordo com o cliente de upload.
file.originalFilename: string | null;
// calculado com base nas opções fornecidas.
file.newFilename: string | null;
// O tipo mime deste arquivo, de acordo com o cliente de upload.
file.mimetype: string | null;
// Um objeto Date (ou `null`) contendo a hora em que este arquivo foi gravado pela última vez.
// Principalmente aqui para compatibilidade com o [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
file.mtime: Date | null;
file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256'
// Se o cálculo `options.hashAlgorithm` foi definido, você pode ler o resumo hexadecimal desta var (no final, será uma string)
file.hash: string | object | null;
}
Este método retorna uma representação JSON do arquivo, permitindo que você JSON.stringify()
o arquivo que é útil para registrar e responder a solicitações.
Emitido após cada bloco de entrada de dados que foi analisado. Pode ser usado para rolar sua própria barra de progresso. Aviso Use isso
apenas para a barra de progresso do lado do servidor. No lado do cliente, é melhor usar XMLHttpRequest
com xhr.upload.onprogress =
form.on('progress', (bytesReceived, bytesExpected) => {});
Emitido sempre que um par campo/valor é recebido.
form.on('field', (name, value) => {});
Emitido sempre que um novo arquivo é detectado no fluxo de upload. Use este evento se desejar transmitir o arquivo para outro lugar enquanto armazena o upload no sistema de arquivos.
form.on('fileBegin', (formName, file) => {
// acessível aqui
// formName o nome no formulário (<input name="thisname" type="file">) ou http filename para octetstream
// file.originalFilename http filename ou null se houver um erro de análise
// file.newFilename gerou hexoid ou o que options.filename retornou
// file.filepath nome do caminho padrão de acordo com options.uploadDir e options.filename
// file.filepath = CUSTOM_PATH // para alterar o caminho final
});
Emitido sempre que um par campo/arquivo é recebido. file
é uma instância de
File
.
form.on('file', (formname, file) => {
// o mesmo que fileBegin, exceto
// é muito tarde para alterar file.filepath
// file.hash está disponível se options.hash foi usado
});
Emitido quando há um erro no processamento do formulário recebido. Uma solicitação que
apresenta um erro é pausada automaticamente, você terá que chamar manualmente
request.resume()
se você quiser que a requisição continue disparando eventos 'data'
.
Pode ter error.httpCode
e error.code
anexados.
form.on('error', (err) => {});
Emitido quando a requisição foi abortada pelo usuário. Agora isso pode ser devido a um
evento 'timeout' ou 'close' no soquete. Após este evento ser emitido, um
O evento error
seguirá. No futuro, haverá um 'timeout' separado
evento (precisa de uma mudança no núcleo do nó).
form.on('aborted', () => {});
Emitido quando toda a solicitação foi recebida e todos os arquivos contidos foram liberados para o disco. Este é um ótimo lugar para você enviar sua resposta.
form.on('end', () => {});
Obtém os primeiros valores dos campos, como pré 3.0.0 sem passar múltiplos em uma
lista de exceções opcionais onde arrays de strings ainda são desejados (<select multiple>
por exemplo)
import { firstValues } from 'formidable/src/helpers/firstValues.js';
// ...
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
//...
}
const exceptions = ['thisshouldbeanarray'];
const fieldsSingle = firstValues(form, fieldsMultiple, exceptions);
// ...
Html form input type="checkbox" envia apenas o valor "on" se marcado, converta-o em booleanos para cada entrada que deve ser enviada como uma caixa de seleção, use somente após a chamada de firstValues ou similar.
import { firstValues } from 'formidable/src/helpers/firstValues.js';
import { readBooleans } from 'formidable/src/helpers/readBooleans.js';
// ...
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
//...
}
const fieldsSingle = firstValues(form, fieldsMultiple);
const expectedBooleans = ['checkbox1', 'wantsNewsLetter', 'hasACar'];
const fieldsWithBooleans = readBooleans(fieldsSingle, expectedBooleans);
// ...
- multipart-parser: um analisador C++ baseado em formidável
- Ryan Dahl por seu trabalho em
http-parser que inspirou fortemente o
multipart_parser.js
inicial.
Se a documentação não estiver clara ou tiver um erro de digitação, clique no botão Edit
da página (ícone de lápis) e sugira uma correção.
Se você gostaria de nos ajudar a corrigir
um bug ou adicionar um novo recurso, verifique nosso [Contributing
Guide][contribuindo-url]. Pull requests são bem-vindos!
Agradecimentos vão para essas pessoas maravilhosas (emoji key):
De uma postagem do blog Felix:
- Sven Lito por corrigir bugs e mesclar patches
- egirshov por contribuir com muitas melhorias para o analisador multipartes formidável de nós
- Andrew Kelley por também ajudar a corrigir bugs e fazer melhorias
- Mike Frey por contribuir com suporte JSON
Formidable é licenciado sob a MIT License.