NGINX 设置以匹配代理中间件配置

NGINX Setup to Match Proxy Middleware Configuration

我有以下 setupProxy.js class 为 api 调用我的服务器配置重定向。

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
    app.use(
        "/api/tours",
        createProxyMiddleware({
            target: "http://localhost:5000", 
            changeOrigin: true,
        })
    );
};

我的App.js长得像

const App = () => {

    const { userDetails } = useContext(AuthContext);
    const { colorMode } = useContext(ColorModeContext);

    let currentTheme = React.useMemo(() =>
        createTheme(deepmerge(getDesignTokens(colorMode)), getThemedComponents(colorMode)),
        [colorMode]
    );
    currentTheme = responsiveFontSizes(currentTheme);
 
    return (
        <ThemeProvider theme={currentTheme}>
            <CssBaseline />
            <Router>
                <Switch>
                    <AuthRoute exact path="/" component={HomePage} />
                    <AuthRoute path="/home" component={HomePage} />
                    <Route path="/public/:id" component={PlayerPage} />
                    <AuthRoute path="/tours/:id" component={PlayerPage} />
                    <Route path="/login">
                        {userDetails == null ? <LoginPage /> : <Redirect to="/home" />}
                    </Route>
                    <Route component={FourOhFour} />
                </Switch>
            </Router>
        </ThemeProvider>
    );
};
export default App;

我可以使用 url 之类的

导航到我的 PlayerPage

localhost:3000/tours/eef67wsrr899iop009

这加载正常,PlayerPage 看起来像

const PlayerPage = () => {

    const history = useHistory();

    const { id } = useParams();
    const mode = LocalStorgaeCache.getItem(APP_COLOR_MODE_KEY);
    const theme = createTheme(deepmerge(getDesignTokens(mode), getThemedComponents(mode)));
    
    const { userDetails } = useContext(AuthContext);
    const { tourDetails, isLoading: isLoadingTour, dispatch } = useContext(TourContext);

    const [loading, setLoading] = useState(true);
    const [showTagInfo, setTagId] = useState(0);
    const [tagInfoWindowVisible, showTagInfoWindow] = useState(false);
    const [results] = useState([]);
    const [setTour] = useState();
    const showTagging = useRef(false);
    const [showScan, setShowScan] = useState(false);
    const [addTag, setAddTag] = useState(false);
    const [tagging, setTagging] = useState(false);
    const [myPos, setPos] = useState([]);

    const myLayer = useRef(0);
    const tagEditing = useRef(false);
    const refreshTag = useRef(false);
    const [myTagNumber, setTagNumber] = useState(0);

    const [showPosInfo, setPosInfo] = useState('');
    const [snackMessage, setSnackMessage] = useState('');
    const [isMobile, setIsMobile] = useState(false);

    const [myTags, setTags] = useState([{}]);
    const [watermark, setWatermark] = useState();

    useEffect(() => {
        return () => {
            dispatch(clearLoadedTour());
        };
    }, []);

    useEffect(() => {
        setIsMobile(navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i));

        const actualLoad = async () => {

            if (userDetails == null) { 
                history.push("/login");
            }
            await getTour(userDetails, id, dispatch);
            setLoading(false);
        };

        if (tourDetails == null) {
            actualLoad();
        }
        else {
            const simulateLoad = async () => {
                await new Promise((r) => setTimeout(r, 1000));
                setLoading(false);
            };
            simulateLoad();
        }
    }, [dispatch, history, id, tourDetails, userDetails]);

    return (
        <ThemeProvider theme={theme}>
            <Fragment>
                <Backdrop
                    sx={{ color: "white", zIndex: (theme) => theme.zIndex.drawer + 1 }}
                    open={loading}>
                    <CircularProgress color="inherit" />
                </Backdrop>
                { tourDetails != null ? (
                <div>
                    {!watermark && <Watermark />}
                    <SimpleSnackBar snackMessage={snackMessage} />
                    <InfoCard
                        tagEditing={tagEditing}
                        setTagging={setTagging}
                        setShow={showTagInfoWindow}
                        refreshTag={refreshTag}
                        setTour={setTour}
                        setShowScan={setShowScan}
                        results={results}
                        tagNumber={myTagNumber}
                        tags={myTags}
                        tour={tourDetails?.MyappTour}
                        pos={myPos}
                        setAddTag={setAddTag}
                        cb={showPosInfo}
                        st={showTagInfo}
                    />
                    {tagInfoWindowVisible && (
                        <MediaCard
                            tagEditing={tagEditing}
                            tagId={showTagInfo}
                            setShow={showTagInfoWindow}
                        />
                    )}
                    <Player
                        token={tourDetails?.azureServiceSasToken}
                        tour={tourDetails?.MyappTour}
                        account={tourDetails?.azureStorageUri}
                        setSnackMessage={setSnackMessage}
                        layer={myLayer}
                        tagging2={tagging}
                        refreshTag={refreshTag}
                        setTagId={setTagId}
                        showTagInfoWindow={showTagInfoWindow}
                        setPosInfo={setPosInfo}
                        setLoading={setLoading}
                        setNewTagPosition={setPos}
                        tagEditing={tagEditing}
                    />
                </div>
                ) : ( <></> ) }
            </Fragment>
        </ThemeProvider>
    );
};
export default PlayerPage;

问题是,当我使用 NGINX 作为反向代理和负载平衡器部署此客户端和服务器时,

myapp.com/tours/eef67wsrr899iop009

不起作用,它只是给出一个空白页面,浏览器控制台 window 正在显示

Uncaught SyntaxError: Unexpected token '<' Manifest: Line: 1, column: 1, Syntax error.

我的manifest.json文件是

{
    "short_name": "Myapp",
    "name": "Myapp by vrpm",
    "icons": [
        {
            "src": "favicon.ico",
            "sizes": "64x64 32x32 24x24 16x16",
            "type": "image/x-icon"
        }
    ],
    "start_url": ".",
    "display": "standalone",
    "theme_color": "#000000",
    "background_color": "#ffffff"
}

我的index.html是

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Myapp</title>
        <meta charset="utf-8" />
        <meta name="theme-color" content="#000000" />
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1"
        />
        <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    </head>
    <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
    </body>
</html>

NGINX 配置文件如下所示

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    upstream loadbalancer {
        server server:5000 weight=3;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        port_in_redirect off;
        absolute_redirect off;

        return 301 https://$host$request_uri;
    }

    server {
        listen [::]:443 ssl;
        listen 443 ssl;

        server_name myapp.app* myapp.co* myapp-dev.uksouth.azurecontainer.io* localhost*;
        error_page 497 https://$host:$server_port$request_uri;

        error_log /var/log/nginx/client-proxy-error.log;
        access_log /var/log/nginx/client-proxy-access.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/certificate.crt;
        ssl_certificate_key /etc/nginx/private.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

         location / {
            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;
            try_files $uri $uri/ /index.html;
        }

         location /api/auth {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }
        
        location /api/tours {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }
    }
}

在本地,此配置工作正常,没有迹象表明我的 manifest.json 或 index.html 文件有任何问题。所以问题我觉得一定是:

如何修改我的 NGINX 代理以允许使用 url myapp.com/tours/<some-id> 通过客户端直接路由?


编辑。请注意,在我的 AuthRoute 中,我使用以下代码记录请求路径

import { useContext } from "react";
import { AuthContext } from "../context/auth/authContext";
import { Route, Redirect } from "react-router-dom";

const AuthRoute = ({ component: Component, ...rest }) => {
    const authContext = useContext(AuthContext);
    const { isAuthenticated, isAuthorizing } = authContext;
    
    console.log(`Navigation request for ${window.location.href}`);

    return (
        <Route
            {...rest}
            render={props =>
                !isAuthenticated && !isAuthorizing ? (<Redirect to="/login" />) : (<Component {...props} />)
            }
        />
    );
};
export default AuthRoute;

在本地,这会打印出请求的路线并导航到;所以

Navigation request for https://localhost:3000/tours/3r4et66ksop093jsn

在 Azure 上部署时,这些消息永远不会显示,这表明 NGINX 正在干预。


我已经实施了建议的更改,我的 .config 现在看起来像

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    upstream loadbalancer {
        server server:5000 weight=3;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        port_in_redirect off;
        absolute_redirect off;

        return 301 https://$host$request_uri;
    }

    server {
        listen [::]:443 ssl;
        listen 443 ssl;

        server_name viewform.app* viewform.co* viewform-dev.uksouth.azurecontainer.io* localhost*;
        error_page 497 https://$host:$server_port$request_uri;

        error_log /var/log/nginx/client-proxy-error.log;
        access_log /var/log/nginx/client-proxy-access.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/certificate.crt;
        ssl_certificate_key /etc/nginx/private.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

        location /api/auth {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }
        
        location /api/tours {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://loadbalancer;
        }

        location / {
            try_files $uri /index.html;
        }
    }
}

现在当我导航到 dev.myapp/public/tours/ 我得到一个空白页面,如下所示

带有以下警告

Uncaught SyntaxError: Unexpected token '<' manifest.json:1 Manifest: Line: 1, column: 1, Syntax error.

我的index.html和manifest.json在上面

因为 nginx 不知道由 react-router 处理的 tours 路径你可以告诉 nginx 将所有请求重新路由到 / 根路由然后传递请求到 index.html 或者您可以添加正则表达式路径并将所有请求路由到 nginx.conf 中的 / 根位置。由于现在 / 根位置正在处理所有其他请求,因此可能不会传递到其他位置,因此最好将当前路由移动到 nginx.conf.

中的静态位置下方

下面是如何将所有请求路由到 index.html 文件 nginx.conf

...
location /api/auth {
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_pass http://loadbalancer;
}

location /api/tours {
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_pass http://loadbalancer;
}

location / {
     try_files $uri /index.html; # change as below
}
...

OP 编辑​​:在我的package.json“主页”:“。”中,需要是“主页”:“/”。这就是导致路由失败的原因 - 偷偷摸摸的。希望这对其他人有帮助。