|
| 1 | +# Run behind reverse proxy |
| 2 | + |
| 3 | +Running behind a reverse proxy is a common scenario for web applications. |
| 4 | +That's why there is proper support for this in ASP.NET Core and hence in MORYX. |
| 5 | +There is ample documentation for it by [Microsoft](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balance). |
| 6 | + |
| 7 | +Depending on your reverse proxy setup there are different levels of awareness needed in your app. |
| 8 | + |
| 9 | +A common use case for reverse proxies is to allow multiple web applications to share the same server and port. |
| 10 | +This can be done in one of two ways. Either by hostname based routing or by path based routing. |
| 11 | + |
| 12 | +## Setup |
| 13 | +If you want to follow along with the examples you can use this docker-compose file together with the nginx configuration examples to setup a reverse proxy and modify the StartProject.Asp project to see if everything works as expected. |
| 14 | +The nginx configuration needs to be placed in a file named `nginx.conf` next to your `docker-compose.yml` when executing `docker compose up`. |
| 15 | +```yaml |
| 16 | +services: |
| 17 | + nginx-proxy: |
| 18 | + image: nginx:latest |
| 19 | + ports: |
| 20 | + - "8080:8080" |
| 21 | + volumes: |
| 22 | + # you can mount ./nginx-stripping.conf instead. To test the case where nginx strips the |
| 23 | + # prefix instead of removing it in asp.net |
| 24 | + - ./nginx.conf:/etc/nginx/nginx.conf:ro |
| 25 | + network_mode: host |
| 26 | +``` |
| 27 | +
|
| 28 | +
|
| 29 | +
|
| 30 | +## Host based routing |
| 31 | +An example for host based routing would be to have a server `example.com` with two subdomains `app1.example.com` and `app2.example.com`. |
| 32 | +This is very convenient for us, because it "just works" from our perspective as an app developer. |
| 33 | +The disadvantage is the required infrastructure. We need DNS Entries for the subdomains that point to our server and if we server our content over HTTPs we need either a wildcard certificate for our subdomain or a certificate that explicitly lists all our applications. |
| 34 | + |
| 35 | +## Path based routing |
| 36 | +That's why it might be more interesting to use path based routing. In this case we only have one hostname `example.com` and use the route after it to distinguish the different apps, like example.com/app1 and example.com/app2. |
| 37 | +There are two different ways to handle this scenario in our reverse proxy. The reverse proxy can either strip this prefix for passing the request along to our application or it can leave it as is. In both cases we need to enable support in our app. |
| 38 | + |
| 39 | + |
| 40 | +### Changes to the frontend code |
| 41 | +Regardless of the specific method, we need support not only in our backend, but also in our frontend applications. This support is already available in all MORYX Modules that are in this repository, but for your own UIs we need a few simple steps. |
| 42 | + |
| 43 | +In the razor pages we usually mostly reference our script files and style sheets. It's important to not use an absolute path, but to start with a `~` instead. |
| 44 | +This is shorthand for the razor engine to automatically include the correct path. This is also required for your module eventstream. |
| 45 | + |
| 46 | +```html |
| 47 | +<!--absolute--> |
| 48 | +<script src="/_content/<YourC#ProjectName>/main.js" type="module"></script> |
| 49 | +
|
| 50 | +<!-- base path aware --> |
| 51 | +<script src="~/_content/<YourC#ProjectName>/main.js" type="module"></script> |
| 52 | +
|
| 53 | +<!-- Example eventstream annotation from VisualInstructions.cshtml -- > |
| 54 | +[ModuleEventStream("~/api/moryx/instructions/stream")] |
| 55 | +``` |
| 56 | + |
| 57 | +Additionally we need to make sure the API Clients in the JS/TS Applications don't try to access the wrong endpoint. |
| 58 | +We usally do this using the environment for angular applications and we have a helper function in the @moryx/ngx-web-framework/environments package for this purpose. This computes the correct path for the APIs from your module path. |
| 59 | + |
| 60 | +```typescript |
| 61 | +// environment.prod.ts |
| 62 | +
|
| 63 | +import { getPathBase } from '@moryx/ngx-web-framework/environments'; |
| 64 | +
|
| 65 | +let path_base = getPathBase("/<Your Module Route>"); |
| 66 | +
|
| 67 | +
|
| 68 | +export const environment = { |
| 69 | + production: true, |
| 70 | + assets: path_base + "/_content/<YourC#ProjectName>/", |
| 71 | + rootUrl: path_base, |
| 72 | +}; |
| 73 | +
|
| 74 | +``` |
| 75 | + |
| 76 | +### Stripping proxy |
| 77 | + |
| 78 | +If the proxy strips the prefix, it should set Headers to inform our application what the original request path was. An example configuration for nginx could look like this: |
| 79 | +```conf |
| 80 | +events {} |
| 81 | +# make sure "PathBase" is either empty or not present in appsettings |
| 82 | +http { |
| 83 | + server { |
| 84 | + listen 8080; |
| 85 | + server_name example.com; # Replace with your domain or IP |
| 86 | +
|
| 87 | + location /test/ { |
| 88 | + proxy_pass https://127.0.0.1:5000/; |
| 89 | + proxy_set_header Host $host; |
| 90 | + proxy_set_header X-Real-IP $remote_addr; |
| 91 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 92 | + proxy_set_header X-Forwarded-Prefix /test; |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +In this case we need to instruct Asp.Net to use these headers using the ForwardedHeaders Middleware like so. |
| 99 | + |
| 100 | + |
| 101 | +```csharp |
| 102 | +// Startup.cs or Program.cs before mapping static files, razor pages and so on. |
| 103 | +app.UseForwardedHeaders(new ForwardedHeadersOptions() |
| 104 | +{ |
| 105 | + ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.All |
| 106 | +}); |
| 107 | +``` |
| 108 | + |
| 109 | +### Route without modifying the request |
| 110 | + |
| 111 | +If the proxy does not strip the prefix, we can use the `UsePathBase` middleware by adding before adding our routing. |
| 112 | + |
| 113 | +```csharp |
| 114 | +app.UsePathBase(requiredPathBase); |
| 115 | +``` |
| 116 | + |
| 117 | +An example nginx.conf for this case: |
| 118 | +```conf |
| 119 | +events {} |
| 120 | +# make sure appsettings contains "PathBase": "/test" |
| 121 | +# this setup does only work, if docker runs on the same host as the moryx app. |
| 122 | +# running the app in windows and the container in WSL *won't* work |
| 123 | +http { |
| 124 | + server { |
| 125 | + listen 8080; |
| 126 | + server_name example.com; # Replace with your domain or IP |
| 127 | +
|
| 128 | + location /test/ { |
| 129 | + proxy_pass https://127.0.0.1:5000/test; |
| 130 | + proxy_set_header Host $host; |
| 131 | + proxy_set_header X-Real-IP $remote_addr; |
| 132 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 133 | + } |
| 134 | + } |
| 135 | +} |
| 136 | +
|
| 137 | +``` |
0 commit comments