Skip to content

RFC: Http interceptors and transformers #2684

@jeffbcross

Description

@jeffbcross

This issue is intended to define requirements for how the Http library should support common operations, like request and response transformations and error handling. The issue doesn't yet have a proposed design, but discussion, comments and suggestions are welcomed. Un-captured use cases are especially welcomed.

History

Two widely-used feature of angular1 are request and response interceptors and request/response transformers. Interceptors and Transformers have different goals and constraints.

  • Interceptors are expected to have side effects, while transformers' exclusive purpose is to mutate requests or responses synchronously.
  • Interceptors may be asynchronous, tranformers must be synchronous
  • Interceptors are global and configured in Angular 1's "compile" phase, while transformers may be global or local. Global transformations can be mutated in the "run" phase.
  • Error interceptors are declared explicitly and separately from normal interceptors, whereas transforms handle normal and error cases.
  • Error interceptors can recover from errors by returning a resolved promise

Some common use cases for interceptors:

  • Setting timeouts for requests
  • Setting custom XSRF tokens to request headers, as described in $http docs
  • Add conditional parameters to all request urls (like an access token, or a/b-testing info)
  • Check authentication state before performing request, prompt user to authenticate
  • Have side effects on other parts of application (i.e. if response says user needs to authenticate, route them to login page)
  • Log all response errors returned from the server to a persisted log
  • Log total request/response time in order to do performance analysis
  • Filter out un-interesting request/responses from logs
  • Recover from request exceptions, or response errors (i.e. if request times out, return old data from cache)
  • Unwrapping responses (i.e. if response is {data: {foo:bar}}, return response.data)
  • Cache responses in a custom cache to be used by other services

Common use cases for transformers

  • serialize/de-serialize data, both directions
  • Strip headers (Angular 1 strips user-specified XSRF header with a default response transformation)

Worth noting are some additional features provided by Angular 1 $http, which would otherwise be handled by transformers:

  • Url-based caching
  • Configurable parameter serialization
  • Header serialization

New Considerations for Angular 2’s Http Library

Angular 2 and Angular 2 Http have some technical differences and philosophical differences from Angular 1.

  • Mutable global state is discouraged (i.e. $http.defaults.transforms.push(...)).
  • Angular 2 has no "phases" like "config" and "run".
  • Angular 2 provides hierarchical dependency injection, allowing users to modify bindings at different levels of an application's component tree.
  • Angular 2 Http is based primarily on Observable instead of Promise
  • Http has goals of supporting upload/download progress events, connection retrying, request cancellation, caching, and polling as first-class use cases.

Current Options

The current library supports creating services that would provide shared interceptors and transformations. Here's an example of an Http-based service that would add custom headers, cache responses and log errors, based on the current RxJS implementation.

import {Http, Request, IRequestOptions} from 'angular2/http';
import {ResponseCache} from './my-response-cache';
import {ErrorLog} from './my-error-log';
class MyConfiguredHttp {
    constructor(public http:Http, public responseCache:ResponseCache, public errorLog:ErrorLog) {}
    request(options:IRequestOptions) {
        if (options.url in this.responseCache){
            //Naive example (This Observable create syntax not yet supported)
            //There's a cached response for this url, return it instead of performing request
            return Rx.Observable.from([this.responseCache[options.url]]);
        }

        var req = new Request(options);
        // Set a custom auth header
        req.headers.set('sessionid', '123');
        return this.http.request(req)
            //Try performing the request up to three times
            .retry(3)
            .do(res => {
                //Naive example, cache the response by the original url
                this.responseCache[options.url] = res;
            })
            .doOnError(res => {
                // Log the error with my app's logging service
                this.errorLog.push(options, res);
            });
    }   
}

Questions to answer with final design:

  • What use cases are not able to be supported by the "shared service" approach?
  • How can the API be improved to make performance, robustness, and security the default path?
  • How is composability impacted by this approach, and how can it be improved?
  • How could this be simple and intuitive?
  • Does this require too deep of knowledge of Observables/Rx?
  • Does anyone actually read this far down on Github issues?

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions