I run a Flask web app where I have a domain model (this example Thing) which properties (e.g. rating) could be updated via a HTTP PATCH request. In my development setup I hard coded the API URL where the request must be made to, but this becomes an open source self hosted app so the URL must be generated & set by Flask.
<script type="module">
import {Thing} from './things.js';
// This string must be available inside the Thing class
// The {{ }} syntax with url_for is Flask/Jinja template style (server side)
const API_URL = '{{ url_for('thing_update', id='{0}' }}';
let stars = document.querySelectorAll('.rating .star')
stars.addEventListener('click', function(el){
// To create an object with object properties, like its id
let thing = new Thing({{ thing.id }});
thing.rating = el.getAttribute('value');
})
</script>
My module looks like:
class Entity {
_API_URL = '';
async update (data) {
let response = await fetch(this._API_URL, {
method: 'PATCH',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(data)
})
if (!response.ok) {
throw new Error(`Server response ${response.status}: `)
} else if (response.status == 204) {
console.debug('Response success, no content')
return true
} else {
let json = await response.json()
console.debug('Response success, body content: ', json)
return json
}
}
}
class Thing extends Entity {
_id = null;
_rating = null;
constructor(id) {
super()
this._id = id
// How to get the proper API URL here???
this._API_URL = super._API_URL.format(id)
}
set rating(value) {
this._rating = value
this.update()
}
async update() {
console.debug('Update rating to ' + this._rating)
super.update({rating: this._rating})
}
}
export {Thing}
I have experience in server side coding (php/python etc) and it just feels "wrong" to set the API URL via the constructor or setter method on the object instance. This should be something as constant on the class level.
However, because I use Flask and don't use javascript for a SPA or something, I struggle how to get this "right". Is there any best practice for this case?
Spring shows - Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.cg.bookstore.entities.OrderDetails com.cg.bookstore.controller.OrderDetailsController.updateDeliveryStatus(int,java.lang.String)]
Console shows - Uncaught (in promise) Error: Request failed with status code 400
class UpdateOrder extends Component {
state = {
deliveryStatus:""
}
handleChange = (event) => {
const deliveryStatus = { ...this.state.deliveryStatus };
this.setState({ deliveryStatus: event.target.value });
};
handleSubmit = (event) => {
// Prevents default behaviour of submit button
event.preventDefault();
console.log(this.state.deliveryStatus)
console.log()
OrderService.updateDeliveryStatus(this.props.match.params.orderDetailsId,this.state.deliveryStatus)
.then((res) => {
this.props.history.push("/admin/orders");
});
};
In OrderService I call the updateDeliveryStatus
async updateDeliveryStatus(orderId,deliveryStatus){
return await axios.patch(BASE_URL+"/"+orderId,deliveryStatus)
}
The updateDeliveryStatus service in spring
#Override
public OrderDetails updateDeliveryStatus(int orderId, String deliveryStatus)
{
Optional<OrderDetails> opt = orderDetailsRepo.findById(orderId);
OrderDetails od;
if (opt.isPresent())
{
od = opt.get();
od.setDeliveryStatus(deliveryStatus);
orderDetailsRepo.save(od);
} else
{
throw new OrderDetailsNotFoundException("Order is not found");
}
return od;
}
While I was testing backend in POSTMAN , I pass the input as plain string and it works fine. Is it because the input in not in form of json the issue? How to fix this ?
Usually, when using #PutMethod and wanting to update a resource you need to provide both ID of the resource you want to update and the new body, which in this case I presume is 'OrderDetails', error suggests is missing there.
Without java controller code it's only assumptions though.
I'm baffled what I'm doing wrong in my code. The GET call gets resolved, but when I try to do a POST call to the same server I get an error. My POST endpoint works fine with Postman.
apiConnection.js
function get(data){
return axios.get("http://localhost:8080/api/questions",
{
params:data.payload
}, {
headers: {
'accept': 'application/json',
}
})
}
function post(data){
console.log(data.payload) //my payload is received here
return axios.post("http://localhost:8080/api/answer", {
params:data.payload
}, {
headers: {
'accept': 'application/json',
}
}
)
}
export { get, post }
Here is the error I get in the console
And here is how I make the call in react
index.js
GET (Receives response normally)
import { get, post } from "apiConnection.js"
...
componentDidMount(){
const data = {
payload: {
linkId: getSlug()
}
}
get(data).then((result) => {
this.setState({reportId: result.data.report.id});
})
}
POST (Throws error)
vote(userVote){
const data = {
payload: {
reportId: this.state.reportId,
}
}
post(data).then((result)=>{
this.state.questions[this.state.currentQuestion].vote = userVote
});
}
I have found the culprit of the issue but if someone can add more information about it, it might be helpful for others.
In my question, for brevity, I changed the request URL from imported constants to hardcoded links.
In my code, I have a variable for both GET and POST
return axios.post(apiEndpoints[data.ep], data.payload)
I import the endpoint variables like so
import * as apiEndpoints from './apiEndpoints';
apiEndpoints.js
const server = 'http://localhost:8080/'
const api_version = 'api/'
const base_url = server+api_version;
export const EP_QUESTIONS = base_url+'questions';
export const EP_ANSWER = base_url+'answer';
For some unknown reason EP_ANSWER throws the error even though I'm not making a typo when I define data.ep (data.ep has EP_ANSWER, which
I checked a million times)
The solution was to just change EP_ANSWER to EP_ANS and everything worked as expected.
No idea why this is the case. It might be some global variable or a reserved word.
Just came across this and noted #Ando's response.
So, knowing that I first tried a hard coded URL, it worked.
I then successfully did url.toString() and it worked.
Not sure why but Javascript seems to treat a an object string differently than a true string.
I try to query an api which is not the same origin with the aurelia-http-client.
My code pretty simple :
import {HttpClient} from 'aurelia-http-client';
export class App {
constructor(){
console.log("constructor called");
let url = 'http://localhost:8081/all';
let client = new HttpClient();
client
.jsonp(url)
.then(data => {
console.log("datas");
console.log(data);
});
}
}
Nothing happens, I can see in network that the url is called, my api engine logs an entry but I never enter in the "then" of the "promise"...
What's wrong ?
Update :
I give you some screenshots with catch
code source
browser result
With JQuery on the same machine no problems.
After reading this post other jsonp case I try to add the work "callback" and now it works !!!
so call jsonp(url, 'callback')
client.jsonp(url, 'callback')
Thanks...
This may not be a direct answer but just a suggestion, I would rather use the aurelia API as I found it more consistent and stable.
just add it as plugin in you main :
.plugin('aurelia-api', config => {
config.registerEndpoint('github', 'https://api.github.com/');
});
and use it as:
import {Endpoint} from 'aurelia-api':
#autoinject
export class Users{
constructor(private githubEndpoint){
}
activate() {
return this.githubEndpoint.find('users')
.then(users => this.users = users);
}
}
Source: https://aurelia-api.spoonx.org/Quick%20start.html
I want to send an auth token when requesting a resource from my API.
I did implement a service using $resource:
factory('Todo', ['$resource', function($resource) {
return $resource('http://localhost:port/todos.json', {port:":3001"} , {
query: {method: 'GET', isArray: true}
});
}])
And I have a service that stores the auth token:
factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
return tokenHandler;
});
I would like to send the token from tokenHandler.get with every request send via the Todo service. I was able to send it by putting it into the call of a specific action. For example this works:
Todo.query( {access_token : tokenHandler.get()} );
But I would prefer to define the access_token as a parameter in the Todo service, as it has to be sent with every call. And to improve DRY.
But everything in the factory is executed only once, so the access_token would have to be available before defining the factory and it cant change afterwards.
Is there a way to put a dynamically updated request parameter in the service?
Thanks to Andy Joslin. I picked his idea of wrapping the resource actions. The service for the resource looks like this now:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
return resource;
}])
As you can see the resource is defined the usual way in the first place. In my example this includes a custom action called update. Afterwards the resource is overwritten by the return of the tokenHandler.wrapAction() method which takes the resource and an array of actions as parameters.
As you would expect the latter method actually wraps the actions to include the auth token in every request and returns a modified resource. So let's have a look at the code for that:
.factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
// wrap given actions of a resource to send auth token with every
// request
tokenHandler.wrapActions = function( resource, actions ) {
// copy original resource
var wrappedResource = resource;
for (var i=0; i < actions.length; i++) {
tokenWrapper( wrappedResource, actions[i] );
};
// return modified copy of resource
return wrappedResource;
};
// wraps resource action to send request with auth token
var tokenWrapper = function( resource, action ) {
// copy original action
resource['_' + action] = resource[action];
// create new action wrapping the original and sending token
resource[action] = function( data, success, error){
return resource['_' + action](
angular.extend({}, data || {}, {access_token: tokenHandler.get()}),
success,
error
);
};
};
return tokenHandler;
});
As you can see the wrapActions() method creates a copy of the resource from it's parameters and loops through the actions array to call another function tokenWrapper() for every action. In the end it returns the modified copy of the resource.
The tokenWrappermethod first of all creates a copy of preexisting resource action. This copy has a trailing underscore. So query()becomes _query(). Afterwards a new method overwrites the original query() method. This new method wraps _query(), as suggested by Andy Joslin, to provide the auth token with every request send through that action.
The good thing with this approach is, that we still can use the predefined actions which come with every angularjs resource (get, query, save, etc.), without having to redefine them. And in the rest of the code (within controllers for example) we can use the default action name.
Another way is to use an HTTP interceptor which replaces a "magic" Authorization header with the current OAuth token. The code below is OAuth specific, but remedying that is a simple exercise for the reader.
// Injects an HTTP interceptor that replaces a "Bearer" authorization header
// with the current Bearer token.
module.factory('oauthHttpInterceptor', function (OAuth) {
return {
request: function (config) {
// This is just example logic, you could check the URL (for example)
if (config.headers.Authorization === 'Bearer') {
config.headers.Authorization = 'Bearer ' + btoa(OAuth.accessToken);
}
return config;
}
};
});
module.config(function ($httpProvider) {
$httpProvider.interceptors.push('oauthHttpInterceptor');
});
I really like this approach:
http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app
where the token is always automagically sent within the request header without the need of a wrapper.
// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';
You could create a wrapper function for it.
app.factory('Todo', function($resource, TokenHandler) {
var res= $resource('http://localhost:port/todos.json', {
port: ':3001',
}, {
_query: {method: 'GET', isArray: true}
});
res.query = function(data, success, error) {
//We put a {} on the first parameter of extend so it won't edit data
return res._query(
angular.extend({}, data || {}, {access_token: TokenHandler.get()}),
success,
error
);
};
return res;
})
I had to deal with this problem as well. I don't think if it is an elegant solution but it works and there are 2 lines of code :
I suppose you get your token from your server after an authentication in SessionService for instance. Then, call this kind of method :
angular.module('xxx.sessionService', ['ngResource']).
factory('SessionService', function( $http, $rootScope) {
//...
function setHttpProviderCommonHeaderToken(token){
$http.defaults.headers.common['X-AUTH-TOKEN'] = token;
}
});
After that all your requests from $resource and $http will have token in their header.
Another solution would be to use resource.bind(additionalParamDefaults), that return a new instance of the resource bound with additional parameters
var myResource = $resource(url, {id: '#_id'});
var myResourceProtectedByToken = myResource.bind({ access_token : function(){
return tokenHandler.get();
}});
return myResourceProtectedByToken;
The access_token function will be called every time any of the action on the resource is called.
I might be misunderstanding all of your question (feel free to correct me :) ) but to specifically address adding the access_token for every request, have you tried injecting the TokenHandler module into the Todo module?
// app
var app = angular.module('app', ['ngResource']);
// token handler
app.factory('TokenHandler', function() { /* ... */ });
// inject the TokenHandler
app.factory('Todo', function($resource, TokenHandler) {
// get the token
var token = TokenHandler.get();
// and add it as a default param
return $resource('http://localhost:port/todos.json', {
port: ':3001',
access_token : token
});
})
You can call Todo.query() and it will append ?token=none to your URL. Or if you prefer to add a token placeholder you can of course do that too:
http://localhost:port/todos.json/:token
Hope this helps :)
Following your accepted answer, I would propose to extend the resource in order to set the token with the Todo object:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
resource.prototype.setToken = function setTodoToken(newToken) {
tokenHandler.set(newToken);
};
return resource;
}]);
In that way there is no need to import the TokenHandler each time you want to use the Todo object and you can use:
todo.setToken(theNewToken);
Another change I would do is to allow default actions if they are empty in wrapActions:
if (!actions || actions.length === 0) {
actions = [];
for (i in resource) {
if (i !== 'bind') {
actions.push(i);
}
}
}