Proxy dengan express.js

168

Untuk menghindari masalah AJAX dengan domain yang sama, saya ingin server web node.js saya meneruskan semua permintaan dari URL /api/BLABLAke server lain, misalnya other_domain.com:3000/BLABLA, dan mengembalikan kepada pengguna hal yang sama dengan yang dilakukan oleh server jauh ini, secara transparan.

Semua URL lain (di sebelah /api/*) harus dilayani secara langsung, tanpa proxy.

Bagaimana cara mencapai ini dengan node.js + express.js? Bisakah Anda memberikan contoh kode sederhana?

(server web dan server jarak jauh 3000berada di bawah kendali saya, keduanya menjalankan node.js dengan express.js)


Sejauh ini saya menemukan ini https://github.com/http-party/node-http-proxy , tetapi membaca dokumentasi di sana tidak membuat saya lebih bijak. Saya berakhir dengan

var proxy = new httpProxy.RoutingProxy();
app.all("/api/*", function(req, res) {
    console.log("old request url " + req.url)
    req.url = '/' + req.url.split('/').slice(2).join('/'); // remove the '/api' part
    console.log("new request url " + req.url)
    proxy.proxyRequest(req, res, {
        host: "other_domain.com",
        port: 3000
    });
});

tetapi tidak ada yang dikembalikan ke server web asli (atau ke pengguna akhir), jadi tidak berhasil.

pengguna124114
sumber
cara Anda melakukannya berfungsi untuk saya, tanpa modifikasi apa pun
Saule
1
Meskipun agak terlambat untuk menjawab, tetapi menghadapi masalah yang sama dan menyelesaikannya dengan menghapus parser tubuh sehingga tubuh permintaan tidak diuraikan sebelum proksi lebih lanjut.
VyvIT

Jawaban:

52

Anda ingin menggunakan http.requestuntuk membuat permintaan serupa ke API jarak jauh dan mengembalikan responsnya.

Sesuatu seperti ini:

const http = require('http');
// or use import http from 'http';


/* your app config here */

app.post('/api/BLABLA', (oreq, ores) => {
  const options = {
    // host to forward to
    host: 'www.google.com',
    // port to forward to
    port: 80,
    // path to forward to
    path: '/api/BLABLA',
    // request method
    method: 'POST',
    // headers to send
    headers: oreq.headers,
  };

  const creq = http
    .request(options, pres => {
      // set encoding
      pres.setEncoding('utf8');

      // set http status code based on proxied response
      ores.writeHead(pres.statusCode);

      // wait for data
      pres.on('data', chunk => {
        ores.write(chunk);
      });

      pres.on('close', () => {
        // closed, let's end client request as well
        ores.end();
      });

      pres.on('end', () => {
        // finished, let's finish client request as well
        ores.end();
      });
    })
    .on('error', e => {
      // we got an error
      console.log(e.message);
      try {
        // attempt to set error message and http status
        ores.writeHead(500);
        ores.write(e.message);
      } catch (e) {
        // ignore
      }
      ores.end();
    });

  creq.end();
});

Perhatikan: Saya belum benar-benar mencoba hal di atas, jadi mungkin berisi kesalahan parse semoga ini akan memberi Anda petunjuk tentang cara membuatnya bekerja.

mekwall
sumber
5
Ya, beberapa modifikasi diperlukan, tapi saya lebih suka ini daripada memperkenalkan dependensi modul "Proxy" ekstra baru. Sedikit bertele-tele, tapi setidaknya aku tahu persis apa yang terjadi. Bersulang.
user124114
Sepertinya Anda perlu melakukan res.writeHead sebelum data chink ditulis, jika tidak Anda akan mendapatkan kesalahan (header tidak dapat ditulis setelah badan).
setec
3
@ user124114 - silakan masukkan solusi lengkap yang telah Anda gunakan
Michal Tsadok
1
tampaknya Anda akan memiliki masalah mengatur tajuk dengan cara ini. Cannot render headers after they are sent to the client
Shnd
1
Saya telah memperbarui jawaban untuk es6 sintaks dan memperbaiki masalah
writeHead
219

permintaan telah ditinggalkan pada Februari 2020, saya akan meninggalkan jawaban di bawah ini karena alasan historis, tapi tolong pertimbangkan untuk pindah ke alternatif yang tercantum dalam masalah ini .

Arsipkan

Saya melakukan sesuatu yang serupa tetapi saya menggunakan permintaan :

var request = require('request');
app.get('/', function(req,res) {
  //modify the url in any way you want
  var newurl = 'http://google.com/';
  request(newurl).pipe(res);
});

Saya harap ini membantu, butuh beberapa saat untuk menyadari bahwa saya bisa melakukan ini :)

pemicu
sumber
5
Terima kasih, jauh lebih sederhana daripada menggunakan permintaan HTTP Node.js
Alex Turpin
17
Lebih sederhana lagi, jika Anda juga mengirim permintaan: stackoverflow.com/questions/7559862/…
Stephan Hoyer
1
Solusi yang bagus dan bersih. Saya memposting jawaban untuk membuatnya berfungsi dengan permintaan POST juga (jika tidak, tidak akan meneruskan isi posting Anda ke API). Jika Anda mengedit jawaban Anda, saya dengan senang hati akan menghapus jawaban saya.
Henrik Peinar
Lihat juga jawaban ini untuk penanganan kesalahan yang lebih baik.
Tamlyn
setiap kali saya mencoba melakukan perutean yang sama (atau yang persis sama) saya berakhir dengan yang berikut: stream.js: 94 throw er; // Kesalahan aliran tidak tertangani dalam pipa. ^ Kesalahan: getaddrinfo ENOTFOUND google.com di errnoException (dns.js: 44: 10) di GetAddrInfoReqWrap.onlookup [sebagai oncomplete] (dns.js: 94: 26) ada ide?
keinabel
82

Saya menemukan solusi yang lebih singkat dan sangat langsung yang bekerja dengan mulus, dan dengan otentikasi juga, menggunakan express-http-proxy:

const url = require('url');
const proxy = require('express-http-proxy');

// New hostname+path as specified by question:
const apiProxy = proxy('other_domain.com:3000/BLABLA', {
    proxyReqPathResolver: req => url.parse(req.baseUrl).path
});

Dan kemudian secara sederhana:

app.use('/api/*', apiProxy);

Catatan: seperti yang disebutkan oleh @MaxPRafferty, penggunaan req.originalUrldi tempat baseUrluntuk melestarikan querystring:

    forwardPath: req => url.parse(req.baseUrl).path

Pembaruan: Seperti yang disebutkan oleh Andrew (terima kasih!), Ada solusi yang sudah jadi menggunakan prinsip yang sama:

npm i --save http-proxy-middleware

Lalu:

const proxy = require('http-proxy-middleware')
var apiProxy = proxy('/api', {target: 'http://www.example.org/api'});
app.use(apiProxy)

Dokumentasi: http-proxy-middleware di Github

Saya tahu saya terlambat untuk bergabung dengan pesta ini, tapi saya harap ini membantu seseorang.

Egois
sumber
3
Req.url tidak memiliki url lengkap, jadi perbarui jawaban untuk menggunakan req.baseUrl alih-alih req.url
Vinoth Kumar
1
Saya juga suka menggunakan req.originalUrl sebagai ganti baseUrl untuk melestarikan querystrings, tetapi ini mungkin tidak selalu merupakan perilaku yang diinginkan.
MaxPRafferty
@MaxPRafferty - komentar vaid. Perlu diperhatikan. Terima kasih.
Cukup egois
4
Ini solusi terbaik. Saya menggunakan http-proxy-middleware, tetapi konsepnya sama. Jangan memutar solusi proxy Anda sendiri ketika sudah ada yang hebat di luar sana.
Andrew
1
@tannerburton terima kasih! Saya memperbarui jawabannya.
Egois
46

Untuk memperluas jawaban trigoman (kredit penuh kepadanya) untuk bekerja dengan POST (bisa juga bekerja dengan PUT dll):

app.use('/api', function(req, res) {
  var url = 'YOUR_API_BASE_URL'+ req.url;
  var r = null;
  if(req.method === 'POST') {
     r = request.post({uri: url, json: req.body});
  } else {
     r = request(url);
  }

  req.pipe(r).pipe(res);
});
Henrik Peinar
sumber
1
Tidak dapat membuatnya bekerja dengan PUT. Tetapi bekerja dengan baik untuk GET dan POST. Terima kasih!!
Mariano Desanze
5
@ Protron untuk permintaan PUT hanya menggunakan sesuatu sepertiif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Jika Anda harus melewati tajuk sebagai bagian dari permintaan PUT atau POST, pastikan untuk menghapus tajuk panjang konten agar permintaan dapat menghitungnya. Jika tidak, server penerima dapat memotong data, yang akan menyebabkan kesalahan.
Carlos Rymer
@ Henrik Peinar, akankah ini membantu ketika saya melakukan permintaan posting login dan berharap untuk mengalihkan dari web.com/api/login ke web.com/
valik
22

Saya menggunakan pengaturan berikut untuk mengarahkan semuanya /restke server backend saya (pada port 8080), dan semua permintaan lainnya ke server frontend (server webpack pada port 3001). Ini mendukung semua metode HTTP, tidak kehilangan meta-info permintaan, dan mendukung soket web (yang saya perlukan untuk memuat ulang panas)

var express  = require('express');
var app      = express();
var httpProxy = require('http-proxy');
var apiProxy = httpProxy.createProxyServer();
var backend = 'http://localhost:8080',
    frontend = 'http://localhost:3001';

app.all("/rest/*", function(req, res) {
  apiProxy.web(req, res, {target: backend});
});

app.all("/*", function(req, res) {
    apiProxy.web(req, res, {target: frontend});
});

var server = require('http').createServer(app);
server.on('upgrade', function (req, socket, head) {
  apiProxy.ws(req, socket, head, {target: frontend});
});
server.listen(3000);
Anthony De Smet
sumber
1
Ini adalah satu-satunya yang juga berhubungan dengan soket web.
sec0ndHand
11

Pertama instal express dan http-proxy-middleware

npm install express http-proxy-middleware --save

Kemudian di server.js Anda

const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();
app.use(express.static('client'));

// Add middleware for http proxying 
const apiProxy = proxy('/api', { target: 'http://localhost:8080' });
app.use('/api', apiProxy);

// Render your site
const renderIndex = (req, res) => {
  res.sendFile(path.resolve(__dirname, 'client/index.html'));
}
app.get('/*', renderIndex);

app.listen(3000, () => {
  console.log('Listening on: http://localhost:3000');
});

Dalam contoh ini kami melayani situs pada port 3000, tetapi ketika permintaan diakhiri dengan / api, kami mengarahkannya ke localhost: 8080.

http: // localhost: 3000 / api / login redirect ke http: // localhost: 8080 / api / login

C. Dupetit
sumber
6

Ok, inilah jawaban siap-salin-tempel menggunakan modul npm ((permintaan ')) dan variabel lingkungan * alih-alih proxy yang dikodekan dengan hardcoded):

naskah kopi

app.use (req, res, next) ->                                                 
  r = false
  method = req.method.toLowerCase().replace(/delete/, 'del')
  switch method
    when 'get', 'post', 'del', 'put'
      r = request[method](
        uri: process.env.PROXY_URL + req.url
        json: req.body)
    else
      return res.send('invalid method')
  req.pipe(r).pipe res

javascript:

app.use(function(req, res, next) {
  var method, r;
  method = req.method.toLowerCase().replace(/delete/,"del");
  switch (method) {
    case "get":
    case "post":
    case "del":
    case "put":
      r = request[method]({
        uri: process.env.PROXY_URL + req.url,
        json: req.body
      });
      break;
    default:
      return res.send("invalid method");
  }
  return req.pipe(r).pipe(res);
});
koderofsalvasi
sumber
2
Daripada pernyataan kasus yang semuanya melakukan hal yang sama kecuali menggunakan fungsi permintaan yang berbeda) Anda dapat membersihkan terlebih dahulu (misalnya pernyataan if yang memanggil default Anda jika metode tersebut tidak ada dalam daftar metode yang disetujui), dan kemudian lakukan saja r = permintaan [metode] (/ * sisanya * /);
Paul
2

Saya menemukan solusi lebih pendek yang melakukan apa yang saya inginkan https://github.com/http-party/node-http-proxy

Setelah menginstal http-proxy

npm install http-proxy --save

Gunakan seperti di bawah ini di server / index / app.js Anda

var proxyServer = require('http-route-proxy');
app.use('/api/BLABLA/', proxyServer.connect({
  to: 'other_domain.com:3000/BLABLA',
  https: true,
  route: ['/']
}));

Saya benar-benar telah menghabiskan waktu berhari-hari mencari kemana-mana untuk menghindari masalah ini, mencoba banyak solusi dan tidak ada yang berhasil kecuali yang ini.

Semoga ini akan membantu orang lain juga :)

hzitoun
sumber
0

Saya tidak punya sampel ekspres, tapi satu dengan http-proxypaket polos . Versi proxy yang sangat saya gunakan untuk blog saya.

Singkatnya, semua paket proksi nodejs http bekerja pada level protokol http, bukan level tcp (socket). Ini juga berlaku untuk express dan semua middleware ekspres. Tak satu pun dari mereka dapat melakukan proxy transparan, atau NAT, yang berarti menjaga IP sumber lalu lintas masuk dalam paket yang dikirim ke server web backend.

Namun, server web dapat mengambil IP asli dari http x-forwarded header dan menambahkannya ke dalam log.

The xfwd: truedalam proxyOptionmengaktifkan fitur sundulan x-maju untuk http-proxy.

const url = require('url');
const proxy = require('http-proxy');

proxyConfig = {
    httpPort: 8888,
    proxyOptions: {
        target: {
            host: 'example.com',
            port: 80
        },
        xfwd: true // <--- This is what you are looking for.
    }
};

function startProxy() {

    proxy
        .createServer(proxyConfig.proxyOptions)
        .listen(proxyConfig.httpPort, '0.0.0.0');

}

startProxy();

Referensi untuk Header X-Forwarded: https://en.wikipedia.org/wiki/X-Forwarded-For

Versi lengkap dari proxy saya: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
sumber