UNPKG

terriajs

Version:

Geospatial data visualization platform.

190 lines (169 loc) 7.81 kB
"use strict"; /*global require*/ var URI = require('urijs'); var defined = require('terriajs-cesium/Source/Core/defined'); var defaultValue = require('terriajs-cesium/Source/Core/defaultValue'); var loadJson = require('./loadJson'); var FeatureDetection = require('terriajs-cesium/Source/Core/FeatureDetection'); var DEFAULT_BASE_PROXY_PATH = 'proxy/'; /** * Rewrites URLs so that they're resolved via the TerriaJS-Server proxy rather than going direct. This is most useful * for getting around CORS restrictions on services that don't have CORS set up or when using pre-CORS browsers like IE9. * Going via the proxy is also useful if you want to change the caching headers on map requests (for instance many map * tile providers set cache headers to no-cache even for maps that rarely change, resulting in a much slower experience * particularly on time-series data). * * @param overrideLoadJson A method for getting JSON from a URL that matches the signature of Core/loadJson * module - this is overridable mainly for testing. * @constructor */ function CorsProxy(overrideLoadJson) { this.loadJson = defaultValue(overrideLoadJson, loadJson); // Note that many of the following are intended to be set by a request to the server performed in {@link CorsProxy#init}, // but these can be overridden if necessary. /** * The base URL of the TerriaJS server proxy, to which requests will be appended. In most cases this is the server's * host + '/proxy'. * @type {String} */ this.baseProxyUrl = undefined; /** * Domains that should be proxied for, as set by config files. Stored as an array of hosts - if a TLD is specified, * subdomains will also be proxied. * @type {String[]} */ this.proxyDomains = undefined; /** * True if we expect that the proxy will proxy any URL - note that if the server isn't set up to do this, having * this set to true will just result in a lot of failed AJAX calls * @type {boolean} */ this.isOpenProxy = false; /** * Domains that are known to support CORS, as set by config files. * @type {String[]} */ this.corsDomains = []; /** * Whether the proxy should be used regardless of whether the domain supports CORS or not. This defaults to true * on IE<10. * @type {boolean} */ this.alwaysUseProxy = FeatureDetection.isInternetExplorer() && FeatureDetection.internetExplorerVersion()[0] < 10; // IE versions prior to 10 don't support CORS, so always use the proxy. /** * Whether the page that Terria is running on is HTTPS. This is relevant because calling an HTTP domain from HTTPS * results in mixed content warnings and going through the proxy is required to get around this. * @type {boolean} */ this.pageIsHttps = typeof window !== 'undefined' && defined(window.location) && defined(window.location.href) && new URI(window.location.href).protocol() === 'https'; } /** * Initialises values with config previously loaded from server. This is the recommended way to use this object as it ensures * the options will be correct for the proxy server it's configured to call, but this can be skipped and the values it * initialises set manually if desired. * * @param {Object} serverConfig Configuration options retrieved from a ServerConfig object. * @param {String} baseProxyUrl The base URL to proxy with - this will default to 'proxy/' * @param {String[]} proxyDomains Initial value for proxyDomains to which proxyable domains from the server will be appended - * defaults to an empty array. * @returns {Promise} A promise that resolves when initialisation is complete. */ CorsProxy.prototype.init = function(serverConfig, baseProxyUrl, proxyDomains) { this.baseProxyUrl = defaultValue(baseProxyUrl, DEFAULT_BASE_PROXY_PATH); this.proxyDomains = defaultValue(proxyDomains, []); if (serverConfig && typeof serverConfig === 'object') { this.isOpenProxy = !! serverConfig.proxyAllDomains; // ignore client list of allowed proxies in favour of definitive server list. if (Array.isArray(serverConfig.allowProxyFor)) { this.proxyDomains = serverConfig.allowProxyFor; } } }; /** * Determines if the proxying service should be used to access the given URL, based on our list of * domains we're willing to proxy for and hosts that are known to support CORS. * * @param {String} url The url to examine. * @return {Boolean} true if the proxy should be used, false if not. */ CorsProxy.prototype.shouldUseProxy = function(url) { if (!defined(url)) { // eg. no url may be passed if all data is embedded return false; } var uri = new URI(url); var host = uri.host(); if (host === '') { // do not proxy local files return false; } if (!this.isOpenProxy && !hostInDomains(host, this.proxyDomains)) { // we're not willing to proxy for this host return false; } if (this.alwaysUseProxy) { return true; } if (this.pageIsHttps && uri.protocol() === 'http') { // if we're accessing an http resource from an https page, always proxy in order to avoid a mixed content error. return true; } if (hostInDomains(host, this.corsDomains)) { // we don't need to proxy for this host, because it supports CORS return false; } // we are ok with proxying for this host and we need to return true; }; /** * Proxies a URL by appending it to {@link CorsProxy#baseProxyUrl}. Optionally inserts a proxyFlag that will override * the cache headers of the response, allowing for caching to be added where it wouldn't otherwise. * * @param {String} resource the URL to potentially proxy * @param {String} proxyFlag the proxy flag to pass - generally this is the length of time that you want to override * the cache headers with. E.g. '2d' for 2 days. * @returns {String} The proxied URL */ CorsProxy.prototype.getURL = function (resource, proxyFlag) { var flag = (proxyFlag === undefined) ? '' : '_' + proxyFlag + '/'; return this.baseProxyUrl + flag + resource; }; /** * Convenience method that combines {@link CorsProxy#shouldUseProxy} and {@link getURL} - if the URL passed needs to be * proxied according to the rules/config of the proxy, this will return a proxied URL, otherwise it will return the * original URL. * * {@see CorsProxy#shouldUseProxy} * {@see CorsProxy#getURL} * * @param {String} resource the URL to potentially proxy * @param {String} proxyFlag the proxy flag to pass - generally this is the length of time that you want to override * the cache headers with. E.g. '2d' for 2 days. * @returns {String} Either the URL passed in or a proxied URL if it should be proxied. */ CorsProxy.prototype.getURLProxyIfNecessary = function (resource, proxyFlag) { if (this.shouldUseProxy(resource)) { return this.getURL(resource, proxyFlag); } return resource; }; /** * Determines whether this host is, or is a subdomain of, an item in the provided array. * * @param {String} host The host to search for * @param {String[]} domains The array of domains to look in * @returns {boolean} The result. */ function hostInDomains(host, domains) { if (!defined(domains)) { return false; } host = host.toLowerCase(); for (var i = 0; i < domains.length; i++) { if (host.match("(^|\\.)" + domains[i] + "$")) { return true; } } return false; } module.exports = CorsProxy;