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.
Related
I have an assignment for school that involves writing a simple web crawler that crawls Wikipedia. The assignment stipulates that I can't use any external libraries so I've been playing around with the java.net.URL class. Based on the official tutorial and some code given by my professor I have:
public static void main(String[] args) {
System.setProperty("sun.net.client.defaultConnectTimeout", "500");
System.setProperty("sun.net.client.defaultReadTimeout", "1000");
try {
URL url = new URL(BASE_URL + "/wiki/Physics");
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String inputLine;
int lineNum = 0;
while ((inputLine = br.readLine()) != null && lineNum < 10) {
System.out.println(inputLine);
lineNum++;
}
is.close();
}
catch (MalformedURLException e) {
System.out.println(e.getMessage());
}
catch (IOException e) {
System.out.println(e.getMessage());
}
}
In addition, the assignment requires that:
Your program should not continuously send requests to wiki. Your program
must wait for at least 1 second after every 10 requests
So my question is, where exactly in the above code is the "request" being sent? And how does this connection work? Is the entire webpage being loaded in one go? or is it being downloaded line by line?
I honestly don't really understand much about networking at all so apologies if I'm misunderstanding something fundamental. Any help would be much appreciated.
InputStream is = url.openStream();
at the above line you will be sending request
BufferedReader br = new BufferedReader(new InputStreamReader(is));
at this line getting the input stream and reading.
Calling url.openStream() initiates a new TCP connection to the server that the URL resolves to. An HTTP GET request is then sent over the connection. If all goes right (i.e., 200 OK), the server sends back the HTTP response message that carries the data payload that is served up at the specified URL. You then need to read the bytes from the InputStream that the openStream() method returns in order to retrieve the data payload into your program.
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) {
this may look like like a problem that's already been solved but it's not, because I have gone through all the questions that deal with UTF-8 and none of the solutions has helped me.
I'm sending http request to my java servlet containing JSON object using the JSON simple library.
I added the UTF-8 encoding in Tomcat xml file
my HTML pages support UTF-8 encoding
both my database and all my tables are also UTF-8 encoded
I changes the default encoding of the JVM to UTF-8 using system variables (yeah! that's how desperate I got)
this is my dispatcher function:
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
AjaxParser cr = AjaxParser.ClientRequestFactory();
ClientRequest msg = cr.ParseClientAjax(request);
HandleRequest HR = new HandleRequest();
HandleRequestStatus HRS = HR.HandleMessage(msg);
AjaxResponseGenerator ARG = new AjaxResponseGenerator();
JSONObject jsonObj = ARG.HandleResponse(HRS);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter out = response.getWriter();
System.out.println(jsonObj);// write the json object to console
out.println(jsonObj);
}
and this is how I do the parsing to String:
public ClientRequest ParseClientAjax(HttpServletRequest request) {
ClientRequest msg = new ClientRequest();
StringBuffer jb = new StringBuffer();
String line = null;
try {
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null)
jb.append(line);
} catch (Exception e) {
e.printStackTrace();
}
JSONParser parser = new JSONParser();
try {
JSONObject obj = (JSONObject) parser.parse(jb.toString());
String opcodeString = (String) obj.get("opcode");
RequestCodeEnum numericEnumCode = (RequestCodeEnum) OpCodesMap
.get(opcodeString);
msg.setOpCode(numericEnumCode);
String entityStr = obj.get("Entity").toString();
Entity entity = makeEntityFromString(numericEnumCode, entityStr);
msg.setEntity(entity);
} catch (ParseException pe) {
System.out.println(pe);
}
return msg;
}
I tried to do some debugging by printing to the Eclipse console (which I also changed to UTF-8 encoding) the text I send throughout my application to find out where the text is not encoded correctly, I found that the text is in the right encoding until right before the execution of my query. after that I check the database manually and the text is inserted there as question marks.
I tried to manually insert Non-English text to my database using Workbench, and it works fine, both in the database itself and when displaying the data in my HTML afterwards.
the problem happens only when I insert data from my web page.
I'm stuck, I have no idea where the problem might be.
Any suggestions?
Try this:
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream , StandardCharsets.UTF_8));
On Servlet side:
for (GameParticipant activePlayer : connector.activePlayers) {
activePlayer.out.println(response);
activePlayer.out.flush();
System.out.println("Server sending board state to all game participants:" + response);
(activePlayer.out is a PrintWriter saved in the server from the HttpResponse object obtained when that client connected the first time)
On clinet side:
private void receiveMessageFromServer() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String input = null;
while ((input = br.readLine()) != null){
sb.append(input).append(" ");
}
}
For some reason, this communication works only the first time, when the client requests connection and waits for response in the same method, while the server uses the PrintWriter obtained directly fron the available HttpRespnse in the doPost method. After that, when the servlet tries to reuse the PrintWriter to talk to the clinet outside of a doPost method, nothing happens, the message never gets to the client. Any ideas?
P.S. In client constructor:
try {
url = new URL("http://localhost:8182/stream");
conn = (HttpURLConnection) url.openConnection();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException ioE) {
ioE.printStackTrace();
}
The response output stream isn't valid outside the doPost() method, or more properly speaking the service() method. It can only be used to send one response. However PrintWriter swallows exceptions, as you will find when you check its error status, so you didn't see the problem.
In other words your entire server-side design is flawed. You can't misuse the Servlet Specification in that way.
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);