2

I have a simple Flask API:

from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/add/<params>', methods = ['GET'])
def add_numbers(params):
    #params is expected to be a dictionary: {'x': 1, 'y':2}
    params = eval(params)
    return jsonify({'sum': params['x'] + params['y']})

if __name__ == '__main__':
    app.run(debug=True)

Now, I want to call this method from Java and extract the result. I have tried using java.net.URL and java.net.HttpURLConnection;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;


public class MyClass {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://127.0.0.1:5000/add/{'x':100, 'y':1}");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/json");

            if (conn.getResponseCode() != 200) {
                throw new RuntimeException("Failed : HTTP error code : "
                        + conn.getResponseCode());
            }

            BufferedReader br = new BufferedReader(new InputStreamReader(
                    (conn.getInputStream())));

            String output;
            System.out.println("Output from Server .... \n");
            while ((output = br.readLine()) != null) {
                System.out.println(output);
            }

            conn.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        }catch (IOException e){
e.printStackTrace();
        }
    }
}

But it doesn't work. In the flask server I get an error message:

code 400, message Bad request syntax ("GET /add/{'x':100, 'y':1} HTTP/1.1")

"GET /add/{'x':100, 'y':1} HTTP/1.1" HTTPStatus.BAD_REQUEST -

and in Java code, I get the error:

Exception in thread "main" java.lang.RuntimeException: Failed : HTTP error code : -1 at MyClass.main(MyClass.java:17)

What am I doing wrong?

My final aim is to pass dictionary objects to my python function and return the response of the function to java. The dictionary can contain text values of over thousand words. How can I achieve this?

Edit

Based on the comments and the answers, I have updated my Flask code to avoid using eval and for better design:

@app.route('/add/', methods = ['GET'])
def add_numbers():
    params = {'x': int(request.args['x']), 'y': int(request.args['y']), 'text': request.args['text']}
    print(params['text'])
    return jsonify({'sum': params['x'] + params['y']})

Now my Url is: "http://127.0.0.1:5000/add?x=100&y=12&text='Test'"

Is this better?

Mohit Motwani
  • 4,662
  • 3
  • 17
  • 45
  • 1
    You probably need to replace the spaces in the URL with `%20` (or just get rid of them all together). However, I must caution you against using `eval()` on un-sanitized user input like this. I get it if it's just for testing purposes, but you'd be better off to redesign your URL design so that you can just pass in plain integers, rather than a full python `dict`. – TallChuck Sep 18 '19 at 13:57
  • @TallChuck Wow you're right. Removing the space actually returned the response. Thank you so much! I'm a bit new to this. Can you suggest how do I really redesign my URL? Should I use a post method and pass parameters in request body? – Mohit Motwani Sep 18 '19 at 14:01
  • @MohitMotwani If you need to use GET then my solution is the correct way to implement backend. I will also provide a POST solution if you prefer (I would suggest POST) – Pitto Sep 18 '19 at 14:22
  • Yes @Pitto. That would be very useful too. At the end, I want to pass large text(over 1000 words) to this api and return a response. Also make these requests over a million times – Mohit Motwani Sep 18 '19 at 14:24
  • @MohitMotwani I do understand what you say but on SO you should ask a specific question and receive a specific answer. You cannot change the question and then expect that people write more and more answers for the same question. – Pitto Sep 18 '19 at 14:40
  • HI @MohitMotwani ! Did you have a moment to read my answer? If you found it useful please consider upvoting it and / or choosing it as final answer for your question. Thanks! – Pitto Sep 19 '19 at 07:32
  • 1
    Yes @Pitto. I've been making the changes you've suggested to my answer. I'm using java to post requests and I'm not super familiar with it, so it's taking longer to verify. Also, I've already upvoted it! – Mohit Motwani Sep 19 '19 at 07:34
  • Thanks @MohitMotwani! Let me know if any extra help or clarification is needed, I'll be happy to update my answer. – Pitto Sep 19 '19 at 07:37
  • @Pitto Also, to answer your previous comment, you're right about asking specific questions. My question was specific initially but when I received feedback about other aspects about my code, I seeked to clarify. – Mohit Motwani Sep 19 '19 at 08:10

2 Answers2

3

As from @TallChuck's comment above, you need to replace or remove spaces in the URL

URL url = new URL("http://127.0.0.1:5000/add?x=100&y=12&text='Test'");

I would suggest to make use of a request object to retrieve parameters in your GET call.

The Request Object

To access the incoming data in Flask, you have to use the request object. The request object holds all incoming data from the request, which includes the mimetype, referrer, IP address, raw data, HTTP method, and headers, among other things. Although all the information the request object holds can be useful we'll only focus on the data that is normally directly supplied by the caller of our endpoint.

As mentioned in the comments to post large amounts of paramters and data, A more appropriate implementation for this task would be probably using the POST method.

Here's an example about the same implementation for POST in the backend:

from flask import Flask, jsonify, request
import json

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/add/', methods = ['POST'])
def add_numbers():
    if request.method == 'POST':
        decoded_data = request.data.decode('utf-8')
        params = json.loads(decoded_data)
        return jsonify({'sum': params['x'] + params['y']})

if __name__ == '__main__':
    app.run(debug=True)

Here's a simple way to test the POST backend using cURL:

 curl -d '{"x":5, "y":10}' -H "Content-Type: application/json" -X POST http://localhost:5000/add

Using Java to post the request:

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class PostClass {
    public static void main(String args[]){
        HttpURLConnection conn = null;
        DataOutputStream os = null;
        try{
            URL url = new URL("http://127.0.0.1:5000/add/"); //important to add the trailing slash after add
            String[] inputData = {"{\"x\": 5, \"y\": 8, \"text\":\"random text\"}",
                    "{\"x\":5, \"y\":14, \"text\":\"testing\"}"};
            for(String input: inputData){
                byte[] postData = input.getBytes(StandardCharsets.UTF_8);
                conn = (HttpURLConnection) url.openConnection();
                conn.setDoOutput(true);
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty( "charset", "utf-8");
                conn.setRequestProperty("Content-Length", Integer.toString(input.length()));
                os = new DataOutputStream(conn.getOutputStream());
                os.write(postData);
                os.flush();

                if (conn.getResponseCode() != 200) {
                    throw new RuntimeException("Failed : HTTP error code : "
                            + conn.getResponseCode());
                }

                BufferedReader br = new BufferedReader(new InputStreamReader(
                        (conn.getInputStream())));

                String output;
                System.out.println("Output from Server .... \n");
                while ((output = br.readLine()) != null) {
                    System.out.println(output);
                }
                conn.disconnect();
            }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }catch (IOException e){
        e.printStackTrace();
    }finally
        {
            if(conn != null)
            {
                conn.disconnect();
            }
        }
    }
}
Mohit Motwani
  • 4,662
  • 3
  • 17
  • 45
Pitto
  • 8,229
  • 3
  • 42
  • 51
  • Thank you I tried your method of using request arguments using url: `http://127.0.0.1:5000/add?x=100&y=12`. It worked. – Mohit Motwani Sep 18 '19 at 14:12
  • @MohitMotwani I also wrote the POST version of it. – Pitto Sep 18 '19 at 14:41
  • So I've verified and your answer has helped me. I'd like to edit your answer to enter the java code that worked and also few changes, inspired from your feedback, in my python. Do I have your permission? – Mohit Motwani Sep 19 '19 at 07:51
  • Specifically, request.form didn't work for me, but request.data did. From what I have read on other answers, `request. data` `Contains the incoming request data as string in case it came with a mimetype Flask does not handle`. I don't know what data in my request the Flask can't handle. – Mohit Motwani Sep 19 '19 at 07:52
  • Sure thing, @MohitMotwani – Pitto Sep 19 '19 at 07:53
  • It all depends on the data you are sending. I was assuming that you would've sent the data as Json mimetype. In that case my code works. You can verify using the cURL command I've provided with the json part (header) or without it. – Pitto Sep 19 '19 at 07:54
  • 1
    Made the changes! – Mohit Motwani Sep 19 '19 at 08:10
2

Your python code has a serious design flaw, which creates a very dangerous security flaw and (luckily for you, given the presence of the security flaw) is the reason your code is not working.

Putting anything beside a simple string in the URL is a bad practice, because:

  1. URLs are supposed to be addresses, and semantically it makes little sense in using them as data carrier
  2. It usually requires messy code to generate and read (in your example, you are forced to use eval, which is extremely dangerous, to parse the request)
  3. URL's rules require encoding the characters (the horrible to read %20 and so on)

If you expect a fixed number of parameters, you should use query parameters, otherwise it's probably better to use the request body. Given what your logic is, I think it would be semantically better to use query parameters (so your request will look like /add?x=100&y=1).

As a general rule, eval is your enemy, not your friend, and eval over something sent to you over the network is your nemesis. If you want to find out why it's bad, there is a nice list of examples and explanations in the answers to this question.

frollo
  • 1,296
  • 1
  • 13
  • 29
  • Thank you for your response. Based on your answer, I have made few changes to my code. Can you please tell me what you think about it? At the end, I will be using this API to pass in text of over 1000 words at times. – Mohit Motwani Sep 18 '19 at 14:19
  • If you need to pass over 1000 words at times, you _need_ to move to request object. Query strings are good for small parameters, not for all that data. You might probably have to take a look at a way to paginate those requests. – frollo Sep 18 '19 at 14:23
  • So the changes that I've made, using the requests objects to access parameters, is the right design? – Mohit Motwani Sep 18 '19 at 14:25
  • No, sorry, my bad. Something in flask terminology confuses me. What you should do is put the huge text inside the request _body_, which will make everything easier to write, read and debug. You should note that having a body in a GET request is not exactly standard and many libraries consider it wrong, so you might have to use a POST. – frollo Sep 18 '19 at 14:30
  • 1
    ahh you're right. That's what I've been considering. Thanks a lot @frollo. You've been very helpful. – Mohit Motwani Sep 18 '19 at 14:32