Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money

preface

In the previous chapter we covered the cross-domain solution of Vue local development requests and the basic implementation principles. In this chapter we continue to delve into the implementation principles of server proxies

Those interested in the previous chapter can check it out. Vue local development request cross-domain resolution and server proxy principle, never be a person who does not learn only [I]

We concluded the previous chapter by saying that proxyTable is a proxy server provided by WebPack in the development environment (using HTTP-proxy-Middleware). Before we talk about HTTP-proxy-middleware we need to talk about Node-HTTP-proxy.

A, node – HTTP proxy

Node-http-proxy is an HTTP programmable proxy library that supports WebSockets. It is suitable for implementing components such as reverse proxies and load balancing.

1.1 What is Agency

Here is a picture to illustrate my simple understanding:

As shown in the figure above, the proxy server stands in front of the server. This layer of proxy is not known to the user and is called a reverse proxy because it is directed to the server. If the user proactively sets the proxy on the client, it is a forward proxy.

The forward proxy is for the client and the reverse proxy is for the server

So what’s the use of having a layer of proxy servers in front of the server?

As shown in the figure, server A listens on port 8001 at 192.168.100.162 (assuming that it mainly stores background interfaces starting with/API). Server B listens on port 8002 at 192.168.100.162 (assuming that static files starting with/are stored). So we in the client access 192.168.100.162:8001 / API/XXX can obtain the return of the interface data; To obtain the corresponding static file, visit 192.168.100.162:8002/${static_file_path}.

This keeps the page and interface separate, but when it comes to actual use, we need to unify them.

So how do you unify them? This requires the proxy server to do some of the work for us.

Suppose the following configuration proxy server is used (the proxy server listens on port 8003). Client access 192.168.100.162:8003 / API / * with the proxy server to help us access 192.168.100.162:8001 / API / *; The proxy server accesses 192.168.100.162:8002/* when accessing 192.168.100.162:8003/*. In this case, the above problem is solved.

1.2 Node-HTTP-proxy source code learning

Now that you have a general understanding of what a proxy is, let’s start learning the source code for Node-HTTP-proxy.

In fact, Node-HTTP-proxy helps us to send the request according to the configuration we input.

I will extract the source code out of a most basic content for easy learning, other content need to understand can see node-HTTP-proxy learning.

The project directory is as follows (same as the source code) :

src/http-proxy.js

const ProxyServer = require('./http-proxy/index.js').Server;

function createProxyServer(options) {
    return new ProxyServer(options);
}

ProxyServer.createProxyServer = createProxyServer;
ProxyServer.createServer      = createProxyServer;
ProxyServer.createProxy       = createProxyServer;

module.exports = ProxyServer;
Copy the code

src/http-proxy/index.js

const httpProxy = module.exports;
const parse_url = require('url').parse;
const http = require("http");
const https = require('https');
const EventEmitter = require('events');
const web = require('./passes/web-incoming');

function createRightProxy(options) {

    return function(req, res, /*, [opts] */) {

        // The following this points to the ProxyServer instance

        const passes = this.webPasses;
        const args = Array.prototype.slice.call(arguments);
        const cntr = args.length - 1;

        const requestOptions = options;
        // Parse the optional opts argument
        if(args[cntr] ! == res) {// Override request options
            Object.assign(requestOptions, args[cntr]);
        }

        ['target'].forEach((e) = > {
            // If target is set to string format, it is parsed as an object
            if (typeof requestOptions[e] === 'string') { requestOptions[e] = parse_url(requestOptions[e]); }});if(! requestOptions.target) {return this.emit('error'.new Error('Must provide a proper URL as target'));
        }

        for(let i=0; i < passes.length; i++) {
            if(passes[i](req, res, requestOptions, this)) {
                break; }}}}class ProxyServer extends EventEmitter {

    constructor(options) {
        super(a);// This method is the core of our project.
        this.web = createRightProxy(options);
        this.options = options || {};
        
        this.webPasses = Object.keys(web).map(function(pass) {
            return web[pass];
        });

        this.on('error'.this.onError);
    }

    onError(err) {
        throw err;
    }

    listen(port) {
        // This closure is passed to the http.createserver method, which returns the http.server instance
        // This refers to the http.Server instance when this method is called, so we need to set this here
        const self = this;
        const closure = function(req, res) {
            self.web(req, res);
        };

        // Create a proxy server and listen on the corresponding port
        this._server  = this.options.ssl ?
        https.createServer(this.options.ssl, closure) :
        http.createServer(closure);
        this._server.listen(port);
        return this;
    }
}

httpProxy.Server = ProxyServer;
Copy the code

src/http-proxy/common.js

const common = exports;
const url = require('url');

const isSSL = /^https|wss/;

common.setupOutgoing = (outgoing, options, req) = > {

    // Use target's port. If not, use the default value
    outgoing.port = options['target'].port || (isSSL.test(options['target'].protocol)? 443 : 80);

    // Get host and hostname
    ['host'.'hostname'].forEach(
        function(e) { outgoing[e] = options['target'][e]; });/ / method
    outgoing.method = options.method || req.method;

    / / headers
    outgoing.headers = Object.assign({}, req.headers);
    if (options.headers) {
        Object.assign(outgoing.headers, options.headers);
    }

    // path
    outgoing.path = url.parse(req.url).path || ' ';

    return outgoing;

}
Copy the code

src/http-proxy/passes/web-incoming.js

const httpNative = require('http');
const httpsNative = require('https');
const common = require('.. /common');
const webOutgoing = require('./web-outgoing');

const web_o = Object.keys(webOutgoing).map(function(pass) {
    return webOutgoing[pass];
});

const nativeAgents = {
    http: httpNative,
    https: httpsNative
};

// web-incoming processes processing requests.
module.exports = {
    stream: (req, res, options, server) = > {

        server.emit('start', req, res, options.target);

        const { http, https } = nativeAgents;

        const proxyReq = (options.target.protocol === 'https:'? https : http).request(
            common.setupOutgoing(options.ssl || {}, options, req)
        );

        proxyReq.on('socket'.(socket) = > {
            // Users can listen for proxyReq events
            if(server) { server.emit('proxyReq', proxyReq, req, res, options) };
        });

        // Error handling related ----
        req.on('aborted'.() = > {
            proxyReq.abort();
        });
        const proxyError = createErrorHandler(proxyReq, options.target);
        req.on('error', proxyError);
        proxyReq.on('error', proxyError);
        function createErrorHandler(proxyReq, url) {
            return function proxyError(err) {
                if (req.socket.destroyed && err.code === 'ECONNRESET') {
                    server.emit('econnreset', err, req, res, url);
                    return proxyReq.abort();
                }
                server.emit('error', err, req, res, url); }}// -----

        // The key here is to pipe the REQ request to the proxyReq request
        req.pipe(proxyReq);

        proxyReq.on('response'.(proxyRes) = > {
            // Users can listen for proxyRes events
            if(server) { server.emit('proxyRes', proxyReq, req, res, options); }
            
            // Process the response
            if(! res.headersSent && ! options.selfHandleResponse) {for(var i=0; i < web_o.length; i++) {
                    if(web_o[i](req, res, proxyRes, options)) { break; }}}if(! res.finished) { proxyRes.on('end'.function () {
                    if (server) server.emit('end', req, res, proxyRes);
                });
                // Pass the proxyRes response through the pipe to the RES response
                proxyRes.pipe(res);
            } else {
                if (server) server.emit('end', req, res, proxyRes); }}); }};Copy the code

src/http-proxy/passes/web-outgoing.js

// The web-outcoming method handles the response request
module.exports = {
    writeStatusCode: function writeStatusCode(req, res, proxyRes) {
        // From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers])
        if(proxyRes.statusMessage) {
          res.statusCode = proxyRes.statusCode;
          res.statusMessage = proxyRes.statusMessage;
        } else{ res.statusCode = proxyRes.statusCode; }}};Copy the code

1.3 example

Well, the content of the source code is basically like the above, to achieve the most core basic functions of Node-HTTP-proxy. Let’s write an example to test it:

examples/test.js

const httpProxy = require('.. /src/http-proxy');
const http = require('http');
const fs = require('fs');

httpProxy.createServer({
    target:'http://localhost:8001'
}).listen(8003);
  
//
// Target Http Server
//
http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true.2));
  res.end();
}).listen(8001);

console.log('proxy server start at 8003');
console.log('server start at 8001');
Copy the code

Run the node examples/test.js command to start the script. Then open the page and go to localhost:8003.

Success!!


1.4 Other usage methods

  1. Custom request logic, as follows:
const httpProxy = require('.. /src/http-proxy');
const http = require('http');

const proxy = httpProxy.createProxyServer({});

const proxyServer = http.createServer((req, res) = > {
    // Customize the request logic before the proxy request
    proxy.web(req, res, { target:'http://localhost:8001' });
});

proxyServer.listen(8003);

// Target Http Server
//
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true.2));
res.end();
}).listen(8001);

console.log('proxy server start at 8003');
console.log('server start at 8001');
Copy the code
  1. The proxy requests headers rewrite
const httpProxy = require('.. /src/http-proxy');
const http = require('http');

const proxy = httpProxy.createProxyServer({});

// Set the proxy to request headers
proxy.on('proxyReq'.function(proxyReq, req, res, options) {
    proxyReq.setHeader('X-Special-Proxy-Header'.'foobar');
});

const proxyServer = http.createServer((req, res) = > {
    // Customize the request logic before the proxy request
    proxy.web(req, res, { target:'http://localhost:8001' });
});

proxyServer.listen(8003);

// Target Http Server
//
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true.2));
res.end();
}).listen(8001);

console.log('proxy server start at 8003');
console.log('server start at 8001');
Copy the code

The request returns the following:

request successfully proxied to: /
{
  "host": "localhost:8003"."connection": "keep-alive"."cache-control": "max-age=0"."upgrade-insecure-requests": "1"."user-agent": "Mozilla / 5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/79.0.3945.88 Safari/537.36"."sec-fetch-user": "? 1"."accept": "text/html,application/xhtml+xml,application/xml; Q = 0.9, image/webp image/apng, * / *; Q = 0.8, application/signed - exchange; v=b3; Q = 0.9"."sec-fetch-site": "cross-site"."sec-fetch-mode": "navigate"."accept-encoding": "gzip, deflate, br"."accept-language": "zh-CN,zh; Q = 0.9"."cookie": "_ga = GA1.1.1152336717.1566564547"."x-special-proxy-header": "foobar" // The headers set was added successfully
}
Copy the code

In this chapter we will finish here, and in the next chapter we will continue to talk about the principle of server proxy. Thank you for watching