From 4f2e54fc2dfe6d30336f6b2390cc563ee9bc5343 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 16 Jan 2018 16:53:19 -0500 Subject: [PATCH] Document multipart data support for WebFlux Issue: SPR-16040 --- src/docs/asciidoc/web/webflux.adoc | 227 ++++++++++++++++++++++++----- src/docs/asciidoc/web/webmvc.adoc | 16 +- 2 files changed, 195 insertions(+), 48 deletions(-) diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index f2e4c1a28a..c6d50b5723 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -372,58 +372,117 @@ This can be automated through the use of [[webflux-web-handler-api]] === WebHandler API -`HttpHandler` is the basis for running on different servers. On that base the WebHandler -API provides a slightly higher level processing chain of -exception handlers -({api-spring-framework}/web/server/WebExceptionHandler.html[WebExceptionHandler]), filters -({api-spring-framework}/web/server/WebFilter.html[WebFilter]), and a target handler -({api-spring-framework}/web/server/WebHandler.html[WebHandler]). - -All components work on `ServerWebExchange` -- a container for the HTTP request and -response that also adds request attributes, session attributes, access to form data, -multipart data, and more. - -The processing chain can be put together with `WebHttpHandlerBuilder` which builds an -`HttpHandler` that in turn can be run with a <>. -To use the builder either add components individually or point to an `ApplicationContext` -to have the following detected: +`HttpHandler` is the lowest level contract for running on different HTTP servers. +On top of that foundation, the WebHandler API provides a slightly higher level, but +still general purpose, set of components that form a chain of +{api-spring-framework}/web/server/WebExceptionHandler.html[WebExceptionHandler's], +{api-spring-framework}/web/server/WebFilter.html[WebFilter's], and a +{api-spring-framework}/web/server/WebHandler.html[WebHandler]. + +All WebHandler API components take `ServerWebExchange` as input which goes beyond +`ServerHttpRequest` and `ServerHttpResponse` to provide extra building blocks for +use in web applications such as request attributes, session attributes, access to parsed +form data, multipart data, and more. + +`WebHttpHandlerBuilder` is used to assemble a request processing chain. You can use +methods on the builder to add components manually, or more likely have them detected from +a Spring `ApplicationContext`, with the resulting `HttpHandler` ready to run via a +<>: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +ApplicationContext context = ... +HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build() +---- + +The table below lists the components that `WebHttpHandlerBuilder` detects: [cols="2,2,1,3", options="header"] |=== |Bean name|Bean type|Count|Description -|"webHandler" -|WebHandler -|1 -|Target handler after filters - | -|WebFilter +|`WebExceptionHandler` |0..N -|Filters +|Exception handlers to apply after all ``WebFilter``'s and the target `WebHandler`. | -|WebExceptionHandler +|`WebFilter` |0..N -|Exception handlers after filter chain +|Filters to invoke before and after the target `WebHandler`. + +|"webHandler" +|`WebHandler` +|1 +|The handler for the request. |"webSessionManager" -|WebSessionManager +|`WebSessionManager` |0..1 -|Custom session manager; `DefaultWebSessionManager` by default +|The manager for ``WebSession``'s exposed through a method on `ServerWebExchange`. + +`DefaultWebSessionManager` by default. |"serverCodecConfigurer" -|ServerCodecConfigurer +|`ServerCodecConfigurer` |0..1 -|Custom form and multipart data decoders; `ServerCodecConfigurer.create()` by default +|For access to ``HttpMessageReader``'s for parsing form data and multipart data that's +then exposed through methods on `ServerWebExchange`. + +`ServerCodecConfigurer.create()` by default. |"localeContextResolver" -|LocaleContextResolver +|`LocaleContextResolver` |0..1 -|Custom resolver for `LocaleContext`; `AcceptHeaderLocaleContextResolver` by default +|The resolver for `LocaleContext` exposed through a method on `ServerWebExchange`. + +`AcceptHeaderLocaleContextResolver` by default. |=== +[[webflux-form-data]] +==== Form Reader + +`ServerWebExchange` exposes the following method for access to form data: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +Mono> getFormData(); +---- + +The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse form data +("application/x-www-form-urlencoded") into a `MultiValueMap`. By default +`FormHttpMessageReader` is configured for use via the `ServerCodecConfigurer` bean +(see <>). + + +[[webflux-multipart]] +==== Multipart Reader +[.small]#<># + +`ServerWebExchange` exposes the following method for access to multipart data: +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +Mono> getMultipartData(); +---- + +The `DefaultServerWebExchange` uses the configured +`HttpMessageReader>` to parse "multipart/form-data" content +into a `MultiValueMap`. At present +https://github.com/synchronoss/nio-multipart[Synchronoss NIO Multipart] is the only 3rd +party library supported, and the only library we know for non-blocking parsing of +multipart requests. It is enabled through the `ServerCodecConfigurer` bean +(see <>). + +To parse multipart data in streaming fashion, use the `Flux` returned from an +`HttpMessageReader` instead. For example in an annotated controller use of +`@RequestPart` implies Map-like access to individual parts by name, and hence requires +parsing multipart data in full. By contrast `@RequestBody` can be used to decode the +content to `Flux` without collecting to a `MultiValueMap`. + + [[webflux-codecs]] === HTTP Message Codecs @@ -987,7 +1046,7 @@ Supports reactive types. |`@RequestPart` |For access to a part in a "multipart/form-data" request. Supports reactive types. -// TODO: See <> and <>. +See <> and <>. |`java.util.Map`, `org.springframework.ui.Model`, `org.springframework.ui.ModelMap` |For access to the model that is used in HTML controllers and exposed to templates as @@ -1127,10 +1186,8 @@ can be customized through a `WebDataBinder`, see <>, or by r ==== @RequestParam [.small]#<># -Use the `@RequestParam` annotation to bind Servlet request parameters (i.e. query -parameters or form data) to a method argument in a controller. - -The following code snippet shows the usage: +Use the `@RequestParam` annotation to bind query parameters to a method argument in a +controller. The following code snippet shows the usage: [source,java,indent=0] [subs="verbatim,quotes"] @@ -1153,16 +1210,25 @@ The following code snippet shows the usage: } ---- -Parameters using this annotation are required by default, but you can specify that a -parameter is optional by setting ``@RequestParam``'s `required` flag to `false` or -declare the argument with a `java.util.Optional` wrapper. +[TIP] +==== +Unlike the Servlet API "request paramater" concept that conflate query parameters, form +data, and multiparts into one, in WebFlux each is accessed individually through the +`ServerWebExchange`. While `@RequestParam` binds to query parameters only, you can +use data binding to apply query paramerters, form data, and multiparts to a +<>. +==== + +Method parameters using using the `@RequestParam` annotation are required by default, but +you can specify that a method parameter is optional by setting ``@RequestParam``'s +`required` flag to `false` or by declaring the argument with an `java.util.Optional` +wrapper. Type conversion is applied automatically if the target method parameter type is not `String`. See <>. When an `@RequestParam` annotation is declared as `Map` or -`MultiValueMap` argument, the map is populated with all request -parameters. +`MultiValueMap` argument, the map is populated with all query parameters. Note that use of `@RequestParam` is optional, e.g. to set its attributes. By default any argument that is a simple value type, as determined by @@ -1436,6 +1502,87 @@ access pre-existing request attributes created earlier, e.g. by a `WebFilter`: ---- +[[webflux-multipart-forms]] +==== Multipart +[.small]#<># + +As explained in <>, `ServerWebExchange` provides access to multipart +content. The best way to handle a file upload form (e.g. from a browser) in a controller +is through data binding to a <>: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +class MyForm { + + private String name; + + private MultipartFile file; + + // ... + +} + +@Controller +public class FileUploadController { + + @PostMapping("/form") + public String handleFormUpload(MyForm form, BindingResult errors) { + // ... + } + +} +---- + +Multipart requests can also be submitted from non-browser clients in a RESTful service +scenario. For example a file along with JSON: + +[literal] +[subs="verbatim,quotes"] +---- +POST /someUrl +Content-Type: multipart/mixed + +--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp +Content-Disposition: form-data; name="meta-data" +Content-Type: application/json; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +{ + "name": "value" +} +--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp +Content-Disposition: form-data; name="file-data"; filename="file.properties" +Content-Type: text/xml +Content-Transfer-Encoding: 8bit +... File Data ... +---- + +You can access the "meta-data" part with `@RequestPart` which would deserialize it from +JSON (similar to `@RequestBody`) through one of the configured <>: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @PostMapping("/") + public String handle(**@RequestPart("meta-data") MetaData metadata, + @RequestPart("file-data") FilePart file**) { + // ... + } +---- + +To access multipart data sequentially, in streaming fashion, use `@RequestBody` with +`Flux` instead. For example: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @PostMapping("/") + public String handle(**@RequestBody Flux parts**) { + // ... + } +---- + [[webflux-ann-modelattrib-methods]] === Model Methods diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index a30c16f9cf..7e691e0ae3 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -859,6 +859,7 @@ request with a simple request parameter. [[mvc-multipart]] === Multipart resolver +[.small]#<># `MultipartResolver` from the `org.springframework.web.multipart` package is a strategy for parsing multipart requests including file uploads. There is one implementation @@ -1797,9 +1798,9 @@ The following code snippet shows the usage: } ---- -Parameters using this annotation are required by default, but you can specify that a -parameter is optional by setting ``@RequestParam``'s `required` flag to `false` or -declare the argument with a `java.util.Optional` wrapper. +Method parameters using this annotation are required by default, but you can specify that +a method parameter is optional by setting ``@RequestParam``'s `required` flag to `false` +or by declaring the argument with an `java.util.Optional` wrapper. Type conversion is applied automatically if the target method parameter type is not `String`. See <>. @@ -2192,6 +2193,7 @@ Therefore the use of flash attributes is recommended mainly for redirect scenari [[mvc-multipart-forms]] ==== Multipart +[.small]#<># After a `MultipartResolver` has been <>, the content of POST requests with "multipart/form-data" is parsed and accessible as regular request @@ -2262,7 +2264,7 @@ public class FileUploadController { ---- Multipart requests can also be submitted from non-browser clients in a RESTful service -scenario with more types of content. For example a file along with JSON: +scenario. For example a file along with JSON: [literal] [subs="verbatim,quotes"] @@ -2293,12 +2295,10 @@ probably want it deserialized from JSON (similar to `@RequestBody`). Use the [source,java,indent=0] [subs="verbatim,quotes"] ---- - @PostMapping("/someUrl") - public String onSubmit(**@RequestPart("meta-data") MetaData metadata, + @PostMapping("/") + public String handle(**@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file**) { - // ... - } ---- -- GitLab