CoolFace
Apppublic

dtsky/nodejs

sourceHugging Faceapache-2.0updated 3y agoView on Hugging Face
0likes
app.js473 linesDownload Raw Back to root
1// © 2013 - 2016 Rob Wu <rob@robwu.nl>2// Released under the MIT license3 4'use strict';5 6var httpProxy = require('http-proxy');7var net = require('net');8var url = require('url');9var getProxyForUrl = require('proxy-from-env').getProxyForUrl;10 11/**12 * Check whether the specified hostname is valid.13 *14 * @param hostname {string} Host name (excluding port) of requested resource.15 * @return {boolean} Whether the requested resource can be accessed.16 */17function isValidHostName(hostname) {18    return !!(19        hostname.indexOf('.') > 0 ||20        net.isIPv4(hostname) ||21        net.isIPv6(hostname)22    );23}24 25/**26 * Adds CORS headers to the response headers.27 *28 * @param headers {object} Response headers29 * @param request {ServerRequest}30 */31function withCORS(headers, request) {32    headers['access-control-allow-origin'] = '*';33    var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge;34    if (corsMaxAge) {35        headers['access-control-max-age'] = corsMaxAge;36    }37    if (request.headers['access-control-request-method']) {38        headers['access-control-allow-methods'] = request.headers['access-control-request-method'];39        delete request.headers['access-control-request-method'];40    }41    if (request.headers['access-control-request-headers']) {42        headers['access-control-allow-headers'] = request.headers['access-control-request-headers'];43        delete request.headers['access-control-request-headers'];44    }45 46    headers['access-control-expose-headers'] = Object.keys(headers).join(',');47 48    return headers;49}50 51/**52 * Performs the actual proxy request.53 *54 * @param req {ServerRequest} Incoming http request55 * @param res {ServerResponse} Outgoing (proxied) http request56 * @param proxy {HttpProxy}57 */58function proxyRequest(req, res, proxy) {59    var location = req.corsAnywhereRequestState.location;60    req.url = location.path;61 62    var proxyOptions = {63        changeOrigin: false,64        prependPath: false,65        target: location,66        headers: {67            host: location.host,68        },69        // HACK: Get hold of the proxyReq object, because we need it later.70        // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L14471        buffer: {72            pipe: function (proxyReq) {73                var proxyReqOn = proxyReq.on;74                // Intercepts the handler that connects proxyRes to res.75                // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L146-L15876                proxyReq.on = function (eventName, listener) {77                    if (eventName !== 'response') {78                        return proxyReqOn.call(this, eventName, listener);79                    }80                    return proxyReqOn.call(this, 'response', function (proxyRes) {81                        if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) {82                            try {83                                listener(proxyRes);84                            } catch (err) {85                                // Wrap in try-catch because an error could occur:86                                // "RangeError: Invalid status code: 0"87                                // https://github.com/Rob--W/cors-anywhere/issues/9588                                // https://github.com/nodejitsu/node-http-proxy/issues/108089 90                                // Forward error (will ultimately emit the 'error' event on our proxy object):91                                // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L13492                                proxyReq.emit('error', err);93                            }94                        }95                    });96                };97                return req.pipe(proxyReq);98            },99        },100    };101 102    var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl(location.href);103    if (proxyThroughUrl) {104        proxyOptions.target = proxyThroughUrl;105        proxyOptions.toProxy = true;106        // If a proxy URL was set, req.url must be an absolute URL. Then the request will not be sent107        // directly to the proxied URL, but through another proxy.108        req.url = location.href;109    }110 111    // Start proxying the request112    try {113        proxy.web(req, res, proxyOptions);114    } catch (err) {115        proxy.emit('error', err, req, res);116    }117}118 119/**120 * This method modifies the response headers of the proxied response.121 * If a redirect is detected, the response is not sent to the client,122 * and a new request is initiated.123 *124 * client (req) -> CORS Anywhere -> (proxyReq) -> other server125 * client (res) <- CORS Anywhere <- (proxyRes) <- other server126 *127 * @param proxy {HttpProxy}128 * @param proxyReq {ClientRequest} The outgoing request to the other server.129 * @param proxyRes {ServerResponse} The response from the other server.130 * @param req {IncomingMessage} Incoming HTTP request, augmented with property corsAnywhereRequestState131 * @param req.corsAnywhereRequestState {object}132 * @param req.corsAnywhereRequestState.location {object} See parseURL133 * @param req.corsAnywhereRequestState.getProxyForUrl {function} See proxyRequest134 * @param req.corsAnywhereRequestState.proxyBaseUrl {string} Base URL of the CORS API endpoint135 * @param req.corsAnywhereRequestState.maxRedirects {number} Maximum number of redirects136 * @param req.corsAnywhereRequestState.redirectCount_ {number} Internally used to count redirects137 * @param res {ServerResponse} Outgoing response to the client that wanted to proxy the HTTP request.138 *139 * @returns {boolean} true if http-proxy should continue to pipe proxyRes to res.140 */141function onProxyResponse(proxy, proxyReq, proxyRes, req, res) {142    var requestState = req.corsAnywhereRequestState;143 144    var statusCode = proxyRes.statusCode;145 146    if (!requestState.redirectCount_) {147        res.setHeader('x-request-url', requestState.location.href);148    }149    // Handle redirects150    if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) {151        var locationHeader = proxyRes.headers.location;152        var parsedLocation;153        if (locationHeader) {154            locationHeader = url.resolve(requestState.location.href, locationHeader);155            parsedLocation = parseURL(locationHeader);156        }157        if (parsedLocation) {158            if (statusCode === 301 || statusCode === 302 || statusCode === 303) {159                // Exclude 307 & 308, because they are rare, and require preserving the method + request body160                requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1;161                if (requestState.redirectCount_ <= requestState.maxRedirects) {162                    // Handle redirects within the server, because some clients (e.g. Android Stock Browser)163                    // cancel redirects.164                    // Set header for debugging purposes. Do not try to parse it!165                    res.setHeader('X-CORS-Redirect-' + requestState.redirectCount_, statusCode + ' ' + locationHeader);166 167                    req.method = 'GET';168                    req.headers['content-length'] = '0';169                    delete req.headers['content-type'];170                    requestState.location = parsedLocation;171 172                    // Remove all listeners (=reset events to initial state)173                    req.removeAllListeners();174 175                    // Remove the error listener so that the ECONNRESET "error" that176                    // may occur after aborting a request does not propagate to res.177                    // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134178                    proxyReq.removeAllListeners('error');179                    proxyReq.once('error', function catchAndIgnoreError() { });180                    proxyReq.abort();181 182                    // Initiate a new proxy request.183                    proxyRequest(req, res, proxy);184                    return false;185                }186            }187            proxyRes.headers.location = requestState.proxyBaseUrl + '/' + locationHeader;188        }189    }190 191    // Strip cookies192    delete proxyRes.headers['set-cookie'];193    delete proxyRes.headers['set-cookie2'];194 195    proxyRes.headers['x-final-url'] = requestState.location.href;196    withCORS(proxyRes.headers, req);197    return true;198}199 200/**201 * @param req_url {string} The requested URL (scheme is optional).202 * @return {object} URL parsed using url.parse203 */204function parseURL(req_url) {205 206    var host = req_url.slice(0, 8);207 208    if (host.startsWith('/')){209        return null;210    } else if (host.includes("://")) {211 212    } else if (host.includes(':/')) {213        req_url = new URL(req_url).href;214    } else {215        req_url = "http://" + req_url;216    }217 218    return url.parse(req_url);219}220 221// Request handler factory222function getHandler(options, proxy) {223    var corsAnywhere = {224        getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use225        maxRedirects: 5,                // Maximum number of redirects to be followed.226        keywordBlacklist: [],           // Requests from these keywords will be blocked.227        originBlacklist: [],            // Requests from these origins will be blocked.228        originWhitelist: [],            // If non-empty, requests not from an origin in this list will be blocked.229        checkRateLimit: null,           // Function that may enforce a rate-limit by returning a non-empty string.230        redirectSameOrigin: false,      // Redirect the client to the requested URL for same-origin requests.231        requireHeader: null,            // Require a header to be set?232        removeHeaders: [],              // Strip these request headers.233        setHeaders: {},                 // Set these request headers.234        corsMaxAge: 0                   // If set, an Access-Control-Max-Age header with this value (in seconds) will be added.235    };236 237    Object.keys(corsAnywhere).forEach(function (option) {238        if (Object.prototype.hasOwnProperty.call(options, option)) {239            corsAnywhere[option] = options[option];240        }241    });242 243    // Convert corsAnywhere.requireHeader to an array of lowercase header names, or null.244    if (corsAnywhere.requireHeader) {245        if (typeof corsAnywhere.requireHeader === 'string') {246            corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()];247        } else if (!Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0) {248            corsAnywhere.requireHeader = null;249        } else {250            corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function (headerName) {251                return headerName.toLowerCase();252            });253        }254    }255    var hasRequiredHeaders = function (headers) {256        return !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function (headerName) {257            return Object.hasOwnProperty.call(headers, headerName);258        });259    };260 261    return function (req, res) {262        req.corsAnywhereRequestState = {263            getProxyForUrl: corsAnywhere.getProxyForUrl,264            maxRedirects: corsAnywhere.maxRedirects,265            corsMaxAge: corsAnywhere.corsMaxAge,266        };267 268        let clientip = req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP269            req.connection.remoteAddress || // 判断 connection 的远程 IP270            req.socket.remoteAddress || // 判断后端的 socket 的 IP271            req.connection.socket.remoteAddress;272 273        console.log(JSON.stringify([274            new Date().toISOString(),275            req.method,276            req.url,277            clientip,278            req.headers["user-agent"]279        ]));280 281        var cors_headers = withCORS({}, req);282        if (req.method === 'OPTIONS') {283            // Pre-flight request. Reply successfully:284            res.writeHead(200, cors_headers);285            res.end();286            return;287        }288 289        //var location = parseURL(decodeURIComponent(req.url.slice(1)));290        try {291            var raw = req.url;292            var idx = raw.indexOf('/', 1);293            var url = raw.slice(idx + 1);294            var location = parseURL(url);295        }catch(err){296            console.log(err.message);297        }298 299        if (!location) {300            // Invalid API call. Show how to correctly use the API301            res.writeHead(200, { 'Content-Type': 'text/plain' });302            res.end('404');303            return;304        }305 306        if (location.host === 'iscorsneeded') {307            // Is CORS needed? This path is provided so that API consumers can test whether it's necessary308            // to use CORS. The server's reply is always No, because if they can read it, then CORS headers309            // are not necessary.310            res.writeHead(200, { 'Content-Type': 'text/plain' });311            res.end('no');312            return;313        }314 315        if (location.port > 65535) {316            // Port is higher than 65535317            res.writeHead(400, 'Invalid port', cors_headers);318            res.end('Port number too large: ' + location.port);319            return;320        }321 322        if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) {323            // Don't even try to proxy invalid hosts (such as /favicon.ico, /robots.txt)324            res.writeHead(404, 'Invalid host', cors_headers);325            res.end('Invalid host: ' + location.hostname);326            return;327        }328        //location = new URL(location).href;329 330        if (!hasRequiredHeaders(req.headers)) {331            res.writeHead(400, 'Header required', cors_headers);332            res.end('Missing required request header. Must specify one of: ' + corsAnywhere.requireHeader);333            return;334        }335 336        if (corsAnywhere.keywordBlacklist.filter(x => location.href.indexOf(x) >= 0).length > 0) {337            res.writeHead(403, 'Forbidden', cors_headers);338            res.end('The keyword "' + corsAnywhere.keywordBlacklist.join(" ") + '" was blacklisted by the operator of this proxy.');339            return;340        }341 342        if (corsAnywhere.originBlacklist.indexOf(location.hostname) >= 0) {343            res.writeHead(403, 'Forbidden', cors_headers);344            res.end('The origin "' + location.hostname + '" was blacklisted by the operator of this proxy.');345            return;346        }347 348        if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(location.hostname) === -1) {349            res.writeHead(403, 'Forbidden', cors_headers);350            res.end('The origin "' + location.hostname + '" was not whitelisted by the operator of this proxy.');351            return;352        }353 354        var origin = req.headers.origin || '';355 356        if (corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === '/' &&357            location.href.lastIndexOf(origin, 0) === 0) {358            // Send a permanent redirect to offload the server. Badly coded clients should not waste our resources.359            cors_headers.vary = 'origin';360            cors_headers['cache-control'] = 'private';361            cors_headers.location = location.href;362            res.writeHead(301, 'Please use a direct request', cors_headers);363            res.end();364            return;365        }366 367        var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']);368        var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host;369 370        corsAnywhere.removeHeaders.forEach(function (header) {371            delete req.headers[header];372        });373 374        Object.keys(corsAnywhere.setHeaders).forEach(function (header) {375            req.headers[header] = corsAnywhere.setHeaders[header];376        });377 378        var host = raw.slice(1, idx);379        var referer = host.indexOf('.') != -1 ? 'https://' + host : location.href;380        console.log('网址' + location.href + ' 来路' + referer)381        req.headers.referer = referer;382 383        req.corsAnywhereRequestState.location = location;384        req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl;385 386        proxyRequest(req, res, proxy);387    };388}389 390// Create server with default and given values391// Creator still needs to call .listen()392function createServer(options) {393    options = options || {};394 395    // Default options:396    var httpProxyOptions = {397        xfwd: true,            // Append X-Forwarded-* headers398    };399    // Allow user to override defaults and add own options400    if (options.httpProxyOptions) {401        Object.keys(options.httpProxyOptions).forEach(function (option) {402            httpProxyOptions[option] = options.httpProxyOptions[option];403        });404    }405 406    var proxy = httpProxy.createServer(httpProxyOptions);407    var requestHandler = getHandler(options, proxy);408    var server;409    if (options.httpsOptions) {410        server = require('https').createServer(options.httpsOptions, requestHandler);411    } else {412        server = require('http').createServer(requestHandler);413    }414 415    // When the server fails, just show a 404 instead of Internal server error416    proxy.on('error', function (err, req, res) {417        if (res.headersSent) {418            // This could happen when a protocol error occurs when an error occurs419            // after the headers have been received (and forwarded). Do not write420            // the headers because it would generate an error.421            // Prior to Node 13.x, the stream would have ended.422            // As of Node 13.x, we must explicitly close it.423            if (res.writableEnded === false) {424                res.end();425            }426            return;427        }428 429        // When the error occurs after setting headers but before writing the response,430        // then any previously set headers must be removed.431        var headerNames = res.getHeaderNames ? res.getHeaderNames() : Object.keys(res._headers || {});432        headerNames.forEach(function (name) {433            res.removeHeader(name);434        });435 436        res.writeHead(404, { 'Access-Control-Allow-Origin': '*' });437        res.end('Not found because of proxy error: ' + err);438    });439 440    return server;441};442 443process.on('uncaughtException', function (exception) {444    console.log(exception);445});446 447// ========================== Start ==========================448 449// Listen on a specific host via the HOST environment variable450var host = process.env.HOST || '0.0.0.0';451// Listen on a specific port via the PORT environment variable452var port = process.env.PORT || 3000;453 454createServer({455    // 移除头部的Key456    removeHeaders: [457        'x-heroku-queue-wait-time',458        'x-heroku-queue-depth',459        'x-heroku-dynos-in-use',460        'x-request-start',461    ],462    keywordBlacklist: [".mpd", ".m4v"],// 阻断的关键字463    originBlacklist: [],// 阻断的名单464    originWhitelist: [],// 如果不为空,只允许白名单465    redirectSameOrigin: true,466    // http-proxy 组件选项配置467    httpProxyOptions: {468        xfwd: false,// 是否添加 X-Forwarded-For 头469        secure: false // ignore ssl470    },471}).listen(port, host, function () {472    console.log(new Date().toISOString() + ' Running CORS Anywhere on ' + host + ':' + port);473});