https://bz.apache.org/bugzilla/show_bug.cgi?id=61282
Bug ID: 61282 Summary: org.apache.catalina.connector.CoyoteInputStream asynchronous read Product: Tomcat 9 Version: 9.0.0.M22 Hardware: PC OS: Linux Status: NEW Severity: normal Priority: P2 Component: Catalina Assignee: dev@tomcat.apache.org Reporter: 94544...@qq.com Target Milestone: ----- the byte array byte[] ab=new byte[4096]; int len=is.read(ab,0,ab.length); After onDataAvailable returns to container, the content of ab is changed if len==4096, the content of ab is not changed if len!=4096. The following is the java class I used to do the test. I issue is explained in the onComplete method. =========================== package zede.consult.webapp; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedQueue; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.ReadListener; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import zede.util.Util; import java.util.concurrent.atomic.AtomicBoolean; import javax.json.Json; import javax.json.stream.JsonGenerator; import javax.servlet.AsyncListener; public class BAtest extends HttpServlet { static String dirUpload; public static String dirTmp, dirTarget, dirTargetPost; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringWriter sw = new StringWriter(); try { JsonGenerator g = Json.createGenerator(sw); //out g.writeStartObject().write("idReq", 0); if (WebApp.writeReason(g, null)) { //no more information } g.writeEnd(); g.flush(); } catch (Throwable t) { if (WebApp.debug) { t.printStackTrace(WebApp.out); WebApp.out.flush(); } try { sw = new StringWriter(); JsonGenerator g = Json.createGenerator(sw); //out g.writeStartObject().write("idReq", 0); if (WebApp.writeReason(g, null)) { //no more information } g.writeEnd(); g.flush(); } catch (Throwable t2) { return; } } try { WebApp.sendRes_json(sw.toString(), response); //os.close(); we do not open it. //PrintWriter out=response.getWriter(); //out.print(json);out.flush(); } catch (Throwable ex) { //just swallow it //Logger.getLogger(BA.class.getName()).log(Level.SEVERE, null, ex); } } @Override protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AsyncContext ac = request.startAsync(); ACHandler_Put h = new ACHandler_Put(ac, request, response); ac.addListener(h); ac.start(h); } class ACHandler_Put implements ReadListener, AsyncListener, Runnable { ServletInputStream is; int bytesReceived, bytesSaved; //this is different from ut.saved which is got from harddisk, this is instructed to save. int saved, size; //int for length is more than enough. ConcurrentLinkedQueue<DataPiece> q2save = new ConcurrentLinkedQueue<>(); public final AsyncContext ac; public final HttpServletRequest request; public final HttpServletResponse response; public int idReq; public Ex_Coded4Web exweb; public ACHandler_Put(AsyncContext ac, HttpServletRequest request, HttpServletResponse response) { this.ac = ac; this.request = request; this.response = response; } @Override public void run() { try { String slength = request.getHeader("Content-Length"); if (slength == null) { //in this case, because the fault of the client's agent, we should reject it, //but since we are nice, so we accept it, but the length is the length specified in the UpLoadTask } else { try { size = Integer.parseInt(slength); } catch (Throwable t) { throw new Ex_Coded4Web(WebApp.Reason_ContentLengthParseFailed, "Content_Length:" + slength + " can not be converted into int"); } } startRead(); return; //we do not call complete at this time point } catch (Ex_Coded4Web e) { exweb = e; } catch (Throwable t) { t.printStackTrace(); exweb = new Ex_Coded4Web(WebApp.Reason_UncaughtException, t.getMessage()); } complete(); } void startRead() { File f = new File("stream.out"); // System.out.println(f.getAbsolutePath()); //tomcat home or catalina home f = new File("/home/jack/Pictures/stream.out"); try { fos = new FileOutputStream(f); } catch (FileNotFoundException ex) { } bytesReceived = bytesSaved = saved; try { is = request.getInputStream(); //System.out.println(is.getClass().getName()); //org.apache.catalina.connector.CoyoteInputStream } catch (IOException ex) { exweb = new Ex_Coded4Web(WebApp.Reason_FailedOnReceiving, "unable get uploading stream:" + ex.getMessage()); complete(); return; } //is=java.util.Base64.getDecoder().wrap(is); is.setReadListener(ACHandler_Put.this); } @Override public void onDataAvailable() { { int len; //we have to check the bytes saved incase someone attack us. byte[] ab = null;//q4096.poll(); if (ab == null) { ab = new byte[4096]; } String msg; while (true) { try { if (!is.isReady()) { //this can throw the same EOFException as read. break; } len = is.read(ab, 0, ab.length); } catch (IOException ex) { //read exception //this happens usually as a result of user refresh the webpage before uploading completes. ex.printStackTrace(); msg = "isread/read:" + ex.getMessage(); exweb = new Ex_Coded4Web(WebApp.Reason_FailedOnReceiving, msg); complete(); return; } if (-1 == len) { //this will not happen, or I did not see it happen //onAllDataRead will be called. //if this happens, then this should have the same effect as onAllDataRead() if (WebApp.debug) { System.out.println("put len=-1"); } return; } // if(len==0){ //will never happen, otherwise isready is useless // break; // } DataPiece dp = new DataPiece(ab, bytesReceived, len); //new DataPiece(ByteBuffer.wrap(ab, 0, len), bytesReceived); bytesReceived += len; if (bytesReceived > size) { msg = "size " + bytesReceived + " is exceeding the inited length:" + size; System.out.println(msg); exweb = new Ex_Coded4Web(WebApp.Reason_SizeExceedDeclaredLength, msg); try { is.close(); } catch (Throwable t) { //exweb !=null, so just swallow } complete(); return; } //System.out.println("read data +" + len + "=" + bytesReceived + "/" + ut.size + " saved:" + bytesSaved); //I would do this in asynchronous mode, but to show the data is correctly received, //so we save it with an outputstream, and we can compre the save file and original file //they are same. if (fos != null) { try { fos.write(ab, 0, len); } catch (IOException ex) { } } q2save.offer(dp); } } } FileOutputStream fos; MappedByteBuffer bb = null; byte[] bbO; int offset; /** * In the current servlet specification, I do not know whether read * returns -1 will happen or not. Since this notification should has the * same effect as read returns -1. * */ @Override public void onAllDataRead() { if (WebApp.debug) { System.out.println("BA.put onAllDataRead, bytesReceived:" + bytesReceived + ", size:" + size); } try { //save(); if (bytesReceived < size) { exweb = new Ex_Coded4Web(WebApp.Reason_SizeLessThanDeclaredLength, "size " + bytesReceived + " is less than the inited length:" + size); } } catch (Throwable t) { if (WebApp.debug) { t.printStackTrace(); } } if (fos != null) { try { fos.flush(); fos.close(); } catch (IOException ex) { } } complete(); } //of reading from network @Override public void onError(Throwable t) { try { //one case, when uploading is not finished, browser crashed, or refreshed, so the uploading is canceled. System.out.println("onError when receiving data from network"); t.printStackTrace(); exweb = new Ex_Coded4Web(WebApp.Reason_FailedOnReceiving, "onError when receiving:" + t.getMessage()); //we do not flush here, since q2save might not be empty yet //we just call save instead complete(); //but why onComplete is not called as expected?? } catch (Throwable t2) { System.out.println("unexpected inside onError"); t2.printStackTrace(); } } //servlet asyncContext complete, onError will also call this? /** * we have two copies of received data: DataPiece.ab and DataPiece.ab2, * we compare them, and we compare them to the original data. * * we can find that the copy returned by ServletInputStream.read is changed * for the len=4096, if the len is not 4096, they are not changed. * * * @param event */ @Override public void onComplete(AsyncEvent event) { try { //this is the original file File f = new File("/home/jack/Pictures/a.pdf"); FileChannel fc = new FileInputStream(f).getChannel(); bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); /* if (bb.hasArray()) { System.out.println("hasArray:yes!"); bbO = bb.array(); offset = bb.arrayOffset(); } else { System.out.println("hasArray:No!"); } */ } catch (IOException ex) { ex.printStackTrace(); } Iterator<DataPiece> I = q2save.iterator(); while (I.hasNext()) { DataPiece dp = I.next(); bb.position(dp.offset); ByteBuffer bb1 = ByteBuffer.wrap(dp.ab, 0, dp.len); //or dp.ab2 boolean e1 = Util.equals(bb1, bb); String msg = "bytesSaved:" + bytesSaved + "+" + dp.len + " bb1:" + e1; bb.position(dp.offset); ByteBuffer bb2 = ByteBuffer.wrap(dp.ab2, 0, dp.len); //or dp.ab msg += " bb2:" + Util.equals(bb2, bb) + " ab:ab2" + Util.equals(dp.ab, 0, dp.len, dp.ab2, 0); System.out.println(msg); /* * I got 9 of bb1:true and 242 bb1:false, 251 of bb2:true and 0 bb2:false 9 of ab:ab2true and 242 ab:ab2false * if I change the bb1 & bb2, then the result reversed. */ } } @Override public void onTimeout(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onStartAsync(AsyncEvent event) throws IOException { } protected void complete() { StringWriter sw = new StringWriter(); try { JsonGenerator g = Json.createGenerator(sw); //out g.writeStartObject().write("idReq", idReq); if (WebApp.writeReason(g, exweb)) { //no more information } g.writeEnd(); g.flush(); } catch (Throwable t) { try { sw = new StringWriter(); JsonGenerator g = Json.createGenerator(sw); //out g.writeStartObject().write("idReq", idReq); if (WebApp.writeReason(g, exweb)) { //no more information } g.writeEnd(); g.flush(); } catch (Throwable t2) { return; } } try { WebApp.sendRes_json(sw.toString(), response); //os.close(); we do not open it. //PrintWriter out=response.getWriter(); //out.print(json);out.flush(); } catch (Throwable ex) { //just swallow it //Logger.getLogger(BA.class.getName()).log(Level.SEVERE, null, ex); } ac.complete(); } } static class DataPiece { //ByteBuffer bb; int offset, len; byte[] ab, ab2; DataPiece(byte[] ab, int offset, int len) { this.ab = ab; this.offset = offset; this.len = len; ab2 = new byte[len]; System.arraycopy(ab, 0, ab2, 0, len); //bb = ByteBuffer.wrap(ab2, 0, len); } } } -- You are receiving this mail because: You are the assignee for the bug. --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org