【问题标题】:Angular 4 image async with bearer headersAngular 4图像异步与承载标头
【发布时间】:2018-03-15 18:33:24
【问题描述】:

我的任务是使用身份验证标头发出异步图像请求。我有这样的图像路径:

<img src="{{file.src}}"/>

并且我需要将承载令牌添加到此类请求的标头中。页面包含许多图像,因此不适合 ajax 请求。 不知道该怎么做。

【问题讨论】:

  • 为什么你认为 XHR-s 不适合那个?
  • @JánHalaša - 你是什么意思?我认为可能在 Angular 4 中有一些默认的东西来解决这个问题,我的意思是 Resful API 项目

标签: html angular bearer-token


【解决方案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 视频。此外,即使可以,视频通常也会以块的形式下载和显示,因此如果您尝试下载然后转换这种格式的视频,您最终会得到糟糕的用户体验
【解决方案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() {  }};
      });
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-08
    • 2015-11-23
    • 2021-10-04
    • 2018-02-16
    • 1970-01-01
    相关资源
    最近更新 更多