46
46
* Implementation of {@link HttpMessageConverter} to read and write 'normal' HTML
47
47
* forms and also to write (but not read) multipart data (e.g. file uploads).
48
48
*
49
- * <p>In other words this converter can read and write the
49
+ * <p>In other words, this converter can read and write the
50
50
* {@code "application/x-www-form-urlencoded"} media type as
51
51
* {@link MultiValueMap MultiValueMap<String, String>} and it can also
52
52
* write (but not read) the {@code "multipart/form-data"} media type as
53
53
* {@link MultiValueMap MultiValueMap<String, Object>}.
54
54
*
55
- * <p>When writing multipart data this converter uses other
55
+ * <p>When writing multipart data, this converter uses other
56
56
* {@link HttpMessageConverter HttpMessageConverters} to write the respective
57
- * MIME parts. By default basic converters are registered (for {@code Strings}
57
+ * MIME parts. By default, basic converters are registered (for {@code Strings}
58
58
* and {@code Resources}). These can be overridden through the
59
- * {@link #setPartConverters(java.util.List) partConverters} property.
59
+ * {@link #setPartConverters partConverters} property.
60
60
*
61
- * <p>For example the following snippet shows how to submit an HTML form:
61
+ * <p>For example, the following snippet shows how to submit an HTML form:
62
62
* <pre class="code">
63
- * RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
63
+ * RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
64
64
* MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
65
- * form.add("field 1", "value 1"); form.add("field 2", "value 2");
65
+ * form.add("field 1", "value 1");
66
+ * form.add("field 2", "value 2");
66
67
* form.add("field 2", "value 3");
67
68
* template.postForLocation("http://example.com/myForm", form);
68
69
* </pre>
75
76
* template.postForLocation("http://example.com/myFileUpload", parts);
76
77
* </pre>
77
78
*
78
- * <p>Some methods in this class were inspired by {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}.
79
+ * <p>Some methods in this class were inspired by
80
+ * {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}.
79
81
*
80
82
* @author Arjen Poutsma
81
83
* @author Rossen Stoyanchev
85
87
public class FormHttpMessageConverter implements HttpMessageConverter <MultiValueMap <String , ?>> {
86
88
87
89
private static final byte [] BOUNDARY_CHARS =
88
- new byte []{'-' , '_' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' ,
90
+ new byte [] {'-' , '_' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '0' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' ,
89
91
'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 'A' ,
90
92
'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' ,
91
93
'V' , 'W' , 'X' , 'Y' , 'Z' };
92
94
93
- private final Random random = new Random ();
94
95
95
96
private Charset charset = Charset .forName ("UTF-8" );
96
97
@@ -100,6 +101,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
100
101
101
102
private List <HttpMessageConverter <?>> partConverters = new ArrayList <HttpMessageConverter <?>>();
102
103
104
+ private final Random random = new Random ();
105
+
103
106
104
107
public FormHttpMessageConverter () {
105
108
this .supportedMediaTypes .add (MediaType .APPLICATION_FORM_URLENCODED );
@@ -157,7 +160,7 @@ public void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
157
160
}
158
161
159
162
/**
160
- * Add a message body converter. Such a converters is used to convert objects
163
+ * Add a message body converter. Such a converter is used to convert objects
161
164
* to MIME parts.
162
165
*/
163
166
public void addPartConverter (HttpMessageConverter <?> partConverter ) {
@@ -236,6 +239,7 @@ public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutpu
236
239
}
237
240
}
238
241
242
+
239
243
private boolean isMultipart (MultiValueMap <String , ?> map , MediaType contentType ) {
240
244
if (contentType != null ) {
241
245
return MediaType .MULTIPART_FORM_DATA .equals (contentType );
@@ -285,56 +289,37 @@ private void writeForm(MultiValueMap<String, String> form, MediaType contentType
285
289
StreamUtils .copy (bytes , outputMessage .getBody ());
286
290
}
287
291
288
- private void writeMultipart (MultiValueMap <String , Object > parts , HttpOutputMessage outputMessage )
289
- throws IOException {
290
-
292
+ private void writeMultipart (MultiValueMap <String , Object > parts , HttpOutputMessage outputMessage ) throws IOException {
291
293
byte [] boundary = generateMultipartBoundary ();
292
-
293
294
Map <String , String > parameters = Collections .singletonMap ("boundary" , new String (boundary , "US-ASCII" ));
295
+
294
296
MediaType contentType = new MediaType (MediaType .MULTIPART_FORM_DATA , parameters );
295
297
outputMessage .getHeaders ().setContentType (contentType );
296
298
297
299
writeParts (outputMessage .getBody (), parts , boundary );
298
- writeEnd (boundary , outputMessage .getBody ());
300
+ writeEnd (outputMessage .getBody (), boundary );
299
301
}
300
302
301
303
private void writeParts (OutputStream os , MultiValueMap <String , Object > parts , byte [] boundary ) throws IOException {
302
304
for (Map .Entry <String , List <Object >> entry : parts .entrySet ()) {
303
305
String name = entry .getKey ();
304
306
for (Object part : entry .getValue ()) {
305
307
if (part != null ) {
306
- writeBoundary (boundary , os );
307
- HttpEntity <?> entity = getEntity (part );
308
- writePart (name , entity , os );
308
+ writeBoundary (os , boundary );
309
+ writePart (name , getHttpEntity (part ), os );
309
310
writeNewLine (os );
310
311
}
311
312
}
312
313
}
313
314
}
314
315
315
- private void writeBoundary (byte [] boundary , OutputStream os ) throws IOException {
316
- os .write ('-' );
317
- os .write ('-' );
318
- os .write (boundary );
319
- writeNewLine (os );
320
- }
321
-
322
- private HttpEntity <?> getEntity (Object part ) {
323
- if (part instanceof HttpEntity ) {
324
- return (HttpEntity <?>) part ;
325
- }
326
- else {
327
- return new HttpEntity <Object >(part );
328
- }
329
- }
330
-
331
316
@ SuppressWarnings ("unchecked" )
332
317
private void writePart (String name , HttpEntity <?> partEntity , OutputStream os ) throws IOException {
333
318
Object partBody = partEntity .getBody ();
334
319
Class <?> partType = partBody .getClass ();
335
320
HttpHeaders partHeaders = partEntity .getHeaders ();
336
321
MediaType partContentType = partHeaders .getContentType ();
337
- for (HttpMessageConverter <?> messageConverter : partConverters ) {
322
+ for (HttpMessageConverter <?> messageConverter : this . partConverters ) {
338
323
if (messageConverter .canWrite (partType , partContentType )) {
339
324
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage (os );
340
325
multipartMessage .getHeaders ().setContentDispositionFormData (name , getFilename (partBody ));
@@ -345,38 +330,39 @@ private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) t
345
330
return ;
346
331
}
347
332
}
348
- throw new HttpMessageNotWritableException (
349
- "Could not write request: no suitable HttpMessageConverter found for request type [" +
350
- partType .getName () + "]" );
333
+ throw new HttpMessageNotWritableException ("Could not write request: no suitable HttpMessageConverter " +
334
+ "found for request type [" + partType .getName () + "]" );
351
335
}
352
336
353
- private void writeEnd (byte [] boundary , OutputStream os ) throws IOException {
354
- os .write ('-' );
355
- os .write ('-' );
356
- os .write (boundary );
357
- os .write ('-' );
358
- os .write ('-' );
359
- writeNewLine (os );
360
- }
361
-
362
- private void writeNewLine (OutputStream os ) throws IOException {
363
- os .write ('\r' );
364
- os .write ('\n' );
365
- }
366
337
367
338
/**
368
339
* Generate a multipart boundary.
369
340
* <p>The default implementation returns a random boundary.
370
341
* Can be overridden in subclasses.
371
342
*/
372
343
protected byte [] generateMultipartBoundary () {
373
- byte [] boundary = new byte [random .nextInt (11 ) + 30 ];
344
+ byte [] boundary = new byte [this . random .nextInt (11 ) + 30 ];
374
345
for (int i = 0 ; i < boundary .length ; i ++) {
375
- boundary [i ] = BOUNDARY_CHARS [random .nextInt (BOUNDARY_CHARS .length )];
346
+ boundary [i ] = BOUNDARY_CHARS [this . random .nextInt (BOUNDARY_CHARS .length )];
376
347
}
377
348
return boundary ;
378
349
}
379
350
351
+ /**
352
+ * Return an {@link HttpEntity} for the given part Object.
353
+ * @param part the part to return an {@link HttpEntity} for
354
+ * @return the part Object itself it is an {@link HttpEntity},
355
+ * or a newly built {@link HttpEntity} wrapper for that part
356
+ */
357
+ protected HttpEntity <?> getHttpEntity (Object part ) {
358
+ if (part instanceof HttpEntity ) {
359
+ return (HttpEntity <?>) part ;
360
+ }
361
+ else {
362
+ return new HttpEntity <Object >(part );
363
+ }
364
+ }
365
+
380
366
/**
381
367
* Return the filename of the given multipart part. This value will be used for the
382
368
* {@code Content-Disposition} header.
@@ -400,11 +386,33 @@ protected String getFilename(Object part) {
400
386
}
401
387
402
388
389
+ private void writeBoundary (OutputStream os , byte [] boundary ) throws IOException {
390
+ os .write ('-' );
391
+ os .write ('-' );
392
+ os .write (boundary );
393
+ writeNewLine (os );
394
+ }
395
+
396
+ private static void writeEnd (OutputStream os , byte [] boundary ) throws IOException {
397
+ os .write ('-' );
398
+ os .write ('-' );
399
+ os .write (boundary );
400
+ os .write ('-' );
401
+ os .write ('-' );
402
+ writeNewLine (os );
403
+ }
404
+
405
+ private static void writeNewLine (OutputStream os ) throws IOException {
406
+ os .write ('\r' );
407
+ os .write ('\n' );
408
+ }
409
+
410
+
403
411
/**
404
412
* Implementation of {@link org.springframework.http.HttpOutputMessage} used
405
413
* to write a MIME multipart.
406
414
*/
407
- private class MultipartHttpOutputMessage implements HttpOutputMessage {
415
+ private static class MultipartHttpOutputMessage implements HttpOutputMessage {
408
416
409
417
private final OutputStream outputStream ;
410
418
0 commit comments