UNPKG

terriajs

Version:

Geospatial data visualization platform.

186 lines (163 loc) 6.8 kB
import defined from "terriajs-cesium/Source/Core/defined"; import URI from "urijs"; /** * 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). */ export default class CorsProxy { static readonly DEFAULT_BASE_PROXY_PATH = "proxy/"; /** * The base URL of the TerriaJS server proxy, to which requests will be appended. In most cases this is the server's * host + '/proxy'. */ baseProxyUrl: string = CorsProxy.DEFAULT_BASE_PROXY_PATH; /** * 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. */ proxyDomains: string[] | undefined = 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 */ isOpenProxy: boolean = false; /** * Domains that are known to support CORS, as set by config files. */ corsDomains: string[] = []; /** * Whether the proxy should be used regardless of whether the domain supports CORS or not. This defaults to true * on IE<10. */ alwaysUseProxy: boolean = false; /** * 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. */ 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 serverConfig Configuration options retrieved from a ServerConfig object. * @param baseProxyUrl The base URL to proxy with - this will default to 'proxy/' * @param proxyDomains Initial value for proxyDomains to which proxyable domains from the server will be appended - * defaults to an empty array. * @returns A promise that resolves when initialisation is complete. */ init( serverConfig: any, baseProxyUrl: string = CorsProxy.DEFAULT_BASE_PROXY_PATH, proxyDomains: string[] = [] ): void { if (serverConfig !== null && serverConfig !== undefined) { 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; } } this.baseProxyUrl = baseProxyUrl; if (this.proxyDomains === null || this.proxyDomains === undefined) { this.proxyDomains = proxyDomains; } } /** * Determines whether this host is, or is a subdomain of, an item in the provided array. * * @param host The host to search for * @param domains The array of domains to look in * @returns The result. */ private hostInDomains(host: string, domains?: string[]) { if (domains === null || domains === undefined) { return false; } host = host.toLowerCase(); for (let i = 0; i < domains.length; i++) { if (host.match("(^|\\.)" + domains[i] + "$")) { return true; } } return false; } /** * 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 resource the URL to potentially proxy * @param 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 The proxied URL */ getURL(resource: string, proxyFlag?: string): string { return this.getProxyBaseURL(proxyFlag) + resource; } getProxyBaseURL(proxyFlag: string | undefined): string { const flag = proxyFlag === undefined ? "" : "_" + proxyFlag + "/"; return this.baseProxyUrl + flag; } /** * 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 resource the URL to potentially proxy * @param 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 Either the URL passed in or a proxied URL if it should be proxied. */ getURLProxyIfNecessary(resource: string, proxyFlag?: string): string { if (this.shouldUseProxy(resource)) { return this.getURL(resource, proxyFlag); } return resource; } /** * 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 url The url to examine. * @return true if the proxy should be used, false if not. */ shouldUseProxy(url: string): boolean { if (!defined(url)) { // eg. no url may be passed if all data is embedded return false; } const uri = new URI(url); const host = uri.host(); if (host === "") { // do not proxy local files return false; } if (!this.isOpenProxy && !this.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 (this.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; } }