1

I'm working with an nginx instance to server html page when the server is down but serve json reponse when you try to call the API. So I have the following nginx config,

error_page 502 503 504 /static/webpack-bundles/5xx.html;

location /api/v1/* {
    internal;
    add_header 'Content-Type' 'application/json charset=UTF-8';

    error_page 502 '{"error": {"status_code": 502,"status": "Bad Gateway"}}';
}

But whenever I try to send a request to /api/v1/users via curl I get the HTML source code in response.

3
  • Is there any reason why you marke the location as internal? Do you have another location (without internal) and proxy to it? Can you share the error, access log and the curl output pls. Commented Apr 23, 2020 at 23:08
  • What is location /api/v1/* supposed to do? Nginx does not use shell wildcard characters in location statements. See this document. Commented Apr 24, 2020 at 6:23
  • The curl output is just the HTML source code for 502 HTML page. But for APIs, we want to serve JSON output instead of the HTML page. The /api/v1/* is meant to mark any API request, for eg- hostname.com/api/v1/users or hostname.com/api/v1/user/id Commented Apr 24, 2020 at 18:41

1 Answer 1

3

In my case, Nginx was responding with the index.html source file thanks to not configuring reverse-proxying properly. In such case, the response returns http 200 (ok) perfectly, but there is practically "no server home" (so to speak) to return the expected json response.

So, the fix is simply to setup reverse-proxying in the Nginx.conf file. Here is an excellent resource that explains how Nginx proxying works: https://www.digitalocean.com/community/tutorials/understanding-nginx-http-proxying-load-balancing-buffering-and-caching

For example, for a React app with a backend dependency on an API (all deployed on Kubernetes, say), the .conf file can be like:

server {
        listen 80;
        
        location / {        
            root  /usr/share/nginx/html;
            index  index.html index.htm;    
            try_files $uri $uri/ /index.html =404;
        }
        
        location /foo {
            proxy_pass http://bar-service.default.svc.cluster.local:8080;
        }

Note the reverse-proxying being enabled here by proxy_pass, in this (example) case to a Kubernetes API service, using its fully-qualified DNS name (with namespace as "default" and port included). Also note that /foo here represents your API server's URL in the frontend. In React it could be implemented like:

export const getFoo = () => {
  const api = '/foo'
  return sendRequest('GET', api);
};

The React app's build can be Dockerised for Nginx's proxying something like:

FROM nginx:alpine
COPY build/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

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

With such you can now setup, for example, a Kubernetes Ingress service using a manifest like:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress
spec:
  rules:
  - host: my-host.com
    http:
      paths:
      - path: /*
        backend:
          serviceName: frontend-service
          servicePort: 80

The Ingress controller in this case can forward all traffic to the frontend React app via a NodePort service like:

apiVersion: v1
kind: Service
metadata:                     
  name: frontend-service
spec:
  type: NodePort      
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: react-frontend

.. with its Deployment manifest as:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-app
spec:
  selector:
    matchLabels:
      app: react-frontend
  template:
    metadata:
      labels:
        app: react-frontend
    spec:
      containers:
        - name: app-container
          image: my-react-app-image
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80

Finally, the backend service that Nginx passes API traffic to can be like (and note the service name):

apiVersion: v1
kind: Service
metadata:
  name: bar-service
spec:
  selector:
    app: api
  ports:
  - port: 8080
    targetPort: 8080

.. with its Deployment manifest say as:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api-container
          image: my-api-container-image
          imagePullPolicy: IfNotPresent
          ports:
          - name: http
            containerPort: 8080
Sign up to request clarification or add additional context in comments.

Comments

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.