javascriptpythonapinext.jsfalconframework

[Python][Javascript] can't figure out how to send API calls from a NEXTjs webpage GUI to a Python Falcon backend


I'm trying to send POST requests from a NEXTjs frontend with a simple form field to a backend located on the same server which is a python script using the Falcon library. The python script itself is ran by Gunicorn and listens on the 8080 port.

Both codes run pretty well without errors but when I try to submit the form all I get is a 415 error which seems to indicate that what I'm trying to send to the API is not a supported media type but, as pointed out in this answer

Falcon has out of the box support for requests with Content-Type: application/json

Since the webpage and the server are hosted on the same VPS I've also tried to use the 127.0.0.1 address in the fetch call but that was unsuccessful as well (the backend API didn't even responded in fact)

Here's the backend code:

#!/usr/bin/env python
# coding=utf-8


import time
import falcon
import json


class Resource(object):

    def on_post(self, req, resp, **kwargs):
        request_body = req.media

        print('POST Request: {}'.format(req))
        print('Request body: {}'.format(request_body))

        start = time.time()

        resp.body = json.dumps({
            'count_identical_pairs': count_identical_pairs(request_body),
            'computation_time': int((time.time() - start) * 1000)
        })


def count_identical_pairs(integers_array):
    total = 0
    count = dict()

    # Type checking
    if not isinstance(integers_array, list):
        return -1

    # Check if N is within the range [0..100,000]
    if len(integers_array) > 100000:
        return -2

    for integer in integers_array:

        # Check if each element of the array is within the range [−1,000,000,000..1,000,000,000]
        if integer not in range(-1000000000, 1000000000):
            return -3

        if str(integer) not in count:
            count[str(integer)] = 1
        else:
            count[str(integer)] += 1

    for key, value in count.items():
        total += value * (value - 1) / 2

    return total


api = application = falcon.API()

api.add_route('/count_identical_pairs', Resource())

And here's the frontend one:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

class Index extends React.Component {
    constructor() {
        super();
        this.state = {
            input_array: [],
        };
        this.onSubmit = this.onSubmit.bind(this);
        this.myHeaders = new Headers();
    }

    onChange = evt => {
        // This triggers everytime the input is changed
        this.setState({
            [evt.target.name]: evt.target.value,
        });
    };

    onSubmit = evt => {
        evt.preventDefault();
        console.log('this.state.input_array = ' + this.state.input_array);
        console.log('JSON.stringify(this.state.input_array) = ' + JSON.stringify(this.state.input_array));
        // Making a post request with the fetch API
        // Test payload [1, 7, 7, 5, 7, 5, 6, 1]
        fetch('http://vps638342.ovh.net:8080/count_identical_pairs', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json; charset=utf-8'
            },
            mode: 'no-cors',    // Security hazard?
            body: JSON.stringify(this.state.input_array),
            redirect: 'follow'
        })
        .then(response => response.text())
        .then(data => console.log('Data: ' + data))
        .catch(error => console.log('Error: ' + error))
    };

    render() {
        return (
            <form onSubmit={this.onSubmit} >
                <input
                    name="input_array"
                    type="text"
                    id="name"
                    value={this.state.input_array}
                    onChange={this.onChange}>
                </input>
                <input type="submit" />
            </form>
        );
    };
}

ReactDOM.render(<Index />, document.getElementById("root"));

EDIT 1: I've tested the python backend API with Postman and I can see that it works pretty well already as you can see pictured here:

postman screenshot EDIT 2: Thanks to @Maku here's the update code on the backend that allows all origins, methods and header. I'm new to server development but I'm guessing it's not a very secure way to code but at least it works (I'll add a third EDIT if I find a more recommended way to do this)


Solution

  • Enable CORS in your falcon server and remove the 'no-cors' flag in your javascript, that worked for me the other day.
    https://github.com/lwcolton/falcon-cors should work for you. To test it you could just allow all origins with something like this (I'm using another python framework so I haven't tested this exact falcon extension)

    cors = CORS(allow_all_origins=True, allow_all_headers=True)
    
    api = falcon.API(middleware=[cors.middleware])
    
    

    Edit: added allow_all_headers=True like discussed in the comments.