Imagine company ABC has two teams developing two SPA apps: app1, app2
each app has its own index.html and associated static assets, for example:
build/
index.html
main.js
This is run from: host1. app follows the same conventions
We would like:
abc.com/app1 to route to host1
abc.com/app2 to route to host2
Say there is a load balancer setup to perform the routing properly.
What solution do I have for this kind of situation?
I tried to use https://github.com/zeit/serve (suggested by create-react-app) but there are tons of problems.
First, rewritePath feature does not work (entirely fails to do anything useful)
Second, I tried to put my static assets 1 layer deeper on the host, the request host1/app1 is a directory listing rather than the index.html page
Even after solving these issues through configs, there are still a ton of issues with React Router (SPA router) and authentication callbacks
What is the actual best practice for this scenario? I imagine it is a very common scenario. As I can see AWS's web console uses a similar approach for routing apps
Basically you want a reverse-proxy like nginx to server two different sites at different URIs. The SPAs should not know about each other, because it should not concern them how they are reached, it's the job of the reverse proxy.
Basic config for nginx:
server {
listen 80;
listen [::]:80;
server_name abc.com;
location /app1 {
rewrite /app1(/?(.*)) /$2 break;
proxy_pass http://localhost:XXXX;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
location /app2 {
rewrite /app2(/?(.*)) /$2 break;
proxy_pass http://localhost:YYYY;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
}
Of course this config misses important stuff like ssl, gzip, caching and so on, but it should give you and idea.
In this example, the SPAs could run in two different docker containers, reachable by ports XXXX and YYYY.
Hope this helps,
lgG
What you’re looking for is a reverse proxy. any modern web server can do this including Nginx or Apache. The trick of it will be getting your rewrite routes just right which is what I suspect you’re running into with React.
Related
I am new to docker and container concepts, I want to host a react website xyz.com with a container at port:3000 and I want to add Admin subdirectory into it like xyz.com/Admin in which main website(xyz.com) is one container and /Admin must be another one (Total 2 containers). Please help me how can I figure this out (like changes in Dockerfile, code or in docker-compose).
What you need for this is a reverse proxy. The reverse proxy will stand in front of the two web applications, and map the appropriate paths to the appropriate containers.
I have a simple example here for you, using docker-compose and nginx. It starts three nginx containers, one acting as the proxy, and the two others just acting as your web applications (root and admin)
Project structure:
/simple-proxy-two-websites
├── docker-compose.yml
├── default.conf
In the docker-compose definition, we map the config into the reverse-proxy and map the listening port (80) to the host machine port 80. And then we just set up two default nginx containers to act as the web applications that we want to serve.
docker-compose.yml
version: "3"
services:
reverse-proxy:
image: nginx
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- "web-admin"
- "web-root"
ports:
- 80:80
web-root:
image: nginx
web-admin:
image: nginx
In the nginx reverse-proxy server configuration (taken from the default config that is shipped with the docker image, with all commented lines removed) we then add location /admin and change location / as seen below. Notice that the proxy_pass parameter is a URL that uses the servicename defined in the docker-compose definition above. Docker is making this easy, when the containers are on the same network (in this case the default bridge network), by allowing us to use the service names.
default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://web-root:80/;
}
location /admin {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://web-admin:80/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
With this configuration the nginx reverse-proxy will do an internal network forward of the request to the proxy_pass destination defined in the /location - the destination does not have to be reachable from the outside.
you can take this example and update it with your servicenames, and specific ports - it should get you going.
I also have a complete example, where I override the default index.html pages of the web-admin and web-root containers, to verify that the correct destination has been reached. Let me know if you want that, then I will make it available in a repository on GitHub.
I'm trying to show users visiting a wildcard subdomain a subfolder:
abc.example.com -> example.com/xyz
This NGINX server block code is working:
server {
# server name with regexp
server_name ~^(?<sub>[^.]+)\.example\.com$;
# this server catches all requests to xxxx.example.com
# and put "xxxx" to $sub variable
location / {
# finally we want to request different URI from remote server
proxy_pass http://localhost:8000;
# proxy_redirect will rewrite Location: header from backend
# or you can leave proxy_redirect off;
proxy_redirect http://localhost:8000 http://$sub.localhost:8000;
}
[certbot code]
}
(found in question 5249883).
But when replacing proxy_pass value "https://localhost:8000" with "https:localhost:8000/xyz", I get these errors and a blank page:
Uncaught SyntaxError: Unexpected token '<'
in both socket.io.js and commons.js.
The app I'm running on example.com is built with React/Gatsby. example.com/demo is working.
EDIT: I put the wrong error messages, those errors appeared when I tried something different.
The problem was (as I understand it now), that Gatsby hosts scripts at example.com/[script-address] and using NGINX proxy_pass, the script address is also changed to example.com/[subfolder]/[script-address].
The solution to this is to set the "path-prefix" value in gatsby.config as explained here: Gatsby documentation.
With doing that, I set a prefix for my complete application, which is not really what I want to do, as the main application is still hosted on example.com, I only want the subdomains to get passed to some subpages. (The subdomains are user created and served dynamically by the main application).
Surprisingly, both (main application and subdomains) work after changing the path prefix.
This seems only to work for the production build (you have to pass a flag when building), so I'm currently still not sure what to do while developing.
If you have an idea how I could solve that better, please message me :)
Thanks to Ivan and Richard for putting me on the right track!
EDIT: Asset prefixed would be the better way: https://www.gatsbyjs.org/docs/asset-prefix/
It's still ugly and I think there's a way to solve this via NGINX.
I still can't use the development build this way.
EDIT 2:
After I've been messing with this for 3 days now, I've again tried to find a similar question & got lucky: https://serverfault.com/questions/840654/nginx-map-subdomain-to-a-subdirectory-on-proxied-server
I've changed my code to:
location / {
proxy_pass http://localhost:8000/xyz$uri/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
and it finally works :)
I have a Loopback App, which provides the API, and serves the static files. And 2 React apps which use the same Loopback App.
I serve the static files (from React Build folders) using serve-static and vhost.
var serveSubdomainAbc = serveStatic('abc/build', {
index: ['index.html']
});
var serveSubdomainXyz = serveStatic('./../../xyz-abc/build', {
index: ['index.html']
});
app.use(vhost('abc.example.com', serveSubdomainAbc));
app.use(vhost('xyz-abc.example.com', serveSubdomainXyz));
I also setup an nginx server with very basic settings.
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
When I directly visit the root of the apps, everything works fine, like
abc.example.com
xyz-abc.example.com
index.html is served, and then the react app starts fine. When I use a router link like,
xyz-abc.example.com/ssoRedirect, then it cannot find the ssoRedirect and it falls back to abc.example.com's index.html and serves that file, so ssoRedirect does not work. How can I make it to fallback to the correct index.html when it cannot find the required static file.
I'm running SailsJS on a digitalocean droplet (MEAN Stack with nginx). All my requests are mapped to my Angular frontend except those on /api which are mapped to a proxy_pass on port 1337 (on which Sails runs). This procedure works fine.
Now I'd like to restrict the access to my API to only allow requests from my frontend. I already tried to deny / allow from within my nginx config but this blocks the the user request itself. I tried several answers like this as well but they didn't work out.
What would be the recommended way to limit access to my Sails API to localhost? I'd like to run multiple apps on my droplet and use Sails as my API that should only be accessible by the apps in my droplet.
My nginx config:
upstream sails_server {
server 127.0.0.1:1337;
keepalive 64;
}
server {
server_name domain.com;
index index.html;
location / {
root /opt/domain/build;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
client_max_body_size 500M;
}
}
– Thanks in advance!
I think you can't do this because angular runs in your client, so you need to get IP from all you users. You can use something simple that works with trustes proxys
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
or use some more complex and trusted like link
I have an app where the admin side is built in angular, but the front consumer facing side is not.. and lives on a different server.
I'd like if visitors coming to:
domain.com/#/admin => angular app
But if they got to:
domain.com
This goes to the other server
Is this possible?
Thanks.
For a little more clarity, I think what I need is this:
I have a rails app as an api that angular consumes. Rails lives on server A and Angular lives on server B.
Both servers use nginx as the web server.
Also, the rails app serves up its own content, so I want any path other than admin to go to rails.
What I want is if users go to:
mydomain.com --> they hit the rails app and content on server A
when they go to:
mydomain.com/admin or mydomain.com/#/admin --> they hit the angular app on server B
I think I almost figured it out with the following nginx configs on the rails server, server A:
upstream serverA {
server rails;
server unix:///var/www/rails/shared/tmp/sockets/puma.sock;
}
upstream serverB {
server angular;
}
server {
listen 80;
server_name serverA;
root /var/www/rails/current/public;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://serverA;
}
location ~* ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
location /admin/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://serverB/#/;
}
}
While this gets me close, my angular scripts and styles that are references by my angular index.html file are not being found.