Angularjs $resource url intercept url encoding - javascript

I'm working on a sort of file-manager application that connects to a RESTFUL file api.
On the angular app, each file and directory is an instance of angular $resource using the file-object property relativePathName as resource id .
js
var File = $resource(url + '/:type/:id', {id: '#relativePathName', type: '#type'}, {…});
The problem is, when updating a file resource, the relativePathName parameter gets url encoded, e.g. / becomes %2F which causes the server to intercept the request before it hits the actual API (I assume the server treats this as a physical address and of returns a 404 response). The API is capable of treating whole url segments as a single param, so basically it'd treat path/to/file as a uri parameter of http://myapp.com/api/files/create/path/to/file and not as a different uri.
My question is, is there a way to modify the request url after it's being generated by the private Router instance inside of the resource constructor? If so, how (found nothing on this in the docs)?. What would be a possible solution? passing relativePathName as a parameter instead of declaring it as the resource id (which would require modifying the API)?
Thanks in advance.
Thomas

Using $resource is not the one stop shop for RESTful service calls, it is merely a convenience service for api's that are structured in a certain way. If $resource cannot do what you need, just create your own service using a mix of $resource and $http that that fits the api you are trying to call.

In our app we wanted a different URL for getByName requests, so we override the resource address with the URL parameter of the action getByName like so:
myapp.factory('ListGroup', ['$resource',
function($resource) {
return $resource(
'/API/List/:Id',
{
Id:'#Id',
Name:'#Name'
},
{
getByName: {method: 'GET', url: '/API/List/Group/:Name', isArray: true}
}
);
}
]);

My question is, is there a way to modify the request url after it's being generated by the private Router instance inside of the resource constructor?
I'm not sure about inside of the resource constructor but you can use interceptors to programatically alter route urls after they have been generated by $resource.
structure
hooks.config.js
hooks.factory.js
hooks.module.js
hooks.module.js
module.exports = angular
.module("app.hooks", [])
.factory("hooks", require("./hooks.factory.js"))
.config(require("./hooks.config.js"));
hooks.config.js
module.exports = ["$httpProvider", function($httpProvider) {
$httpProvider.interceptors.push("hooks");
}];
hooks.factory.js
module.exports = ["$q", "$location", function($q, $location) {
var basePrefix = "/api/v1";
return {
//invoked on every http request
request: function(request) {
request.url = interpret(request.url);
return $q.when(request);
}
};
function interpret(str) {
//if requesting an html template, don't do anything
if (str.indexOf(".html") > -1)
return str;
//if you're accessing the api, append the base prefix globally here
else
return basePrefix + str;
}
}];

Related

AngularJS include API key in a get request

I am trying to get information from a fantasy data API using AngularJS. I am using $resource to perform my get request in my controller, but I haven't been able to figure out how to correctly include the API key. Do I need to include it as a header? Thanks.
nflApp.controller('mainController', ['$scope','$resource','$routeParams', function($scope, $resource, $routeParams) {
$scope.fantasyAPI = $resource("https://api.fantasydata.net/nfl/v2/JSON/DailyFantasyPlayers/2015-DEC-28", { callback: "JSON_CALLBACK" }, { get: { method: "JSONP"}});
console.log($scope.fantasyAPI);
}]);
Below is the http request info from the site.
You should set a header with the API key, AngularJS will send them with every request in the following case:
$http.defaults.headers.common["Ocp-Apim-Subscription-Key"] = key;
When adding '.common' you are telling angular to send this in every request so you do not need to add it to every resource that hits the API.
A easy way to do that is by creating your own interceptors from $httpProvider at "config" fase.
To do that, just write something like:
mymodule.config(['$httpProvider', function($httpProvider){
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
config.headers['Ocp-Apim-Subscription-Key'] = SomeUserClass.AuthToken();
return config;
},
'response': function (response) {
return response;
}
};
});
});
You need to modify request header in JSONP. Unfortunately it is not possible. As the browser is responsible for header creation and you just can't manipulate that when using JSONP method.
how to change the headers for angularjs $http.jsonp
Set Headers with jQuery.ajax and JSONP?
From that link - https://johnnywey.wordpress.com/2012/05/20/jsonp-how-does-it-work/
Why NOT To Use JSONP?
Deciding against using JSONP is directly related to how it works. First of all, the only HTTP method you can use is GET since that is the only method script tags support. This immediately eliminates the use of JSONP as an option to interact with nice RESTful APIs that use other HTTP verbs to do fun stuff like CRUD. And while we’re on the subject of GET, note that using anything other than URL parameters to communicate with the server API (e.g. sending some JSON over) is also not possible. (You could encode JSON as a URL parameter, but shame on you for even thinking that.)
If they only work with header manipulation you will need to do that call from your server side.

How exactly means this $resource web service connection configuration in AngularJS?

I am absolutly new in AngularJS and I am studying a tutorial that show how to access to this external weather forecast API: http://openweathermap.org/forecast to retrieve and use weather information.
So, into my application, I have a controller like this:
weatherApp.controller('forecastController', ['$scope', '$resource', '$routeParams', 'cityService', function($scope, $resource, $routeParams, cityService) {
// It contain the city selected in the view:
$scope.city = cityService.city;
$scope.days = $routeParams.days || 2;
$scope.weatherAPI = $resource("http://api.openweathermap.org/data/2.5/forecast/daily",
{ APPID: 'MY_PERSONAL_KEY',
callback: "JSON_CALLBACK",
cnt: 5
},
{ get: { method: "JSONP" }});
$scope.weatherResult = $scope.weatherAPI.get({ q: $scope.city, cnt: $scope.days });
$scope.convertToFahrenheit = function(degK) {
return Math.round((1.8 * (degK - 273)) + 32);
}
$scope.convertToDate = function(dt) {
return new Date(dt * 1000);
};
}]);
And I am finding some difficulties tho understand how exactly the service is called and the informations are required.
So from what I have understand (but I am absolutly not sure about it so correct me if I do wrong assertion) I have the following situation:
1) Into the controller I inject the $resource service provided by Angular that from what I have understand is an object that I can use for a user friendly RESTful web service interaction.
So by this line:
$scope.city = cityService.city;
I put a city (inserted by the user in the view) into the city property of the $scope of this controller and it will be user as one of the parameter used for the webservice query.
Then I create the weatherAPI property on the $scope object that I think should represent the way to access to this resource
$scope.weatherAPI = $resource("http://api.openweathermap.org/data/2.5/forecast/daily",
{ APPID: 'MY_PERSONAL_KEY',
callback: "JSON_CALLBACK"
},
{ get: { method: "JSONP" }});
So I think that weatherAPI is a resource object creted by the $resource service (or am I missing something?).
On this I set the web service URL (http://api.openweathermap.org/data/2.5/forecast/daily) and a couple of JSON objects, these:
{
APPID: 'MY_PERSONAL_KEY',
callback: "JSON_CALLBACK"
},
in which I declare the APPID field that is my personal key to use the key and the callback: "JSON_CALLBACK" field. What exactly represent this callback field?
And this second object:
{ get: { method: "JSONP" }}
Ok, the roole of these 2 objects for me are pretty obscure.
What exactly define? It seems to me that are something related to the security of my application but I am absolutly not sure about it
Finnally it retrieve data from the web service by:
$scope.weatherResult = $scope.weatherAPI.get({ q: $scope.city, cnt: $scope.days });
where I thonk that I am simply passing the 2 parameter (that are something like ?q=value&?cnt=value of a classic HTTPrequest)
So I think that weatherAPI is a resource object creted by the $resource service (or am I missing something?).
No, you're not missing anything. weatherAPI is exactly what you're think it should be.
in which I declare the APPID field that is my personal key to use the key and the callback: "JSON_CALLBACK" field. What exactly represent this callback field?
callback field is just an default GET parameter passed to API on each request. It is set here, so you don't have to pass it each time requesting something from API.
What exactly define? It seems to me that are something related to the security of my application but I am absolutly not sure about it
Third parameter passed into $resource call is an definition of available methods on your API.
In outer object, key is name of method. It is just name on which that method will be available. value is an object of settings for that method, in that case it is only specifying type of your method, which is "JSONP".
JSONP is an special type of request, it can be only an "GET" request, browsers won't allow "POST" here. JSONP must return simple function call, passing one parameter into it, JSON with result data. This function will be called on your JavaScript.
JSONP is just an workaround for making cross site calls.

Globals across Angular services?

Can I define global variables or functions that can be accessed by all of my Angular service modules?
For example, I have several services that make http requests to various endpoints of the same url root. I would like to be able to change this root.
My service modules look like this.
var host = 'http://my-api.com';
return {
get: function(id) {
return $http({
url: host + '/contacts/' + id,
method: 'GET'
});
},
...
switchHost: function(url){
host = url;
}
});
So I can call ContactService.switchHost('http://new-url') in my Controllers to update this particular Service.
I would like to have some sort of Root Service where I coul define host and switchHost globally.
Note: The use case here is that some of our clients will be accessing our company API, and others will be self-hosting their resources.
i suggest you to create an interceptor which will digest an angular value like this.
angular.module('...')
.value('HOST', 'http://www.fu.bar.com/')
.factory('InterceptorFactory', function (HOST) {
return {
request: function(config) {
config.url = HOST + config.url;
return config;
}
};
})
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('InterceptorFactory');
}]);
Whenever you call the $http service, the factory will be called and will add your host.
If you want to update the host, you just need to inject in any block the value and update it.
Thanks for the responses. I just did something like this.
angular.module('app.services').factory('RootService', function($http) {
return {
switchHost: function(url){
this.host = url;
},
host: 'http://app.apiary-mock.com'
}
});
This way the client can just type in a url on the settings page and the root will change, which is what I want.

Angular http request with json param

In my RESTful api one of the resources exposes a GET method that accept json as a parameter named 'query'. This parameter is passed directly to the MongoDB query allowing users to query the database directly using mongo syntax.
The problem I'm having is that the request always looks like this:
?&query=%7B%22source%22:%22incident%22%7D
Where it should look something like this:
?&query={'source': 'incident'}
This is how I am sending the GET request:
var query = {};
if ($scope.sourceFilter) { query.source = $scope.sourceFilter; }
var query = JSON.stringify(query);
$http.get('/api/feedbackEntries', {params: {limit: $scope.limit, query: query}}).success(function(data) { .......
I am doing the same thing on other get requests and I don't get this issue.
Am I doing something wrong here ? Is this to do with the way angular parses params ?
Thanks
Like the $http docs say
params – {Object.<string|Object>} – Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified.
Latter emphasis is added by me.
So the query property of the object you pass to the params configuration option is an Object. This means is will be JSONified, which means the same as
JSON.stringify(query);
So this
{'source': 'incident'}
Turns to this:
'{"source": "incident"}'
As RFC 1738 states:
... only alphanumerics, the special characters "$-_.+!*'(),", and
reserved characters used for their reserved purposes may be used
unencoded within a URL.
As it happens {, } and " are not on that list and have to be url encoded to be used in a url. In your case %7B corresponds to {, %7D corresponds to } and %22 corresponds to ".
So what is happening is normal and most server software automatically decodes the url query parameters for you, so they will be presented normally. Most likely you'll need to parse it back to JSON somehow!
Hope this helps!
When using RESTFul apis concider using ngResource, ngResource docs
Include it in your module:
yourApp = angular.module('yourApp', ['ngResource'])
Add your service:
yourApp.factory('YourService', ['$resource', function($resource){
return $resource('link/to/your/object', {});
}]);
Add your controller
yourApp.controller('YourController', [$scope,
'YourService', function($scope, YourService) {
$scope.yourData = YourService.get();
$scope.yourData = YourService.query(); //(When obtaining arrays from JSON.)
I've found this is the best way using a RESTFull api.
After some digging I figured this one out. Looking at the request I was making:
$http.get('/api/feedbackEntries',
I saw that the url does not end with a trailing slash. This was the only difference I could see compared with other requests that were working fine. So I added the trailing slash and magically it works. I can't explain why, whether it's something within angular or elsewhere .. but this is how I fixed the problem.
Hope this helps someone in the future.
Below is the code I used to get search result from my server, it works for me sending POST request with JSON params without .
var service = {
getResult: function ({"username": "roman", "gender": "male"}) {
var promise = $http({
url: ServerManager.getServerUrl(),
method: "POST",
data: params,
headers: {
'Content-Type': 'application/json'
}
})
.success(function (data, status, headers, config) {
console.log('getResult success.');
return data;
}).error(function (data, status) {
console.log('getResult error.');
});
return promise;
}
}
return service;

Using Angular.js - how to serve binary data from a backend that requires authentication?

In my angularjs application I am communicating with a backend server that requires basic access authentication via http header. I have implemented the authentication mechanism on the client side as described here.
angular.module('myAuthModule')
.config(['$httpProvider', '$stateProvider',
function ($httpProvider, $stateProvider) {
$httpProvider.interceptors.push('securityInterceptor');
}])
.factory('securityInterceptor', ['$location', '$window', '$q',
function ($location, $window, $q) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers['Auth-Key'] = $window.sessionStorage.token;
}
return config;
},
response: function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/login');
}
return response || $q.when(response);
}
};
}
]);
So far so good, handling xhr requests within the angular app works as expected.
The problem is that I need to provide a download link for pdf documents. My backend server has a /Document/Pdf/:id resource that serves a application/pdf response with ContentDisposition: attachment which also requires authentication. I understand that I cannot initiate a download using xhr, however both providing a link to the document download via ngHref and calling a function that does for example $window.open('/Document/Pdf/13') lead to a 401 Unauthorized response by the server.
What am I missing here?
Having explored the possibilities given by #Geoff Genz with the addition of a fourth - data-uri option, which unfortunately does not allow defining filenames - I decided to go for a different approach.
I added a method to the API which generates a one-time download link based on a normally authenticated request and download it straight away. The angular handler becomes very simple
.factory('fileFactory', ['$http', '$window',
function ($http, $window) {
return {
downloadFile: function (fileId) {
return $http(
{
method: "POST",
data: fileId,
url: '/api/Files/RequestDownloadLink',
cache: false
}).success(function (response) {
var url = '/api/File/' + response.downloadId;
$window.location = url;
});
}
};
}]);
This is not perfect but I feel is least hack-ish. Also this works for me because I have full control of the front- and back-end.
There is not a simple solution to this. You've already discovered that you cannot download via Ajax, so you can't set a custom header that way. Nor can you set a custom header on a browser generated GET (like an href) or POST (like a form submit). I can suggest three different approaches, all of which will require some modifications on your server:
(1) Use Basic or Digest auth on your web page, so the browser will generate and send the Authorization header with those credentials.
(2) Set the token in "authorization" cookie that will be passed with the request and validate the token server side.
(3) Finally, the way we've implemented this is to use a POST request instead of a GET for the download. We POST to a hidden IFrame on the same page and have the server set the appropriate Content-Disposition header such as "attachment; filename="blah.pdf"" on the response. We then send the authorization token as a hidden field in the form.
None of these are ideal, and I know our solution feels kind of hacky, but I've not seen any more elegant approaches.

Categories