1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package nonblocking; 18 19 import java.io.IOException; 20 import java.nio.charset.StandardCharsets; 21 22 import javax.servlet.AsyncContext; 23 import javax.servlet.ReadListener; 24 import javax.servlet.ServletException; 25 import javax.servlet.ServletInputStream; 26 import javax.servlet.ServletOutputStream; 27 import javax.servlet.WriteListener; 28 import javax.servlet.http.HttpServlet; 29 import javax.servlet.http.HttpServletRequest; 30 import javax.servlet.http.HttpServletResponse; 31 32 /** 33 * This doesn't do anything particularly useful - it just counts the total 34 * number of bytes in a request body while demonstrating how to perform 35 * non-blocking reads. 36 */ 37 public class ByteCounter extends HttpServlet { 38 39 private static final long serialVersionUID = 1L; 40 41 @Override doGet(HttpServletRequest req, HttpServletResponse resp)42 protected void doGet(HttpServletRequest req, HttpServletResponse resp) 43 throws ServletException, IOException { 44 45 resp.setContentType("text/plain"); 46 resp.setCharacterEncoding("UTF-8"); 47 48 resp.getWriter().println("Try again using a POST request."); 49 } 50 51 @Override doPost(HttpServletRequest req, HttpServletResponse resp)52 protected void doPost(HttpServletRequest req, HttpServletResponse resp) 53 throws ServletException, IOException { 54 55 resp.setContentType("text/plain"); 56 resp.setCharacterEncoding("UTF-8"); 57 58 // Non-blocking IO requires async 59 AsyncContext ac = req.startAsync(); 60 61 // Use a single listener for read and write. Listeners often need to 62 // share state to coordinate reads and writes and this is much easier as 63 // a single object. 64 @SuppressWarnings("unused") 65 CounterListener listener = new CounterListener( 66 ac, req.getInputStream(), resp.getOutputStream()); 67 } 68 69 70 /** 71 * Keep in mind that each call may well be on a different thread to the 72 * previous call. Ensure that changes in values will be visible across 73 * threads. There should only ever be one container thread at a time calling 74 * the listener. 75 */ 76 private static class CounterListener implements ReadListener, WriteListener { 77 78 private final AsyncContext ac; 79 private final ServletInputStream sis; 80 private final ServletOutputStream sos; 81 82 private volatile boolean readFinished = false; 83 private volatile long totalBytesRead = 0; 84 private byte[] buffer = new byte[8192]; 85 CounterListener(AsyncContext ac, ServletInputStream sis, ServletOutputStream sos)86 private CounterListener(AsyncContext ac, ServletInputStream sis, 87 ServletOutputStream sos) { 88 this.ac = ac; 89 this.sis = sis; 90 this.sos = sos; 91 92 // In Tomcat, the order the listeners are set controls the order 93 // that the first calls are made. In this case, the read listener 94 // will be called before the write listener. 95 sis.setReadListener(this); 96 sos.setWriteListener(this); 97 } 98 99 @Override onDataAvailable()100 public void onDataAvailable() throws IOException { 101 int read = 0; 102 // Loop as long as there is data to read. If isReady() returns false 103 // the socket will be added to the poller and onDataAvailable() will 104 // be called again as soon as there is more data to read. 105 while (sis.isReady() && read > -1) { 106 read = sis.read(buffer); 107 if (read > 0) { 108 totalBytesRead += read; 109 } 110 } 111 } 112 113 @Override onAllDataRead()114 public void onAllDataRead() throws IOException { 115 readFinished = true; 116 117 // If sos is not ready to write data, the call to isReady() will 118 // register the socket with the poller which will trigger a call to 119 // onWritePossible() when the socket is ready to have data written 120 // to it. 121 if (sos.isReady()) { 122 onWritePossible(); 123 } 124 } 125 126 @Override onWritePossible()127 public void onWritePossible() throws IOException { 128 if (readFinished) { 129 // Must be ready to write data if onWritePossible was called 130 String msg = "Total bytes written = [" + totalBytesRead + "]"; 131 sos.write(msg.getBytes(StandardCharsets.UTF_8)); 132 ac.complete(); 133 } 134 } 135 136 @Override onError(Throwable throwable)137 public void onError(Throwable throwable) { 138 // Should probably log the throwable 139 ac.complete(); 140 } 141 } 142 } 143