You are viewing the documentation for Blueriq 17. Documentation for other versions is available in our documentation directory.

Blueriq has a standard Material Theme packaged but when creating your own theme this must be deployed somehow. 
You need a web server for this that conforms to these requirements:

  1. The web server should be able to serve static content
  2. The web server should be able to proxy API requests to the runtime

By proxying the runtime requests, the theme and the backend share the same origin, so XHR-requests from theme to runtime are allowed by the default browser security policy.

Suitable web servers include, but are not limited to nginx, Apache httpd and IIS.

Yarn build

The Material Kickstarter comes with 2 build scripts and both use a different environment.ts file which can be configured if needed. Configurating in this case means changing the baseUrl.

  1. Change the `environment.runtime.ts` baseUrl to `/server`
  2. Run `yarn build:runtime` (which uses the environment.runtime.ts's configuration)
  3. When finished: Copy the content of /dist to a application server you prefer (for nginx that would be: /html)

nginx

For getting started with nginx please take a look at their documentation: https://www.nginx.com/resources/wiki/ 
The following configuration is the minimum needed to get the theme up and running. You could need adjustments to fit your own specific setup.

nginx.conf
error_log logs/error.log;

events {
	worker_connections 1024;
}

http {
	upstream revProxyRuntime {
		# 1: Replace <host>:<port>, example: my.runtime.local:8080
		server <host>:<port>;
	}
	
	server {
		include ../conf/mime.types;
		listen 1337;

		# Instruct the browser to always verify that its cache is up-to-date
		add_header Cache-Control 'max-age=0, must-revalidate';

		location / {
			root html;
			index index.html index.htm;
			try_files $uri $uri/ /index.html =404;
		}
		
		# 2: /server matches with what was configured in the environment.ts baseUrl
		location /server/ {
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-Host $host:$server_port;
			proxy_set_header X-Forwarded-Server $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
			
			# 3: This uses the upstream (step 1): So this becomes: http://<host>:<port>/<context>/server/$and_the_rest_of_it;
			# Change the <context> part to the context root of the runtime. This is 'Runtime' if you installed Blueriq with the installer.
			proxy_pass http://revProxyRuntime/<context>$request_uri;
			
			proxy_cookie_path /<context> /;
		}

	}
}

External Theme

When your custom theme is configured as a external theme in the Development Tools Component, you should add an additional location block for /runtime calls. Without doing so the development toolbar is unable to retrieve session information from the runtime when you start a project using the your custom theme as the external theme. More information about configuring external themes can be found here

nginx.conf
error_log logs/error.log;

events {
	worker_connections 1024;
}

http {
	upstream revProxyRuntime {
		# 1: Replace <host>:<port>, example: my.runtime.local:8080
		...
	}
	
	server {
		include ../conf/mime.types;
		listen 1337;

		# Instruct the browser to always verify that its cache is up-to-date
		add_header Cache-Control 'max-age=0, must-revalidate';

		location / {
			...
		}
		
		# 2: /server matches with what was configured in the environment.ts baseUrl
		location /server/ {
			...
		}

		# 3: /runtime matches Runtime calls which are made by the development toolbar
		location /runtime/ {
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-Host $host:$server_port;
			proxy_set_header X-Forwarded-Server $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
			
			proxy_pass http://revProxyRuntime$request_uri;
			
			proxy_cookie_path /Runtime /;
		}

	}
}

  • No labels

2 Comments

  1. Sometimes the frontend is build with the following baseUrl `/runtime`, but `/runtime/server/` is expected. The following rewrite rule will rewrite this 
    `rewrite ^/runtime/(?!server)(.*)$ /runtime/server/$1 break;`

    Note how it only rewrites if it did not already contain `/server`

  2. If the frontend is on a aws s3 bucket, you can also add a S3ReverseProxyServlet: 

    package nl.nn.blueriq.reverseproxy;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.concurrent.TimeUnit;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import com.amazonaws.ClientConfiguration;
    import com.amazonaws.Protocol;
    import com.amazonaws.SdkClientException;
    import com.amazonaws.services.s3.AmazonS3;
    import com.amazonaws.services.s3.AmazonS3ClientBuilder;
    import com.amazonaws.services.s3.model.GetObjectRequest;
    import com.amazonaws.services.s3.model.Region;
    import com.amazonaws.services.s3.model.S3Object;
    import com.amazonaws.services.s3.model.S3ObjectInputStream;

    public class S3ReverseProxyServlet extends HttpServlet
    {
        private static final String REGION_ID = Region.EU_Ireland.getFirstRegionId();
        protected String bucketName = "mybucket";

        private AmazonS3 s3Client;

        @Override
        public String getServletInfo()
        {
            return "s3 reverse proxy";
        }

        @Override
        public void init()
            throws ServletException
        {
            ClientConfiguration config = new ClientConfiguration();
            config.setProtocol(Protocol.HTTPS);
            config.setRequestTimeout((int) TimeUnit.MINUTES.toMillis(3));
            config.setSocketTimeout((int) TimeUnit.MINUTES.toMillis(3));
            s3Client = AmazonS3ClientBuilder.standard().withRegion(FIRST_REGION_ID).withClientConfiguration(config).build();

       }

        @Override
        public void destroy()
        {
            if (s3Client != null)
                super.destroy();
        }

        @Override
        protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws ServletException, IOException
        {
        String pathInfo = servletRequest.getPathInfo();
        if (pathInfo != null && pathInfo.length() > 1) {
          // Remove starting slash
          String filename = pathInfo.substring(1);
          String mimeType = getServletContext().getMimeType(filename);
          servletResponse.setContentType(mimeType);
          streamS3File(servletResponse.getOutputStream(), bucketName, filename);
          System.out.println("BlueriqReverseProxy handled request:" + pathInfo + "  ContentType: " + mimeType);
        }
        else {
          servletResponse.getOutputStream().println("BlueriqReverseProxy");
        }
        }

        private void streamS3File(final OutputStream output, String bucketName, String key)
        {
            GetObjectRequest req = new GetObjectRequest(bucketName, key);
            S3Object s3Object = s3Client.getObject(req);
            S3ObjectInputStream objectContent = s3Object.getObjectContent();
            try {
                byte[] buffer = new byte[1024 * 10];
                int bytesRead = -1;
                while ((bytesRead = objectContent.read(buffer)) > -1) {
                    output.write(buffer, 0, bytesRead);
                }
            }
            catch (IOException ex) {
                objectContent.abort();
                throw new SdkClientException("Unable to transfer content from Amazon S3 to the output stream", ex);
            }
            finally {
                try {
                    objectContent.close();
                }
                catch (IOException ex) {
                }
            }
        }
    }