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?
2 Answers 2
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();
}
}
}
}
7 Comments
http://127.0.0.1:5000/add?x=100&y=12. It worked.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.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:
- URLs are supposed to be addresses, and semantically it makes little sense in using them as data carrier
- 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) - URL's rules require encoding the characters (the horrible to read
%20and 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.
%20(or just get rid of them all together). However, I must caution you against usingeval()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 pythondict.