Estou tentando criar um novo objeto de solicitação canalizando a partir de uma solicitação Express. Isso funciona bem para solicitações GET, mas para solicitações POST que têm um corpo, o corpo não parece ter sido copiado para a nova solicitação. Tentei copiar manualmente sobre o corpo assim:
let pipedReq = req.pipe(request({ url: 'http://www.example.com', form: req.body }));
Isso copia sobre o corpo, mas recebo um erro "escrever após o fim" quando pipedReq
é limpo. Pode estar relacionado a # 1659.
É fácil reproduzir o problema com o seguinte aplicativo Express simples:
'use strict';
let express = require('express');
let request = require('request');
let app = express();
app.use('/', (req, res) => {
req.pipe(request.post({ url: 'http://www.example.com', form: { foo: 'bar' }}));
});
app.listen(process.env.PORT || 3000);
Backtrace:
Error: write after end
at ClientRequest.OutgoingMessage.write (_http_outgoing.js:413:15)
at Request.write (/.../node_modules/request/request.js:1354:25)
at end (/.../node_modules/request/request.js:548:16)
at Immediate._onImmediate (/.../node_modules/request/request.js:576:7)
at processImmediate [as _immediateCallback] (timers.js:358:17)
Experimente com este:
var express = require('express')
var request = require('request')
var app = express()
// put it before any other middleware
app.use(function(req, res) {
req.pipe(
request[req.method.toLowerCase()]('http://example.com'))
.pipe(res)
})
app.listen(3000, function () {
console.log('Express server listening on port 3000')
})
Caso queira modificar o corpo, basta construir uma nova solicitação e canalizar para a resposta.
@simov Não quero modificar o corpo, apenas a URL. Portanto, quero manter os cabeçalhos e outras propriedades da solicitação original, mas canalizar para uma nova solicitação com um novo URL. O problema é que o corpo não é copiado quando a solicitação original é canalizada para a nova (não tenho certeza se isso é um bug) e se eu tentar copiar o corpo sobre mim especificando a propriedade form
no construtor request
, recebo o erro mencionado acima.
Quando você diz "construir uma nova solicitação", está sugerindo que eu simplesmente crie uma nova solicitação do zero com o corpo que desejo e copie os cabeçalhos da solicitação original, em vez de enviá-los? Posso fazer isso, mas seria menos repetitivo e frágil se pudesse usar o suporte de tubo integrado para copiar a solicitação original no novo.
Ainda acho que um ou ambos os problemas são provavelmente um bug (ou seja, 1) o corpo da solicitação não é copiado durante o encanamento e 2) a exceção mencionada em minha postagem original).
Não sei muito sobre expresso ou solicitação (apenas usando-os para um proxy de aplicativo de página única sem realmente entender como funcionam), mas isso corrigiu o erro write after end
para mim:
request.post({ url: 'http://www.example.com', form: { foo: 'bar' }}).pipe(res);
Ao invés de:
req.pipe(request.post({ url: 'http://www.example.com', form: { foo: 'bar' }})).pipe(res);
@matthewgertner experimente meu exemplo de código, ele funciona.
@simov Definitivamente funciona, mas seu exemplo não cobre o caso em que req
tem um corpo que desejo copiar. Na verdade, eu estava usando um código quase idêntico ao seu exemplo, mas não consegui lidar com as solicitações POST, pois o corpo não é copiado para o novo objeto de solicitação.
@matthewgertner você pode me dar seu exemplo de código _exact_ que não funciona?
@simov É o exemplo da minha postagem original. Se eu usar este, o corpo não será enviado:
'use strict';
let express = require('express');
let request = require('request');
let app = express();
app.use('/', (req, res) => {
req.pipe(request.post('http://www.example.com'));
});
app.listen(process.env.PORT || 3000);
Se eu usar isso, recebo a exceção:
'use strict';
let express = require('express');
let request = require('request');
let app = express();
app.use('/', (req, res) => {
req.pipe(request.post({ url: 'http://www.example.com', form: { foo: 'bar' }}));
});
app.listen(process.env.PORT || 3000);
Você pode substituir http://www.example.com
por algum URL gerado em http://requestb.in/ e colar seu link aqui? Deve ser semelhante a http://requestb.in/1mv5l8h1?inspect
Ou você pode usar meu código de exemplo acima também, porque é isso que estou usando para servidor. Então estou executando este script:
var request = require('request')
request.post('http://localhost:3000', {form:{some:'data'}})
Como você pode ver no meu link, enviei form:{some:'data'}
.
Deixe-me explicar meu caso de uso. Implementei um proxy de API da web simples (consulte https://github.com/salsita/web-api-proxy). Tudo o que ele faz é receber solicitações HTTP e redirecioná-los para outro host, substituindo os parâmetros de URL, se necessário, por variáveis de ambiente no servidor proxy. O objetivo é evitar a incorporação de chaves secretas no código do cliente.
Ele funciona muito bem para solicitações GET, mas recentemente tive a necessidade de usá-lo para POST também. Portanto, não estou inicializando o corpo da minha solicitação como em seu comentário anterior, pois o corpo vem da solicitação expressa. Seu exemplo mais recente definitivamente funciona, mas isso realmente não me ajuda. O que eu preciso fazer é:
req.pipe(request.post('http://some/new/url'));
Parece-me que se req
tiver um corpo, ele não será copiado neste caso. E, de qualquer forma, a verdade é que _não_ preciso modificar o corpo (uma vez que pode haver marcadores que preciso substituir usando variáveis de ambiente), então terei que fazer:
req.pipe(request.post({ url: 'http://some/new/url', form: { some: 'form', data: 'here' }));
Isso faz com que uma exceção seja lançada devido ao que eu suspeito ser um bug em request
.
De qualquer forma, posso construir um objeto request
completamente novo sozinho, mas queria ter certeza de que não estava faltando nada e também relatar esse problema caso seja um bug.
Não funciona porque você está usando uma solicitação GET
sempre. Dê uma olhada no meu exemplo de código novamente.
request[req.method.toLowerCase()]('http://example.com'))
define o método de solicitação com base na solicitação de entrada.
Além disso, se você precisar modificar o corpo, novamente desde o meu primeiro comentário:
Caso queira modificar o corpo, basta construir uma nova solicitação e canalizar para a resposta.
Não funciona porque você está sempre usando uma solicitação GET. Dê uma olhada no meu exemplo de código novamente.
Mas estou usando request.post
. Isso é a mesma coisa que request['post']
.
Caso queira modificar o corpo, basta construir uma nova solicitação e canalizar para a resposta.
É uma chatice que eu basicamente preciso reescrever o código que já está em request
para canalizar solicitações para novas solicitações (copiando cabeçalhos, etc.). Pode haver um monte de casos extremos com os quais eu preciso lidar. Mas sim, acho que é isso que vou acabar fazendo.
OMI deve ser possível canalizar uma solicitação para outra que tenha um corpo de formulário sem que uma exceção seja lançada, ou você acha que há algum motivo para esse comportamento?
Isso é fazer GET
solicitação sempre porque você não está especificando nada explicitamente.
A construção de um novo objeto só se aplica quando você deseja modificar o corpo.
Claro, a base de código atual não suporta POST. Estou tentando consertar isso, e foi quando me deparei com os problemas descritos aqui. Eu apenas referenciei a base de código existente para explicar melhor o caso de uso.
Tenho quase certeza de que preciso criar um novo objeto sempre, mesmo que não queira modificar o corpo, já que o corpo não é copiado pela operação de tubo. Você tentou o exemplo que forneci? Você precisa de uma solicitação Express com um corpo que canalize para um objeto request
. Você verá que o corpo é undefined
no novo objeto.
GET
pedidos não têm um corpo, isso é por design.
Este funciona (seu exemplo):
'use strict';
let express = require('express');
let request = require('request');
let app = express();
app.use('/', (req, res) => {
req.pipe(request.post('http://www.example.com'));
});
app.listen(process.env.PORT || 3000);
Usando isso para fazer a solicitação:
var request = require('request')
request.post('http://localhost:3000', {form:{some:'data'}})
some=data
foi enviado.
Você verá que o corpo está indefinido no novo objeto.
Onde está isso, eu não estou te seguindo.
Hmmm, ok. Quando eu uso requestb.in (boa dica!), Posso ver que o corpo realmente _é_ enviado. Na verdade, tive (e ainda tenho) dois problemas que me levaram a acreditar que não estava sendo enviado:
1) Quando eu uso cURL para enviar uma solicitação ao meu servidor (usando o código em seu comentário anterior), ele trava após enviar a solicitação (ou seja, cURL não termina).
2) Quando vejo request.body
, é undefined
passo que este não é o caso se eu criar a solicitação usando request.post
com uma propriedade form
explícita.
Eu acho que 2) é irrelevante e o corpo é simplesmente armazenado em outro lugar. Eu estaria interessado em saber se você pode reproduzir 1) embora. Se você enviar a solicitação para localhost:3000
por meio de cURL ( curl --data "some=data" localhost:3000
), ela termina ou trava?
1) O pedido trava porque no seu exemplo você não está devolvendo nada, usei apenas para ter certeza de que estamos na mesma página. Basta .pipe(res)
para receber a resposta.
2) request.body
é undefined
ao usar a API de fluxo
Na verdade, usei .pipe(res)
no meu exemplo real e verifiquei que não funciona. Ele não parece enviar a solicitação e trava. Como seu exemplo funciona, descobri rapidamente a causa do problema:
app.use(bodyParser.urlencoded({ extended: true }));
Parece-me que, quando uso body-parser
, a tubulação não funciona mais. Você concorda e, em caso afirmativo, tem ideia de qual pode ser a causa?
Sim, se você der uma olhada no meu primeiro comentário novamente:
// coloque antes de qualquer outro middleware
body-parser
processa a solicitação antes de entrar em seu middleware, mas você precisa da solicitação bruta para canalizá-la com sucesso.
Ufa! Ok, obrigado _a ton_ por sua ajuda e paciência. Demorou um pouco para chegar ao fundo disso. Eu ainda acho que é uma chatice que eu não posso canalizar todos os cabeçalhos e outras coisas e depois modificar o corpo, mas acho que não há maneira de contornar isso.
@simov
app.use(function(req, res) {
req.pipe(
request[req.method.toLowerCase()]('http://example.com'))
.pipe(res)
})
irá falhar para DELETE. já que request.del é o método e não req.delete
Eu sei que isso é antigo, mas tive dificuldade em encontrar uma solução adequada para isso e pensei que poderia ser útil para outras pessoas.
'use strict';
let express = require('express');
let request = require('request');
let app = express();
app.use('/', (req, res) => {
req.pipe(request.post({ url: 'http://www.example.com', form: { foo: 'bar' }}), {end: false}).pipe(res);
});
app.listen(process.env.PORT || 3000);
Conforme mostrado em https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options , adicionar isso faz com que o novo fluxo não seja encerrado automaticamente quando req é encerrado.
Embora isso tenha feito este exemplo funcionar, notei alguns problemas ao criar um proxy para outros métodos usando isso. O corpo de alguma forma faz com que tudo feche adequadamente, mas sem um corpo ele ainda trava. Se estiver usando um analisador de corpo json, isso não deve causar problemas, pois faz com que o corpo seja lido como {}
mesmo quando não há corpo, mas sem um ainda pode haver problemas. Minha solução atual é verificar se há um cabeçalho de comprimento de conteúdo na solicitação e usar o objeto de opções, se houver.
@JeffreyAngell tente a sua resolução, ela funciona. valeu
Isso funciona para mim:
`router.put ('/', função (client_req, client_res, next) {
var json = JSON.stringify (client_req.body);
var options = createOptions ('PUT', client_req.baseUrl, json);
var proxy = https.request(options, function (res) {
client_res.statusCode = res.statusCode;
res.pipe(client_res, {
end: true
});
});
client_req.pipe(proxy, {
end: true
});
proxy.write(json);
proxy.end();
});
function createOptions (method, targetUrl, json) {
targetUrl = targetUrl.replace ('jira /', '');
console.log ("servindo:" + targetUrl);
const auth = 'Basic ' + new Buffer('usr' + ':' + 'pass').toString('base64');
var headers = {
'Authorization': auth,
'Content-Type': 'application/json'
};
return {
hostname: 'jira.acme.com',
port: 443,
path: targetUrl,
method: method,
headers: headers
};
} `
@simov
Sim, se você der uma olhada no meu primeiro comentário novamente:
// coloque antes de qualquer outro middleware
body-parser processa a solicitação antes de entrar em seu middleware, mas você precisa da solicitação bruta para canalizá-la com sucesso.
Eu experimentei o mesmo problema. Você poderia explicar por que tem que ser antes de determinado middleware, no meu caso, apenas o middleware bodyParser
causa o erro? Parece que posso usar outro middleware, por exemplo, funções de usuário, higienização sem problemas.
EDITAR:
Eu só quero carregar minhas rotas depois que todo o middleware for carregado, então optei por esta solução que exclui bodyParser de todas as rotas com proxy
no início:
pathToRegexp = require('path-to-regexp')
exports.excludeMiddleware = (path, middleware) ->
(req, res, next) ->
if pathToRegexp(path).test req.path
next()
else
middleware req, res, next
app.use(routesService.excludeMiddleware '/proxy/(.*)', bodyParser.json({
extended: false,
parameterLimit: 10000,
limit: 1024 * 1024 * 10
}))
https://stackoverflow.com/questions/27117337/exclude-route-from-express-middleware
Comentários muito úteis
Eu sei que isso é antigo, mas tive dificuldade em encontrar uma solução adequada para isso e pensei que poderia ser útil para outras pessoas.
Conforme mostrado em https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options , adicionar isso faz com que o novo fluxo não seja encerrado automaticamente quando req é encerrado.
Embora isso tenha feito este exemplo funcionar, notei alguns problemas ao criar um proxy para outros métodos usando isso. O corpo de alguma forma faz com que tudo feche adequadamente, mas sem um corpo ele ainda trava. Se estiver usando um analisador de corpo json, isso não deve causar problemas, pois faz com que o corpo seja lido como
{}
mesmo quando não há corpo, mas sem um ainda pode haver problemas. Minha solução atual é verificar se há um cabeçalho de comprimento de conteúdo na solicitação e usar o objeto de opções, se houver.