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 org.apache.coyote.http11;
18
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.util.Arrays;
22
23 import org.apache.coyote.ActionCode;
24 import org.apache.coyote.CloseNowException;
25 import org.apache.coyote.Response;
26 import org.apache.tomcat.util.buf.ByteChunk;
27 import org.apache.tomcat.util.buf.MessageBytes;
28 import org.apache.tomcat.util.net.SocketWrapperBase;
29 import org.apache.tomcat.util.res.StringManager;
30
31 /**
32 * Provides buffering for the HTTP headers (allowing responses to be reset
33 * before they have been committed) and the link to the Socket for writing the
34 * headers (once committed) and the response body. Note that buffering of the
35 * response body happens at a higher level.
36 */
37 public class Http11OutputBuffer implements HttpOutputBuffer {
38
39 // -------------------------------------------------------------- Variables
40
41 /**
42 * The string manager for this package.
43 */
44 protected static final StringManager sm = StringManager.getManager(Http11OutputBuffer.class);
45
46
47 // ----------------------------------------------------- Instance Variables
48
49 /**
50 * Associated Coyote response.
51 */
52 protected final Response response;
53
54
55 /**
56 * Finished flag.
57 */
58 protected boolean responseFinished;
59
60
61 /**
62 * The buffer used for header composition.
63 */
64 protected final ByteBuffer headerBuffer;
65
66
67 /**
68 * Filter library for processing the response body.
69 */
70 protected OutputFilter[] filterLibrary;
71
72
73 /**
74 * Active filters for the current request.
75 */
76 protected OutputFilter[] activeFilters;
77
78
79 /**
80 * Index of the last active filter.
81 */
82 protected int lastActiveFilter;
83
84
85 /**
86 * Underlying output buffer.
87 */
88 protected HttpOutputBuffer outputStreamOutputBuffer;
89
90
91 /**
92 * Wrapper for socket where data will be written to.
93 */
94 protected SocketWrapperBase<?> socketWrapper;
95
96
97 /**
98 * Bytes written to client for the current request
99 */
100 protected long byteCount = 0;
101
102
103 protected Http11OutputBuffer(Response response, int headerBufferSize) {
104
105 this.response = response;
106
107 headerBuffer = ByteBuffer.allocate(headerBufferSize);
108
109 filterLibrary = new OutputFilter[0];
110 activeFilters = new OutputFilter[0];
111 lastActiveFilter = -1;
112
113 responseFinished = false;
114
115 outputStreamOutputBuffer = new SocketOutputBuffer();
116 }
117
118
119 // ------------------------------------------------------------- Properties
120
121 /**
122 * Add an output filter to the filter library. Note that calling this method
123 * resets the currently active filters to none.
124 *
125 * @param filter The filter to add
126 */
127 public void addFilter(OutputFilter filter) {
128
129 OutputFilter[] newFilterLibrary = Arrays.copyOf(filterLibrary, filterLibrary.length + 1);
130 newFilterLibrary[filterLibrary.length] = filter;
131 filterLibrary = newFilterLibrary;
132
133 activeFilters = new OutputFilter[filterLibrary.length];
134 }
135
136
137 /**
138 * Get filters.
139 *
140 * @return The current filter library containing all possible filters
141 */
142 public OutputFilter[] getFilters() {
143 return filterLibrary;
144 }
145
146
147 /**
148 * Add an output filter to the active filters for the current response.
149 * <p>
150 * The filter does not have to be present in {@link #getFilters()}.
151 * <p>
152 * A filter can only be added to a response once. If the filter has already
153 * been added to this response then this method will be a NO-OP.
154 *
155 * @param filter The filter to add
156 */
157 public void addActiveFilter(OutputFilter filter) {
158
159 if (lastActiveFilter == -1) {
160 filter.setBuffer(outputStreamOutputBuffer);
161 } else {
162 for (int i = 0; i <= lastActiveFilter; i++) {
163 if (activeFilters[i] == filter)
164 return;
165 }
166 filter.setBuffer(activeFilters[lastActiveFilter]);
167 }
168
169 activeFilters[++lastActiveFilter] = filter;
170
171 filter.setResponse(response);
172 }
173
174
175 // --------------------------------------------------- OutputBuffer Methods
176
177 @Override
178 public int doWrite(ByteBuffer chunk) throws IOException {
179
180 if (!response.isCommitted()) {
181 // Send the connector a request for commit. The connector should
182 // then validate the headers, send them (using sendHeaders) and
183 // set the filters accordingly.
184 response.action(ActionCode.COMMIT, null);
185 }
186
187 if (lastActiveFilter == -1) {
188 return outputStreamOutputBuffer.doWrite(chunk);
189 } else {
190 return activeFilters[lastActiveFilter].doWrite(chunk);
191 }
192 }
193
194
195 @Override
196 public long getBytesWritten() {
197 if (lastActiveFilter == -1) {
198 return outputStreamOutputBuffer.getBytesWritten();
199 } else {
200 return activeFilters[lastActiveFilter].getBytesWritten();
201 }
202 }
203
204
205 // ----------------------------------------------- HttpOutputBuffer Methods
206
207 /**
208 * Flush the response.
209 *
210 * @throws IOException an underlying I/O error occurred
211 */
212 @Override
213 public void flush() throws IOException {
214 if (lastActiveFilter == -1) {
215 outputStreamOutputBuffer.flush();
216 } else {
217 activeFilters[lastActiveFilter].flush();
218 }
219 }
220
221
222 @Override
223 public void end() throws IOException {
224 if (responseFinished) {
225 return;
226 }
227
228 if (lastActiveFilter == -1) {
229 outputStreamOutputBuffer.end();
230 } else {
231 activeFilters[lastActiveFilter].end();
232 }
233
234 responseFinished = true;
235 }
236
237
238 // --------------------------------------------------------- Public Methods
239
240 /**
241 * Reset the header buffer if an error occurs during the writing of the
242 * headers so the error response can be written.
243 */
244 void resetHeaderBuffer() {
245 headerBuffer.position(0).limit(headerBuffer.capacity());
246 }
247
248
249 /**
250 * Recycle the output buffer. This should be called when closing the
251 * connection.
252 */
253 public void recycle() {
254 nextRequest();
255 socketWrapper = null;
256 }
257
258
259 /**
260 * End processing of current HTTP request.
261 * Note: All bytes of the current request should have been already
262 * consumed. This method only resets all the pointers so that we are ready
263 * to parse the next HTTP request.
264 */
265 public void nextRequest() {
266 // Recycle filters
267 for (int i = 0; i <= lastActiveFilter; i++) {
268 activeFilters[i].recycle();
269 }
270 // Recycle response object
271 response.recycle();
272 // Reset pointers
273 headerBuffer.position(0).limit(headerBuffer.capacity());
274 lastActiveFilter = -1;
275 responseFinished = false;
276 byteCount = 0;
277 }
278
279
280 public void init(SocketWrapperBase<?> socketWrapper) {
281 this.socketWrapper = socketWrapper;
282 }
283
284
285 public void sendAck() throws IOException {
286 if (!response.isCommitted()) {
287 socketWrapper.write(isBlocking(), Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length);
288 if (flushBuffer(true)) {
289 throw new IOException(sm.getString("iob.failedwrite.ack"));
290 }
291 }
292 }
293
294
295 /**
296 * Commit the response.
297 *
298 * @throws IOException an underlying I/O error occurred
299 */
300 protected void commit() throws IOException {
301 response.setCommitted(true);
302
303 if (headerBuffer.position() > 0) {
304 // Sending the response header buffer
305 headerBuffer.flip();
306 try {
307 SocketWrapperBase<?> socketWrapper = this.socketWrapper;
308 if (socketWrapper != null) {
309 socketWrapper.write(isBlocking(), headerBuffer);
310 } else {
311 throw new CloseNowException(sm.getString("iob.failedwrite"));
312 }
313 } finally {
314 headerBuffer.position(0).limit(headerBuffer.capacity());
315 }
316 }
317 }
318
319
320 /**
321 * Send the response status line.
322 */
323 public void sendStatus() {
324 // Write protocol name
325 write(Constants.HTTP_11_BYTES);
326 headerBuffer.put(Constants.SP);
327
328 // Write status code
329 int status = response.getStatus();
330 switch (status) {
331 case 200:
332 write(Constants._200_BYTES);
333 break;
334 case 400:
335 write(Constants._400_BYTES);
336 break;
337 case 404:
338 write(Constants._404_BYTES);
339 break;
340 default:
341 write(status);
342 }
343
344 headerBuffer.put(Constants.SP);
345
346 // The reason phrase is optional but the space before it is not. Skip
347 // sending the reason phrase. Clients should ignore it (RFC 7230) and it
348 // just wastes bytes.
349
350 headerBuffer.put(Constants.CR).put(Constants.LF);
351 }
352
353
354 /**
355 * Send a header.
356 *
357 * @param name Header name
358 * @param value Header value
359 */
360 public void sendHeader(MessageBytes name, MessageBytes value) {
361 write(name);
362 headerBuffer.put(Constants.COLON).put(Constants.SP);
363 write(value);
364 headerBuffer.put(Constants.CR).put(Constants.LF);
365 }
366
367
368 /**
369 * End the header block.
370 */
371 public void endHeaders() {
372 headerBuffer.put(Constants.CR).put(Constants.LF);
373 }
374
375
376 /**
377 * This method will write the contents of the specified message bytes
378 * buffer to the output stream, without filtering. This method is meant to
379 * be used to write the response header.
380 *
381 * @param mb data to be written
382 */
383 private void write(MessageBytes mb) {
384 if (mb.getType() != MessageBytes.T_BYTES) {
385 mb.toBytes();
386 ByteChunk bc = mb.getByteChunk();
387 // Need to filter out CTLs excluding TAB. ISO-8859-1 and UTF-8
388 // values will be OK. Strings using other encodings may be
389 // corrupted.
390 byte[] buffer = bc.getBuffer();
391 for (int i = bc.getOffset(); i < bc.getLength(); i++) {
392 // byte values are signed i.e. -128 to 127
393 // The values are used unsigned. 0 to 31 are CTLs so they are
394 // filtered (apart from TAB which is 9). 127 is a control (DEL).
395 // The values 128 to 255 are all OK. Converting those to signed
396 // gives -128 to -1.
397 if ((buffer[i] > -1 && buffer[i] <= 31 && buffer[i] != 9) ||
398 buffer[i] == 127) {
399 buffer[i] = ' ';
400 }
401 }
402 }
403 write(mb.getByteChunk());
404 }
405
406
407 /**
408 * This method will write the contents of the specified byte chunk to the
409 * output stream, without filtering. This method is meant to be used to
410 * write the response header.
411 *
412 * @param bc data to be written
413 */
414 private void write(ByteChunk bc) {
415 // Writing the byte chunk to the output buffer
416 int length = bc.getLength();
417 checkLengthBeforeWrite(length);
418 headerBuffer.put(bc.getBytes(), bc.getStart(), length);
419 }
420
421
422 /**
423 * This method will write the contents of the specified byte
424 * buffer to the output stream, without filtering. This method is meant to
425 * be used to write the response header.
426 *
427 * @param b data to be written
428 */
429 public void write(byte[] b) {
430 checkLengthBeforeWrite(b.length);
431
432 // Writing the byte chunk to the output buffer
433 headerBuffer.put(b);
434 }
435
436
437 /**
438 * This method will write the specified integer to the output stream. This
439 * method is meant to be used to write the response header.
440 *
441 * @param value data to be written
442 */
443 private void write(int value) {
444 // From the Tomcat 3.3 HTTP/1.0 connector
445 String s = Integer.toString(value);
446 int len = s.length();
447 checkLengthBeforeWrite(len);
448 for (int i = 0; i < len; i++) {
449 char c = s.charAt (i);
450 headerBuffer.put((byte) c);
451 }
452 }
453
454
455 /**
456 * Checks to see if there is enough space in the buffer to write the
457 * requested number of bytes.
458 */
459 private void checkLengthBeforeWrite(int length) {
460 // "+ 4": BZ 57509. Reserve space for CR/LF/COLON/SP characters that
461 // are put directly into the buffer following this write operation.
462 if (headerBuffer.position() + length + 4 > headerBuffer.capacity()) {
463 throw new HeadersTooLargeException(
464 sm.getString("iob.responseheadertoolarge.error"));
465 }
466 }
467
468
469 //------------------------------------------------------ Non-blocking writes
470
471 /**
472 * Writes any remaining buffered data.
473 *
474 * @param block Should this method block until the buffer is empty
475 * @return <code>true</code> if data remains in the buffer (which can only
476 * happen in non-blocking mode) else <code>false</code>.
477 * @throws IOException Error writing data
478 */
479 protected boolean flushBuffer(boolean block) throws IOException {
480 return socketWrapper.flush(block);
481 }
482
483
484 /**
485 * Is standard Servlet blocking IO being used for output?
486 * @return <code>true</code> if this is blocking IO
487 */
488 protected final boolean isBlocking() {
489 return response.getWriteListener() == null;
490 }
491
492
493 protected final boolean isReady() {
494 boolean result = !hasDataToWrite();
495 if (!result) {
496 socketWrapper.registerWriteInterest();
497 }
498 return result;
499 }
500
501
502 public boolean hasDataToWrite() {
503 return socketWrapper.hasDataToWrite();
504 }
505
506
507 public void registerWriteInterest() {
508 socketWrapper.registerWriteInterest();
509 }
510
511
512 boolean isChunking() {
513 for (int i = 0; i < lastActiveFilter; i++) {
514 if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) {
515 return true;
516 }
517 }
518 return false;
519 }
520
521
522 // ------------------------------------------ SocketOutputBuffer Inner Class
523
524 /**
525 * This class is an output buffer which will write data to a socket.
526 */
527 protected class SocketOutputBuffer implements HttpOutputBuffer {
528
529 /**
530 * Write chunk.
531 */
532 @Override
533 public int doWrite(ByteBuffer chunk) throws IOException {
534 try {
535 int len = chunk.remaining();
536 SocketWrapperBase<?> socketWrapper = Http11OutputBuffer.this.socketWrapper;
537 if (socketWrapper != null) {
538 socketWrapper.write(isBlocking(), chunk);
539 } else {
540 throw new CloseNowException(sm.getString("iob.failedwrite"));
541 }
542 len -= chunk.remaining();
543 byteCount += len;
544 return len;
545 } catch (IOException ioe) {
546 response.action(ActionCode.CLOSE_NOW, ioe);
547 // Re-throw
548 throw ioe;
549 }
550 }
551
552 @Override
553 public long getBytesWritten() {
554 return byteCount;
555 }
556
557 @Override
558 public void end() throws IOException {
559 socketWrapper.flush(true);
560 }
561
562 @Override
563 public void flush() throws IOException {
564 socketWrapper.flush(isBlocking());
565 }
566 }
567 }
568