Skip to content

Commit

Permalink
feat: fallback to loading page when SSR is taking too long (#1511)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhhyi committed Sep 25, 2023
1 parent a7aa858 commit 82a3956
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 0 deletions.
30 changes: 30 additions & 0 deletions docs/guides/nginx-startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ kb_sync_latest_only

# Building and Running NGINX Docker Image

- [Building and Running NGINX Docker Image](#building-and-running-nginx-docker-image)
- [Building](#building)
- [Configuration](#configuration)
- [HTTPS or SSL](#https-or-ssl)
- [Basic Auth](#basic-auth)
- [Multi-Site](#multi-site)
- [Ignore Parameters During Caching](#ignore-parameters-during-caching)
- [Access ICM Sitemap](#access-icm-sitemap)
- [Override Identity Providers by Path](#override-identity-providers-by-path)
- [Other](#other)
- [Features](#features)
- [Cache](#cache)
- [Loading Fallback](#loading-fallback)
- [Further References](#further-references)

We provide a Docker image based on [nginx](https://nginx.org/) for the [PWA deployment](../concepts/pwa-building-blocks.md#pwa---nginx).

## Building
Expand Down Expand Up @@ -163,6 +178,21 @@ If the cache feature is switched off, all caching for pre-rendered pages is disa
The cache duration for pre-rendered pages can be customized using `CACHE_DURATION_NGINX_OK` (for successful responses) and `CACHE_DURATION_NGINX_NF` (for 404 responses).
The value supplied must be in the `time` format that is supported by [nginx proxy_cache_valid](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_valid).

### Loading Fallback

When encountering high loads on the SSR container, the nginx container can be configured to serve a loading page after a certain timeout.
This way the user will get a fast response that can be completed by the browser using the ICM REST API.
Meanwhile the nginx container will cache the page in the background after it finishes rendering.The fully rendered page will be served on the next request.

Requests from Crawlers will bypass this mechanism and will be served directly from the SSR container with potentially longer response durations.

To activate this feature, set the environment variable `LOADING_FALLBACK` to `"on"`.
The timeout can be configured using the environment variable `LOADING_FALLBACK_TIMEOUT` using a time format (i.e. `40s`, `15m`, ...).

> :warning: It is not recommended to use this feature by default, as it will not solve the underlying issue.
> In most cases the SSR container should be scaled out dynamically to handle intermittent high loads.
> It might also be worth investigating default render times and do some code optimizations to get faster SSR responses.

## Further References

- [Concept - Multi-Site Handling](../concepts/multi-site-handling.md)
Expand Down
3 changes: 3 additions & 0 deletions nginx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FROM nginx:1.22
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y apache2-utils libnss3-tools
COPY *.conf /etc/nginx/
COPY loading-fallback.sh.tmpl /loading-fallback.sh.tmpl
COPY features /etc/nginx/features/
COPY templates /etc/nginx/templates/
COPY docker-entrypoint.d/*.sh /docker-entrypoint.d/
Expand All @@ -21,5 +22,7 @@ ENV CACHE_DURATION_NGINX_OK=10m
ENV CACHE_DURATION_NGINX_NF=60m
ENV LOGFORMAT=main
ENV LOG_ALL=on
ENV LOADING_FALLBACK=off
ENV LOADING_FALLBACK_TIMEOUT=10s

EXPOSE 80 443 9113
3 changes: 3 additions & 0 deletions nginx/docker-entrypoint.d/40-gomplate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ then
fi

/gomplate -d "domains=$MULTI_CHANNEL_SOURCE" -d "overrideIdentityProviders=$OVERRIDE_IDENTITY_PROVIDERS_SOURCE" -d "cachingIgnoreParams=$CACHING_IGNORE_PARAMS_SOURCE" -d 'ipwhitelist=env:///BASIC_AUTH_IP_WHITELIST?type=application/yaml' --input-dir="/etc/nginx/templates" --output-map='/etc/nginx/conf.d/{{ .in | strings.ReplaceAll ".conf.tmpl" ".conf" }}'

/gomplate -d "domains=$MULTI_CHANNEL_SOURCE" -f /loading-fallback.sh.tmpl -o /loading-fallback.sh
chmod +x /loading-fallback.sh
5 changes: 5 additions & 0 deletions nginx/docker-entrypoint.d/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ if env | grep -iqE "^PROMETHEUS=(on|1|true|yes)$"
then
(sleep 5 && /nginx-prometheus-exporter)&
fi

if env | grep -iqE "^LOADING_FALLBACK=(on|1|true|yes)$"
then
(sleep 5 && /loading-fallback.sh)&
fi
27 changes: 27 additions & 0 deletions nginx/loading-fallback.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{{- define "clean-domain" -}}
{{ . | strings.ReplaceAll ".+" "_" | strings.ReplaceAll ".*" "_" }}
{{- end -}}
#!/bin/sh

mkdir -p /tmp/loading

fetch() {
target="/tmp/loading/$1/$2.html"
mkdir -p "$(dirname "$target")"
curl -s -o "$target" -H "Host: $1" "http://localhost:8080$2"
}

while true
do
{{- range $domain, $mapping := (ds "domains") }}
{{- $cdomain := tmpl.Exec "clean-domain" $domain -}}
{{- if $mapping | test.IsKind "map" }}
fetch "{{ $cdomain }}" "/loading"
{{- else }}
{{- range $mapping }}
fetch "{{ $cdomain }}" "{{ .baseHref }}/loading"
{{- end }}
{{- end }}
{{- end }}
sleep 300
done
1 change: 1 addition & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ http {
include /etc/nginx/conf.d/features.conf;

include /etc/nginx/conf.d/multi-channel.conf;
include /etc/nginx/conf.d/loading-fallback.conf;
}
53 changes: 53 additions & 0 deletions nginx/templates/loading-fallback.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{{- define "clean-domain" -}}
{{ . | strings.ReplaceAll ".+" "_" | strings.ReplaceAll ".*" "_" }}
{{- end -}}

{{ if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }}
upstream real_server {
server localhost:8080 fail_timeout=0;
keepalive 15;
}

{{- range $domain, $mapping := (ds "domains") }}
server {
listen 80;
server_name ~^{{ $domain }}$;
proxy_set_header Host $http_host;
proxy_cache_bypass true;

# https://mailman.nginx.org/pipermail/nginx/2007-May/001031.html
if ($is_bot) {
rewrite ^(.*)$ /bot-bypass$1;
}
location /bot-bypass {
rewrite ^/bot-bypass(.*)$ $1 break;
proxy_pass http://real_server;
}

{{ if (has $mapping "channel") }}
location / {
proxy_pass http://real_server;
proxy_read_timeout {{ getenv "LOADING_FALLBACK_TIMEOUT" }};
error_page 504 =200 /loading.html;
}
location = /loading.html {
root /tmp/loading/{{ tmpl.Exec "clean-domain" $domain }};
}
{{- else -}}
{{ range $mapping }}
location {{ .baseHref }} {
proxy_pass http://real_server;
proxy_read_timeout {{ getenv "LOADING_FALLBACK_TIMEOUT" }};
error_page 504 =200 {{ .baseHref }}/loading.html;
}
location = {{ .baseHref }}/loading.html {
root /tmp/loading/{{ tmpl.Exec "clean-domain" $domain }};
}
{{ end }}
location / {
proxy_pass http://real_server;
}
{{ end }}
{{ end }}
}
{{ end }}
6 changes: 6 additions & 0 deletions nginx/templates/multi-channel.conf.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@

rewrite '^(.*)$' '$1$default_rewrite_params' break;

{{ if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }}
proxy_ignore_client_abort on;
{{ end }}

proxy_pass {{ getenv "UPSTREAM_PWA" }};
proxy_buffer_size 128k;
proxy_buffers 4 256k;
Expand Down Expand Up @@ -91,6 +95,8 @@ server {

# https://ma.ttias.be/force-redirect-http-https-custom-port-nginx/
error_page 497 https://$http_host$request_uri;
{{ else if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }}
listen 8080;
{{ else }}
listen 80;
{{- end }}
Expand Down

0 comments on commit 82a3956

Please sign in to comment.