Proxy với express.js


168

Để tránh các sự cố AJAX cùng tên miền, tôi muốn máy chủ web node.js của tôi chuyển tiếp tất cả các yêu cầu từ URL /api/BLABLAsang máy chủ khác, ví dụ:other_domain.com:3000/BLABLA , và trả lại cho người dùng điều tương tự mà máy chủ từ xa này trả lại, trong suốt.

Tất cả các URL khác (bên cạnh /api/* ) sẽ được phục vụ trực tiếp, không ủy quyền.

Làm cách nào để đạt được điều này với node.js + express.js? Bạn có thể đưa ra một ví dụ mã đơn giản?

(cả máy chủ web và máy chủ từ xa 3000đều nằm dưới sự kiểm soát của tôi, cả chạy node.js với express.js)


Cho đến nay tôi đã tìm thấy https://github.com/http-party/node-http-proxy này , nhưng đọc tài liệu ở đó không làm cho tôi khôn ngoan hơn. Tôi đã kết thúc với

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

nhưng không có gì được trả về máy chủ web gốc (hoặc cho người dùng cuối), vì vậy không có may mắn.


cách bạn làm nó đang làm việc cho tôi, mà không cần sửa đổi gì
Saule

1
Mặc dù hơi muộn để trả lời, nhưng đã phải đối mặt với vấn đề tương tự và giải quyết nó bằng cách xóa trình phân tích cú pháp cơ thể để cơ thể yêu cầu không bị phân tích cú pháp trước khi ủy quyền thêm.
VyvIT

Câu trả lời:


52

Bạn muốn sử dụng http.request để tạo một yêu cầu tương tự với API từ xa và trả về phản hồi của nó.

Một cái gì đó như thế này:

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

Lưu ý: Tôi chưa thực sự thử ở trên, vì vậy nó có thể chứa lỗi phân tích cú pháp hy vọng điều này sẽ cho bạn một gợi ý về cách làm cho nó hoạt động.


5
Vâng, một số sửa đổi là cần thiết, nhưng tôi thích điều này tốt hơn là giới thiệu một phụ thuộc mô-đun "Proxy" mới. Một chút dài dòng, nhưng ít nhất tôi biết chính xác những gì đang xảy ra. Chúc mừng.
dùng124114

Có vẻ như bạn cần phải thực hiện res.writeHead trước khi viết chink dữ liệu, nếu không bạn sẽ gặp lỗi (tiêu đề không thể được viết sau phần thân).
setec

3
@ user124114 - vui lòng đặt giải pháp đầy đủ mà bạn đã sử dụng
Michal Tsadok

1
Dường như bạn sẽ gặp vấn đề khi đặt tiêu đề theo cách này. Cannot render headers after they are sent to the client
Shnd

1
Tôi đã cập nhật câu trả lời cho cú pháp es6 và khắc phục sự cố writeHead
mekwall

219

yêu cầu đã không được chấp nhận kể từ tháng 2 năm 2020, tôi sẽ để lại câu trả lời dưới đây vì lý do lịch sử, nhưng vui lòng xem xét chuyển sang một giải pháp thay thế được liệt kê trong vấn đề này .

Lưu trữ

Tôi đã làm một cái gì đó tương tự nhưng tôi đã sử dụng yêu cầu thay thế:

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

Tôi hy vọng điều này có ích, tôi đã mất một thời gian để nhận ra rằng tôi có thể làm điều này :)


5
Cảm ơn, đơn giản hơn nhiều so với sử dụng yêu cầu HTTP của Node.js
Alex Turpin

17
Thậm chí đơn giản hơn, nếu bạn cũng thực hiện yêu cầu: stackoverflow.com/questions/7559862/ trên
Stephan Hoyer

1
Giải pháp đẹp và sạch sẽ. Tôi cũng đã đăng một câu trả lời để làm cho nó hoạt động với yêu cầu POST (nếu không nó sẽ không chuyển tiếp phần thân bài của bạn tới API). Nếu bạn chỉnh sửa câu trả lời của mình, tôi sẽ vui lòng xóa câu trả lời của tôi.
Henrik Peinar

Cũng xem câu trả lời này để cải thiện xử lý lỗi.
Tamlyn

Bất cứ khi nào tôi cố gắng thực hiện định tuyến tương tự (hoặc chính xác như vậy), tôi sẽ kết thúc bằng cách sau: stream.js: 94 throw er; // Lỗi luồng không được xử lý trong đường ống. ^ Lỗi: getaddrinfo ENOTFOUND google.com tại errnoException (dns.js: 44: 10) tại GetAddrInfoReqWrap.onlookup [như oncomplete] (dns.js: 94: 26) có ý tưởng nào không?
keinabel

82

Tôi đã tìm thấy một giải pháp ngắn hơn và rất đơn giản, hoạt động trơn tru và cũng như xác thực bằng cách sử dụng 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
});

Và sau đó đơn giản là:

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

Lưu ý: như được đề cập bởi @MaxPRafferty, hãy sử dụng req.originalUrlthay thế baseUrlđể bảo vệ chuỗi truy vấn:

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

Cập nhật: Như Andrew đã đề cập (cảm ơn bạn!), Có một giải pháp được thực hiện theo nguyên tắc tương tự:

npm i --save http-proxy-middleware

Và sau đó:

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

Tài liệu: http-proxy-middleware trên Github

Tôi biết tôi đến muộn để tham gia bữa tiệc này, nhưng tôi hy vọng điều này sẽ giúp được ai đó.


3
Req.url không có url đầy đủ, vì vậy, đã cập nhật câu trả lời để sử dụng req.baseUrl thay vì req.url
Vinoth Kumar

1
Tôi cũng thích sử dụng req.origenUrl thay cho baseUrl để bảo toàn các truy vấn, nhưng điều này có thể không phải luôn luôn là hành vi mong muốn.
MaxPRafferty

@MaxPRafferty - bình luận trả tiền. Đáng chú ý. Cảm ơn.
Ích kỷ

4
Đây là giải pháp tốt nhất. Tôi đang sử dụng http-proxy-middleware, nhưng đó là cùng một khái niệm. Đừng quay vòng giải pháp proxy của riêng bạn khi đã có những giải pháp tuyệt vời.
Andrew

1
@tannerburton cảm ơn! Tôi cập nhật câu trả lời.
Ích kỷ

46

Để mở rộng câu trả lời của trigoman (tín dụng đầy đủ cho anh ta) để làm việc với POST (cũng có thể thực hiện công việc với PUT, v.v.):

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
Không thể làm cho nó hoạt động với PUT. Nhưng hoạt động tuyệt vời cho GET và POST. Cảm ơn bạn!!
Mariano Desanze

5
@Protron cho các yêu cầu PUT chỉ cần sử dụng một cái gì đó nhưif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil

Nếu bạn cần chuyển qua các tiêu đề như một phần của yêu cầu PUT hoặc POST, hãy đảm bảo xóa tiêu đề có độ dài nội dung để yêu cầu có thể tính toán nó. Nếu không, máy chủ nhận có thể cắt dữ liệu, điều này sẽ dẫn đến lỗi.
Carlos Rymer

@Henrik Peinar, điều này sẽ giúp ích khi tôi thực hiện yêu cầu đăng nhập và mong muốn chuyển hướng từ web.com/api/login sang web.com/
valik

22

Tôi đã sử dụng thiết lập sau để hướng mọi thứ vào /restmáy chủ phụ trợ của mình (trên cổng 8080) và tất cả các yêu cầu khác đến máy chủ frontend (máy chủ webpack trên cổng 3001). Nó hỗ trợ tất cả các phương thức HTTP, không mất bất kỳ thông tin meta yêu cầu nào và hỗ trợ websockets (mà tôi cần để tải lại nóng)

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
Đây là người duy nhất cũng giao dịch với các ổ cắm web.
sec0ndHand

11

Lần đầu tiên cài đặt express và http-proxy-middleware

npm install express http-proxy-middleware --save

Sau đó, trong server.js của bạn

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

Trong ví dụ này, chúng tôi phục vụ trang web trên cổng 3000, nhưng khi yêu cầu kết thúc bằng / api, chúng tôi chuyển hướng nó đến localhost: 8080.

http: // localhost: 3000 / api / đăng nhập chuyển hướng đến http: // localhost: 8080 / api / đăng nhập


6

Ok, đây là câu trả lời sẵn sàng để sao chép bằng cách sử dụng mô-đun npm yêu cầu ('request') và biến môi trường * thay vì proxy được mã hóa cứng):

cà phê

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
Thay vì một câu lệnh tình huống tất cả đều làm điều tương tự ngoại trừ sử dụng một hàm yêu cầu khác) trước tiên bạn có thể vệ sinh (ví dụ: một câu lệnh if gọi mặc định của bạn nếu phương thức không có trong danh sách các phương thức được phê duyệt), và sau đó chỉ cần làm r = request [phương thức] (/ * phần còn lại * /);
Paul

2

Tôi đã tìm thấy một giải pháp ngắn hơn thực hiện chính xác những gì tôi muốn https://github.com/http-party/node-http-proxy

Sau khi cài đặt http-proxy

npm install http-proxy --save

Sử dụng nó như dưới đây trong máy chủ / index / app.js của bạn

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

Tôi thực sự đã dành nhiều ngày tìm kiếm khắp nơi để tránh vấn đề này, đã thử rất nhiều giải pháp và không có giải pháp nào trong số đó có hiệu quả.

Hy vọng nó cũng sẽ giúp được người khác :)


0

Tôi không có mẫu nhanh, nhưng một mẫu có http-proxygói đơn giản . Một phiên bản rút gọn của proxy mà tôi đã sử dụng cho blog của mình.

Nói tóm lại, tất cả các gói proxy của nodejs đều hoạt động ở cấp độ giao thức http, không phải ở cấp độ tcp (socket). Điều này cũng đúng với express và tất cả phần mềm trung gian express. Không ai trong số họ có thể thực hiện proxy minh bạch, cũng như NAT, có nghĩa là giữ IP nguồn lưu lượng truy cập đến trong gói được gửi đến máy chủ web phụ trợ.

Tuy nhiên, máy chủ web có thể lấy IP gốc từ các tiêu đề chuyển tiếp http x và thêm nó vào nhật ký.

Các xfwd: truetrong proxyOptionkích hoạt tính năng tiêu đề x-forward cho 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();

Tham khảo cho Tiêu đề chuyển tiếp X: https://en.wikipedia.org/wiki/X-Forwarded-For

Phiên bản đầy đủ của proxy của tôi: https://github.com/J-Siu/ghost-https-nodejs-proxy

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.