Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
771 blopes 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
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
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
 
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
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
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
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
137
        public void onError(Throwable throwable) {
138
            // Should probably log the throwable
139
            ac.complete();
140
        }
141
    }
142
}