0

I'm trying to include the React part of a simple CRUD React/Spring-boot application into Docker. It works fine when neither side is in Docker. But when I create a docker file from the front end and run it, I get the initial login page, but after that, it's the default white label error page with a 404 code.

The network activity shows it's trying to make a call to http://localhost:808/private, which looks like it may be the redirect to the error page.

I'm thinking the problem may be that the back end needs to be in the container as well, which is the ultimate plan. But I'm not sure if that's the problem. It seems like an app in a container should be able to make an API call to the outside of the container.

Here's the nginx.conf file:

# auto detects a good number of processes to run
worker_processes auto;

#Provides the configuration file context in which the directives that affect connection processing are specified.
events {
    # Sets the maximum number of simultaneous connections that can be opened by a worker process.
    worker_connections 8000;
    # Tells the worker to accept multiple connections at a time
    multi_accept on;
}


http {
    # what times to include
    include       /etc/nginx/mime.types;
    # what is the default one
    default_type  application/octet-stream;

    # Sets the path, format, and configuration for a buffered log write
    log_format compression '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $upstream_addr '
        '"$http_referer" "$http_user_agent"';

    server {
        # listen on port 80
        listen 80;

        # save logs here
        access_log /var/log/nginx/access.log compression;

        # nginx root directory
        root /var/www;

        # what file to server as index
        index index.html index.htm;

        location / {
            # First attempt to serve request as file, then
            # as directory, then fall back to redirecting to index.html
            try_files $uri $uri/ /index.html;
        }

        # Media: images, icons, video, audio, HTC
        location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
          expires 1M;
          access_log off;
          add_header Cache-Control "public";
        }

        # Javascript and CSS files
        location ~* \.(?:css|js)$ {
            try_files $uri =404;
            expires 1y;
            access_log off;
            add_header Cache-Control "public";
        }

        # Any route containing a file extension (e.g. /devicesfile.js)
        location ~ ^.+\..+$ {
            try_files $uri =404;
        }
    }
}

Edit: more files

DockerFile:

#### Stage 1: Build the react application
FROM node:12.4.0-alpine as build

# Configure the main working directory inside the docker image. 
# This is the base directory used in any further RUN, COPY, and ENTRYPOINT 
# commands.
WORKDIR /app

# Copy the package.json as well as the package-lock.json and install 
# the dependencies. This is a separate step so the dependencies 
# will be cached unless changes to one of those two files 
# are made.
COPY package.json package-lock.json ./
RUN yarn

# Copy the main application
COPY . ./

# Arguments
ARG REACT_APP_API_BASE_URL
ENV REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL}

# Build the application
RUN yarn build

#### Stage 2: Serve the React application from Nginx 
FROM nginx:1.17.0-alpine

# Copy the react build from Stage 1
COPY --from=build /app/build /var/www

# Copy our custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf

# Expose port 3000 to the Docker host, so we can access it 
# from the outside.
EXPOSE 80

ENTRYPOINT ["nginx","-g","daemon off;"]

App.js

import React, { Component } from 'react';
import './App.css';
import Home from './Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import LicenseList from './LicenseList';
import LicenseEdit from './LicenseEdit';
import { CookiesProvider } from 'react-cookie';

class App extends Component {
  render() {
    return (
      <CookiesProvider>
        <Router>
          <Switch>
            <Route path='/' exact={true} component={Home}/>
            <Route path='/licenses' exact={true} component={LicenseList}/>
            <Route path='/licenses/:id' component={LicenseEdit}/>
          </Switch>
        </Router>
      </CookiesProvider>
    )
  }
}

export default App;

Home.js

import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
import { withCookies } from 'react-cookie';

class Home extends Component {
  state = {
    isLoading: true,
    isAuthenticated: false,
    user: undefined
  };

  constructor(props) {
    super(props);
    const {cookies} = props;
    this.state.csrfToken = cookies.get('XSRF-TOKEN');
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
  }

  async componentDidMount() {
    const response = await fetch('/api/user', {credentials: 'include'});
    const body = await response.text();
    if (body === '') {
      this.setState(({isAuthenticated: false}))
    } else {
      this.setState({isAuthenticated: true, user: JSON.parse(body)})
    }
  }

  login() {
    let port = (window.location.port ? ':' + window.location.port : '');
    if (port === ':3000') {
      port = ':8080';
    }
    window.location.href = '//' + window.location.hostname + port + '/private';
  }

  logout() {
    fetch('/api/logout', {method: 'POST', credentials: 'include',
      headers: {'X-XSRF-TOKEN': this.state.csrfToken}}).then(res => res.json())
      .then(response => {
        window.location.href = response.logoutUrl + "?id_token_hint=" +
          response.idToken + "&post_logout_redirect_uri=" + window.location.origin;
      });
  }

  render() {
    const message = this.state.user ?
      <h2>Welcome, {this.state.user.name}!</h2> :
      <p>Please log in to manage Analytics Licenses</p>;

    const button = this.state.isAuthenticated ?
      <div>
        <Button color="link"><Link to="/licenses">Manage Analytics Licenses</Link></Button>
        <br/>
        <Button color="link" onClick={this.logout}>Logout</Button>
      </div> :
      <Button color="primary" onClick={this.login}>Login</Button>;

    return (
      <div>
        <AppNavbar/>
        <Container fluid>
          {message}
          {button}
        </Container>
      </div>
    );
  }
}

export default withCookies(Home);

Any ideas/thoughts are most welcome.

2
  • Can you share the docker file? Commented Apr 7, 2020 at 4:54
  • @MjZac - I've add that and some of the React code. Commented Apr 7, 2020 at 11:35

1 Answer 1

1

It looks like you have nginx running in a container bound to port 3000 in localhost and API server is running at port 8080. Since both are running at separate ports, it is similar to websites running at different domains. The api call made from react app should contain full url of the api server. ie, await fetch('/api/user',... is not enough. It should instead be await fetch('http://localhost:8080/api/user',....

You can make use of environment variable to switch the api host while building react app.

const API_HOST = process.env.REACT_APP_API_HOST || '';

class Home extends Component {
...

Then your subsequent api calls can be made like:

fetch(API_HOST + '/api/user', ... 

You just need to set the env variable REACT_APP_API_HOST to http://localhost:8080 while building the react application.

PS: You also need to make use of ajax calls for login instead of just replacing the href attribute.

Sign up to request clarification or add additional context in comments.

3 Comments

This looks great. I will try it later on when I get some time.
I think the problem may be a CORS error. Not sure why it doesn't manifest on the display till after I hit login, but it immediately shows up in the console (then gets cleared out). I guess that would makes sense. Because the way I have it set up makes Docker a de-facto proxy server, and it's probably not passing along the CORS header, maybe.
Since spring boot app is not running in a container, just enabling CORS in the backend should fix any CORS error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.