In Play2, I understand the concepts of action composition and using Async {...} for asynchronous responses, but I've not seen an example of these approaches used together.
To be clear, say you're using action composition to ensure that a user is authenticated:
def index = Authenticated { user =>
Action { request =>
Async {
Ok("Hello " + user.name)
}
}
}
In the implementation of Authenticated, if we assume that this a DB is being looked up to retrieve a user, then it appears to me that this part would be a blocking call, leaving only the response within the Action body as non-blocking.
Can somebody explain how I can do non-blocking async I/O that encompasses the authentication part as well?
My solution has been to compose actions inside out, like so:
def index = Action { request =>
Async {
Authenticated { user =>
Ok("Hello " + user.name)
}
}
}
My Authenticated signature would then look something like:
def Authenticated(body: => Result): Result = {
// authenticate, and then
body
}
The downside here is that body parsing will occur before authentication, which may or may not be an issue for you.
I can give a full example: http://pastebin.com/McHaqrFv
The essence of it is:
def WithAuthentication(f: WithAuthentication => Result) = Action {
implicit request => AsyncResult {
// async auth logic goes here
}
}
In controllers simply:
def indexAction = WithAuthentication {
implicit request =>
Ok(views.html.index())
}
or even:
def someAction = WithAuthentication {
implicit request => Async {
// more async logic here
}
}
It's a template-friendly solution, feel free to use it. Maybe sometimes i'll create a plugin from it. Just coded for my application, so far it seems working.
Related
I have a form control input to search for results.
component.html
<input matInput [formControl]="input">
I'm using valueChanges observable to check for changes.
component.ts
this.xx = this.input.valueChanges.pipe(
switchMap(res => {
return this.someService.test(res);
}),
);
What i don't understand is that on fast typing the cancellation of the previous http request is only working on some of the endpoints.
someService.ts
public test(q: string): Observable<any> {
// api-1 cancelling
return this.http.get<any>('http://localhost:8080/api/geosuggestion?q=' + q);
// api-2 not cancelling
// return this.http.get<any>('http://localhost:8080/api/items?q=' + q, { headers: { 'auth-token': this.authService.getToken() } });
}
Similar questions always origin from the fact that they used different obsersvable chains. In my case there has to be a different reason as i'm using the exact same logic in both cases and only replace the endpoint url.
api-1 (successfully cancelling)
api-2 (not cancelling)
Why is switchMap cancellation only working on some of my endpoints?
I would like to handle asynchronous stream when using Observable in Angular 2.
In detail, before every user's request, I need to get service ticket, if that ticket is valid, user can get proper response. Therefore, I have to do http request first before actual request, as below, I call the method called getServiceTicket(), however, because of asynchronous stream, before I get the valid service ticket, the following http request (getDetail) is performed without valid service ticket. So I tried to use flag like isServiceTicket, but I realize it does not guarantee the sequential running of these two methods. I tried to do some research, but I could not find satisfactory answer. If someone has good solution for this, could you give some advice?
getServiceTicket() {
this.userAuthServie.getServiceTicket().subscribe(
(data: string) => {this.serviceTicket = data; this.isServiceTicket = true;}
);
}
getDetail(id: string) {
this.getServiceTicket();
return this.http.get('https://localhost/detail/' + id + '?ticket=' + this.serviceTicket)
.map( (responseData) => {
return <User>responseData.json();
}
);
}
You can change yor code like below:
user:User;
getDetail(id: string) {
this.userAuthServie.getServiceTicket()
.subscribe((data: string) => {
this.serviceTicket = data;
this.isServiceTicket = true;
this.http.get('https://localhost/detail/' + id + '?ticket=' + this.serviceTicket)
.map( (responseData) => {
this.user= <User>responseData.json();
});
});
}
You can place the second function that you want to run in the success part of the subscribe method of the first function.
getServiceTicket() {
this.userAuthServie.getServiceTicket().subscribe(
(data: string) => {
this.serviceTicket = data; this.isServiceTicket = true;
this.getDetail(id);
}
);
}
If you want to wait for the service ticket to be loaded before any "detail requests" are performed, you should wait on the response of service ticket to resolve. There are multiple ways, one way might be to add the details to a queue, or have getDetails wait until the service ticket is loaded.
getServiceTicket() {
// you need to return the observable from service ticket
return this.userAuthServie
.getServiceTicket()
// use map here to only intercept the value.
// remember that you need to subscribe somewhere else
// if called from elsewhere than getDetail
.map((data: string) => {
this.serviceTicket = data;
this.isServiceTicket = true;
return data;
});
}
getDetail(id: string) {
return this.getServiceTicket()
// we switch the observable from ticket to the one from detail using switchMap
.switchMap((data: string) => {
let url = 'https://localhost/detail/' + id + '?ticket=' + this.serviceTicket;
return this.http
.get(url)
.map((responseData) => {
return <User>responseData.json();
});
});
}
}
I have created a service that makes a simple GET request:
private accountObservable = null;
constructor(private _http: Http) {
}
getAccount () {
// If we have account cached, use it instead
if (this.accountObservable === null) {
this.accountObservable = this._http.get('http://localhost/api/account')
.map(res => <Account> res.json().data)
.catch(this.handleError);
}
return this.accountObservable;
}
I have added that service in my bootstrap function to provide it globally (my hope is to provide the same instance to all components):
provide(AccountService, { useClass: AccountService })
The problem is when I call this service in different components, a GET request is made every time. So if I add it to 3 components, 3 GET requests will be made even though I check if an observable already exist.
ngOnInit() {
this._accountService.getAccount().subscribe(
account => this.account = account,
error => this.errorMessage = <any>error
);
}
How can I prevent the GET request to be made multiple times?
Use Observable.share():
if (this.accountObservable === null) {
this.accountObservable = this._http.get('./data/data.json')
.share()
.map(res => res.json())
.catch(this.handleError);
}
Plunker
In the Plunker, AppComponent and Component2 both call getAccount().subscribe() twice.
With share(), the Chrome Developer tools Network tab shows one HTTP request for data.json. With share() commented out, there are 4 requests.
There are two types of observables.
Cold Observable : each subscriber receive all the events ( from the begining )
Hot observable : each subscriber receive the events that are emited after subscription.
Cold Observables are the default one. That's what the WS calling is triggered many times.
To make an Observable Hot you have to use following Rx's operators chain :
.publish().refCount()
In your case :
getAccount () {
let accountObservable = this._http.get('http://localhost/api/account')
.map(res => <Account> res.json().data)
.catch(this.handleError);
return accountObservable.publish().refCount();
}
In my case it was because of form post and button clik was set to same listener
The updated solution is:
1) Change your getAccount() method to use share:
getAccount () {
// If we have account cached, use it instead
if (this.accountObservable === null) {
this.accountObservable = this._http.get('http://localhost/api/account')
.pipe(share())
.map(res => <Account> res.json().data)
.catch(this.handleError);
}
return this.accountObservable;
}
2) Add import { share } from 'rxjs/operators'; to the top of your .ts file to get rid of the error on share.
I'm writing a piece of Redux Middleware which is responsbile for adding the user's OAuth AccessToken to API_CALL cations before they hit the redux-api-middleware.
// sign appends the `Authorization` HTTP Header to an API_CALL action
function sign(action) {
action[CALL_API].headers = {
...action[CALL_API].headers,
Authorization: `Bearer ${getState()auth.accessToken}`;
}
}
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action[CALL_API]) {
sign(action);
}
return next(action);
}
}
However, I also want this middleware to detect when the User's AccesToken has expired...
function tokenExpired() {
return (Date.now() > getState().auth.expirationTime);
}
When this happens, the middleware detains the action (prevents it from being passed to the next middleware in the chain) and stores it in an internal list. It then kicks off the async. Token refresh process by dispatching a 'refresh access token' FSA:
if (tokenExpired()) {
// detainActions is an array, declared outside
// of the middleware's scope.
detainActions.push(action);
dispatch(refreshAccessToken());
}
else {
next(sign(action));
}
Finally, I want to listen for when the 'refresh access token' flow has completed and flush (re-dispatch) all detained actions. Currently I am doing this by looking out for AUTHENTICATION_RESPONSE FSA's as they flow through my middleware (the AUTHENTICATION_RESPONSE action is dispatched as as side-effect of the refreshAccessToken thunk.
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action.type === AUTHENTICATION_RESPONSE && !action.error) {
// Let the AuthResponse action pass to the store.
next(action);
// Flush detained actions now we have a new grant.
return flushAndRedispatchDetainedActions(dispatch);
}
else {
// Sign CALL_API requests logic as above.
}
}
}
However I am not happy with this approach as there is no certainty that the AUTHENTICATION_RESPONSE FSA will actually hit the reducers (it may be intercepted by other middleware, or be further defered).
Possible alternative approaches I have considered are having the refreshAccessToken actionCreator return a thunk which returns a Promise; that way my middleware can wait for that promise to resolve before flushing and replaying all requests, ie:
if (tokenExpired()) {
// refreshAccessToken thunk returns a Promise.
dispatch(refreshAccessToken());
.then(flushAndRedispatchDetainedActions();
}
Or alternativley I could have my middleware observe the store directly and trigger an action when the auth.accessToken value changes - however, I'm not clear on what the guidance is for middleware observing the store (I'm guessing it's not possible as middleware needs to be instantiated before the final store object is created.)
Thanks :)
Update
Thinking about the problem on the way home; if I were to move the actual authentication logic out of refreshAccessToken (a thunk'd action creator), and into the middleware itself then I would save a lot of pain:
// Redux middleware signature.
return ({dispatch, getState}) => {
return next => action => {
if (action[CALL_AUTH]) {
authHelper.refreshGrant()
.then(grant => dispatch(processGrant(newGrant));
}
else {
// Sign CALL_API requests logic as above.
}
}
}
I have an async actionCreator which handle my app's authentication flow:
function createAuthenticationResponse(err, grant) {
return {
type: AUTHENTICATION_RESPONSE,
payload: err || grant,
error: Boolean(err)
}
}
function authenticate() {
// return a thunk.
return dispatch => {
// Notify the system that we are authenticating.
dispatch({ type: AUTHENTICATE });
// Trigger the auth flow.
myAuthModule.authorize((err, grant) => {
// Trigger a state-change on the outcome.
dispatch(createAuthenticationResponse(err, grant));
// Q: How do I handle this side-effect?
if (!err) {
dispatch(extractUserInfo(grant));
}
});
};
}
My actionCreator contains business logic to extract the user info from the grant if the user was succesfully authenticated; should this logic live in my action creator? If not, where should I place it, inside my reducer?
In other architectures I would bind a command to trigger on AUTHENTICATION_RESPONSE; but this doesn't feel like a middleware job?
I think what you're suggesting is totally sensible.
You can use Redux Thunk both for control flow and side effects.
You should never put side effects into the reducers.