export class GitComponent implements OnInit {
http: HttpClient;
headerForAjax: HttpHeaders;
constructor(http: HttpClient) {
this.http = http;
}
ngOnInit(): void {
const headers = 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzYXNobyIsImF1dGgiOiJST0xFX1VTRVIiLCJleHAiOjE1MjQwODcyMzJ9.MUv5RgI9LxQyrrCfjfX8HR2-XiQmz4vjLqH7V_0Du7VFLC0WrK_y3FfeNoT2Nj_uguIK2ss7jv-LNiHuCGtz4A';
this.headerForAjax = new HttpHeaders().set('Authorization', headers);
const linkBindingModel = {
title: 'angular2',
linkUrl: 'angularUr2l',
imageUrl: 'imageUrl22'
};
this.http.post('http://localhost:8080/links/add', linkBindingModel, {headers: this.headerForAjax}).subscribe((e) => {
console.log(e);
});
}
}
So this ajax is send to my spring server and the server saves the data correctly in the DB, it basically all works well.
But i can't get the response status, i mean how can i get the 200 response status number or text that my server sends back?
try this, you can pass in an object with observe key to get the complete response
this.http.post('http://localhost:8080/links/add', linkBindingModel, {headers:
this.headerForAjax, observe: 'response'}).subscribe((response) => {
console.log(response.status); // response status
console.log(response.body); // response body (returned response)
});
Related
I am trying to set value form localStorage() into my variable and use that variable in functions but the variable comes as undefined
code
export class DashboardService {
public token: any;
constructor(
private env: EnvService,
private http: HttpClient,
private storage: NativeStorage
) {
this.storage.getItem('token').then((token) => {
this.token = token.access_token;
console.log('storage token ', token.access_token); // see screenshot below
}).catch(error => console.error(error));
}
getDashboard() {
const headers = new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token
});
console.log('my token ', this.token); // see screenshot below
console.log('my headers ', headers); // see screenshot below
return this.http.get(this.env.Dashboard + '/dashboard', {headers})
.pipe(
tap(data => {
console.log(data);
})
);
}
}
Screenshot
another issue is my request header sends 2 values in header instead of 3 (not sure if it's because of token being undefined or not)
it supposed to send Accept,Content-Type,Authorization instead only sent Accept and Content-Type
Any idea?
Update
this is my component that I call my service above
export class DashboardPage implements OnInit {
schools: any = [];
constructor(
private authService: AuthenticationService,
private menu: MenuController,
private dashboardService: DashboardService
) {
this.getSchool();
}
ngOnInit() {
this.menu.enable(true);
}
logout() {
this.authService.logout();
}
getSchool() {
this.dashboardService.getDashboard().subscribe((res) => {
this.schools = res;
});
}
}
SOLVED
I'd to change both services and component file to get my data, here is my final code
services
getDashboard(): Observable<any> {
const httpOptions = {
headers: new HttpHeaders({
Accept: 'application/json, text/plain',
'Content-Type': 'application/json',
Authorization: this.token.access_token
})
};
return this.http.get(`${this.env.Dashboard}` + '/dashboard', httpOptions).pipe(
map(data => data)
);
}
component
async getSchool() {
this.loading = await this.loadingController.create({
message: 'Please wait...',
spinner: 'dots',
duration: 3000
});
await this.loading.present();
this.dashboardService.getDashboard().subscribe((res) => {
for (const school of res.data) {
this.schools.push(school);
}
this.hideLoading();
});
}
private hideLoading() {
this.loading.dismiss();
}
now my page loads include my data with proper loading screen.
hope it help others as well.
I am using angular 8 to make a SPA.
Firebase is used to authenticate the user both in the client as well as in the backend, so I need to send the jwt token in http.get request to the backend to authenticate the user.
Backend is an API made with django 2.2 and django rest framework which sends the api to be consumed in client application.
auth.service.ts
#Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any; // Save logged in user data
public userToken: string;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
/* Saving user data in localstorage when
logged in and setting up null when logged out */
this.afAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
GetToken(): string {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
// this shows the userToken
console.log('token inside getToken method ' + this.userToken);
});
}
});
// this shows userToken as undefined
console.log('before return ' + this.userToken);
return this.userToken;
}
}
api.service.ts
#Injectable({
providedIn: 'root'
})
export class ApiService {
private url = environment.baseUrl;
token: any;
data: any;
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
// old method to get emloyees data
// public getEmployees(): Observable<Employee[]> {
// return this.http.get<Employee[]>(`${this.url}/employee/`);
// }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + this.authService.GetToken()
}),
};
public getEmployees(): Observable<Employee[]> {
// token is undefined here
console.log('token inside getEmployees method ' + this.token);
return this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions);
}
}
The backend is working perfectly which I verified by adding the token in the httpOptions, like so:
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + 'ey.....'
}),
};
But when I try doing the same as given in code it doesn't work.
The user token remains undefined.
Peter's answer has the crux of it: getIdToken() is asynchronous, so by the time your return this.userToken; runs, the this.userToken = idToken; hasn't run yet. You should be able to see this from the output of your console.log statements.
For more on this see How to return value from an asynchronous callback function? I highly recommend studying this answer for a while, as this asynchronous behavior is incredibly common when dealing with web APIs.
The fix for your code is to return a Promise, instead of trying to return the value:
GetToken(): Promise<string> {
return new Promise((resolve, reject) => {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
resolve(idToken);
});
}
});
})
}
In words: GetToken returns a promise that resolves once an ID token is available. If you know the user is already signed in when you call this function, you can simplify it to:
GetToken(): string {
const user = firebase.authentication().currentUser;
return user.getIdToken()
}
The difference is that the second function does not wait for the user to be signed in, so will fail if there is no signed in user.
You then use either of the above functions like this in getEmployees:
public getEmployees(): Observable<Employee[]> {
return new Promise((resolve, reject) =>
this.authService.GetToken().then((idToken) => {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + idToken
}),
};
this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions)
.then(resolve).catch(reject);
})
})
}
It is undefined here console.log('before return ' + this.userToken); because getIdToken() returns a Promise which means it is asynchronous, therefore the only way to access the userToken is inside the then() method.
I am developing an Angular application that shows some images to the user.
I would like to obtain those images from a single REST call to a web service: given the fact i am already uploading the images via a FormData object, i would like to receive those images in the same way (so, basically, via content-type: multipart/form-data).
At the moment, using the following code:
this.http.post('api/load', {}, {headers: {'Accept': 'multipart/form-data'},
responseType:'text', observe: 'response'});
i am actually receiving the full body of the response in a text format, like this:
--974b5730-ab25-4554-8a69-444664cab379
Content-Disposition: form-data; name=result
{"bar":[1,2,3,4], "foo": true}
--974b5730-ab25-4554-8a69-444664cab379
Content-Disposition: form-data; name=image; filename=image1.jpg; filename*=utf-8''image1.jpg
--- binarycontent...
But it's in a raw, text format.
How can i receive a multipart/form-data response formatted by its boundaries, or a in clean way in Angular 7.x?
One of the solution is to implement an interceptor service where you can format multipart/form-data response.
For example, your inteceptor will be - multipart.interceptor.ts :
#Injectable()
export class MultipartInterceptService implements HttpInterceptor {
private parseResponse(response: HttpResponse<any>): HttpResponse<any> {
const headerValue = response.headers.get('Content-Type');
const body = response.body;
const contentTypeArray = headerValue ? headerValue.split(';') : [];
const contentType = contentTypeArray[0];
switch (contentType) {
case 'multipart/form-data':
if (!body) {
return response.clone({ body: {} });
}
const boundary = body?.split('--')[1].split('\r')[0];
const parsed = this.parseData(body, boundary); // function which parse your data depends on its content (image, svg, pdf, json)
if (parsed === false) {
throw Error('Unable to parse multipart response');
}
return response.clone({ body: parsed });
default:
return response;
}
}
// intercept request and add parse custom response
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(customRequest).pipe(
map((response: HttpResponse<any>) => {
if (response instanceof HttpResponse) {
return this.parseResponse(response);
}
})
);
}
}
I'm sending a request to an API, it returns an array of data, but I don't know how to extract the headers from that url, this is what i've tried in my service
#Injectable()
export class ResourcesService {
private resourcesurl = "http://localhost:9111/v1/resources";
constructor(private http: Http) { }
getResources() {
let headers = new Headers();
headers.append("api_key", "123456");
return this.http.get(this.resourcesurl, { headers: headers
}).map(this.extractData).catch(this.handleError);
}
getresourceheaders(){
let headers = new Headers();
headers.append("api_key", "123456");
let options = new RequestOptions();
let testsss = options.headers
let headerapi = this.http.request(this.resourcesurl, options);
let test = this.http.get(this.resourcesurl, { headers: headers });
console.log(headerapi);
}
private extractData(res: Response) {
let body = res.json();
return body.data || {};
}
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
I want to get the headers from that response that in this case is resourceurl
any idea?
Clear angular 5 answer
By default, this.http.whatever's returned observable will be on the data returned, not the HttpResponse.
If you have a peak at: https://angular.io/api/common/http/HttpClient
You'll notice the options take an "observe" parameter of a HttpObserve type. While it's not documented what the HttpObserve is, if you put it as "response" then you will instead receive an instance of HttpResponse<T>(https://angular.io/api/common/http/HttpResponse)
So, here's an example request:
this.http.get(url, {observe: 'response'})
.subscribe(resp => console.log(resp.headers))
Note: Due to browser cors security, you will not be-able to see headers unless the API provides Access-Control-Expose-Headers: with your custom headers if your api and angular app do not have the same domain.
The headers are part of the Response class, so you should be able to see them in a handler like
http.get('/path/to/resource')
.subscribe((res:Response) => {
console.log(res.headers);
// you can assign the value to any variable here
});
When you do .map(this.extractData) the let body = res.json() from this.extractData function takes out everything from the response except the body.
Instead if you do following, .map((res: Response) => res), that will return the whole response and you can access all the attributes and assign them to variables.
Here's a Plunker demo.
A bit more of an exotic example in Angular 5 shown below. Using HttpClient to post to a GraphQL server, read the response and then extract a response header value and a response body value. The header is Total-Count in this case. cars is a field (array of Car) under another field data in the body. Also shows use of the rxjs first operator.
import { HttpClient, HttpHeaders, HttpResponse } from '#angular/common/http';
import { first } from 'rxjs/operators/first';
import { Car, CarPage } from '../models/car';
..........
..........
public find(filter: string, sort: string, limit: number): Observable<CarPage> {
let headers = new HttpHeaders().set("Content-Type", "application/graphql");
let carPage: CarPage = { cars: [], totalCount: 0 };
return this.http.post<HttpResponse<any>>('/graphql',
`query cars { cars(filter: "${filter}", sort: "${sort}", limit: ${limit}) {
id
make
model
year
}
}`,
{ headers: headers, observe: "response" }
)
.first((_, index) => index === 0, (response: HttpResponse<any>) => {
let totalCountHeaderValues = response.headers.getAll("Total-Count");
carPage.totalCount = (totalCountHeaderValues.length > 0) ? parseInt(totalCountHeaderValues[0]) : 0;
carPage.cars = response.body.data.cars;
return carPage;
})
}
The return type of the angular Http.get method returns a Response type. This object has a headers object that contains information about the headers. It also has a url property.
this.http.get(url).map(resp => console.log(resp));
TLDR: My task is to complete 3 requests instead of 1 and return the last response as a response to the first request without any additional modifications of the request initiator.
I have extended the Angular Http class to automatically append authorization headers to all of my requests and implement my own authorization error handling.
It looks like this:
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
// ... append some headers
super.request(url, options).catch((error: Response) => {
if (error.status === 401 || error.status === 403 ) {
// todo: Send refreshToken request to get new credentials
// todo: Send current request again with new credentials
// todo: If request is completed properly pretend everything was fine and return response
}
});
}
I want to catch authorization errors, fix them by sending token refresh request and return proper response to the initial request.
There's a lot of code using http now and I don't want to change it so the fixed response has to be returned as the initial would have been without anybody knowing about it.
One of the approaches was to use synchronous requests but it's not a good idea I think.
Could you tell please if the solution is possible and how can I achieve it?
PS. There may be a problem when another request is executed while the token is being refreshed and crash into an authorization causing one more token refresh. But this is not that important now.
The aim was achieved mostly by using flatMap to compose requests.
Key functions:
Check if request request returns 401
If 401: tries to fix renew necessary tokens and sends request again
The subscriber knows nothing about error if it's fixed
It's designed to work with the REST authentication model which includes:
guest token - for unauthorized users (gToken)
auth token - for authorized users - (aToken)
refresh token - to refresh expired aToken (refresh_token)
Most likely you will need to rewrite requests to fit your backend but here's a well-commented Services to be provided instead of default Http:
import {Injectable} from '#angular/core';
import {
Http, XHRBackend, RequestOptions, RequestOptionsArgs, Request, Response, RequestMethod,
Headers
} from "#angular/http";
import { Observable } from "rxjs";
import { StorageService } from "../storage.service";
import { AppService } from "./app.service";
#Injectable()
export class HttpClientService extends Http {
private autoAppendHeadersDefault = true;
constructor(
backend: XHRBackend,
defaultOptions: RequestOptions,
private storageService: StorageService,
private appState: AppService,
) {
super(backend, defaultOptions);
this.autoAppendHeadersDefault = this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS;
}
request(url: string | Request, options?: RequestOptionsArgs, disableTryFix = false): Observable<Response> {
// Checking if the request needs headers to be appended
let assetRequest = false;
if(url instanceof Request) {
if(url.url.startsWith("/assets")) {
assetRequest = true;
}
}
// Appending headers
if(!assetRequest && this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS && url instanceof Request) {
// append aToken || gToken
let token = this.storageService.get('aToken');
if('undefined' === typeof token || !token) {
token = this.storageService.get('gToken');
}
if('undefined' !== typeof token && token) {
url.headers.set('Authorization', `Bearer ${token}`);
} else {
// neither aToken nor gToken are set
if(disableTryFix) {
this.removeAllTokens();
return Observable.throw({error: "Can't reauth: 01"});
}
return this.tryFixAuth().flatMap(
(res:any) => {
res = res.json();
this.storageService.set('gToken', res.access_token);
return this.request(url, options, true);
}
);
}
// headers appended to every request
if(!url.headers.get('Content-Type')) {
url.headers.append('Content-Type', 'application/json');
}
}
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = this.autoAppendHeadersDefault;
return super.request(url, options).catch((error: Response) => {
if (error.status === 401 /* || error.status === 403 */ ) {
if(disableTryFix) {
this.removeAllTokens();
this.navigateOnAuthFail();
return Observable.throw({error: "Can't reauth: 02"});
}
return this.tryFixAuth().flatMap(
(res: any) => {
res = res.json();
if('undefined' !== typeof res.refresh_token)
{
// got aToken & refresh_token
this.storageService.set('aToken', res.access_token);
this.storageService.set('refresh_token', res.refresh_token);
}
else if('undefined' !== typeof res.access_token)
{
// got only gToken
this.storageService.set('gToken', res.access_token);
}
else
{
console.log('tryFix: nothing useful returned')
// got no aToken, no gToken, no refresh_token
}
// retry request
return this.request(url, options, true);
}
);
}
// handle invalid refresh_token
if(disableTryFix && error.status === 400) {
console.log('Wrong refresh token (400)');
this.storageService.remove('refresh_token');
this.storageService.remove('aToken');
this.navigateOnAuthFail();
// handle invalid refresh token
}
return Observable.throw(error);
});
}
private tryFixAuth(): Observable<Response> {
console.log('Trying to fix auth');
if(this.storageService.get('refresh_token'))
{
return this.refreshToken();
}
else if(this.storageService.get('aToken'))
{
// no refresh_token, but aToken
// since aToken is dead it's not useful
this.storageService.remove('aToken');
}
else
{
// no aToken, no refresh_token
// possibly there's a gToken
// since the request is trying to fix itself (is failed) the gToken is most likely not valid
return this.guestToken();
}
}
// sends request with refresh_token to get new aToken
// the request returns only aToken and refresh_token, no gToken
private refreshToken(): Observable<Response> {
// is called only when refresh_token is set
let refreshToken = this.storageService.get('refresh_token');
// check refresh_token in case it's not checked before
if('undefined' === typeof refreshToken || !refreshToken || refreshToken == 'undefined') {
this.storageService.remove('refresh_token');
// there's no refresh_token saved
return Observable.throw({error: "Refresh token is not set"});
}
// form refresh_token request
const headers = new Headers();
headers.append('Authorization', `Bearer ${this.storageService.get('gToken')}`);
headers.append('Content-Type', 'application/json');
const url = `${this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token`;
const localData = JSON.stringify({
"client_id": this.appState.config.CLIENT_ID,
"client_secret": this.appState.config.CLIENT_SECRET,
"grant_type": 'refresh_token',
"refresh_token": refreshToken
});
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;
// refresh_token request
return this.request(
new Request({
method: RequestMethod.Post,
url: url,
headers: headers,
body: localData
}),
null, true);
}
// sends request to get new gToken
private guestToken(): Observable<Response> {
const url = `${
this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token?client_id=${
this.appState.config.CLIENT_ID}&client_secret=${
this.appState.config.CLIENT_SECRET}&grant_type=client_credentials`;
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;
return super.get(url);
}
// Aux methods
private navigateOnAuthFail() {
console.warn('Page is going to be refreshed');
// redirect to auth is performed after reload by authGuard
// it's possible to add some warning before reload
window.location.reload();
}
private removeAllTokens() {
this.storageService.remove('aToken');
this.storageService.remove('gToken');
this.storageService.remove('refresh_token');
}
}