diff --git a/factories/esSearcherFactory.js b/factories/esSearcherFactory.js index 1ad1ca8..a833e4f 100644 --- a/factories/esSearcherFactory.js +++ b/factories/esSearcherFactory.js @@ -114,6 +114,7 @@ var self = this; var uri = esUrlSvc.parseUrl(self.url); var apiMethod = self.config.apiMethod; + var proxyUrl = self.config.proxyUrl; if ( esUrlSvc.isBulkCall(uri) ) { apiMethod = 'BULK'; @@ -135,7 +136,7 @@ }); } var url = esUrlSvc.buildUrl(uri); - var transport = transportSvc.getTransport({apiMethod: apiMethod}); + var transport = transportSvc.getTransport({apiMethod: apiMethod, proxyUrl: proxyUrl}); var queryDslWithPagerArgs = angular.copy(self.queryDsl); if (self.pagerArgs) { @@ -382,6 +383,7 @@ var uri = esUrlSvc.parseUrl(self.url); var apiMethod = self.config.apiMethod; + var proxyUrl = self.config.proxyUrl; var templateCall = isTemplateCall(self.args); @@ -399,7 +401,7 @@ // }); //} var url = esUrlSvc.buildRenderTemplateUrl(uri); - var transport = transportSvc.getTransport({apiMethod: apiMethod}); + var transport = transportSvc.getTransport({apiMethod: apiMethod, proxyUrl: proxyUrl}); var queryDslWithPagerArgs = angular.copy(self.queryDsl); if (self.pagerArgs) { diff --git a/factories/httpJsonpTransportFactory.js b/factories/httpJsonpTransportFactory.js index fb648a9..b245833 100644 --- a/factories/httpJsonpTransportFactory.js +++ b/factories/httpJsonpTransportFactory.js @@ -6,11 +6,11 @@ angular.module('o19s.splainer-search') .factory('HttpJsonpTransportFactory', [ 'TransportFactory', - '$http', + '$http','$sce', HttpJsonpTransportFactory ]); - function HttpJsonpTransportFactory(TransportFactory, $http) { + function HttpJsonpTransportFactory(TransportFactory, $http, $sce) { var Transport = function(options) { TransportFactory.call(this, options); }; @@ -21,6 +21,8 @@ Transport.prototype.query = query; function query(url) { + url = $sce.trustAsResourceUrl(url); + // you don't get header or payload support with jsonp, it's akin to GET requests that way. return $http.jsonp(url, { jsonpCallbackParam: 'json.wrf' }); } diff --git a/factories/httpProxyTransportFactory.js b/factories/httpProxyTransportFactory.js new file mode 100644 index 0000000..dc8f0c5 --- /dev/null +++ b/factories/httpProxyTransportFactory.js @@ -0,0 +1,37 @@ +'use strict'; + +/*jslint latedef:false*/ + +(function() { + angular.module('o19s.splainer-search') + .factory('HttpProxyTransportFactory', [ + 'TransportFactory', + '$http', + 'HttpJsonpTransportFactory', + HttpProxyTransportFactory + ]); + + function HttpProxyTransportFactory(TransportFactory, $http, HttpJsonpTransportFactory) { + var Transport = function(options) { + TransportFactory.call(this, options); + }; + + Transport.prototype = Object.create(TransportFactory.prototype); + Transport.prototype.constructor = Transport; + + Transport.prototype.query = query; + + function query(url, payload, headers) { + var transport = this.options().transport; + + // It doesn't make sense to use JSONP instead of GET with a proxy + if (transport instanceof HttpJsonpTransportFactory) { + throw new Error('It does not make sense to proxy a JSONP connection, use GET instead.'); + } + url = this.options().proxyUrl + url; + return transport.query(url, payload, headers); + } + + return Transport; + } +})(); diff --git a/factories/searchApiSearcherFactory.js b/factories/searchApiSearcherFactory.js index 2d89fa1..0bd2821 100644 --- a/factories/searchApiSearcherFactory.js +++ b/factories/searchApiSearcherFactory.js @@ -40,6 +40,7 @@ + /* jshint unused: false */ function addDocToGroup (groupedBy, group, searchApiDoc) { /*jslint validthis:true*/ console.log('addDocToGroup'); @@ -59,9 +60,10 @@ /*jslint validthis:true*/ const self= this; var apiMethod = self.config.apiMethod; + var proxyUrl = self.config.proxyUrl; var url = self.url; var uri = esUrlSvc.parseUrl(self.url); - var transport = transportSvc.getTransport({apiMethod: apiMethod}); + var transport = transportSvc.getTransport({apiMethod: apiMethod, proxyUrl: proxyUrl}); // maybe the url and the payload should be managed inside the transport? // i don't like how it's not more seamless what to do on a GET and a POST diff --git a/factories/solrSearcherFactory.js b/factories/solrSearcherFactory.js index 86bfa75..9e2a7ce 100644 --- a/factories/solrSearcherFactory.js +++ b/factories/solrSearcherFactory.js @@ -6,7 +6,6 @@ angular.module('o19s.splainer-search') .factory('SolrSearcherFactory', [ '$q', - '$sce', '$log', 'SolrDocFactory', 'SearcherFactory', @@ -18,7 +17,7 @@ ]); function SolrSearcherFactory( - $q, $sce, $log, + $q, $log, SolrDocFactory, SearcherFactory, transportSvc, activeQueries, defaultSolrConfig, solrSearcherPreprocessorSvc @@ -231,16 +230,16 @@ activeQueries.count++; return $q(function(resolve, reject) { - var trustedUrl = $sce.trustAsResourceUrl(url); var apiMethod = defaultSolrConfig.apiMethod; // Solr defaults to JSONP if (self.config && self.config.apiMethod) { apiMethod = self.config.apiMethod; } + var proxyUrl = self.config.proxyUrl; + + var transport = transportSvc.getTransport({apiMethod: apiMethod, proxyUrl: proxyUrl}); - var transport = transportSvc.getTransport({apiMethod: apiMethod}); - - transport.query(trustedUrl, null, null) + transport.query(url, null, null) .then(function success(resp) { var solrResp = resp.data; activeQueries.count--; diff --git a/factories/vectaraSearcherFactory.js b/factories/vectaraSearcherFactory.js index 9ce89fb..4a3262f 100644 --- a/factories/vectaraSearcherFactory.js +++ b/factories/vectaraSearcherFactory.js @@ -106,8 +106,9 @@ /*jslint validthis:true*/ const self= this; var apiMethod = 'POST'; + var proxyUrl = self.config.proxyUrl; var url = self.url; - var transport = transportSvc.getTransport({apiMethod: apiMethod}); + var transport = transportSvc.getTransport({apiMethod: apiMethod, proxyUrl: proxyUrl}); var queryDslWithPagerArgs = angular.copy(self.queryDsl); if (self.pagerArgs) { diff --git a/pages/home/HomeCtrl.js b/pages/home/HomeCtrl.js index fa6f4c6..6362055 100644 --- a/pages/home/HomeCtrl.js +++ b/pages/home/HomeCtrl.js @@ -5,4 +5,4 @@ angular.module('myApp').controller('HomeCtrl', ['$scope', 'o19sRSearch', function($scope, o19sRSearch) { //TODO - put any directive code here -}]); \ No newline at end of file +}]); diff --git a/services/transportSvc.js b/services/transportSvc.js index 695dcb6..54a99c0 100644 --- a/services/transportSvc.js +++ b/services/transportSvc.js @@ -6,11 +6,13 @@ angular.module('o19s.splainer-search') 'HttpGetTransportFactory', 'HttpJsonpTransportFactory', 'BulkTransportFactory', + 'HttpProxyTransportFactory', function transportSvc( HttpPostTransportFactory, HttpGetTransportFactory, HttpJsonpTransportFactory, - BulkTransportFactory + BulkTransportFactory, + HttpProxyTransportFactory ) { var self = this; @@ -27,16 +29,23 @@ angular.module('o19s.splainer-search') if (apiMethod !== undefined) { apiMethod = apiMethod.toUpperCase(); } - + let transport = null; if (apiMethod === 'BULK') { - return bulkTransport; + transport = bulkTransport; } else if (apiMethod === 'JSONP') { - return httpJsonpTransport; + transport = httpJsonpTransport; } else if (apiMethod === 'GET') { - return httpGetTransport; + transport = httpGetTransport; } else { - return httpPostTransport; + transport = httpPostTransport; + } + + var proxyUrl = options.proxyUrl; + if (proxyUrl !== undefined) { + transport = new HttpProxyTransportFactory({proxyUrl: proxyUrl, transport: transport}); + //transport = proxyTransport; } + return transport; } } ]); diff --git a/test/spec/bulkTransportFactory.js b/test/spec/bulkTransportFactory.js index 6d58ddb..9b19806 100644 --- a/test/spec/bulkTransportFactory.js +++ b/test/spec/bulkTransportFactory.js @@ -6,8 +6,6 @@ describe('Service: transport: es bulk transport', function() { beforeEach(module('o19s.splainer-search')); var $httpBackend; - var $q; - var $rootScope; var $timeout; var BulkTransportFactory; @@ -17,8 +15,6 @@ describe('Service: transport: es bulk transport', function() { beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend'); - $q = $injector.get('$q'); - $rootScope = $injector.get('$rootScope'); $timeout = $injector.get('$timeout'); })); diff --git a/test/spec/esSearchSvc.js b/test/spec/esSearchSvc.js index cf749ff..c22e5ff 100644 --- a/test/spec/esSearchSvc.js +++ b/test/spec/esSearchSvc.js @@ -1276,6 +1276,63 @@ describe('Service: searchSvc: ElasticSearch', function() { expect(called).toEqual(1); }); }); + describe('templated and proxied search', function() { + beforeEach(inject(function () { + + // the 'id' tells us that we have a templated search. + var mockEsParams = { + id: 'tmdb-title-search-template', + params: { + search_query: 'star' + } + }; + + var config = { + apiMethod: 'POST', + proxyUrl: 'http://myserver/api?url=' + } + + searcher = searchSvc.createSearcher( + mockFieldSpec, + mockEsUrl, + mockEsParams, + mockQueryText, + config, + 'es' + ); + })); + + it('returns docs, and removes _source and highlight query params', function() { + $httpBackend.expectPOST('http://myserver/api?url=' + mockEsUrl + '/template', function verifyParamsStripped(data) { + var esQuery = angular.fromJson(data); + return ( + (esQuery.id === 'tmdb-title-search-template') && + (angular.isDefined(esQuery.highlight) == false) && + (angular.isDefined(esQuery._source) == false) && + (angular.isDefined(esQuery.from) == false) && + (angular.isDefined(esQuery.size) == false) && + (esQuery.params.from === 0) && + (esQuery.params.size === 10) + ); + }). + respond(200, mockES7Results); + + var called = 0; + + searcher.search() + .then(function() { + var docs = searcher.docs; + expect(docs.length === 2); + + expect(searcher.numFound).toEqual(2); + called++; + }); + + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + expect(called).toEqual(1); + }); + }); describe('scripted fields', function() { var mockScriptedResults = { hits: { diff --git a/test/spec/proxyTransport.js b/test/spec/proxyTransport.js new file mode 100644 index 0000000..c0600d9 --- /dev/null +++ b/test/spec/proxyTransport.js @@ -0,0 +1,115 @@ +'use strict'; + +/*global describe,beforeEach,inject,it,expect*/ +describe('Service: transport', function() { + // load the service's module + beforeEach(module('o19s.splainer-search')); + + var $httpBackend; + var $timeout; + var HttpGetTransportFactory; + + // instantiate service + var transportSvc; + + beforeEach(inject(function (_HttpGetTransportFactory_) { + HttpGetTransportFactory = _HttpGetTransportFactory_; + })); + + beforeEach(inject(function($injector) { + $httpBackend = $injector.get('$httpBackend'); + $timeout = $injector.get('$timeout'); + })); + + beforeEach(inject(function (_transportSvc_) { + transportSvc = _transportSvc_; + + })); + + + + + var mockResultsTemplate = { + hits: { + total: 2, + 'max_score': 1.0, + hits: [ + { + } + ] + } + }; + + + it('ignores when a payload provided', function () { + var url = 'http://es.splainer-search.com/foods/tacos/_msearch'; + var headers = {'header': 1}; + var headersToReceive = { + "header":1, + "Accept":"application/json, text/plain, */*" + } + var getTransport = new HttpGetTransportFactory(); + var payloadTemplate = {'test': 0}; + var payload = angular.copy(payloadTemplate); + getTransport.query(url, payload, headers); + $httpBackend.expectGET(url,headersToReceive).respond(200, mockResultsTemplate); + $timeout.flush(); + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + }); + + it('can take options', function () { + var url = 'http://es.splainer-search.com/foods/tacos/_msearch'; + var headers = {'header': 1}; + var options = {option1: true, "option2":"hello"}; + var getTransport = new HttpGetTransportFactory(options); + var payloadTemplate = {'test': 0}; + var payload = angular.copy(payloadTemplate); + getTransport.query(url, payload, headers); + expect(getTransport.options()).toEqual(options); + $httpBackend.expectGET(url).respond(200, mockResultsTemplate); + $timeout.flush(); + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + }); + + it('A POST can be wrapped in a proxy', function () { + var url = 'http://es.splainer-search.com/foods/tacos/_search'; + var options = { + apiMethod: 'POST', + proxyUrl: 'http://localhost/proxy?url=' + }; + var transport = transportSvc.getTransport(options); + + var headers = {'header': 1}; + + var payloadTemplate = {'test': 0}; + var payload = angular.copy(payloadTemplate); + transport.query(url, payload, headers); + $httpBackend.expectPOST('http://localhost/proxy?url=' + url).respond(200, mockResultsTemplate); + $timeout.flush(); + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + }); + + it('A GET can be wrapped in a proxy', function () { + var url = 'http://es.splainer-search.com/foods/tacos/_search'; + var options = { + apiMethod: 'GET', + proxyUrl: 'http://localhost/proxy/' + }; + var transport = transportSvc.getTransport(options); + + var headers = {'header': 1}; + + var payloadTemplate = {'test': 0}; + var payload = angular.copy(payloadTemplate); + transport.query(url, payload, headers); + $httpBackend.expectGET('http://localhost/proxy/' + url).respond(200, mockResultsTemplate); + $timeout.flush(); + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + }); + + +}); diff --git a/test/spec/settingsValidatorFactory.js b/test/spec/settingsValidatorFactory.js index f7cf69e..17cc02f 100644 --- a/test/spec/settingsValidatorFactory.js +++ b/test/spec/settingsValidatorFactory.js @@ -1,4 +1,4 @@ -'use strict'; + 'use strict'; /*global describe,beforeEach,inject,it,expect*/ describe('Factory: Settings Validator', function () { @@ -14,6 +14,7 @@ describe('Factory: Settings Validator', function () { })); describe('Solr:', function () { + var settings = { searchUrl: 'http://solr.splainer-searcher.io/solr/statedecoded/select', searchEngine: 'solr' @@ -190,6 +191,49 @@ describe('Factory: Settings Validator', function () { $httpBackend.verifyNoOutstandingExpectation(); expect(called).toBe(1); }); + + it('throws an error when you try to PROXY with JSONP to the Solr instance'), function () { + var proxyUrl = "http://myserver/proxy?proxy=" + var settings = { + searchUrl: 'http://solr.splainer-searcher.io/solr/statedecoded/select', + searchEngine: 'solr', + apiMethod: 'GET', + proxyUrl: proxyUrl + }; + validator = new SettingsValidatorFactory(settings); + + expect(validator.validateUrl()).toThrowError('It does not make sense to proxy a JSONP connection, use GET instead.'); + }; + + it('makes a successful PROXIED call to the Solr instance', function () { + var proxyUrl = "http://myserver/proxy?proxy=" + var settings = { + searchUrl: 'http://solr.splainer-searcher.io/solr/statedecoded/select', + searchEngine: 'solr', + apiMethod: 'GET', + proxyUrl: proxyUrl + }; + validator = new SettingsValidatorFactory(settings); + + + var expectedUrl = proxyUrl + + settings.searchUrl + + '?' + + 'q=*:*&fl=*&wt=json&debug=true&debug.explain.structured=true&hl=false&rows=10'; + //urlContainsParams fails on parsing out the url because of our proxied format + $httpBackend.expectGET(expectedUrl) + .respond(200, fullResponse); + + var called = 0; + validator.validateUrl() + .then(function() { + called++; + }); + + $httpBackend.flush(); + $httpBackend.verifyNoOutstandingExpectation(); + expect(called).toBe(1); + }); }); });