HttpURLConnection.getRequestProperties() throws IllegalStateException: "Already connected" - java

After executing a request I would like to check the request headers, but it doesn't work.
I call getRequestProperties() on an instance of sun.net.www.protocol.http.HttpURLConnection and I always get an IllegalStateException with the message "Already connected". As if I wanted to set request properties. But I only want to read them.
The responsible code for this behaviour is in the HttpUrlConnection:
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/net/www/protocol/http/HttpURLConnection.java#HttpURLConnection.getRequestProperties%28%29
public synchronized Map<String, List<String>> getRequestProperties() {
if (connected)
throw new IllegalStateException("Already connected");
// ...
}
Ok so maybe I should only read the request properties after disconnecting. But it turns out, disconnect() doesn't set connected to false. Although it should: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/net/www/protocol/http/HttpURLConnection.java#HttpURLConnection.disconnect%28%29
It also doesn't seem to make a difference if I read the stream to the end or not. Closing the InputStream before or after calling disconnect doesn't make a difference either.
I'm confused. Can you help me?
Why doesn't disconnect() set connected to false?
Why can't I read request properties while the urlConnection is connected?
How do you properly read request headers after the request?
The code to reproduce this is a Unit test for Android (I use Robolectric), but I think you can use it in a Java project as well and call it from main() after removing the test annotation:
/**
* Test if HttpUrlConnection works as expected, because in some cases it seems it doesn't
*
* #throws Exception
*/
#Test
public void testHttpUrlConnection() throws Exception
{
final URL url = new URL("http://www.stackoverflow.com");
final HttpURLConnection urlConnection = ( HttpURLConnection ) url.openConnection( );
urlConnection.setRequestMethod("GET");
InputStream is = null;
try
{
is = urlConnection.getInputStream();
assertEquals(200, urlConnection.getResponseCode());
}
catch (IOException ex)
{
is = urlConnection.getErrorStream( );
}
final String result = copyStreamToString(is); // some html response
// Streams must be closed before disconnecting (according to http://stackoverflow.com/a/11056207/3596676)
is.close();
assertTrue((Boolean) getFieldViaRecursiveReflection(urlConnection, "connected"));
// urlConnection should always be disconnected (according to http://developer.android.com/reference/java/net/HttpURLConnection.html)
urlConnection.disconnect();
assertFalse((Boolean) getFieldViaRecursiveReflection(urlConnection, "connected")); // AssertionError
// getRequestProperties throws IllegalStateException ("already connected")
Map<String, List<String>> requestProperties = urlConnection.getRequestProperties();
// do stuff with the properties
// return the result
}
private static String copyStreamToString( final InputStream is ) throws IOException
{
if ( is == null )
{
return "";
}
BufferedReader reader = new BufferedReader( new InputStreamReader( is ) );
String result = copyBufferedReaderToString( reader );
reader.close( );
return result;
}
private static String copyBufferedReaderToString( final BufferedReader bufferedReader ) throws IOException
{
StringBuffer sb = new StringBuffer( );
String line;
while ( ( line = bufferedReader.readLine( ) ) != null )
{
sb.append( line );
}
return sb.toString( );
}
private static Object getFieldViaRecursiveReflection(final Object object, final String attribute) throws Exception
{
return getFieldViaRecursiveReflection(object, object.getClass(), attribute);
}
private static Object getFieldViaRecursiveReflection(final Object object, final Class<?> c, final String attribute) throws Exception
{
try
{
final Field field = c.getDeclaredField(attribute);
field.setAccessible(true);
return field.get(object);
}
catch (NoSuchFieldException ex)
{
/* end of type hierarchy? */
Class<?> superClass = c.getSuperclass();
if (superClass == null)
{
throw ex;
}
else
{
return getFieldViaRecursiveReflection(object, superClass, attribute);
}
}
}

As no one posted an answer in the 2 months since I asked the question, but today I had to deal with the problem again and found a solution, I will answer my own question.
I can't answer all the questions in the answer (e.g. "why doesn't urlConnection.disconnect() set the connected attribute of urlConnection to false?"), but I found the solution for the main problem, which was that reading the headers of a request didn't work when the urlConnection was connected.
For some reason, which I can't remember, I wanted/needed to check the request headers after the request was done and the response was there. But I looked at the implementation of getRequestProperties() in sun.net.www.protocol.http.HttpURLConnection again (see the code here) and noticed that a method called filterAndAddHeaders gets called. So it seems that headers not only get read in that method, but set. I'm not sure why this is done in a getter method (the getRequestProperties() one), but it makes sense that when the request is already done, you should warn the user when he tries to add request headers - in this case with the IllegalStateException that bothered me so much.
To come to the solution:
I just moved the call to getRequestProperties() to before the request gets sent. And now everything works fine.
P.S.:
Please note that this is not all there is to it. One of my unit tests ran successfully even though I called getRequestProperties() after the request. In that case the urlConnection internal attribute connected was set to false. I haven't figured it all out, but it may have been related to the response status code 304 (not modified). Maybe this helps as a hint if you have to deal with this problem and for some reason can't move the getRequestProperties() call to before sending the request.

Just to clarify, backenddev says "I just moved the call ... to before the request gets sent", but when is that?
The error message is "already connected" so you may think that you have to get the parameters before the connection is opened, which is impossible (because the object that you need is created by that call). As he says, you have to get them before the request is sent.
The code below works for me. In my case, I'm making an https request. The TIMEOUT value is an integer (in milliseconds) and payload is a String containing the body. HTTP_FAILURE is the value 401. My call needed an authorization header, the value of which is not shown.
The getOutputStream() call sends the headers, and once you've done that, you can't change them (which is sensible) but you can't look at them either (which is not so sensible).
I had a very puzzling problem, so I wanted to display all of the parameters. (Actually, although I called setRequestProperty() three times, my debug only showed the Content-type value. The other properties are not parameters, apparently. Go figure!)
byte[] buffer = new byte[payload.length()];
buffer = payload.getBytes();
bout = new ByteArrayOutputStream();
bout.write(buffer);
byte[] payloadAsBytes = bout.toByteArray();
bout.close();
String contentLength = String.valueOf(payloadAsBytes.length);
String contentType = "application/xml; charset=utf-8";
URL url = new URL(serviceURL);
URLConnection connection = url.openConnection();
httpConn = (HttpsURLConnection)connection;
// Set the appropriate HTTP parameters.
httpConn.setRequestProperty("Content-Length", contentLength);
httpConn.setRequestProperty("Content-Type", contentType);
httpConn.setRequestProperty("Authorization", authHeader);
httpConn.setRequestMethod("POST");
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
httpConn.setConnectTimeout(TIMEOUT);
httpConn.setReadTimeout(TIMEOUT);
// Display the params for debug
if (logger.isDebugEnabled()) {
logger.debug("{} - HTTP headers:", m);
Map<String, List<String>> props = httpConn.getRequestProperties();
Set<String> keys = props.keySet();
for (String key: keys) {
StringBuilder values = new StringBuilder();
for (String value: props.get(key)) {
values.append("\"").append(value).append("\" ");
}
logger.debug("{} - {}: {}", m, key, values.toString());
}
logger.debug("{} - request method {}", m, httpConn.getRequestMethod());
logger.debug("{} - url {}", m, httpConn.getURL().toString());
}
// Send the request
out = httpConn.getOutputStream(); // send the headers
out.write(payloadAsBytes); // send the body
out.close();
// check the response
if (httpConn.getResponseCode() == HTTP_FAILURE) {

Related

How to submit HTTP request with an INTENTIONAL syntax error?

I'm trying to write a simple test where I submit a request to http://localhost:12345/%, knowing that this is an illegal URI, because I want to assert that my HTTP Server's error-handling code behaves correctly. However, I am having a hard time forcing Java to do this.
If I try to create a Java 11 HttpRequest with URI.create("localhost:12345/%"), I get a URISyntaxException, which is correct and not helpful.
Similarly, using a ws-rs WebTarget:
ClientBuilder.newBuilder().build().target("http://localhost:12345").path("/%")
builds me a WebTarget pointing to /%25, which would normally be very helpful, but is not what I want in this particular situation.
Is there a way to test my error-handling behavior without resorting to low-level bytestream manipulation?
Another possibility is just to use plain Socket - it's easy enough if you know the protocol (especially if using the new text-block feature). This will allow you to misformat the request in any way you like. Reading the response and analysing the result is - of course - a bit more involved:
String request = """
GET %s HTTP/1.1\r
Host: localhost:%s\r
Connection: close\r
\r
""".formatted("/%", port);
try (Socket client = new Socket("localhost", port);
OutputStream os = client.getOutputStream();
InputStream in = client.getInputStream()) {
os.write(request.getBytes(StandardCharsets.US_ASCII));
os.flush();
// This is optimistic: the server should close the
// connection since we asked for it, and we're hoping
// that the response will be in ASCII for the headers
// and UTF-8 for the body - and that it won't use
// chunk encoding.
byte[] bytes = in.readAllBytes();
String response = new String(bytes, StandardCharsets.UTF_8);
System.out.println("response: " + response);
}
Noah's comment lead me down the right path; I was able to do this with the URL class:
#Test
public void testUriMalformed() throws Exception {
final URL url = new URL(TEST_URI + "/%");
final HttpURLConnection connection = (HttpURLConnection)url.openConnection();
final int code = connection.getResponseCode();
final String contentType = connection.getHeaderField("Content-Type");
final String entity = IOUtils.toString(connection.getErrorStream(), Charsets.UTF_8);
assertEquals(500, code);
assertEquals(MediaType.APPLICATION_JSON, contentType);
assertTrue(entity.contains("error_id"));
}

how to dispose a java.net.URLConnection object in order to ensure that subsequent calls to the same URL don't share session data?

I am having a problem calling the same https URL several times in a row. The first requests are successful, but after an indeterminate amount of time, a 401 HTTP error code exception is thrown, suggesting that the user credentials are invalid.
I discussed this problem with the person in charge of the database/server and he told me that the problem I was experiencing was normal because after some fixed amount of time, the server invalidates session data, causing subsequent calls to the same URL with the same user credentials to result in a 401 HTTP error code.
He indicated that if I let different URLConnection objects handle all the calls that need to be made, then I should not have to worry about expired session data.
His explanation seems to make sense, but as the snippet of code below shows, I am already using a brand new URLConnection object for each request to the same url with the same user credentials. So if what I was told is correct, then I guess that the problem is that the URLConnection objects are all using the same underlying connection and for that reason sharing the same session data.
Assuming that I am on the right track, how should I modify my code so that each time I make a new request to the same URL with the same user credentials I don't run into problems caused by expired session data? Is it just a matter of calling disconnect() on the underlying HttpsURLConnection object?
public static void main(String[] args)
{
String url = "https://...";//some https url
int x = 0;
while(true)
{
try
{
System.out.print("call#: " + (++x));
//call download() with a valid username & password
String result = download(url, "some-valid-username", "some-valid-password");
System.out.println(result);
}
catch(Throwable e)
{
//after hundreds of successful calls,
//a 401 HTTP error code exception
e.printStackTrace();
break;
}
}
}
public static String download(String url, String user, String pass) throws IOException
{
//new URLConnection object
java.net.URLConnection connection = new java.net.URL(url).openConnection();
connection.setRequestProperty("Authorization",
"Basic " +
javax.xml.bind.DatatypeConverter.printBase64Binary(
(user + ":" + pass).getBytes("UTF-8")));
//get response
InputStream is = null;
byte[] response = null;
try
{
is = connection.getInputStream();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] bytes = new byte[16384];
int x = 0;
while((x = is.read(bytes, 0, bytes.length)) != -1){
stream.write(bytes, 0, x);
}
stream.flush();
response = stream.toByteArray();
}
finally
{
if (is != null)
{
is.close();
}
}
//((javax.net.ssl.HttpsURLConnection)connection).disconnect();// ?
return response != null ? new String(response, "UTF-8") : null;
}
Based on your login credentials server will create the session id and that will maintain by the server. Your subsequent calls will be validated against the session id not by your credentials.
You need to get the session id at first time and maintain with in your application. Pass that to server for subsequent calls. The session will be expired after certain predifined time period if no request sent to the server.
Please read it
http://en.wikipedia.org/wiki/Session_%28computer_science%29

Making a HTTPS Post request using XML in Java

I'm trying to use the API from Web Of Knowledge(WoK) to obtain some data. The documentation explain that you have to do POST Requests through HTTPS, sending a XML which contains the queries. But I only get the error 400 form server. (Bad Request)
Here is my code, I found it in Google and I make some fixes for my case.
public static void main(String[] args) throws Exception {
// Get target URL
String strURL = /*Here the Server URL*/;
// Get file to be posted
String strXMLFilename = "src/main/resources/xml/wosdata.xml";
File input = new File(strXMLFilename);
// Prepare HTTP post
PostMethod post = new PostMethod(strURL);
// Request content will be retrieved directly
// from the input stream
// Per default, the request content needs to be buffered
// in order to determine its length.
// Request body buffering can be avoided when
// content length is explicitly specified
post.setRequestEntity(new InputStreamRequestEntity(
new FileInputStream(input), input.length()));
// Specify content type and encoding
// If content encoding is not explicitly specified
// ISO-8859-1 is assumed
post.setRequestHeader(
"Content-type", "text/xml; charset=ISO-8859-1");
// Get HTTP client
HttpClient httpclient = new HttpClient();
// Execute request
try {
int result = httpclient.executeMethod(post);
// Display status code
System.out.println("Response status code: " + result);
// Display response
System.out.println("Response body: ");
System.out.println(post.getResponseBodyAsString());
}catch (Exception e) {
e.printStackTrace();
} finally {
// Release current connection to the connection pool
// once you are done
post.releaseConnection();
}
}
There is something wrong with the XML you are sending. You will have to look at server logs to find out exactly what, as 400 deliberately tells you as little as possible.
You should do it like this. First read the contents of the xml to String and do post using a StringRequestEntity.
// Get file to be posted
String strXMLFilename = "src/main/resources/xml/wosdata.xml";
StringBuilder contents = new StringBuilder();
try {
BufferedReader input = new BufferedReader(new FileReader(new File(strXMLFilename)));
try {
while (( line = input.readLine()) != null){
contents.append(line);
contents.append(System.getProperty("line.separator"));
}
}
finally {
input.close();
}
StringEntity requestEntity = new StringEntity(contents.toString());
post.setEntity(requestEntity);

FileNotFoundException while getting the InputStream object from HttpURLConnection

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","*/*");
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();
FileNotFound is just an unfortunate exception used to indicate that the web server returned a 404.
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.
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.
FileNotFound in this case means you got a 404 from your server - could it be that the server does not like "POST" requests?
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
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)
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();

Prevent IllegalStateException when reading from request

I have an interceptor for catching Exceptions and sending emails of this exceptions.
All my struts actions extend CoreController which implements SerlvetRequestAware.
In mail service class then I have:
CoreController cc = (CoreController)invocation.getAction();
HttpServletRequest request = cc.getRequest();
I want to insert request body to email, if exists. Like so:
StringWriter msg = new StringWriter();
msg.write("Requested URI: " + request.getRequestURI()+NEW_LINE);
msg.write("Requested Query String: " + request.getQueryString()+NEW_LINE);
msg.write("Request method: "+request.getMethod()+NEW_LINE);
try {
if (request.getReader() != null) {
msg.write("Request body: "+ request.getReader().readLine()+NEW_LINE);
request.getReader().close();
}
} catch (IOException e) {
e.printStrackTrace();
} catch(IllegalStateException e) {
e.printStrackTrace();
}
Now it always throws an IllegalStateException, when reader is not null. How could I "revert" reader or how any other way to read the request body?
EDIT
Exception: getInputStream() has already been called for this request
Perhaps you should try using the request's InputStream rather than its Reader if you receive an IllegalStateException:
BufferedReader bufferedReader;
try {
bufferedReader = request.getReader();
} catch (IllegalStateException e) {
InputStream inputStream = request.getInputStream();
// As per BalusC's comment:
String charsetName = request.getCharacterEncoding();
if (charsetName == null) {
charsetName = "UTF-8";
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
bufferedReader = new BufferedReader(inputStreamReader);
}
bufferedReader.readLine();
You will get that exception if someone else has already read the request body or if the other kind of reader (in your case the InputStream) has been opened by someone.
My guess is that this happens in the code which parses the request. So at this stage, you can't read the request body anymore. Instead, you should check the field which contains the exception. Your struts config must contain this code somewhere:
<exception
key="exception"
path="/UserExists.jsp"
type="java.lang.Exception"/>
This means you can find the exception in the request attribute exception.
If you want to read the request body more than once, you could add a Filter that wraps the original HttpServletRequest inside a custom implementation that supports multiple reads (e.g. by storing the request body into a byte array and creating a new ByteArrayInputStream for each call).
Note: Wrapping HttpServletRequest and HttpServletResponse isn't uncommon (see for instance Jetty's GzipFilter, especially its GZIPResponseWrapper class). You should do this carefully though as changing (the behavior of) the request object might affect other filters or servlets.

Categories

Resources