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图像异步与承载标头的主要内容,如果未能解决你的问题,请参考以下文章

Ionic 4 Angular模板与异步管道绑定到可观察

Angular:如何将异步属性数据绑定到背景图像

Angular 4.3.3 HttpClient:如何从响应的标头中获取值?

使用 Angular 4.3 的 HttpInterceptor 拦截 HTTP 响应标头

asp核心和angular7中没有“Access-Control-Allow-Origin”标头

使用 Angular 4 保存 cookie