View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http.multipart;
17  
18  import io.netty.handler.codec.http.DefaultHttpRequest;
19  import io.netty.handler.codec.http.HttpConstants;
20  import io.netty.handler.codec.http.HttpRequest;
21  
22  import java.io.IOException;
23  import java.nio.charset.Charset;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.IdentityHashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  
32  /**
33   * Default factory giving {@link Attribute} and {@link FileUpload} according to constructor.
34   *
35   * <p>According to the constructor, {@link Attribute} and {@link FileUpload} can be:</p>
36   * <ul>
37   * <li>MemoryAttribute, DiskAttribute or MixedAttribute</li>
38   * <li>MemoryFileUpload, DiskFileUpload or MixedFileUpload</li>
39   * </ul>
40   */
41  public class DefaultHttpDataFactory implements HttpDataFactory {
42  
43      /**
44       * Proposed default MINSIZE as 16 KB.
45       */
46      public static final long MINSIZE = 0x4000;
47      /**
48       * Proposed default MAXSIZE = -1 as UNLIMITED
49       */
50      public static final long MAXSIZE = -1;
51  
52      private final boolean useDisk;
53  
54      private final boolean checkSize;
55  
56      private long minSize;
57  
58      private long maxSize = MAXSIZE;
59  
60      private Charset charset = HttpConstants.DEFAULT_CHARSET;
61  
62      /**
63       * Keep all {@link HttpData}s until cleaning methods are called.
64       * We need to use {@link IdentityHashMap} because different requests may be equal.
65       * See {@link DefaultHttpRequest#hashCode} and {@link DefaultHttpRequest#equals}.
66       * Similarly, when removing data items, we need to check their identities because
67       * different data items may be equal.
68       */
69      private final Map<HttpRequest, List<HttpData>> requestFileDeleteMap =
70              Collections.synchronizedMap(new IdentityHashMap<HttpRequest, List<HttpData>>());
71  
72      /**
73       * HttpData will be in memory if less than default size (16KB).
74       * The type will be Mixed.
75       */
76      public DefaultHttpDataFactory() {
77          useDisk = false;
78          checkSize = true;
79          minSize = MINSIZE;
80      }
81  
82      public DefaultHttpDataFactory(Charset charset) {
83          this();
84          this.charset = charset;
85      }
86  
87      /**
88       * HttpData will be always on Disk if useDisk is True, else always in Memory if False
89       */
90      public DefaultHttpDataFactory(boolean useDisk) {
91          this.useDisk = useDisk;
92          checkSize = false;
93      }
94  
95      public DefaultHttpDataFactory(boolean useDisk, Charset charset) {
96          this(useDisk);
97          this.charset = charset;
98      }
99      /**
100      * HttpData will be on Disk if the size of the file is greater than minSize, else it
101      * will be in memory. The type will be Mixed.
102      */
103     public DefaultHttpDataFactory(long minSize) {
104         useDisk = false;
105         checkSize = true;
106         this.minSize = minSize;
107     }
108 
109     public DefaultHttpDataFactory(long minSize, Charset charset) {
110         this(minSize);
111         this.charset = charset;
112     }
113 
114     @Override
115     public void setMaxLimit(long maxSize) {
116         this.maxSize = maxSize;
117     }
118 
119     /**
120      * @return the associated list of {@link HttpData} for the request
121      */
122     private List<HttpData> getList(HttpRequest request) {
123         List<HttpData> list = requestFileDeleteMap.get(request);
124         if (list == null) {
125             list = new ArrayList<HttpData>();
126             requestFileDeleteMap.put(request, list);
127         }
128         return list;
129     }
130 
131     @Override
132     public Attribute createAttribute(HttpRequest request, String name) {
133         if (useDisk) {
134             Attribute attribute = new DiskAttribute(name, charset);
135             attribute.setMaxSize(maxSize);
136             List<HttpData> list = getList(request);
137             list.add(attribute);
138             return attribute;
139         }
140         if (checkSize) {
141             Attribute attribute = new MixedAttribute(name, minSize, charset);
142             attribute.setMaxSize(maxSize);
143             List<HttpData> list = getList(request);
144             list.add(attribute);
145             return attribute;
146         }
147         MemoryAttribute attribute = new MemoryAttribute(name);
148         attribute.setMaxSize(maxSize);
149         return attribute;
150     }
151 
152     @Override
153     public Attribute createAttribute(HttpRequest request, String name, long definedSize) {
154         if (useDisk) {
155             Attribute attribute = new DiskAttribute(name, definedSize, charset);
156             attribute.setMaxSize(maxSize);
157             List<HttpData> list = getList(request);
158             list.add(attribute);
159             return attribute;
160         }
161         if (checkSize) {
162             Attribute attribute = new MixedAttribute(name, definedSize, minSize, charset);
163             attribute.setMaxSize(maxSize);
164             List<HttpData> list = getList(request);
165             list.add(attribute);
166             return attribute;
167         }
168         MemoryAttribute attribute = new MemoryAttribute(name, definedSize);
169         attribute.setMaxSize(maxSize);
170         return attribute;
171     }
172 
173     /**
174      * Utility method
175      */
176     private static void checkHttpDataSize(HttpData data) {
177         try {
178             data.checkSize(data.length());
179         } catch (IOException ignored) {
180             throw new IllegalArgumentException("Attribute bigger than maxSize allowed");
181         }
182     }
183 
184     @Override
185     public Attribute createAttribute(HttpRequest request, String name, String value) {
186         if (useDisk) {
187             Attribute attribute;
188             try {
189                 attribute = new DiskAttribute(name, value, charset);
190                 attribute.setMaxSize(maxSize);
191             } catch (IOException e) {
192                 // revert to Mixed mode
193                 attribute = new MixedAttribute(name, value, minSize, charset);
194                 attribute.setMaxSize(maxSize);
195             }
196             checkHttpDataSize(attribute);
197             List<HttpData> list = getList(request);
198             list.add(attribute);
199             return attribute;
200         }
201         if (checkSize) {
202             Attribute attribute = new MixedAttribute(name, value, minSize, charset);
203             attribute.setMaxSize(maxSize);
204             checkHttpDataSize(attribute);
205             List<HttpData> list = getList(request);
206             list.add(attribute);
207             return attribute;
208         }
209         try {
210             MemoryAttribute attribute = new MemoryAttribute(name, value, charset);
211             attribute.setMaxSize(maxSize);
212             checkHttpDataSize(attribute);
213             return attribute;
214         } catch (IOException e) {
215             throw new IllegalArgumentException(e);
216         }
217     }
218 
219     @Override
220     public FileUpload createFileUpload(HttpRequest request, String name, String filename,
221             String contentType, String contentTransferEncoding, Charset charset,
222             long size) {
223         if (useDisk) {
224             FileUpload fileUpload = new DiskFileUpload(name, filename, contentType,
225                     contentTransferEncoding, charset, size);
226             fileUpload.setMaxSize(maxSize);
227             checkHttpDataSize(fileUpload);
228             List<HttpData> list = getList(request);
229             list.add(fileUpload);
230             return fileUpload;
231         }
232         if (checkSize) {
233             FileUpload fileUpload = new MixedFileUpload(name, filename, contentType,
234                     contentTransferEncoding, charset, size, minSize);
235             fileUpload.setMaxSize(maxSize);
236             checkHttpDataSize(fileUpload);
237             List<HttpData> list = getList(request);
238             list.add(fileUpload);
239             return fileUpload;
240         }
241         MemoryFileUpload fileUpload = new MemoryFileUpload(name, filename, contentType,
242                 contentTransferEncoding, charset, size);
243         fileUpload.setMaxSize(maxSize);
244         checkHttpDataSize(fileUpload);
245         return fileUpload;
246     }
247 
248     @Override
249     public void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data) {
250         if (!(data instanceof HttpData)) {
251             return;
252         }
253 
254         // Do not use getList because it adds empty list to requestFileDeleteMap
255         // if request is not found
256         List<HttpData> list = requestFileDeleteMap.get(request);
257         if (list == null) {
258             return;
259         }
260 
261         // Can't simply call list.remove(data), because different data items may be equal.
262         // Need to check identity.
263         Iterator<HttpData> i = list.iterator();
264         while (i.hasNext()) {
265             HttpData n = i.next();
266             if (n == data) {
267                 i.remove();
268 
269                 // Remove empty list to avoid memory leak
270                 if (list.isEmpty()) {
271                     requestFileDeleteMap.remove(request);
272                 }
273 
274                 return;
275             }
276         }
277     }
278 
279     @Override
280     public void cleanRequestHttpData(HttpRequest request) {
281         List<HttpData> list = requestFileDeleteMap.remove(request);
282         if (list != null) {
283             for (HttpData data : list) {
284                 data.release();
285             }
286         }
287     }
288 
289     @Override
290     public void cleanAllHttpData() {
291         Iterator<Entry<HttpRequest, List<HttpData>>> i = requestFileDeleteMap.entrySet().iterator();
292         while (i.hasNext()) {
293             Entry<HttpRequest, List<HttpData>> e = i.next();
294 
295             // Calling i.remove() here will cause "java.lang.IllegalStateException: Entry was removed"
296             // at e.getValue() below
297 
298             List<HttpData> list = e.getValue();
299             for (HttpData data : list) {
300                 data.release();
301             }
302 
303             i.remove();
304         }
305     }
306 
307     @Override
308     public void cleanRequestHttpDatas(HttpRequest request) {
309         cleanRequestHttpData(request);
310     }
311 
312     @Override
313     public void cleanAllHttpDatas() {
314         cleanAllHttpData();
315     }
316 }