代码之家  ›  专栏  ›  技术社区  ›  YPCrumble

服务器呈现的React ExpressJS前端泄漏用户的Redux存储数据

  •  5
  • YPCrumble  · 技术社区  · 6 年前

    我有一个ExpressJS服务器,它有时在初始呈现时呈现错误的用户数据。请参阅下面的(稍微简化的)版本。

    index.ejs 文件经常在 reduxState ...

    我的困惑是因为我希望电话 import { store } from 'routes.js' {} 根据每个用户的要求。问题似乎是 store 正在成为网站上每个用户的组合存储。

    如何确保每个用户在站点上只看到他们的数据?

    // src/routes.js
    import React from 'react';
    import { createStore, applyMiddleware, compose } from "redux";
    import routerConfig from "base/routes/routes";
    import thunk from "redux-thunk";
    import { rootReducer } from "base/reducers";
    
    let initialState = {};
    
    const store = createStore(
      rootReducer, initialState, compose(applyMiddleware(thunk))
    );
    
    const routes = routerConfig(store);
    
    export {store};
    export default routes;
    

    服务器.js

    import { store } from 'routes';
    
    let getReduxPromise = (renderProps, request) => {
      let store = require('./routes/index.jsx').store
      let { query, params } = renderProps
    
      let comp = renderProps.components[renderProps.components.length - 1];
    
      let at = null;
    
      if (request && request.cookies && request.cookies.accessToken) {
        at = request.cookies.accessToken
      }
    
      if (comp.fetchData) {
        return comp.fetchData({ query, params, store, at }).then(response => {
          if (request) {
            if (request.cookies && request.cookies.accessToken && request.cookies.userInfo) {
              store.dispatch(
                actions.auth(request.cookies.userInfo),
                request.cookies.accessToken
              )
            } else {
              store.dispatch(actions.logout())
            }
    
          }
          return Promise.resolve({response, state: store.getState()})
        });
      } else {
        return Promise.resolve();
      }
    }
    
    app.get('*', (request, response) => {
      let htmlFilePath = path.resolve('build/index.html' );
      // let htmlFilePath = path.join(__dirname, '/build', 'index.html');
      let error = () => response.status(404).send('404 - Page not found');
      fs.readFile(htmlFilePath, 'utf8', (err, htmlData) => {
        if (err) {
          console.log('error 1')
          error();
        } else {
          match({routes, location: request.url}, (err, redirect, renderProps) => {
            if (err) {
              console.log('error 2')
              error();
            } else if (redirect) {
              return response.redirect(302, redirect.pathname + redirect.search)
            } else if (renderProps) {
              let parseUrl = request.url.split('/');
    
              if (request.url.startsWith('/')) {
                parseUrl = request.url.replace('/', '').split('/');
              }
    
              // User has a cookie, use this to help figure out where to send them.
              if (request.cookies.userInfo) {
                const userInfo = request.cookies.userInfo
    
                if (parseUrl[0] && parseUrl[0] === 'profile' && userInfo) {
                  // Redirect the user to their proper profile.
                  if (renderProps.params['id'].toString() !== userInfo.id.toString()) {
                    parseUrl[1] = userInfo.id.toString();
                    const url = '/' + parseUrl.join('/');
                    return response.redirect(url);
                  }
                }
              }
    
              getReduxPromise(renderProps, request).then((initialData) => {
                let generatedContent = initialData.response ? render(request, renderProps, initialData.response) : render(request, renderProps, {});
    
                const title = initialData.response.seo.title || '';
                const description = initialData.response.seo.description || '';
    
                var draft = [];
    
                const currentState =  initialData.state;
    
                if (currentState) {
                  const reduxState = JSON.stringify(currentState, function(key, value) {
                    if (typeof value === 'object' && value !== null) {
                      if (draft.indexOf(value) !== -1) {
                        // Circular reference found, discard key
                        return;
                      }
                      // Store value in our collection
                      draft.push(value);
                    }
                    return value;
                  });
                  draft = null;
    
                  ejs.renderFile(
                    path.resolve('./src/index.ejs' ),
                    {
                      jsFile,
                      cssFile,
                      production,
                      generatedContent,
                      reduxState,
                      title,
                      description
                    }, {},
                    function(err, str) {
                      if (err) {
                        console.log('error 3')
                        console.log(err);
                      }
                      response.status(200).send(str);
                    });
                } else {
                  console.log('error 4')
                  console.log(err)
                  error();
                }
    
              }).catch(err => {
                console.log('error 5')
                console.log(err)
                error();
              });
    
            } else {
              console.log('error 6')
              console.log(err)
              error();
            }
          });
        }
      })
    });
    

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title><%- title %></title>
        <meta name="description" content="<%- description %>"/>
        <link href="<%- cssFile %>" rel="stylesheet"/>
        <script type="text/javascript" charset="utf-8">
          window.__REDUX_STATE__ = <%- reduxState %>;
        </script>
      </head>
      <body>
        <div id="root"><%- generatedContent %></div>
        <script type="text/javascript" src="<%- jsFile %>" defer></script>
      </body>
    </html>
    

    例子 fetchData 在反应组件中的功能

    ExamplePage.fetchData = function (options) {
      const { store, params, at } = options
    
      return Promise.all([
        store.dispatch(exampleAction(params.id, ACTION_TYPE, userAccessToken))
      ]).spread(() => {
        let data = {
          seo: {
            title: 'SEO Title',
            description: 'SEO Description'
          }
        }
    
        return Promise.resolve(data)
      })
    }
    
    1 回复  |  直到 6 年前
        1
  •  4
  •   DDS    6 年前

    在模块作用域中定义的变量在整个运行时环境中只有一个副本。这意味着每个node.js进程都有自己的副本,每个浏览器选项卡/框架都有自己的副本。但是,在每个选项卡或每个进程中,只有一个副本。这意味着您不能将存储定义为模块级常量,并且仍然为每个用户有一个新的存储。你可以这样解决:

    src/routes.js公司

    import React from 'react';
    import { createStore, applyMiddleware, compose } from "redux";
    import routerConfig from "base/routes/routes";
    import thunk from "redux-thunk";
    import { rootReducer } from "base/reducers";
    
    let initialState = {};
    
    export function newUserEnv() {
      const store = createStore(
        rootReducer, initialState, compose(applyMiddleware(thunk))
      );
    
      const routes = routerConfig(store);
      return { store, routes };
    }
    

    服务器.js

    import { newUserEnv } from 'routes';
    
    let getReduxPromise = (renderProps, request) => {
      const { store } = newUserEnv();
      let { query, params } = renderProps
    ...
    

    这将为每个请求创建一个新存储,并允许每个用户拥有自己的数据。注意,如果您需要来自不同模块的同一个存储,则需要传递它。你不能只是 import { newUserEnv }