Angular2 – 为每个请求设置标题
我需要在用户login后为每个后续请求设置一些授权标头。
信息:
要为特定请求设置标题,
import {Headers} from 'angular2/http'; var headers = new Headers(); headers.append(headerName, value); // HTTP POST using these headers this.http.post(url, data, { headers: headers }) // do something with the response
参考
但以这种方式为每个请求手动设置请求标头是不可行的。
如何在用户login后设置标题集,并在注销时删除这些标题?
Angular 2中不支持HTTP拦截器,但在Angular Github上有一个有趣的讨论: https : //github.com/angular/angular/issues/2684 。
为了回答你的问题,你可以提供一个包装Angular2的原始Http
对象的服务。 像下面描述的那样。
import {Injectable} from '@angular/core'; import {Http, Headers} from '@angular/http'; @Injectable() export class HttpClient { constructor(private http: Http) {} createAuthorizationHeader(headers: Headers) { headers.append('Authorization', 'Basic ' + btoa('username:password')); } get(url) { let headers = new Headers(); this.createAuthorizationHeader(headers); return this.http.get(url, { headers: headers }); } post(url, data) { let headers = new Headers(); this.createAuthorizationHeader(headers); return this.http.post(url, data, { headers: headers }); } }
而不是注入Http
对象,你可以注入这个( HttpClient
)。
import { HttpClient } from './http-client'; export class MyComponent { // Notice we inject "our" HttpClient here, naming it Http so it's easier constructor(http: HttpClient) { this.http = httpClient; } handleSomething() { this.http.post(url, data).subscribe(result => { // console.log( result ); }); } }
我也认为可以通过提供自己的类来扩展Http
类来为Http
类使用多个提供者来完成一些事情…请参阅此链接: http : //blog.thoughtram.io/angular2/2015/11/23/multi -providers-in-angular-2.html 。
在这种情况下扩展BaseRequestOptions
可能会有很大的帮助。 看看下面的代码:
import {provide} from 'angular2/core'; import {bootstrap} from 'angular2/platform/browser'; import {HTTP_PROVIDERS, Headers, Http, BaseRequestOptions} from 'angular2/http'; import {AppCmp} from './components/app/app'; class MyRequestOptions extends BaseRequestOptions { constructor () { super(); this.headers.append('My-Custom-Header','MyCustomHeaderValue'); } } bootstrap(AppCmp, [ ROUTER_PROVIDERS, HTTP_PROVIDERS, provide(RequestOptions, { useClass: MyRequestOptions }) ]);
这应该包括每个调用中的“我的自定义标题”。
更新:
为了能够随时更改标题而不是以上代码,还可以使用以下代码添加新标题:
this.http._defaultOptions.headers.append('Authorization', 'token');
删除你可以做
this.http._defaultOptions.headers.delete('Authorization');
另外还有一个函数可以用来设置值:
this.http._defaultOptions.headers.set('Authorization', 'token');
以上解决scheme在打字稿上下文中仍然不完全有效。 _defaultHeaders是受保护的,不应该像这样使用。 我会build议上述解决scheme的快速解决scheme,但长期来看它更好地编写自己的包装http呼叫也处理auth的包装。 以auth0为例,这个例子更好,更干净。
https://github.com/auth0/angular2-jwt/blob/master/angular2-jwt.ts
HTTP拦截器现在可以通过来自@angular/common/http
的新HttpClient
从Angular 4.3.x版本开始 。
现在为每个请求添加一个头是很简单的:
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, } from '@angular/common/http'; export class AddHeaderInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // Clone the request to add the new header const clonedRequest = req.clone({ headers: req.headers.set('Authorization', 'Bearer 123') }); // Pass the cloned request instead of the original request to the next handle return next.handle(clonedRequest); } }
有一个不可变性的原则 ,这就是在设置新的请求之前需要克隆请求的原因。
由于编辑标题是一个非常普遍的任务,实际上它有一个快捷方式(克隆请求时):
const clonedRequest = req.clone({ setHeaders: { Authorization: 'Bearer 123' } });
在创build拦截器之后,你应该使用HTTP_INTERCEPTORS
提供的注册它。
import { HTTP_INTERCEPTORS } from '@angular/common/http'; @NgModule({ providers: [{ provide: HTTP_INTERCEPTORS, useClass: AddHeaderInterceptor, multi: true, }], }) export class AppModule {}
尽pipe我很晚才回答,但可能会帮助其他人。 要在使用@NgModule
时向所有请求注入标头,可以执行以下操作:
(我在Angular 2.0.1中testing了这个)
/** * Extending BaseRequestOptions to inject common headers to all requests. */ class CustomRequestOptions extends BaseRequestOptions { constructor() { super(); this.headers.append('Authorization', 'my-token'); this.headers.append('foo', 'bar'); } }
现在在@NgModule
执行以下操作:
@NgModule({ declarations: [FooComponent], imports : [ // Angular modules BrowserModule, HttpModule, // This is required /* other modules */ ], providers : [ {provide: LocationStrategy, useClass: HashLocationStrategy}, // This is the main part. We are telling Angular to provide an instance of // CustomRequestOptions whenever someone injects RequestOptions {provide: RequestOptions, useClass: CustomRequestOptions} ], bootstrap : [AppComponent] })
在Angular 2.1.2
我通过扩展angularHttp来解决这个问题:
import {Injectable} from "@angular/core"; import {Http, Headers, RequestOptionsArgs, Request, Response, ConnectionBackend, RequestOptions} from "@angular/http"; import {Observable} from 'rxjs/Observable'; @Injectable() export class HttpClient extends Http { constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) { super(_backend, _defaultOptions); } _setCustomHeaders(options?: RequestOptionsArgs):RequestOptionsArgs{ if(!options) { options = new RequestOptions({}); } if(localStorage.getItem("id_token")) { if (!options.headers) { options.headers = new Headers(); } options.headers.set("Authorization", localStorage.getItem("id_token")) } return options; } request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> { options = this._setCustomHeaders(options); return super.request(url, options) } }
然后在我的应用程序提供商,我能够使用自定义工厂提供'Http'
import { RequestOptions, Http, XHRBackend} from '@angular/http'; import {HttpClient} from './httpClient'; import { RequestOptions, Http, XHRBackend} from '@angular/http'; import {HttpClient} from './httpClient';//above snippet function httpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http { return new HttpClient(xhrBackend, requestOptions); } @NgModule({ imports:[ FormsModule, BrowserModule, ], declarations: APP_DECLARATIONS, bootstrap:[AppComponent], providers:[ { provide: Http, useFactory: httpClientFactory, deps: [XHRBackend, RequestOptions]} ], }) export class AppModule { constructor(){ } }
现在我不需要声明每个Http方法,并可以在我的应用程序中正常使用http
。
通过扩展Angular 2 Http
Provider创build一个自定义的Http类,并简单地在你自定义的Http类中覆盖constructor
和request
方法。 下面的示例在每个http请求中添加Authorization
标头。
import {Injectable} from '@angular/core'; import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; @Injectable() export class HttpService extends Http { constructor (backend: XHRBackend, options: RequestOptions) { let token = localStorage.getItem('auth_token'); // your custom token getter function here options.headers.set('Authorization', `Bearer ${token}`); super(backend, options); } request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> { let token = localStorage.getItem('auth_token'); if (typeof url === 'string') { // meaning we have to add the token to the options, not in url if (!options) { // let's make option object options = {headers: new Headers()}; } options.headers.set('Authorization', `Bearer ${token}`); } else { // we have to add the token to the url object url.headers.set('Authorization', `Bearer ${token}`); } return super.request(url, options).catch(this.catchAuthError(this)); } private catchAuthError (self: HttpService) { // we have to pass HttpService's own instance here as `self` return (res: Response) => { console.log(res); if (res.status === 401 || res.status === 403) { // if not authenticated console.log(res); } return Observable.throw(res); }; } }
然后configuration您的主要app.module.ts
提供XHRBackend
作为ConnectionBackend
提供程序和RequestOptions
到您的自定义Http类:
import { HttpModule, RequestOptions, XHRBackend } from '@angular/http'; import { HttpService } from './services/http.service'; ... @NgModule({ imports: [..], providers: [ { provide: HttpService, useFactory: (backend: XHRBackend, options: RequestOptions) => { return new HttpService(backend, options); }, deps: [XHRBackend, RequestOptions] } ], bootstrap: [ AppComponent ] })
之后,您现在可以在您的服务中使用自定义的http提供程序。 例如:
import { Injectable } from '@angular/core'; import {HttpService} from './http.service'; @Injectable() class UserService { constructor (private http: HttpService) {} // token will added automatically to get request header getUser (id: number) { return this.http.get(`/users/${id}`).map((res) => { return res.json(); } ); } }
这是一个全面的指南 – http://adonespitogo.com/articles/angular-2-extending-http-provider/
迟到比从未好… =)
您可以采取扩展的BaseRequestOptions
的概念(从这里https://angular.io/docs/ts/latest/guide/server-communication.html#!#override-default-request-选项; ),并刷新标题“上飞“(不仅在构造函数)。 你可以使用getter / setter“headers”属性重写,如下所示:
import { Injectable } from '@angular/core'; import { BaseRequestOptions, RequestOptions, Headers } from '@angular/http'; @Injectable() export class DefaultRequestOptions extends BaseRequestOptions { private superHeaders: Headers; get headers() { // Set the default 'Content-Type' header this.superHeaders.set('Content-Type', 'application/json'); const token = localStorage.getItem('authToken'); if(token) { this.superHeaders.set('Authorization', `Bearer ${token}`); } else { this.superHeaders.delete('Authorization'); } return this.superHeaders; } set headers(headers: Headers) { this.superHeaders = headers; } constructor() { super(); } } export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
以下是已接受答案的改进版本,已更新为Angular2 final:
import {Injectable} from "@angular/core"; import {Http, Headers, Response, Request, BaseRequestOptions, RequestMethod} from "@angular/http"; import {I18nService} from "../lang-picker/i18n.service"; import {Observable} from "rxjs"; @Injectable() export class HttpClient { constructor(private http: Http, private i18n: I18nService ) {} get(url:string):Observable<Response> { return this.request(url, RequestMethod.Get); } post(url:string, body:any) { return this.request(url, RequestMethod.Post, body); } private request(url:string, method:RequestMethod, body?:any):Observable<Response>{ let headers = new Headers(); this.createAcceptLanguageHeader(headers); let options = new BaseRequestOptions(); options.headers = headers; options.url = url; options.method = method; options.body = body; options.withCredentials = true; let request = new Request(options); return this.http.request(request); } // set the accept-language header using the value from i18n service that holds the language currently selected by the user private createAcceptLanguageHeader(headers:Headers) { headers.append('Accept-Language', this.i18n.getCurrentLang()); } }
当然,如果需要的话,它应该扩展到delete
和put
方法(在我的项目中,目前我还不需要它们)。
优点是get
/ post
/ …方法中的重复代码更less。
请注意,在我的情况下,我使用Cookie进行身份validation。 我需要i18n( Accept-Language
头文件)的头文件,因为我们的API返回的很多值都是以用户的语言翻译的。 在我的应用程序中,i18n服务包含用户当前select的语言。
经过一番调查,我发现最后和最简单的方法是扩展我更喜欢的BaseRequestOptions
。
以下是我尝试和放弃的原因:
1.扩展BaseRequestOptions
,并在constructor()
添加dynamic头文件。 如果我login,它不能工作。 它将被创build一次。 所以它不是dynamic的。
2.扩展Http
同样的原因,我不能在constructor()
添加dynamic头。 如果我重写request(..)
方法,并设置标题,如下所示:
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> { let token = localStorage.getItem(AppConstants.tokenName); if (typeof url === 'string') { // meaning we have to add the token to the options, not in url if (!options) { options = new RequestOptions({}); } options.headers.set('Authorization', 'token_value'); } else { url.headers.set('Authorization', 'token_value'); } return super.request(url, options).catch(this.catchAuthError(this)); }
你只需要覆盖这个方法,但不是每个get / post / put方法。
3.我的首选解决scheme是扩展BaseRequestOptions
并覆盖merge()
:
@Injectable() export class AuthRequestOptions extends BaseRequestOptions { merge(options?: RequestOptionsArgs): RequestOptions { var newOptions = super.merge(options); let token = localStorage.getItem(AppConstants.tokenName); newOptions.headers.set(AppConstants.authHeaderName, token); return newOptions; } }
这个merge()
函数将被调用每个请求。
虽然我很晚才回答,但如果有人正在寻求一个更简单的解决scheme。
我们可以使用angular2-jwt。 当从Angular 2应用程序发出HTTP请求时,angular2-jwt会自动附加一个JSON Web Token(JWT)作为授权标头。
我们可以使用高级configuration选项来设置全局标题
export function authHttpServiceFactory(http: Http, options: RequestOptions) { return new AuthHttp(new AuthConfig({ tokenName: 'token', tokenGetter: (() => sessionStorage.getItem('token')), globalHeaders: [{'Content-Type':'application/json'}], }), http, options); }
并发送每个请求令牌
getThing() { let myHeader = new Headers(); myHeader.append('Content-Type', 'application/json'); this.authHttp.get('http://example.com/api/thing', { headers: myHeader }) .subscribe( data => this.thing = data, err => console.log(error), () => console.log('Request Complete') ); // Pass it after the body in a POST request this.authHttp.post('http://example.com/api/thing', 'post body', { headers: myHeader }) .subscribe( data => this.thing = data, err => console.log(error), () => console.log('Request Complete') ); }
如何保持一个单独的服务如下
import {Injectable} from '@angular/core'; import {Headers, Http, RequestOptions} from '@angular/http'; @Injectable() export class HttpClientService extends RequestOptions { constructor(private requestOptionArgs:RequestOptions) { super(); } addHeader(headerName: string, headerValue: string ){ (this.requestOptionArgs.headers as Headers).set(headerName, headerValue); } }
而当你从另一个地方调用这个使用this.httpClientService.addHeader("Authorization", "Bearer " + this.tok);
你会看到添加的标题,例如: – 授权如下
我喜欢这个想法来覆盖默认选项,这似乎是一个很好的解决scheme。
但是,如果您正在扩展Http
类。 确保阅读这个!
这里的一些答案实际上显示了request()
方法的不正确的重载,这可能导致难以捕捉的错误和怪异的行为。 我自己偶然发现了
该解决scheme基于Angular 4.2.x
request()
方法实现,但应该是未来兼容的:
import {Observable} from 'rxjs/Observable'; import {Injectable} from '@angular/core'; import { ConnectionBackend, Headers, Http as NgHttp, Request, RequestOptions, RequestOptionsArgs, Response, XHRBackend } from '@angular/http'; import {AuthenticationStateService} from '../authentication/authentication-state.service'; @Injectable() export class Http extends NgHttp { constructor ( backend: ConnectionBackend, defaultOptions: RequestOptions, private authenticationStateService: AuthenticationStateService ) { super(backend, defaultOptions); } request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> { if ('string' === typeof url) { url = this.rewriteUrl(url); options = (options || new RequestOptions()); options.headers = this.updateHeaders(options.headers); return super.request(url, options); } else if (url instanceof Request) { const request = url; request.url = this.rewriteUrl(request.url); request.headers = this.updateHeaders(request.headers); return super.request(request); } else { throw new Error('First argument must be a url string or Request instance'); } } private rewriteUrl (url: string) { return environment.backendBaseUrl + url; } private updateHeaders (headers?: Headers) { headers = headers || new Headers(); // Authenticating the request. if (this.authenticationStateService.isAuthenticated() && !headers.has('Authorization')) { headers.append('Authorization', 'Bearer ' + this.authenticationStateService.getToken()); } return headers; } }
请注意,我正在以这种方式import { Http as NgHttp } from '@angular/http';
导入原始类import { Http as NgHttp } from '@angular/http';
以防止名称冲突。
这里解决的问题是
request()
方法有两个不同的调用签名。 当传递Request
对象而不是URLstring
,Angular会忽略options
参数。 所以这两种情况都要妥善处理。
这里是如何使用DI容器注册这个重写的类的例子:
export const httpProvider = { provide: NgHttp, useFactory: httpFactory, deps: [XHRBackend, RequestOptions, AuthenticationStateService] }; export function httpFactory ( xhrBackend: XHRBackend, requestOptions: RequestOptions, authenticationStateService: AuthenticationStateService ): Http { return new Http( xhrBackend, requestOptions, authenticationStateService ); }
有了这样的方法,你可以正常注入Http
类,但是你重写的类将被神奇地注入。 这使您可以轻松地整合您的解决scheme,而无需更改应用程序的其他部分(多态行为)。
只需将httpProvider
添加到模块元数据的providers
属性即可。
angular2.0.1和更高版本有一些变化:
import {RequestOptions, RequestMethod, Headers} from '@angular/http'; import { BrowserModule } from '@angular/platform-browser'; import { HttpModule } from '@angular/http'; import { AppRoutingModule } from './app.routing.module'; import { AppComponent } from './app.component'; //you can move this class to a better place class GlobalHttpOptions extends RequestOptions { constructor() { super({ method: RequestMethod.Get, headers: new Headers({ 'MyHeader': 'MyHeaderValue', }) }); } } @NgModule({ imports: [ BrowserModule, HttpModule, AppRoutingModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ], providers: [ { provide: RequestOptions, useClass: GlobalHttpOptions} ] }) export class AppModule { }
我可以select一个简单的解决scheme>添加一个新的Headers默认选项合并或加载通过您的API(或其他)function。
get(endpoint: string, params?: any, options?: RequestOptions) { if (!options) { options = new RequestOptions(); options.headers = new Headers( { "Accept": "application/json" } ); <<<< } // [...] }
当然,你可以在默认选项或类中的任何东西外部化这个头。 这是在Ionic生成的api.ts @Injectable()导出类API {}
这是非常快,它为我工作。 我不想要json / ld格式。
这是我为每个请求设置标记所做的工作。
import { RequestOptions, BaseRequestOptions, RequestOptionsArgs } from '@angular/http'; export class CustomRequestOptions extends BaseRequestOptions { constructor() { super(); this.headers.set('Content-Type', 'application/json'); } merge(options?: RequestOptionsArgs): RequestOptions { const token = localStorage.getItem('token'); const newOptions = super.merge(options); if (token) { newOptions.headers.set('Authorization', `Bearer ${token}`); } return newOptions; } }
并注册在app.module.ts
@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [ { provide: RequestOptions, useClass: CustomRequestOptions } ], bootstrap: [AppComponent] }) export class AppModule { }
您可以在路线中使用canActive
,如下所示:
import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { CanActivate } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private auth: AuthService, private router: Router) {} canActivate() { // If user is not logged in we'll send them to the homepage if (!this.auth.loggedIn()) { this.router.navigate(['']); return false; } return true; } } const appRoutes: Routes = [ { path: '', redirectTo: '/deals', pathMatch: 'full' }, { path: 'special', component: PrivateDealsComponent, /* We'll use the canActivate API and pass in our AuthGuard. Now any time the /special route is hit, the AuthGuard will run first to make sure the user is logged in before activating and loading this route. */ canActivate: [AuthGuard] } ];
取自: https : //auth0.com/blog/angular-2-authentication