FileNotFoundException while getting the InputStream object from HttpURLConnection
Asked Answered
B

8

126

I am trying to send a post request to a url using HttpURLConnection (for using cUrl in java). The content of the request is xml and at the end point, the application processes the xml and stores a record to the database and then sends back a response in form of xml string. The app is hosted on apache-tomcat locally.

When I execute this code from the terminal, a row gets added to the db as expected. But an exception is thrown as follows while getting the InputStream from the connection

java.io.FileNotFoundException: http://localhost:8080/myapp/service/generate
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1401)
    at org.kodeplay.helloworld.HttpCurl.main(HttpCurl.java:30)

Here is the code

public class HttpCurl {
    public static void main(String [] args) {

        HttpURLConnection con;

        try {
            con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setDoInput(true);

            File xmlFile = new File("test.xml");

            String xml = ReadWriteTextFile.getContents(xmlFile);                

            con.getOutputStream().write(xml.getBytes("UTF-8"));
            InputStream response = con.getInputStream();

            BufferedReader reader = new BufferedReader(new InputStreamReader(response));
            for (String line ; (line = reader.readLine()) != null;) {
                System.out.println(line);
            }
            reader.close();

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

Its confusing because the exception is traced to the line InputStream response = con.getInputStream(); and there doesn't seem to be any file involved for a FileNotFoundException.

When I try to open a connection to an xml file directly, it doesn't throw this exception.

The service app uses spring framework and Jaxb2Marshaller to create the response xml.

The class ReadWriteTextFile is taken from here

Thanks.

Edit: Well it saves the data in the DB and sends back a 404 response status code at the same time.

I also tried doing a curl using php and print out the CURLINFO_HTTP_CODE which turns out to be 200.

Any ideas on how do I go about debugging this ? Both service and client are on the local server.

Resolved: I could solve the problem after referring to an answer on SO itself.

It seems HttpURLConnection always returns 404 response when connecting to a url with a non standard port.

Adding these lines solved it

con.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
con.setRequestProperty("Accept","*/*");
Bender answered 21/3, 2011 at 14:43 Comment(6)
"When I execute this code from the terminal" - which code? It's unclear what's working vs what's not working.Deafmute
HttpCurl is the name of the class that has this main method. This class is compiled and run from the terminalBender
I experienced the same issue, but none of the solutions here worked. I eventually figured out it was a problem with Java 1.7.0_05 and updated to the latest version 1.7.0_21 and the problem went away. I also realized the problem did not occur in Java 1.6. Just an FYI for anyone still stuck.Headstream
Guys! see "Resolved" comment on the question rather than answers!Pizor
Possible duplicate of Read error response body in JavaScold
You litteraly saved me a lot of time !! Just adding the headers like you did solve my problem. Thank you so much !!Esp
T
144

I don't know about your Spring/JAXB combination, but the average REST webservice won't return a response body on POST/PUT, just a response status. You'd like to determine it instead of the body.

Replace

InputStream response = con.getInputStream();

by

int status = con.getResponseCode();

All available status codes and their meaning are available in the HTTP spec, as linked before. The webservice itself should also come along with some documentation which overviews all status codes supported by the webservice and their special meaning, if any.

If the status starts with 4nn or 5nn, you'd like to use getErrorStream() instead to read the response body which may contain the error details.

InputStream error = con.getErrorStream();
Trident answered 21/3, 2011 at 14:54 Comment(8)
Ok I tried this and it returns status 404. But strangely, it's also saving in DB! Also from what you mention, does it mean any REST service will only return a status code ? What if i want to return back more information such as a xml validation error message or a url if the post is successful ?Bender
Yes, on modification requests like POST/PUT/etc it usually won't return a body. You'd usually like to determine response status before reading the input or error stream. I added some detail to the answer. But if it is really returning status 404 then there's probably some bug in the webservice. I'd report to its maintainer.Trident
In this case the maintainer of the service happens to be me! .. Well I tried doing a curl using php and it returns 200 (have edited my question) Also tried getErrorStream() as suggested. It throws a NullPointerException on new InputStreamReader(con.getErrorStream()).Bender
Sorry, I am not famliar with Spring webservices. I've only hands on experience with JAX-WS/RS from the standard Java EE 5/6 API. I'd suggest to put a breakpoint on the method responsible for processing the XML file and then step further from there.Trident
This behavior seems very non-useful, particularly since it makes referring to things by the more generic URLConnection problematic. I wonder why the Java guys didn't simply implement getInputStream() in HttpUrlConnection along the lines of return responseCode == 200 ? super.getInputStream() : this.getErrorStream(). But anyhow; informative answer, +1.Sporophore
For anyone wondering why they don't have the "getResponseCode()" method: just cast your connection to a HttpURLConnectionNeedful
I stopped getting FileNotFoundException with this line: InputStream error = con.getErrorStream(); Although that doesn't make the web service call successful as one can assume. In my case, the error is also not consistent. Occurs for specific scenarios with no pattern found till now.Medication
Greate answer, 💪🙏Bloodroot
D
58

FileNotFound is just an unfortunate exception used to indicate that the web server returned a 404.

Deafmute answered 21/3, 2011 at 14:47 Comment(7)
That doesn't explain why the row gets added to DB as stated by the OP.Trident
@BalusC: Unless the server is adding a row and then returning a 404.Deafmute
yes it seems to be behaving in this manner. What does it mean ?Bender
@naiquevin: It's hard to say, but given that it's a service running locally, you should be able to debug it yourself.Deafmute
Thanks for all the help. I could solve the problem. see edited answerBender
It seems that any empty response body will cause this exception to be thrown. 404s often have empty response bodies, but others may as well.Owl
HttpURLConnection also throws FileNotFoundException for 403 responses (and probably others). (Even if the response body is not empty, it seems.) Anyway, always investigate getResponseCode() before calling getInputStream().Nephology
T
34

To anyone with this problem in the future, the reason is because the status code was a 404 (or in my case was a 500). It appears the InpuStream function will throw an error when the status code is not 200.

In my case I control my own server and was returning a 500 status code to indicate an error occurred. Despite me also sending a body with a string message detailing the error, the inputstream threw an error regardless of the body being completely readable.

If you control your server I suppose this can be handled by sending yourself a 200 status code and then handling whatever the string error response was.

Terbium answered 25/5, 2014 at 17:9 Comment(1)
To be clear, you're just handling it wrong. You should use connection.getResponseCode to check if it was alright. Then use connection.getErrorStream to get the error body instead of getInputStream. (or you can use getResponseMessage) You shouldn't send a 200 status code if it was an error, use the http error codes as intended.Eben
T
6

For anybody else stumbling over this, the same happened to me while trying to send a SOAP request header to a SOAP service. The issue was a wrong order in the code, I requested the input stream first before sending the XML body. In the code snipped below, the line InputStream in = conn.getInputStream(); came immediately after ByteArrayOutputStream out = new ByteArrayOutputStream(); which is the incorrect order of things.

ByteArrayOutputStream out = new ByteArrayOutputStream();
// send SOAP request as part of HTTP body 
byte[] data = request.getHttpBody().getBytes("UTF-8");
conn.getOutputStream().write(data); 

if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
  Log.d(TAG, "http response code is " + conn.getResponseCode());
  return null;
}

InputStream in = conn.getInputStream();

FileNotFound in this case was an unfortunate way to encode HTTP response code 400.

Townsville answered 10/3, 2014 at 15:24 Comment(2)
FileNotFound was because the connection doesn't have an uput stream. It had nothing to do with encoding response code 400. You can get the responsecode with getResponseCode(). Then if it's not HTTP_OK you should get the body with conn.getErrorStream() or getResponseMessage()Eben
new ByteArrayOutputStream() has nothing to do with it whatsoever. The issue is the order as between getInputStream() and getResponseCode().Apical
K
4

FileNotFound in this case means you got a 404 from your server - could it be that the server does not like "POST" requests?

Kangaroo answered 21/3, 2011 at 14:45 Comment(3)
But it does save a record to the db. Could Jaxb2Marshaller be the problem here ?Bender
This does not only happen for 404s. It also happens for any response with an empty body.Owl
This answer is, unfortunately, wrong. The FileNotFound in this situation meant only that HttpURLConnection#getInputStream() was called when the response code was above 399, while in this situation the HttpURLConnection#getErrorStream() should've been called. OTOH, if the server wouldn't accept POSTs, it would've returned 405 Method Not Allowed. No relation to FileNotFound being thrown there.Sofiasofie
B
2

FileNotFound in this case means you got a 404 from your server

You Have to Set the Request Content-Type Header Parameter Set “content-type” request header to “application/json” to send the request content in JSON form.

This parameter has to be set to send the request body in JSON format.

Failing to do so, the server returns HTTP status code “400-bad request”.

con.setRequestProperty("Content-Type", "application/json; utf-8");

Full Script ->

public class SendDeviceDetails extends AsyncTask<String, Void, String> {

@Override
protected String doInBackground(String... params) {

    String data = "";
    String url = "";

    HttpURLConnection con = null;
    try {

        // From the above URL object,
        // we can invoke the openConnection method to get the HttpURLConnection object.
        // We can't instantiate HttpURLConnection directly, as it's an abstract class:
        con = (HttpURLConnection)new URL(url).openConnection();
        //To send a POST request, we'll have to set the request method property to POST:
        con.setRequestMethod("POST");

        // Set the Request Content-Type Header Parameter
        // Set “content-type” request header to “application/json” to send the request content in JSON form.
        // This parameter has to be set to send the request body in JSON format.
        //Failing to do so, the server returns HTTP status code “400-bad request”.
        con.setRequestProperty("Content-Type", "application/json; utf-8");
        //Set Response Format Type
        //Set the “Accept” request header to “application/json” to read the response in the desired format:
        con.setRequestProperty("Accept", "application/json");

        //To send request content, let's enable the URLConnection object's doOutput property to true.
        //Otherwise, we'll not be able to write content to the connection output stream:
        con.setDoOutput(true);

        //JSON String need to be constructed for the specific resource.
        //We may construct complex JSON using any third-party JSON libraries such as jackson or org.json
        String jsonInputString = params[0];

        try(OutputStream os = con.getOutputStream()){
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        int code = con.getResponseCode();
        System.out.println(code);

        //Get the input stream to read the response content.
        // Remember to use try-with-resources to close the response stream automatically.
        try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"))){
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            System.out.println(response.toString());
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (con != null) {
            con.disconnect();
        }
    }

    return data;
}

@Override
protected void onPostExecute(String result) {
    super.onPostExecute(result);
    Log.e("TAG", result); // this is expecting a response code to be sent from your server upon receiving the POST data
}

and call it

new SendDeviceDetails().execute("");

you can find more details in this tutorial

https://www.baeldung.com/httpurlconnection-post

Borchert answered 20/1, 2021 at 21:43 Comment(0)
K
-1

The solution:
just change localhost for the IP of your PC
if you want to know this: Windows+r > cmd > ipconfig
example: http://192.168.0.107/directory/service/program.php?action=sendSomething
just replace 192.168.0.107 for your own IP (don't try 127.0.0.1 because it's same as localhost)

Kiefer answered 13/8, 2016 at 19:29 Comment(0)
A
-5

Please change

con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();

To

con = (HttpURLConnection) new URL("http://YOUR_IP:8080/myapp/service/generate").openConnection();
Addams answered 29/1, 2016 at 9:32 Comment(2)
Change it why? The OP has specifically stated that the service is running locally.Apical
In case u need get image resource from localhost, it does not work.Addams

© 2022 - 2024 — McMap. All rights reserved.