Angular 4图像异步与承载标头
Posted
技术标签:
【中文标题】Angular 4图像异步与承载标头【英文标题】:Angular 4 image async with bearer headers 【发布时间】:2018-03-15 18:33:24 【问题描述】:我的任务是使用身份验证标头发出异步图像请求。我有这样的图像路径:
<img src="file.src"/>
并且我需要将承载令牌添加到此类请求的标头中。页面包含许多图像,因此不适合 ajax 请求。 不知道该怎么做。
【问题讨论】:
为什么你认为 XHR-s 不适合那个? @JánHalaša - 你是什么意思?我认为可能在 Angular 4 中有一些默认的东西来解决这个问题,我的意思是 Resful API 项目 【参考方案1】:假设您已经实现了一个 HttpIntercepter 来添加标头,这是一个实际可行的解决方案(在 Angular 4 中):
import Pipe, PipeTransform from '@angular/core';
import HttpClient from '@angular/common/http';
import Observable from 'rxjs/Observable';
@Pipe(
name: 'secure'
)
export class SecurePipe implements PipeTransform
constructor(private http: HttpClient)
transform(url: string)
return new Observable<string>((observer) =>
// This is a tiny blank image
observer.next('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
// The next and error callbacks from the observer
const next, error = observer;
this.http.get(url, responseType: 'blob').subscribe(response =>
const reader = new FileReader();
reader.readAsDataURL(response);
reader.onloadend = function()
observer.next(reader.result);
;
);
return unsubscribe() ;
);
你可以这样使用它:
<img [src]="'/api/image/42' | secure | async" />
以前的解决方案存在很大缺陷。我不保证这是完美的,但它实际上已经过测试并为我工作。
你不能返回你从 http.get 得到的 observable!我不知道为什么以前的解决方案假设你可以。 http.get 的 observable 指示何时从服务器检索数据。但是,在此之后还必须完成另一个异步过程:对 reader.readAsDataURL 的调用。因此,您需要创建一个 Observable,您将在该过程完成后调用 next。
此外,如果您不立即将某些内容放入图像中,浏览器仍会尝试使用 HTTP 加载图像,并且您的 javascript 控制台中会出现错误。这就是第一个调用observer.next 的原因,它会放入一个空的、微小的GIF 图像。
这种方法的一个问题是,如果图像被多次使用,它将每次都加载。即使浏览器缓存,您每次都会转换为 base64。我创建了一个缓存来存储图像,这样以后的查询就不需要了。
【讨论】:
我也面临同样的问题。每次都下载图像。介意分享您的解决方案以避免这种情况吗?谢谢【参考方案2】:现在,无法仅通过 html 中的标签进行授权调用,浏览器不为此提供 API,因此您必须发出 XHR 请求。这是一个解决方法:通过 XHR 获取图像,将其转换为 blob,然后将 blob 转换为 base64 并将图像插入标签的 src。该解决方案需要明确两个管道:一个是用于进行 XHR 调用的自定义管道,另一个是 Angular 的内置管道 async
。这是我们的自定义管道:
import Pipe, PipeTransform from '@angular/core';
import Http, RequestOptions, Headers, ResponseContentType from @angular/http';
import Observable from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
@Pipe(name: 'image')
export class ImagePipe implements PipeTransform
constructor(private http: Http)
transform(url: string)
const headers = new Headers('Authorization': 'MY TOKEN', 'Content-Type': 'image/*'); /* tell that XHR is going to receive an image as response, so it can be then converted to blob, and also provide your token in a way that your server expects */
return this.http.get(url, new RequestOptions(headers: headers, responseType: ResponseContentType.Blob)) // specify that response should be treated as blob data
.map(response => response.blob()) // take the blob
.switchMap(blob =>
// return new observable which emits a base64 string when blob is converted to base64
return Observable.create(observer =>
const reader = new FileReader();
reader.readAsDataURL(blob); // convert blob to base64
reader.onloadend = function()
observer.next(reader.result); // emit the base64 string result
);
);
这是你的html:
<img [src]="('https://www.w3schools.com/css/trolltunga.jpg' | image) | async" />
我们使用管道获取 base64 字符串的 observable,并使用 async
将实际发出的字符串插入到 src
标记中。
如果您查看 Network
选项卡内部,您会看到您的 Authorization 标头是在 XHR 调用期间提供的:
您需要记住的一件事是 CORS:您的图像服务服务器应该配置为接受来自运行 Angular 应用程序的域中的图像的 XHR 调用,此外,您必须为自定义提供绝对 url管道,否则它将向 Angular 应用程序的域本身发出请求。
【讨论】:
这在 Angular 4 中不起作用。它必须使用已弃用的 HttpClient 版本? 它使用的是 Http,而不是 HttpClient,是的,它已被弃用。使用 HttpClient 时需要指定一些内容。 @FanJin 我不这么认为。视频是一种不同的格式,我不认为你可以只使用 base64 视频。此外,即使可以,视频通常也会以块的形式下载和显示,因此如果您尝试下载然后转换这种格式的视频,您最终会得到糟糕的用户体验 例如,见blog.strongbrew.io/safe-image-requests-in-angular【参考方案3】:如果您已经为 api 实现了 HttpInterceptor,您可以通过让拦截器处理标头来简化上述管道代码。以下是使用 HttpClient 的更新版本。
@Pipe(
name: 'image',
)
export class ImagePipe implements PipeTransform
constructor(
private http: HttpClient,
private config: ConfigProvider
)
transform(url: string)
return this.http.get(url, responseType: "blob")
.switchMap(blob =>
// return new observable which emits a base64 string when blob is converted to base64
return Observable.create(observer =>
const reader = new FileReader();
reader.readAsDataURL(blob); // convert blob to base64
reader.onloadend = function ()
observer.next(reader.result); // emit the base64 string result
);
);
`
这里是示例interceptor:
@Injectable()
export class TokenInterceptor implements HttpInterceptor
constructor(private config: ConfigProvider)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
const authReq = req.clone(
setHeaders:
Authorization: this.config.getAuthorization(),
'X-App-ClientId': this.config.authentication['X-App-ClientId']
);
return next.handle(authReq);
【讨论】:
这不起作用。它必须使用已弃用的 HttpClient 版本,因为 switchMap 似乎不再存在或至少无法按原样编译。而且,它不承认这里连续有两个异步进程:http.get 和 readAsDataURL。 @Charles,只是为了让你知道 switchMap 已经成为一个 rxjs 运算符,必须在管道函数中使用:learnrxjs.io/operators/transformation/switchmap.html【参考方案4】:这个 Angular 5 解决方案以及来自 Armen Vardanyan 和 Charles 的混合解决方案。 Armen 的解决方案适用于 Angular 5,但首先尝试加载 http://localhost/null url。为了解决这个问题,我加入了查尔斯的小空白图片:
@Pipe(name: 'secure')
export class SecurePipe implements PipeTransform
constructor(private http: Http,
public g: BaseGroupService)
transform(url: string)
return new Observable<string>((observer) =>
observer.next('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
const next, error = observer;
const headers = new Headers('Authorization': 'TOKEN', 'Content-Type': 'image/*');
this.http.get(url, new RequestOptions(headers: headers, responseType: ResponseContentType.Blob)).subscribe(response =>
const reader = new FileReader();
reader.readAsDataURL(response.blob());
reader.onloadend = function()
observer.next(reader.result);
;
);
return unsubscribe() ;
);
【讨论】:
以上是关于Angular 4图像异步与承载标头的主要内容,如果未能解决你的问题,请参考以下文章
Angular 4.3.3 HttpClient:如何从响应的标头中获取值?
使用 Angular 4.3 的 HttpInterceptor 拦截 HTTP 响应标头