พร็อกซีที่มี express.js


168

เพื่อหลีกเลี่ยงปัญหา AJAX ในโดเมนเดียวกันฉันต้องการให้ node.js เว็บเซิร์ฟเวอร์ของฉันส่งต่อคำขอทั้งหมดจาก URL /api/BLABLAไปยังเซิร์ฟเวอร์อื่นother_domain.com:3000/BLABLAและกลับสู่ผู้ใช้ในสิ่งเดียวกันกับที่รีโมตเซิร์ฟเวอร์นี้ส่งคืนโดยโปร่งใส

URL อื่น ๆ ทั้งหมด (ด้านข้าง/api/*) จะต้องให้บริการโดยตรงไม่ต้องใช้ผู้รับมอบฉันทะ

ฉันจะบรรลุสิ่งนี้ด้วย node.js + express.js ได้อย่างไร คุณสามารถยกตัวอย่างรหัสง่ายๆได้หรือไม่?

(ทั้งเว็บเซิร์ฟเวอร์และเซิร์ฟเวอร์ระยะไกล3000อยู่ภายใต้การควบคุมของฉันทั้งสองทำงานอยู่ node.js กับ express.js)


จนถึงตอนนี้ฉันพบhttps://github.com/http-party/node-http-proxyแต่การอ่านเอกสารมีไม่ได้ทำให้ฉันฉลาดขึ้น ฉันลงเอยด้วย

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
    });
});

แต่ไม่มีสิ่งใดถูกส่งคืนไปยังเว็บเซิร์ฟเวอร์ดั้งเดิม (หรือผู้ใช้ปลายทาง) ดังนั้นจึงไม่มีโชค


วิธีที่คุณใช้งานได้กับฉันโดยไม่มีการดัดแปลงใด ๆ
Saule

1
แม้ว่าจะสายเกินไปที่จะตอบ แต่ก็ประสบปัญหาที่คล้ายกันและแก้ไขได้โดยการลบตัวแยกวิเคราะห์เนื้อหาเพื่อไม่ให้มีการแยกวิเคราะห์เนื้อหาคำขอก่อนที่จะพร็อกซีมันต่อไป
VyvIT

คำตอบ:


52

คุณต้องการใช้http.requestเพื่อสร้างคำขอที่คล้ายกันกับรีโมต API และส่งคืนการตอบกลับ

บางสิ่งเช่นนี้

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();
});

ข้อสังเกต: ฉันไม่ได้ลองด้านบนจริง ๆ ดังนั้นจึงอาจมีข้อผิดพลาดในการแยกวิเคราะห์ได้โดยหวังว่าจะให้คำแนะนำเกี่ยวกับวิธีการทำให้ใช้งานได้


5
ใช่การแก้ไขบางอย่างมีความจำเป็น แต่ฉันชอบสิ่งนี้ดีกว่าการแนะนำการพึ่งพาโมดูล "พร็อกซี" ใหม่พิเศษ ค่อนข้าง verbose แต่อย่างน้อยฉันก็รู้ว่าเกิดอะไรขึ้น ไชโย
user124114

ดูเหมือนว่าคุณจำเป็นต้องทำ res.writeHead ก่อนที่ข้อมูลจะถูกเขียนมิฉะนั้นคุณจะได้รับข้อผิดพลาด (ไม่สามารถเขียนส่วนหัวได้หลังจากร่างกาย)
setec

3
@ user124114 - โปรดใส่โซลูชันทั้งหมดที่คุณใช้
Michal Tsadok

1
ดูเหมือนว่าคุณจะมีปัญหาในการตั้งค่าส่วนหัวด้วยวิธีนี้ Cannot render headers after they are sent to the client
Shnd

1
ฉันได้อัปเดตคำตอบสำหรับไวยากรณ์ของ es6 และแก้ไขปัญหา writeHead แล้ว
mekwall

219

คำขอเลิกการใช้งานในเดือนกุมภาพันธ์ 2020 ฉันจะฝากคำตอบไว้ด้านล่างด้วยเหตุผลทางประวัติศาสตร์ แต่โปรดลองย้ายไปใช้ตัวเลือกอื่นที่ระบุไว้ในปัญหานี้

เอกสารเก่า

ฉันทำสิ่งที่คล้ายกัน แต่ฉันขอใช้แทน:

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);
});

ฉันหวังว่าสิ่งนี้จะช่วยได้โปรดใช้เวลาสักครู่ในการตระหนักว่าฉันสามารถทำได้ :)


5
ขอบคุณง่ายกว่าการใช้คำขอ HTTP ของ Node.js
Alex Turpin

17
ง่ายยิ่งขึ้นหากคุณส่งคำขอ: stackoverflow.com/questions/7559862/…
Stephan Hoyer

1
ทางออกที่ดีและสะอาด ฉันโพสต์คำตอบเพื่อให้ทำงานกับคำขอ POST ด้วย (ไม่เช่นนั้นจะไม่ส่งต่อเนื้อความของคุณไปยัง API) หากคุณแก้ไขคำตอบของฉันฉันยินดีที่จะลบของฉัน
Henrik Peinar

ดูคำตอบนี้เพื่อปรับปรุงการจัดการข้อผิดพลาด
Tamlyn

เมื่อใดก็ตามที่ฉันพยายามทำเส้นทางที่คล้ายกัน (หรือเหมือนกัน) ฉันจะจบลงด้วยต่อไปนี้: stream.js: 94 throw er; // ข้อผิดพลาดการสตรีมที่ไม่สามารถจัดการได้ในไปป์ ^ ข้อผิดพลาด: getaddrinfo ENOTFOUND google.com ที่ errnoException (dns.js: 44: 10) ที่ GetAddrInfoReqWrap.onlookup [ตามที่สมบูรณ์] (dns.js: 94: 26) ความคิดใด ๆ
keinabel

82

ฉันพบโซลูชันที่สั้นและตรงไปตรงมามากซึ่งทำงานได้อย่างราบรื่นและมีการรับรองความถูกต้องโดยใช้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
});

แล้วก็:

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

หมายเหตุ: ตามที่ระบุไว้โดย @MaxPRafferty ให้ใช้req.originalUrlแทนbaseUrlการเก็บรักษาการสืบค้น:

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

อัปเดต: ตามที่กล่าวถึงโดย Andrew (ขอขอบคุณ!) มีวิธีแก้ปัญหาพร้อมใช้หลักการเดียวกัน:

npm i --save http-proxy-middleware

แล้ว:

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

เอกสารประกอบ: http-proxy-middleware บน Github

ฉันรู้ว่าฉันมาสายเพื่อเข้าร่วมปาร์ตี้นี้ แต่ฉันหวังว่าจะช่วยได้


3
req.url ไม่มี URL แบบเต็มดังนั้นอัปเดตคำตอบเพื่อใช้ req.baseUrl แทน req.url
Vinoth Kumar

1
ฉันชอบที่จะใช้ req.originalUrl แทน baseUrl เพื่อรักษาความน่าสงสัย แต่นี่อาจไม่ใช่พฤติกรรมที่ต้องการเสมอไป
MaxPRafferty

@MaxPRafferty - ความคิดเห็นที่ vaid น่าสังเกต ขอบคุณ
เห็นแก่ตัว

4
นี่คือทางออกที่ดีที่สุด ฉันใช้ http-proxy-middleware แต่เป็นแนวคิดเดียวกัน อย่าหมุนโซลูชันพร็อกซีของคุณเองเมื่อมีคนที่ยอดเยี่ยมอยู่แล้ว
แอนดรู

1
@ แทนเนอร์เบอร์ตันขอบคุณ! ฉันอัพเดตคำตอบแล้ว
เห็นแก่ตัว

46

ในการขยายคำตอบของtrigoman (ให้เครดิตเขา) เพื่อทำงานกับ POST (สามารถทำงานกับ PUT และอื่น ๆ ได้):

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);
});

1
ไม่สามารถทำงานกับ PUT ได้ แต่ใช้งานได้ดีสำหรับ GET และ POST ขอบคุณ!!
Mariano Desanze

5
@Protron สำหรับคำขอ PUT ใช้สิ่งที่ต้องการif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil

หากคุณต้องการผ่านส่วนหัวเป็นส่วนหนึ่งของคำขอ PUT หรือ POST ตรวจสอบให้แน่ใจว่าได้ลบส่วนหัวที่มีความยาวเนื้อหาเพื่อให้คำขอสามารถคำนวณได้ มิฉะนั้นเซิร์ฟเวอร์ที่รับอาจตัดทอนข้อมูลซึ่งจะทำให้เกิดข้อผิดพลาด
Carlos Rymer

@Henrik Peinar จะช่วยเหลือเมื่อฉันทำคำขอโพสต์เข้าสู่ระบบและคาดว่าจะเปลี่ยนเส้นทางจาก web.com/api/login ไปยัง web.com/
valik

22

ฉันใช้การตั้งค่าต่อไปนี้เพื่อนำทุกอย่าง/restไปยังเซิร์ฟเวอร์ backend ของฉัน (บนพอร์ต 8080) และคำขออื่น ๆ ทั้งหมดไปยังเซิร์ฟเวอร์ส่วนหน้า (เซิร์ฟเวอร์ webpack ที่พอร์ต 3001) รองรับเมธอด HTTP ทั้งหมดไม่สูญเสียเมตาดาต้าใด ๆ และรองรับ websockets (ซึ่งฉันต้องการสำหรับการโหลดซ้ำร้อน)

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);

1
นี่เป็นเพียงหนึ่งเดียวที่เกี่ยวข้องกับเว็บซ็อกเก็ต
sec0ndHand

11

ก่อนติดตั้ง express และ http-proxy-middleware

npm install express http-proxy-middleware --save

จากนั้นใน server.js ของคุณ

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');
});

ในตัวอย่างนี้เราให้บริการเว็บไซต์บนพอร์ต 3000 แต่เมื่อคำขอสิ้นสุดด้วย / api เราจะเปลี่ยนเส้นทางไปยัง localhost: 8080

http: // localhost: 3000 / api / loginเปลี่ยนเส้นทางไปที่http: // localhost: 8080 / api / login


6

ตกลงนี่เป็นคำตอบแบบพร้อมคัดลอกโดยใช้โมดูลต้องการ ('คำขอ') npm และตัวแปรสภาพแวดล้อม * แทนพร็อกซีฮาร์ดโค้ด)

CoffeeScript

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);
});

2
แทนที่จะใช้คำสั่ง case ซึ่งทั้งหมดทำสิ่งเดียวกันยกเว้นใช้ฟังก์ชั่นคำขอที่แตกต่างกัน) คุณสามารถฆ่าเชื้อก่อน (เช่นคำสั่ง if ที่เรียกค่าเริ่มต้นของคุณหากวิธีไม่ได้อยู่ในรายการวิธีที่ได้รับอนุมัติ) จากนั้นทำ r = ขอ [วิธีการ] (/ * ส่วนที่เหลือ * /);
Paul

2

ฉันพบโซลูชันที่สั้นกว่าซึ่งทำสิ่งที่ฉันต้องการได้อย่างแน่นอนhttps://github.com/http-party/node-http-proxy

หลังจากติดตั้งแล้ว http-proxy

npm install http-proxy --save

ใช้เหมือนด้านล่างในเซิร์ฟเวอร์ / index / app.js ของคุณ

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

ฉันใช้เวลาหลายวันในการดูทุกที่เพื่อหลีกเลี่ยงปัญหานี้ลองใช้วิธีแก้ไขปัญหามากมายและไม่มีวิธีใดที่ทำงานได้

หวังว่ามันจะช่วยคนอื่นด้วย :)


0

ฉันไม่มีตัวอย่างที่ชัดเจน แต่มีhttp-proxyแพ็คเกจที่ธรรมดา พร็อกซีเวอร์ชันที่ตัดลงที่ฉันใช้สำหรับบล็อกของฉัน

กล่าวโดยสรุปคือ nodejs http proxy package ทั้งหมดทำงานที่ระดับโปรโตคอล http ไม่ใช่ระดับ tcp (ซ็อกเก็ต) สิ่งนี้ยังเป็นจริงสำหรับ express และ middleware express ทั้งหมด ไม่มีใครสามารถทำพร็อกซีแบบโปร่งใสหรือ NAT ซึ่งหมายถึงการเก็บ IP ต้นทางของทราฟฟิกขาเข้าในแพ็คเก็ตที่ส่งไปยังเว็บเซิร์ฟเวอร์ด้านหลัง

อย่างไรก็ตามเว็บเซิร์ฟเวอร์สามารถรับ IP ต้นฉบับจากส่วนหัว http x-forwarded และเพิ่มลงในบันทึก

xfwd: trueในproxyOptionคุณลักษณะส่วนหัวเปิดใช้งาน http-proxyX-ไปข้างหน้าสำหรับ

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();

ข้อมูลอ้างอิงสำหรับส่วนหัว X-Forwarded: https://en.wikipedia.org/wiki/X-Forwarded-For

เวอร์ชันเต็มของพร็อกซีของฉัน: https://github.com/J-Siu/ghost-https-nodejs-proxy

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.