View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.io;
29  
30  import java.io.IOException;
31  import java.io.OutputStream;
32  
33  import org.apache.http.annotation.NotThreadSafe;
34  import org.apache.http.io.SessionOutputBuffer;
35  import org.apache.http.util.Args;
36  
37  /**
38   * Output stream that cuts off after a defined number of bytes. This class
39   * is used to send content of HTTP messages where the end of the content entity
40   * is determined by the value of the {@code Content-Length header}.
41   * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE}
42   * long.
43   * <p>
44   * Note that this class NEVER closes the underlying stream, even when close
45   * gets called.  Instead, the stream will be marked as closed and no further
46   * output will be permitted.
47   *
48   * @since 4.0
49   */
50  @NotThreadSafe
51  public class ContentLengthOutputStream extends OutputStream {
52  
53      /**
54       * Wrapped session output buffer.
55       */
56      private final SessionOutputBuffer out;
57  
58      /**
59       * The maximum number of bytes that can be written the stream. Subsequent
60       * write operations will be ignored.
61       */
62      private final long contentLength;
63  
64      /** Total bytes written */
65      private long total = 0;
66  
67      /** True if the stream is closed. */
68      private boolean closed = false;
69  
70      /**
71       * Wraps a session output buffer and cuts off output after a defined number
72       * of bytes.
73       *
74       * @param out The session output buffer
75       * @param contentLength The maximum number of bytes that can be written to
76       * the stream. Subsequent write operations will be ignored.
77       *
78       * @since 4.0
79       */
80      public ContentLengthOutputStream(final SessionOutputBuffer out, final long contentLength) {
81          super();
82          this.out = Args.notNull(out, "Session output buffer");
83          this.contentLength = Args.notNegative(contentLength, "Content length");
84      }
85  
86      /**
87       * <p>Does not close the underlying socket output.</p>
88       *
89       * @throws IOException If an I/O problem occurs.
90       */
91      @Override
92      public void close() throws IOException {
93          if (!this.closed) {
94              this.closed = true;
95              this.out.flush();
96          }
97      }
98  
99      @Override
100     public void flush() throws IOException {
101         this.out.flush();
102     }
103 
104     @Override
105     public void write(final byte[] b, final int off, final int len) throws IOException {
106         if (this.closed) {
107             throw new IOException("Attempted write to closed stream.");
108         }
109         if (this.total < this.contentLength) {
110             final long max = this.contentLength - this.total;
111             int chunk = len;
112             if (chunk > max) {
113                 chunk = (int) max;
114             }
115             this.out.write(b, off, chunk);
116             this.total += chunk;
117         }
118     }
119 
120     @Override
121     public void write(final byte[] b) throws IOException {
122         write(b, 0, b.length);
123     }
124 
125     @Override
126     public void write(final int b) throws IOException {
127         if (this.closed) {
128             throw new IOException("Attempted write to closed stream.");
129         }
130         if (this.total < this.contentLength) {
131             this.out.write(b);
132             this.total++;
133         }
134     }
135 
136 }