SpineEventEngine / web

Spine web server and JS client library.
https://spine.io
Apache License 2.0
1 stars 3 forks source link

[1.x] Allow customisation of `HttpClient` #194

Closed armiol closed 1 year ago

armiol commented 1 year ago

This changeset addresses #164, #165, and #189.

Customising HttpClient

It is now possible to customise HttpClient for each type of requests (sending commands, queries, or interacting with subscriptions):

/**
 * Custom client implementation for querying the server-side.
 * 
 * Must extend `HttpClient`.
 */
class CustomHttpClientForQuerying extends HttpClient { ... }

// ...

const queryingHttpClient = new CustomHttpClientForQuerying(endpoint);

const client = init({
      // ...
      forQueries: {
        // ...
        httpClient: queryingHttpClient  /* optional custom implementation; `HttpClient` by default. */
      },
      forSubscriptions: {
        // ...
        httpClient: /* optional custom HTTP client for subscriptions; `HttpClient` by default.  */
      },
      forCommands: {
        // ...
        httpClient: /* optional custom HTTP client for sending commands; `HttpClient` by default.  */
      }

      // ,  ....
    });

Such a custom implementation now allows to override methods, responsible for setting HTTP headers, setting the request mode, or transforming the original proto message into an HTTP body. Here is an excerpt from HttpClient showing the default implementations for each overridable method:

  /**
   * Returns the mode in which the HTTP request transferring the given message is sent.
   *
   * This implementation returns `cors`.
   *
   * @param {!TypedMessage} message a message to send, as a {@link TypedMessage}
   * @return {string} the mode of HTTP requests to use
   */
  requestMode(message) {
    return 'cors';
  }

  /**
   * Returns the string-typed map of HTTP header names to header values,
   * which to use in order to send the passed message.
   *
   * In this implementation, returns {'Content-Type': 'application/x-protobuf'}.
   *
   * @param {!TypedMessage} message a message to send, as a {@link TypedMessage}
   * @returns {{"Content-Type": string}}
   */
  headers(message) {
    return {
      'Content-Type': 'application/x-protobuf'
    };
  }

  /**
   * Transforms the given message to a string, which would become a POST request body.
   *
   * Uses {@link TypedMessage#toBase64 Base64 encoding} to transform the message.
   *
   * @param {!TypedMessage} message a message to transform into a POST body
   * @returns {!string} transformed message
   */
  toBody(message) {
    return message.toBase64();
  }

Interpreting Server Responses

Additionally, a new HttpResponseHandler routine has been extracted from the existing code. It is responsible for transforming the raw response content into JS objects. The default implementation — what we used to have in previous versions as a hard-coded behaviour — expects the server-side to return a JSON string, and parses it into a JS object.

Now, it is possible to provide a custom HttpResponseHandler, in the same manner as HttpClient:

/**
 * A custom response handler for query responses from the server-side.
 *
 * Must extend `HttpResponseHandler`.
 */
class QueryResponseHandler extends HttpResponseHandler { ... }

// ...

const client = init({
      // ... 
      forQueries: {
        // ...
        httpResponseHandler: queryResponseHandler  /* optional; `HttpResponseHandler` by default. */
      },
      forSubscriptions: {
        // ...
        httpResponseHandler: /* optional custom implementation; `HttpResponseHandler` by default. */
      },
      forCommands: {
        // ...
        httpResponseHandler: /* optional custom implementation; `HttpResponseHandler` by default. */
      }
    });

Here is a default implementation of parsing, as specified by HttpResponseHandler. This is a @protected method designed to be overridable in descendants:

  /**
   * Transforms the response into JS object by parsing the response contents.
   *
   * This implementation expects the response to contain JSON data.
   *
   * @param response an HTTP response
   * @return {Promise<Object|SpineError>} a promise of JS object,
   *                                      or a rejection with the corresponding `SpineError`
   * @protected
   */
  parse(response) {
      return response.json()
          .then(json => Promise.resolve(json))
          .catch(error =>
              Promise.reject(new SpineError('Failed to parse response JSON', error))
          );
  }

The library version is set to 1.9.0-SNAPSHOT.9.

codecov[bot] commented 1 year ago

Codecov Report

Merging #194 (95a0105) into v1 (4532cf3) will increase coverage by 0.29%. The diff coverage is 84.48%.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## v1 #194 +/- ## ============================================ + Coverage 62.88% 63.17% +0.29% Complexity 214 214 ============================================ Files 95 96 +1 Lines 2697 2724 +27 Branches 46 46 ============================================ + Hits 1696 1721 +25 - Misses 990 992 +2 Partials 11 11 ```