I have a question pertaining to the use of the ServletInputStream and ServletOutputStream available in Java Servlets. First I'll give some much needed context:
The assignment I am working on calls on implementing Task Queues in the google app engine. I've been able to get tasks to be added to the app engine and the appropriate workers to be called. However, I am struggling to figure out how to pass an ArrayList<> of serializable objects to the worker's doPost() method. The pervailing method is apparently to use the input and outputstreams of the HTTP request and response objects, respectively, to handle this communication between servlets. I've googled extensively but haven't been able to find a clear example of how to prepare such an arraylist for transmission as an outputstream, adding it to the response of the first servlet, then retrieving it from the request in the second servlet and finally converting it back into an arraylist for use in the code of the doPost() method. So that is basically my question. Due to my inexperience with Java, it is difficult for me to figure it all out by myself and am mostly struggling to wrap my head around it.
To clarify a bit more, I'll post the doPost() method of the worker in question:
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
try
{
ArrayList<Quote> qs = /*Here the list needs to be read in.*/ null;
EntityManager manager = EMF.get().createEntityManager();
CarRentalModel.get().confirmQuotes(qs, manager);
}
catch (ReservationException e)
{
}
}
Any help would be greatly appreciated.
Thank you in advance,
Kevin
It's worth to follow BalusC's advice. If you are looking for a simple and quick solution, you can do it with Java's serialization:
In your doPost() method, you can create an ObjectInputStream which reads data from the underlying servlet input stream and deserializes (makes objects out of) the data.
ServletInputStream sis = req.getInputStream();
ObjectInputStream ois = new ObjectInputStream(sis);
ArrayList<Quote> qs = (ArrayList<Quote>) ois.readObject();
You write the object on the other side analogously with an ObjectOutputStream and its writeObject() method. If this doesn't work on spot, try to .flush() or .close() your output stream after finishing your write operations to trigger sending over any remaining buffered data.
Related
I want to send data (what I call a response) to a previous Http requester (assuming a newer request came to the same servlet) through the already old formed session (which is still alive).
The reason is that an occurring event (which is NOT another request in the same session in question) wants the servlet to say something (a string) through our old session to our old requester (assuming another requester came).
Is there an way (a hint) to do that?
(( Edit :
I know it's not usual for HTTP communication. And I'm afraid that if the socket (of layer 5 of OSI) closes after the "service" method ends then my question doesn't hold in the first place and probably I would have to stop socket from closing by going into an API of the server container ("Tomcat" in my case).
This is probably the case by looking onto this.
))
Anyway, here is what I've tried to do :
1) I tried to save the HttpServletResponse object (in the "service" method) of the session in question, but then when a newer request (from another requester) came (and by consequence the "service" method in the same servlet, name it "servlet1", will be executed again), it appeared to me, oddly enough to me, clearly that the HttpServletResponse that I saved is overritten.
2) I found the same result (I was even more surprised) when I tried to save the PrintWriter object (in the following shown code) I got from HttpServletResponse.getWriter(). And again, it was overritten when another request came.
3) I had a hope with HttpSession but I doubt I'm able to write through it. (check its methods here)
Again, I just want to write a string and get it there in (my) client side. If there is a sort of control I can take on the session to send a string, and a way to get it on the client, that would be good.
class Session {
protected PrintWriter printWriter;
protected int index;
Session(PrintWriter pW, int index) {
this.printWriter = pW;
this.index = index;
}
}
#WebServlet("/servlet1")
public class Servlet1 extends HttpServlet {
private static int session_index = 0;
protected static volatile ArrayList<Session> list = new ArrayList<Session>();
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
session_index++;
System.out.println( "Servlet1 : " + session_index );
response.getWriter().append("Served at: ").append(request.getContextPath());
list.add(new Session(response.getWriter(), session_index));
if( list.get(0).printWriter.equals( response.getWriter() ) ) {
System.out.println( "Servlet1 : same old PrintWriter object" );
}
}
I expected this message "Servlet1 : same old PrintWriter object" to not appear in my console, but it did in all subsequent requests.
It is Tomcat 9.0.21 installed on Linux, that I'm working with on the server side (localhost).
It looks like I will need to modify the server container itself, so I will download the source code from here and will follow this guideline to build the Tomcat. The modification must be made in such a way not to close the socket. I may add a method in the servlet to close the socket manually.
How can I detect that the client side of a tomcat servlet request has disconnected? I've read that I should do a response.getOutputStream().print(), then a response.getOutputStream().flush() and catch an IOException, but is there a way I can detect this without writing any data?
EDIT:
The servlet sends out a data stream that doesn't necessarily end, but doesn't necessarily have any data flowing through it (it's a stream of real time events). I need to actually detect when the client disconnects because I have some cleanup I have to do at that point (resources to release, etcetera). If I have the HttpServletRequest available, will trying to read from that throw an IOException if the client disconnects?
is there a way I can detect this
without writing any data?
No because there isn't a way in TCP/IP to detect it without writing any data.
Don't worry about it. Just complete the request actions and write the response. If the client has disappeared, that will cause an IOException: connection reset, which will be thrown into the servlet container. Nothing you have to do about that.
I need to actually detect when the client disconnects because I have some cleanup I have to do at that point (resources to release, etcetera).
There the finally block is for. It will be executed regardless of the outcome. E.g.
OutputStream output = null;
try {
output = response.getOutputStream();
// ...
output.flush();
// ...
} finally {
// Do your cleanup here.
}
If I have the HttpServletRequest available, will trying to read from that throw an IOException if the client disconnects?
Depends on how you're reading from it and how much of request body is already in server memory. In case of normal form encoded requests, whenever you call getParameter() beforehand, it will usually be fully parsed and stored in server memory. Calling the getInputStream() won't be useful at all. Better do it on the response instead.
Have you tried to flush the buffer of the response:
response.flushBuffer();
Seems to throw an IOException when the client disconnected.
Im currently building an web app, using Java Servlets in Tomcat 7.
This webapp uses Jupload as a client side applet to provide a more comfortable way of uploading multiple files to server.
However, currently this applet sends the files one per post request. My implemented Servlet reads the data from input stream and stores it local. Thats fine and this works.
But additional i have to store filename and paths and such things in DB. Thats why I wanted to store such informations in an object and keep them in a list, and collecting this infos during the incoming requests from the applet.
The list is currently realized as class variable.
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private ArrayList<ImageUploadInformation> uploadInfos; //I know, thats bad.
public UploadServlet() {
super();
uploadInfos = new ArrayList<>();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// not relevant stuff...
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//accessing data stream directly
//jUpload sends file information and file binary data in one stream, so we have to deal with mixed data in streams
InputStream inputStream = request.getInputStream();
DataInputStream dataInputStream = new DataInputStream(inputStream);
//some other stuff, not relevant
byte[] b = IOUtils.toByteArray(inputStream);
File file = null;
if (finalFilename != null) {
file = new File(finalFilename);
}
if (file != null) {
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(b);
bos.close();
fos.close();
}
else
throw new IOException("File Creation on Server failed!");
//adding Meta informations about file in list
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
}
}
But i read on some threads here, that it is really a bad thing to do, in case of threadsafety. Im not very experinced in writing web applications, so maybe the following approach is completely wrong.
I tried to bind the list as session attribute of the request.
request.getSession().setAttribute("uploadInfos", uploadInfos);
However, I cannot use this, because it is a entirely new post request which comes from the applet, and that why I dont have access to this list, in another request.
I read something about binding objects in ServletContext, but I think this is also a bad practice, but i couldnt find any proof for that. How can I achieve, that I can store this list over multiple independent requests.
Would it be better, if all files will be sent to servlet in only one post request, where i can create the list inside of the doPost() Method ?
Think this is configurable within Jupload, but actually the files could be very large.
Is it common practice to send large amount of files in one request ?
Thanks for any help and links to additional literature on that kind of stuff.
#edit: additional stuff
tried also this..
if (request.getSession().getAttribute("uploadInfos") != null) {
uploadInfos = (ArrayList<ImageUploadInformation>)request.getSession().getAttribute("uploadInfos");
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
System.out.println("UploadInfos found in Session, current size: " +uploadInfos.size());
request.getSession().setAttribute("uploadInfos", uploadInfos);
}
else {
System.out.println("No UploadInfos found, creating new List...");
uploadInfos = new ArrayList<>();
uploadInfos.add(new ImageUploadInformation(filename, relativeDir,"1"));
request.getSession().setAttribute("uploadInfos", uploadInfos);
}
Here's the output of the test:
Incoming post request
No UploadInfos found, creating new List...
Incoming post request
No UploadInfos found, creating new List...
Incoming post request
No UploadInfos found, creating new List...
You're almost there. The session is where to store state, on the server, that you want to keep across requests.
Servlets may be called from multiple threads and from different clients; the container may also create or destroy instances as it pleases, so the servlets should not hold state themselves. The field "uploadInfos" needs to be removed. That list should be a thread-safe collection, e.g. CopyOnWriteArrayList, and stored in the session instead. First get the attribute from the session, if it's null, create a new list and store it in the session. Then add your entry to the list as before.
It's also worth mentioning that storing state between requests on the server is sometimes undesirable as it can make systems harder to scale out. An alternative would be to store the state on the client, using JavaScript. In your case, though, I wouldn't bother with that, just store it in a session. It's easier.
Let's say I'm writing chunks of data to an HttpServletResponse. So my method receives an array of bytes, I write it to response.getOutputStream(), and then I release the thread. When I receive another chunk of data, my method will be awaken, and it will write to getOutputStream() again. In the end, I call to AsyncContext.complete().
Now, the WriteListener.onWritePossible() spec says:
this method will be invoked by the container the first time when it is
possible to write data. The container will subsequently invoke the
onWritePossible method if and only if isReady method on
ServletOutputStream, described below, returns false.
It seems that when the container calls this method, I already need to have my response buffered somewhere, because onWritePossible() may never be called again. So, for the previous example, I need to write all chunks of data I receive in a buffer (a byte array, or maybe a temporary file if the buffer is big enough), and if onWritePossible() is called before my response is complete, what should I do? Block the thread until I have all my response data?
Check code in following link. May not be the exact solution but it's idea can be a solution for your case:
Servlet 3.1 Async IO Echo
hmm.. Maintaining a buffer seems to be the way
public void onWritePossible() throws IOException {
doWrite();
}
// this is some callback where you received next chunk of data
public void onMoreDataReceived(byte[] data) {
putDataToBuffer(data);
doWrite();
}
// this method is non-blocking
private void synchronized doWrite() throws IOException {
while (_servletOutputStream.isReady() && hasMoreDataInBuffer()) {
_servletOutputStream.write(getMoreBytesFromBuffer());
}
}
Do I need to "flush" the OutputStream from the HttpServletResponse?
I already saw from to Should I close the servlet outputstream? that I don't need to close it, but it's not clear if I need to flush it. Should I expect it from the container as well?
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
byte[] response = getResponse();
String responseType = getResponseType();
response.setContentLength(response.length);
response.setContentType(responseType);
response.getOutputStream().write(response);
response.getOutputStream().flush(); // yes/no/why?
}
You don't need to. The servletcontainer will flush and close it for you. The close by the way already implicitly calls flush.
See also chapter 5.6 of Servlet 3.1 specification:
5.6 Closure of Response Object
When a response is closed, the container must immediately flush all remaining
content in the response buffer to the client. The following events indicate that the servlet has satisfied the request and that the response object is to be closed:
The termination of the service method of the servlet.
The amount of content specified in the setContentLength or
setContentLengthLong method of the response has been greater than zero and
has been written to the response.
The sendError method is called.
The sendRedirect method is called.
The complete method on AsyncContext is called.
Calling flush while still running the servlet's service is usually only beneficial when you have multiple writers on the same stream and you want to switch of the writer (e.g. file with mixed binary/character data), or when you want to keep the stream pointer open for an uncertain time (e.g. a logfile).
Guess that the same answer you got in your other question applies here: if it is your stream, flush and close it. Otherwise the stream creator should be doing it, unless otherwise stated.
To point out an insidious exception to the rule “no need to flush”: Working with IBM WebSphere Application Server and using the response Writer (rather than the OutputStream) I found that I had to flush it; otherwise a final portion of my response data was lost. I suppose that IBM's HttpServletResponse class does indeed flush the OutputStream but uses a separate buffer for the Writer and does not flush it. Other application servers seem to do this.
So if you send your response data to the Writer it is safer to flush it. But there is no need to flush the OutputStream into the bargain.
(I would have posted this as a comment but lack the reputation to do it.)
java.lang.Object
extended byjava.io.Writer
extended byjavax.servlet.jsp.JspWriter
close
public abstract void close()
throws IOException
Close the stream, flushing it first.
This method needs not be invoked explicitly for the initial JspWriter as the code generated by the JSP container will automatically include a call to close().
Closing a previously-closed stream, unlike flush(), has no effect.
Throws:
IOException - If an I/O error occurs
============================
So, DO NOT close the output stream explicitly.