Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GET http://api:1337/games net::ERR_NAME_NOT_RESOLVED for nuxt.js pages using asyncData

I have somewhat complicated setup with docker. Everything's working as expected except I have this weird problem. Visiting index page or /pages/_id pages I have no errors. But when I try to open /other-page it crashes. All are using the same API url.

Error found in the console when opening /other-page:

GET http://api:1337/games net::ERR_NAME_NOT_RESOLVED

Not sure what to do, any suggestions?

nuxt.config.js

  axios: {
    baseURL: 'http://api:1337'
  },

docker-compose.yml

version: '3'

services:
  api:
    build: .
    image: strapi/strapi
    environment:
      - APP_NAME=strapi-app
      - DATABASE_CLIENT=mongo
      - DATABASE_HOST=db
      - DATABASE_PORT=27017
      - DATABASE_NAME=strapi
      - DATABASE_USERNAME=
      - DATABASE_PASSWORD=
      - DATABASE_SSL=false
      - DATABASE_AUTHENTICATION_DATABASE=strapi
      - HOST=api
      - NODE_ENV=production
    ports:
      - 1337:1337
    volumes:
      - ./strapi-app:/usr/src/api/strapi-app
      #- /usr/src/api/strapi-app/node_modules
    depends_on:
      - db
    restart: always
    links:
      - db

  nuxt:
    # build: ./app/
    image: "registry.gitlab.com/username/package:latest"
    container_name: nuxt
    restart: always
    ports:
      - "3000:3000"
    links:
      - api:api
    command:
      "npm run start"


  nginx:
    image: nginx:1.14.2
    expose:
      - 80
    container_name: nginx
    restart: always
    ports:
      - "80:80"
    volumes:
      - ./nginx:/etc/nginx/conf.d
    depends_on:
      - nuxt
    links:
      - nuxt

index.vue

...
  async asyncData({ store, $axios }) {
    const games = await $axios.$get('/games')
    store.commit('games/emptyList')
    games.forEach(game => {
      store.commit('games/add', {
        id: game.id || game._id,
        ...game
      })
    })
    return { games }
  },
...

page.vue

...
  async asyncData({ store, $axios }) {
    const games = await $axios.$get('/games')
    store.commit('games/emptyList')
    games.forEach(game => {
      store.commit('games/add', {
        id: game.id || game._id,
        ...game
      })
    })
    return { games }
  },
...

Nginx conf

upstream webserver {
  ip_hash;
  server nuxt:3000;
}

server {
  listen 80;
  access_log off;
  connection_pool_size 512k;
  large_client_header_buffers 4 512k;

  location / {
    proxy_pass http://webserver;
    proxy_http_version 1.1;
    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_max_temp_file_size 0;
  }

UPDATE:

Tried what Thomasleveil suggested. Now I'm receiving following error:

nuxt | [2:09:35 PM] Error: connect ECONNREFUSED 127.0.0.1:80

So, it seems like now /api is being forwarded to 127.0.0.1:80. Not sure why ^^

nuxt.config.js

  axios: {
    baseURL: '/api'
  },
  server: {
    proxyTable: {
      '/api': {
         target: 'http://localhost:1337',
         changeOrigin: true,
         pathRewrite: {
           "^/api": ""
         }
      }
    }
  }

docker-compose.yml

version: '3'

services:
  reverse-proxy:
    image: traefik # The official Traefik docker image
    command: --api --docker # Enables the web UI and tells Traefik to listen to docker
    ports:
      - "80:80"     # The HTTP port
      - "8080:8080" # The Web UI (enabled by --api)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock # listen to the Docker events
    networks:
      - mynet

  api:
    build: .
    image: strapi/strapi
    container_name: api
    environment:
      - APP_NAME=strapi-app
      - DATABASE_CLIENT=mongo
      - DATABASE_HOST=db
      - DATABASE_PORT=27017
      - DATABASE_NAME=strapi
      - DATABASE_USERNAME=
      - DATABASE_PASSWORD=
      - DATABASE_SSL=false
      - DATABASE_AUTHENTICATION_DATABASE=strapi
      - HOST=api
      - NODE_ENV=development
    ports:
      - 1337:1337
    volumes:
      - ./strapi-app:/usr/src/api/strapi-app
      #- /usr/src/api/strapi-app/node_modules
    depends_on:
      - db
    restart: always
    networks:
      - mynet
    labels:
      - "traefik.backend=api"
      - "traefik.docker.network=mynet"
      - "traefik.frontend.rule=Host:example.com;PathPrefixStrip:/api"
      - "traefik.port=1337"

  db:
    image: mongo
    environment:
      - MONGO_INITDB_DATABASE=strapi
    ports:
      - 27017:27017
    volumes:
      - ./db:/data/db
    restart: always
    networks:
      - mynet

  nuxt:
    # build: ./app/
    image: "registry.gitlab.com/username/package:latest"
    container_name: nuxt
    restart: always
    ports:
      - "3000:3000"
    command:
      "npm run start"
    networks:
      - mynet
    labels:
      - "traefik.backend=nuxt"
      - "traefik.frontend.rule=Host:example.com;PathPrefixStrip:/"
      - "traefik.docker.network=web"
      - "traefik.port=3000"

networks:
  mynet:
    external: true
like image 692
emirowski Avatar asked Mar 10 '19 16:03

emirowski


1 Answers

Visiting index page or /pages/_id pages I have no errors. But when I try to open /other-page it crashes.

To reformulate:

  • I have a main page at / that shows some links targeting pages at /pages/_id (where _id is a valid game id)
  • When I open / or /pages/_id, the content shows up
  • But if I click a link from page / targeting /pages/xxx (where xxx is a valid id), I got an error
  • Furthermore if I refresh the page, I then see the content and not the error
  • content for those pages comes from an api server. Pages are supposed to fetch the content by calling the api server and then render the page contents with the response.

What's happening here?

AsyncData

The way asyncData works in nuxt.js is the following:

on first page load

  1. the user enters the url http://yourserver/pages/123 in its browser
  2. the nuxt web server handles the request, resolve the route and mount the vue component for that page
  3. the asyncData method from the vue component is called from the nuxt.js server side
  4. the nuxt.js server (not the user browser) then fetch the content by making different call to http://api:1337/games/123, receive the response and the content renders.

when the user clicks a link for another page

Something a bit different happens now.

  1. The user is still on the page http://api:1337/games/123 which has a link to the main page listing all the games (http://yourserver/) and click it.
  2. the browser does not load any new page. Instead, the user browser makes an ajax call to http://api:1337/games to try to fetch the new content. And fails due to a name resolution error

Why?

This is a feature brought to you by nuxt.js to speed up page content loading time. from the documentation, the important bit of information is:

asyncData is called every time before loading the page component. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes.

  • server-side means the call is made from the nuxt server to the api server
  • client-side means the call is made from the user browser to the api server

Now the fun part:

  • the nuxt server is running in a first container
  • the api server is running in a second container and is listening on port 1337
  • from the nuxt container, the url for calling the api server is http://api:1337/, and this works fine
  • from the user browser, calling http://api:1337 fails (net::ERR_NAME_NOT_RESOLVED) because the user computer does not know how to translate the domain name api to an IP address. And even if it could, this IP Address would be unreachable anyway.

What can be done?

  • You need to set up a reverse proxy that will forward requests made by the user browsers to url starting with http://yourserver/api/ to the api container on port 1337.
  • And you need to configure nuxt.js so that links to the api made client-side (from the user browser) use the url http://yourserver/api instead of http://api:1337/
  • And you need to configure nuxt.js so that it keeps calling http://api:1337 for calls made server-side.

adding a reverse proxy for calls made from nuxt (server-side)

Since you are using the nuxt.js Axios module to make calls to the api container, you are half way there.

The Axios module has a proxy option that can be set to true in nuxtjs.config.js

Bellow is an example of setting up a reverse proxy for your project using Traefik, but the documentation state that the proxy is incompatible with the usage of the baseURL option. The prefix option must be used instead.

Your nuxt.config.js should then look like this:

  axios: {
    prefix: '/api',
    proxy: true
  }, 
  proxy: {
    '/api/': {
      target: 'http://localhost:1337',
      pathRewrite: {
        '^/api/': ''
      }
    }
  },

This works fine from your development computer, where if strapi is running and responding at http://localhost:1337. But this won't work in a container because we there need to replace http://localhost:1337 with http://api:1337. To do so, we can introduce an environment variable (STRAPI_URL):

  axios: {
    prefix: '/api',
    proxy: true
  }, 
  proxy: {
    '/api/': {
      target: process.env.STRAPI_URL || 'http://localhost:1337',
      pathRewrite: {
        '^/api/': ''
      }
    }
  },

We will later set the STRAPI_URL in the docker-compose.yml file.

adding a reverse proxy for calls made from the user browser (client-side)

Since I gave up on implementing reverse proxies with nginx when using docker, here's an example with Traefik:

docker-compose.yml:

version: '3'

services:
  reverseproxy:  # see https://docs.traefik.io/#the-traefik-quickstart-using-docker
    image: traefik:1.7
    command: --docker
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  api:
    image: strapi/strapi
    environment:
      - ...
    expose:
      - 1337
    labels:
      traefik.frontend.rule: PathPrefixStrip:/api
      traefik.port: 1337

  nuxt:
    image: ...
    expose:
      - 3000
    command:
      "npm run start"
    labels:
      traefik.frontend.rule: PathPrefixStrip:/
      traefik.port: 3000

Now all HTTP requests made by the user browser to http://yourserver will be handled by the Traefik reverse proxy.

Traefik will configure forwarding rules by looking at labels starting with traefik. on the nuxt and api containers.

What changed?

You now have 2 reverse proxies:

  • one for server-side requests (the nuxt.js Proxy module)
  • one for client-side requests (Traefik)

It's not done yet

We now need to instruct the nuxt.js Proxy module that it must forward requests to http://api:1337/. We are going to use the STRAPI_URL environment variable for that.

And we need to instruct nuxt Axios module that the user browser must call the api on http://yourserver/api. This is done with the API_URL_BROWSER environment variable.


All together

nuxt.config.js

  axios: {
    prefix: '/api',
    proxy: true
  }, 
  proxy: {
    '/api/': {
      target: process.env.STRAPI_URL || 'http://localhost:1337',
      pathRewrite: {
        '^/api/': ''
      }
    }
  },

docker-compose.yml

version: '3'

services:
  reverseproxy:  # see https://docs.traefik.io/#the-traefik-quickstart-using-docker
    image: traefik:1.7
    command: --docker
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  api:
    image: strapi/strapi
    environment:
      - ...
    expose:
      - 1337
    labels:
      traefik.frontend.rule: PathPrefixStrip:/api
      traefik.port: 1337

  nuxt:
    image: ...
    expose:
      - 3000
    command:
      "npm run start"
    environment:
      NUXT_HOST: 0.0.0.0
      STRAPI_URL: http://api:1337/
      API_URL_BROWSER: /api
    labels:
      traefik.frontend.rule: PathPrefixStrip:/
      traefik.port: 3000

like image 184
Thomasleveil Avatar answered Nov 15 '22 07:11

Thomasleveil