I have a servlet, mapped to an URL, which does a long task and outputs some data while it's working.
What I want to do is to call this url and see output in real-time.
Let's take this as an example:
package com.tasks;
public class LongTaskWithOutput extends HttpServlet {
private static final long serialVersionUID = 2945022862538743411L;
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.addHeader("Content-Type", "text/plain");
PrintStream out = new PrintStream(response.getOutputStream(), true);
for(int i=0; i<10;i++) {
out.println("# " + i);
out.flush();
try {
Thread.sleep(1000);
} catch(Exception e){}
}
}
}
With the following in web.xml:
...
<servlet>
<servlet-name>LongTaskServlet</servlet-name>
<servlet-class>com.tasks.LongTaskWithOutput</servlet-class>
<description>Long Task Servlet</description>
</servlet>
<servlet-mapping>
<servlet-name>LongTaskServlet</servlet-name>
<url-pattern>/longTask</url-pattern>
</servlet-mapping>
...
What happens
If I browse localhost/myApp/longTask, the browser makes me wait 10 seconds, then prints out all text at once.
What should happen
The text should be sent to the browser as soon as it's written to the output stream, and the browser should render one line every second.
As you can see, I already put an out.flush() to be sure that the stream flushes every second, but it still doesn't work.
I also tried with response.flushBuffer(), but I had the same result.
Is there a way to achieve this?
Update
As #MadConan suggested, I tried to use the output stream directly:
OutputStream out = response.getOutputStream();
for(int i=0; i<10;i++) {
out.write(("# " + i + "\n").getBytes());
out.flush();
try {
Thread.sleep(1000);
} catch(Exception e){}
}
The result, unfortunately, is still the same.
This is an upstream issue. The browser is not necessarily going to display that data as it receives it. It may wait until the request is complete. You might have additional chunking if you are going through a proxy. If you snoop on the network traffic, I bet it will go through as expected.
You are flushing the content to the response stream, but your response is not committed to the client. Understand it this way - You are giving some things to be somebody, to take and leave the room and hand over to somebody else. Until the person leaves the room and hands it over, your package will not be delivered.
For your requirement, you can keep on pushing the data to some global space and then have a PULL mechanism from client to read that space every X seconds and display the content to client. There is also option of PUSH mechanism to do the same, it depends on your project whether to use PUSH or PULL.
This basically means that in one request you can cannot update client and return back to server and do same over and over again. Request dies once response is committed to the client. Then there should be another PULL request from client or PUSH from server.
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.
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.
I've created a mock XMPP server that processes PLAIN encryption stanzas. I'm able to use Pidgin and go through the entire session creation, to the point where Pidgin thinks the user is on an actually XMPP server and is sending regular pings.
However, it seems like not all messages are processed correctly and when I do get a successful login, it was just luck. I'm talking, maybe 1/10th of the time I actually get connected. The other times it seems like Pidgin missed a message or I dumped messages to fast on the transport.
If I enable Pidgin's XMPP Console plugin, the first connection is ALWAYS successful, but a second user fails to make it through, typically dying when Pidgin requests Service Discovery.
My Mina code is something like this:
try
{
int PORT = 20600;
IoAcceptor acceptor = null;
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addFirst("codec", new ProtocolCodecFilter( new ProtocolCodecFactoryImpl()));
acceptor.getFilterChain().addLast("executor", new ExecutorFilter(IoEventType.MESSAGE_RECEIVED));
acceptor.setHandler( new SimpleServerHandler());
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
acceptor.bind( new InetSocketAddress(PORT));
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
}
and the SimpleServerHandler is responsible for message/stanza processing and session creation. The messageReceived function looks like:
#Override
public void messageReceived(IoSession session, Object msg) throws Exception
{
String str = msg.toString();
System.out.println("MESSAGE: " + str);
process(session, str);
}
and finally, process is in charge of parsing the message out, and writing the response. I do use sychonized on my write:
public void sessionWrite(IoSession session, String buf)
{
synchronized(session)
{
WriteFuture future = session.write(buf);
}
}
I have omitted my processing code for brevity, but it simply looks for certain pieces of data, crafts a response and calls sessionWrite(...)
My question is, will this pattern work? And if not, should I consider shoving received messages in a Queue and simply processing the Queue from say a Timer?
It turns out, Pidgin would send two IQ stanzas, but I wasn't handling them correctly. My decoder now determines the end of a stanza and only writes a stanza to the buffer I read from.
Works like a dream now!
I'm trying to run a Jetty Server that can have a number of people connect to the server and see a list of print outs. I want everybody who connects to see the same values printed out.
For instance, if I have a single list keeping track of the time and I want 5 or so people to be able to go to my website (e.g. localhost:8080/time) and have them all see what time it is every 30 seconds, how would i set that up?
What I have:
I am using Jetty.
I created a single server bound to port 8080.
I created my own handler that extends AbstractHandler
this writes to the screen saying when an event has transpired (i.e. 30 seconds have passed)
If two people connect to this page, they each see a print out every minute (that is it switches back and forth letting each person know when every other event has transpired)
If 3 people connect, only two can stay connected and the third just spins getting no output to the screen
I have not set up an Connectors of my own since my attempts to do so have been unsuccessful and i'm not sure how I understand if that is the solution to my problem.
Any help would be much appreciated and if anybody has some idea but needs some clarification on what I am doing I would be glad to give more details.
Thanks!
Handler code:
#Override
public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException
{
httpServletResponse.setContentType("text/html;charset=utf-8");
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
request.setContextPath("/time");
request.setHandled(true);
while (true) {
synchronized(Main.list) {
while (!Main.list.isEmpty()) {
Double time = Main.list.get(0);
httpServletResponse.getWriter().println("<h1>The time now is " + time + "</h1>");
httpServletResponse.flushBuffer();
Main.list.remove(0);
}
try {
Main.list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
So the list object is a static ArrayList defined in the Main class that I wake up (i.e. notify) every 30 seconds. Hopefully this helps someone understand more what I am talking about as i'm not sure what I could change in the handler...
How are you feeding clients into your handler? Browsers have limits to the number of connections are made to to a particular host, perhaps your seeing that.
there is nothing intrinsically wrong that handler code aside from it being a generally odd thing to see in a handler
We have a system where a client makes an HTTP GET request, the system does some processing on the backend, zips the results, and sends it to the client. Since the processing can take some time, we send this as a ZipOutputStream wrapping the response.getOutputStream().
However, when we have an exceptionally small amount of data in the first ZipEntry, and the second entry takes a long time, the browser the client is using times out. We've tried flushing the stream buffer, but no response seems to be sent to the browser until at least 1000 bytes have been written to the stream. Oddly, once the first 1000 bytes have been sent, subsequent flushes seem to work fine.
I tried stripping down the code to bare-bones to give an example:
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
ZipOutputStream _zos = new ZipOutputStream( response.getOutputStream());
ZipEntry _ze = null;
long startTime = System.currentTimeMillis();
long _lByteCount = 0;
response.setContentType("application/zip");
while (_lByteCount < 2000) {
_ze = new ZipEntry("foo");
_zos.putNextEntry( _ze );
//writes 100 bytes and then waits 10 seconds
_lByteCount += StreamWriter.write(
new ByteArrayInputStream(DataGenerator.getOutput().toByteArray()),
_zos );
System.out.println("Zip: " + _lByteCount + " Time: " + ((System.currentTimeMillis() - startTime) / 1000));
//trying to flush
_zos.finish();
_zos.flush();
response.flushBuffer();
response.getOutputStream().flush();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
I set my browser timeout to be about 20 seconds for easy reproduction. Despite writing the 100 bytes a couple of times, nothing is sent to the browser and the browser times out. If I expand the browser timeout, nothing gets sent until 1000 bytes have been written and then the browser pops up the "Would you like to save..." dialog. Again, after the initial 1000 bytes, each addition 100 bytes sends fine, rather than buffering to 1000 byte chunks.
If I set the max byte count in the while condition to 200 or so, it works fine, sending only 200 bytes.
What can I do to force the servlet to send back really small initial amounts of data?
It turns out there is a limit on the underlying Apache/Windows IP stack that buffers data from a stream in an attempt to be efficient. Since most people have the problem of too much data, not the problem of too little data, this is right most of the time. What we ended up doing was requiring the user to request enough data that we'd hit the 1000 byte limit before timing out. Sorry for taking so long to answer the question.
I know this is a really, really old question, but for the record, I wanted to post an answer that should be a fix all for the issue that you are experiencing.
The key is that you want to flush the response stream, not the zip stream. Because the ZIP stream cannot flush what is not yet ready to write. Your client, as you mentioned, is timing out because it is not receiving a response in a predetermined amount of time, but once it receives data, it is patient and will wait a very long time to download the file, thus the fix is easy, provided you flush the correct stream. I recommend the following:
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
ZipOutputStream _zos = new ZipOutputStream( response.getOutputStream());
ZipEntry _ze = null;
long startTime = System.currentTimeMillis();
long _lByteCount = 0;
response.setContentType("application/zip");
// force an immediate response of the expected content
// so the client can begin the download process
response.flushBuffer();
while (_lByteCount < 2000) {
_ze = new ZipEntry("foo");
_zos.putNextEntry( _ze );
//writes 100 bytes and then waits 10 seconds
_lByteCount += StreamWriter.write(
new ByteArrayInputStream(DataGenerator.getOutput().toByteArray()),
_zos );
System.out.println("Zip: " + _lByteCount + " Time: " + ((System.currentTimeMillis() - startTime) / 1000));
//trying to flush
_zos.finish();
_zos.flush();
}
} catch (Throwable e) {
e.printStackTrace();
}
Now, what should happen here, is the header and response codes will be committed along with anything in the response buffer's OutputStream. This does not close the stream, so any additional writes to the stream are appended. The downside to doing it this way, is that you cannot know the content-length to assign to the header. The positive is that you are starting the download immediately, and not allowing the browser to timeout.
My guess is that the zip output stream doesn't actually write anything before beeing able to compress stuff. Huffmann algorithm used for zipping requires all data to be known before actually beeing able to compress anything. It can't start before everything is known basically.
Zipping might be a win if the amount of data is big, but I don't think you can achieve asynchronous reponse while zipping data.
I entirely can't reproduce your problem. Below is your code, slightly altered, running in an embedded Jetty server. I ran it in IntelliJ and requested http://localhost:8080 from Firefox. As expected, the "Save or Open" dialog popped up after 1 second. Selecting "save" and waiting for 20 seconds results in a zip file which can be opened and contains 20 separate entries, named foo<number> each containing a single line 100 characters wide and ending with <number>. This is on Windows 7 Premium 64 with JDK 1.6.0_26. Chrome acts the same way. IE, on the other hand, seems to normally wait for 5 seconds (500 bytes), though once it showed the dialog immediately, and another time it seemed to wait for 9 or 10 seconds. Try it in different browsers:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZippingAndStreamingServlet {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
context.addServlet(new ServletHolder(new BufferingServlet()), "/*");
server.start();
System.out.println("Listening on 8080");
server.join();
}
static class BufferingServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
ZipOutputStream _zos = new ZipOutputStream(response.getOutputStream());
ZipEntry _ze;
long startTime = System.currentTimeMillis();
long _lByteCount = 0;
int count = 1;
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=my.zip");
while (_lByteCount < 2000) {
_ze = new ZipEntry("foo" + count);
_zos.putNextEntry(_ze);
byte[] bytes = String.format("%100d", count++).getBytes();
System.out.println("Sending " + bytes.length + " bytes");
_zos.write(bytes);
_lByteCount += bytes.length;
sleep(1000);
System.out.println("Zip: " + _lByteCount + " Time: " + ((System.currentTimeMillis() - startTime) / 1000));
_zos.flush();
}
_zos.close();
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new IllegalStateException("Unexpected interrupt!", e);
}
}
}
}
You could be getting screwed by the Java API.
Looking through the JavaDocs of the various OutputStream family of classes (OutputStream, ServletOutputStream, FilterOutputStream, and ZipOutputStream) , they either mention that they rely on the underlying stream for flush() or they declare that flush() doesn't do anything (OutputStream).
ZipOutputStream inherits flush() and write() from FilterOutputStream.
From the FilterOutputStream JavaDoc:
The flush method of FilterOutputStream calls the flush method of its
underlying output stream.
In the case of ZipOutputStream, it is being wrapped around the stream returned from ServletResponse.getOutputStream() which is a ServletOutputStream. It turns out that ServletOutputStream doesn't implement flush() either, it inherits it from OutputStream which specifically mentions in its JavaDoc:
flush public void flush()
throws IOExceptionFlushes
this output stream and forces any
buffered output bytes to be written out. The general contract of flush
is that calling it is an indication that, if any bytes previously
written have been buffered by the implementation of the output stream,
such bytes should immediately be written to their intended
destination. If the intended destination of this stream is an
abstraction provided by the underlying operating system, for example a
file, then flushing the stream guarantees only that bytes previously
written to the stream are passed to the operating system for writing;
it does not guarantee that they are actually written to a physical
device such as a disk drive.
**The flush method of OutputStream does nothing.**
Maybe this is a special case, I don't know. I do know that flush() has been around a long time and it is unlikely that no one has noticed a hole in the functionality there.
It makes me wonder if there is an operating system component to the stream buffering that could be configured to remove the 1k buffer effect.
A related question has a similiar issue but was working directly with a file instead of from a Stream abstraction from Java and this answer points to the MSDN articles involved regarding file buffering and file caching.
A similar scenario was listed in the bug database.
Summary
The Java IO library relies on the OS implementation for Streams. If the OS has caching turned on, Java code may not be able to force a different behavior. In the case of Windows you have to open the file and send non-standard parameters to allow for write-through-cache or no-buffereing functionality. I doubt the Java SDK provides such OS-specific options since they are trying to create platform-generic APIs.
The issue is that by default each servlet implementation buffers the data whereas SSE and other custom requirements might/will need data immediately.
The solution is to do the following:
response.setBufferSize(1) // or some similar small number for such servlets.
This will ensure that the data is written out earlier (with the resultant performance loss)