George D
George D

Reputation: 41

Angular 8 and Flask REST API as Docker swarm services CORS request did not succeed

I have a small project that has 3 components:

I am trying to put everything in Docker, have each component as an image and gather everything together in a stack.yaml file that I deploy as a swarm. All of this is just to learn Docker and how everything links.

The problem is I can't get CORS to work. Right now I have the following setup:

Flask

The following I feel are the important parts of my app.py:

from flask_cors import CORS, cross_origin

app = Flask(__name__)
CORS(app, resources={r"/the_library/*": {"origins": "*", 'methods': 'POST'}})

@app.route('/the_library/upload', methods=["POST"])
def upload_file():
   # here I am using request.files['document'] to get a file and I return a JSON

@app.route("/the_library/find", methods=["POST"])
def find_files():
   # this returns a JSON

if __name__ == '__main__':
    app.run(host='library_api', debug=True, port=5000)

As you can see I added flask_cors and enabled CORS. I also added the following, hoping that it would do something (from my understanding, the following lines and flask_cors both do the same thing):

@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
    return response

Angular 8

The web app is served by nginx. This is the Dockerfile:

FROM node:12.6 as builder

# set working directory
WORKDIR /app

# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package.json package-lock.json /app/
RUN cd /app && npm install
RUN npm install -g @angular/cli

# add app
COPY . /app

# start app
RUN cd /app && npm run build

FROM nginx:1.17.8
RUN rm -rf /usr/share/nginx/html/*
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/dist/TheLibrary/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

The web app loads (everything that is static), but the developer console in Firefox throws Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://library_api:5000/the_library/find. (Reason: CORS request did not succeed). when trying to call the API.

Also here is nginx.conf:

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    server {
        listen 80;
        server_name  library_frontend;

        root   /usr/share/nginx/html;
        index  index.html index.htm;
        include /etc/nginx/mime.types;

        gzip on;
        gzip_min_length 1000;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        location / {
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                #
                # Custom headers and headers various browsers *should* be OK with but aren't
                #
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                #
                # Tell client that this pre-flight info is valid for 20 days
                #
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain; charset=utf-8';
                add_header 'Content-Length' 0;
                return 204;
            }
            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
            try_files $uri $uri/ /index.html;
        }

        location ~ /library_api/ {
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                #
                # Custom headers and headers various browsers *should* be OK with but aren't
                #
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                #
                # Tell client that this pre-flight info is valid for 20 days
                #
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain; charset=utf-8';
                add_header 'Content-Length' 0;
                return 204;
            }
            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
        }
    }
}

As you can see I tried to enable CORS on 2 locations, but I am not very sure this is the way, or if I even need nginx to get this working, I would be perfectly happy to get this working without nginx. I was just trying different things to solve the CORS problem and ended up following some tutorials that used nginx.

This is the stack.yaml file that links everything together:

version: '3.3'

services:
  api:
    image: library_api
    hostname: library_api
    networks:
      - api_network

  frontend:
    image: library_frontend
    hostname: frontend
    ports:
      - 8080:80
    networks:
      - api_network

networks:
  api_network:
    driver: overlay

Things I tried

I entered the Docker services with docker exec -it <container> bash and pinged the services that should reach each other and they are fine.

I tried to also forward the API to the host machine by adding

ports:
  - 5000:5000

to stack.yaml, but it didn't change anything.

I got everything working outside Docker just by adding CORS to my Flask API, but I am getting something wrong in Docker and I just don't get what.

Upvotes: 0

Views: 442

Answers (1)

George D
George D

Reputation: 41

I eventually found the problem, poor understanding of how Angular serves the app. I thought Angular has a backend that does the API calls, but in reality, the code gets compiled and served as sort of a static website and as such, my API calls end up being AJAX calls done from the client side.

The problem was I used docker_api_hostname:port as the base URL for my API calls in the Angular app, instead I should've used localhost:port and forward the port used by the API to the host as explained in the Things I tried section of my question.

Upvotes: 1

Related Questions