Let's start with a simple HTTP GET. Similar to Spring MVC endpoints, Spring WebFlux supports Flux operations as shown here:
@GetMapping(API_BASE_PATH + "/images") Flux<Image> images() { return Flux.just( new Image("1", "learning-spring-boot-cover.jpg"), new Image("2", "learning-spring-boot-2nd-edition-cover.jpg"), new Image("3", "bazinga.png") ); }
This preceding controller can be described as follows:
- Using the same Flux.just() helper, we return a rather contrived list
- The Spring controller returns a Flux<Image> Reactor type, leaving Spring in charge of properly subscribing to this flow when the time is right
Before we can move forward, we need to define this Image data type like this:
@Data @NoArgsConstructor public class Image { private String id; private String name; public Image(String id, String name) { this.id = id; this.name = name; } }
The preceding POJO class can be described as follows:
- @Data is a Lombok annotation that generates getters, toString, hashCode, equals as well as setters for all non-final fields
- @NoArgsConstructor is a Lombok annotation to generate a no-argument constructor
- It has id and name fields for storing data
- We have crafted a custom constructor to load up fields of data
With this simple data type, we can now focus on reactively interacting with them.
Nothing is simple without creating new data. To do that, we can write an HTTP POST operation as follows:
@PostMapping(API_BASE_PATH + "/images") Mono<Void> create(@RequestBody Flux<Image> images) { return images .map(image -> { log.info("We will save " + image + " to a Reactive database soon!"); return image; }) .then(); }
The last code can be described as follows:
- @PostMapping indicates this method will respond to HTTP POST calls. The route is listed in the annotation.
- @RequestBody instructs Spring to fetch data from the HTTP request body.
- The container for our incoming data is another Flux of Image objects.
- To consume the data, we map over it. In this case, we simply log it and pass the original Image onto the next step of our flow.
- To wrap this logging operation with a promise, we invoke Flux.then(), which gives us Mono<Void>. Spring WebFlux will make good on this promise, subscribing to the results when the client makes a request.
If we run this code and submit some JSON, we can check out the results.
First, let's use HTTPie (https://httpie.org):
http --json -v POST localhost:8080/api/images id=10 name=foo
The verbose results are easy to read and are as follows:
POST /api/images HTTP/1.1 Accept: application/json, */* Accept-Encoding: gzip, deflate Connection: keep-alive Content-Length: 27 Content-Type: application/json Host: localhost:8080 User-Agent: HTTPie/0.9.8 { "id": "10", "name": "foo" } HTTP/1.1 200 Content-Length: 0 Date: Sat, 28 Jan 2017 20:14:35 GMT
In this case, HTTPie nicely sent a single item and our Spring WebFlux controller parsed it perfectly, like this:
... c.g.learningspringboot.ApiController ... We will save
Image(id=10, name=foo) to a Reactive database soon!
Single entry Flux has been nicely handled.
If we want to send a JSON array, we can either embed the JSON array in a file or just send it directly with curl, as follows:
curl -v -H 'Content-Type:application/json' -X POST -d '[{"id":10,
"name": "foo"}, {"id":11, "name":"bar"}]' localhost:8080/api/images
Ta-dah!
c.g.learningspringboot.ApiController ... We will save Image(id=10,
name=foo) to a Reactive database soon! c.g.learningspringboot.ApiController ... We will save Image(id=11,
name=bar) to a Reactive database soon!
Want to dial up the log levels? With Spring Boot, adjusting logging levels is a snap. Rename the application.properties file supplied by start.spring.io as application.yml, and edit it to look like this:
logging: level: io: netty: DEBUG reactor: DEBUG
The preceding code will punch up Netty and Project Reactor to spit out DEBUG level messages.
If we fetch the list of images again (http localhost:8080/api/images), we can see stuff like this in the server logs:
2017-01-28 15:46:23.470 DEBUG 28432 --- [ctor-http-nio-4] r.i.n.http.server.HttpServerOperations : New http connection, requesting read 2017-01-28 15:46:23.471 DEBUG 28432 --- [ctor-http-nio-4] r.ipc.netty.http.server.HttpServer : [id: 0x9ddcd1ba, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:65529] RECEIVED: 145B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 47 45 54 20 2f 61 70 69 2f 69 6d 61 67 65 73 20 |GET /api/images | |00000010| 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 |HTTP/1.1..Host: | |00000020| 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d 0a |localhost:8080..| |00000030| 55 73 65 72 2d 41 67 65 6e 74 3a 20 48 54 54 50 |User-Agent: HTTP| |00000040| 69 65 2f 30 2e 39 2e 38 0d 0a 41 63 63 65 70 74 |ie/0.9.8..Accept| |00000050| 2d 45 6e 63 6f 64 69 6e 67 3a 20 67 7a 69 70 2c |-Encoding: gzip,| |00000060| 20 64 65 66 6c 61 74 65 0d 0a 41 63 63 65 70 74 | deflate..Accept| |00000070| 3a 20 2a 2f 2a 0d 0a 43 6f 6e 6e 65 63 74 69 6f |: */*..Connectio| |00000080| 6e 3a 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a 0d |n: keep-alive...| |00000090| 0a |. | +--------+-------------------------------------------------+----------------+ 2017-01-28 15:46:23.471 DEBUG 28432 --- [ctor-http-nio-4] r.ipc.netty.channel.ChannelOperations : [HttpServer] handler is being applied: org.springframework.http.server.reactive.ReactorHttpHandlerAdapter@3a950f21
This shows the incoming web request to GET /api/images, headers and all. The output can also be read, but given the volume of data from Netty, its verbose output is not shown. Nevertheless, these log levels provide a handy means to debug traffic on the wire.