(English) Running A Local Angular Development Environment Behind A Spring Cloud Gateway Service

Home » Blog » Software » Enterprise Software » (English) Running A Local Angular Development Environment Behind A Spring Cloud Gateway Service

Leider ist der Eintrag nur auf Britisches Englisch verfügbar. Der Inhalt wird unten in einer verfügbaren Sprache angezeigt. Klicken Sie auf den Link, um die aktuelle Sprache zu ändern.

Having multiple microservices running behind a reverse proxy / API gateway in lieu of a single monolithic back-end application is a common pattern these days.

With such an architecture it often comes in handy to have an Angular application that serves as a front-end for such a modularised back-end be served from behind a reverse proxy running locally as well in order to treat it is if it were running on the same host and port as the back-end. Not only will this be the most likely production scenario but it’ll also avoid problems with Cross-Origin Resource Sharing (CORS), Content Security Policy (CSP), the same-origin policy and authentication during development.

In Java-based environments Netflix Zuul used to be a common solution as a reverse proxy. However, since Zuul is built on the Java Servlet API 2.5, uses blocking APIs and hence doesn’t allow non-blocking access and routing of long-lived connections such as WebSocket it isn’t supported anymore by more recent versions of Spring Boot and Spring Cloud.

The lack of WebSocket support comes as a downside during Angular development in particular because the Angular CLI live reload feature is implemented using WebSocket. So, while technically it’s possible to develop an Angular app running behind a local reverse proxy much of the benefit of the quick turnaround between code changes that usually comes with Angular development is lost due to having to manually reload the app after each code change (at which point the application state might also be lost if it isn’t entirely dependent on the current URL).

Spring Cloud Gateway is a new library for building API gateways and reverse proxies that is supposed to replace Zuul. Spring Cloud Gateway has been built with Spring 5 and reactive patterns, Reactor in particular, and supports WebSockets.

That said, Zuul 2 also supports asynchronous and non-blocking architectures as well as WebSocket connections. The question of why Spring Cloud won’t support Zuul 2, too, probably is at least slightly political, which is why I won’t go into that.

Be that as it may, Spring Cloud Gateway lends itself to the task of running a reverse proxy in a local development environment.

When doing so, however, I ran into two issues. I’d like to share those – including their respective fixes – both so others might profit from these solutions and so I have them readily available for future reference myself.

So, without further ado, these are the issues and their solutions:

1. Problem: When having your local development environment running behind a Spring Cloud Gateway reverse proxy under a particular host and port, say http://localhost:7090 and initially trying to open your Angular app you’ll perhaps be greeted with the following error message on an otherwise blank page:

Invalid host header

This issue occurs if you use Docker as container technology for packaging and running your microservices. Due to how networking works under Docker host.docker.internal will be used as an alias to the host machine’s (i.e. the actual hardware Docker runs on) local network address instead of the usual localhost.

Due to a security fix in webpack DevServer (the small web server used by Angular CLI for running the app during local development) that prevents DNS rebinding attacks an Angular app running locally on webpack DevServer by default can only be accessed through localhost.

Solution:

While webpack DevServer has had an allowedHosts option for adding additional allowed hosts for quite some time now Angular CLI unfortunately has not. Hence, other than deactivating that feature entirely using the

--disable-host-check

option when running ng serve from the command line you’re out of luck. Doing so isn’t a good idea, though, because it opens up the potential security hole mentioned above.

A secure solution is to use custom Angular webpack builders (also see @angular-builders) as described here in more detail to add the allowedHosts option to the Angular webpack build.

This is done by first adding @angular-builders/custom-webpack to your npm devDependencies:


npm i --save-dev @angular-builders/custom-webpack

Afterwards, add a configuration section such as this one to your app’s angular.json:


"projects": {
  “myApp": {
    ...
    "architect": {
      "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        "options": {
          "customWebpackConfig": {
            "path": "./allowed-hosts.config.js"
          },
          ...
        },
        ...
      "serve": {
        "builder": "@angular-builders/custom-webpack:dev-server",
        "options": {
          "browserTarget": “myApp:build"
        },
        ...

Finally, add a new file named allowed-hosts.config.js with this content to your app’s project root folder:


module.exports = {
  devServer: {
    allowedHosts: [
      'localhost',
      'host.docker.internal'
    ]
  }
};

2. Problem: Again, when having your local development environment running behind a Spring Cloud Gateway reverse proxy under a particular host and port such as the aforementioned http://localhost:7090 and the proxy internally routing requests to your Angular app to the usual http://localhost:4200 the Angular app will likely reload after 30 seconds even though the source code hasn’t been changed. In addition to that you’ll see this error message in your browser’s DevTools console:

[WDS] Disconnected

After quite some research on this I found out that this is caused by a CORS issue: Since the WebSocket connection Angular uses for its live reload feature is initiated from http://localhost:4200 but routed through http://localhost:7090 the latter will appear as the connection’s originator. As by default HTTP resources will only be available to applications residing under the exact same host and port combination the Angular app now isn’t allowed to access its own resources. Therefore, the WebSocket connection is interrupted after a default timeout of 30 seconds, which results in the error message above and an enforced application reload.

Solution: In order to whitelist the Angular app running under http://localhost:4200 the following configuration has to be added to the Spring Cloud Gateway application’s application.yml (or application.properties) configuration file:


spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:4200"
            allowedMethods:
              - GET
              - PUT
              - POST
              - PATCH
              - DELETE
              - OPTIONS
About the author: Bjoern
Entrepreneur

Leave a Comment

* Die DSGVO-Checkbox ist ein Pflichtfeld.

*

I agree