In this post I will describe an web application which contains of: Spring WebFlux, Spring Boot 2, Mysql r2dbc and a lot of fun.
My set up:
Ubuntu 18.04 64-bit
Java 8 (Oracle)
docker 18.09.7
mysql Ver 8.0.17 for Linux on x86_64 (MySQL Community Server - GPL)
Next, Mysql runs as a docker container with exposed ports, I could connect to it outside. I've created a schema and inserted data using
*.sql
scripts. One for creation if exists and the second one for insertion.Next, created a gradle project with the following dependencies:
buildscript {
ext {
springFrameworkVersion = '5.2.0.M3'
springBootVersion = '2.1.9.RELEASE'
springDataR2dbc = '1.0.0.RC1'
applicationName = 'be-board-allocator'
junitFive = '5.5.2'
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.1.9.RELEASE'
}
configurations {
compile.exclude module: "spring-boot-starter-tomcat"
}
}
plugins {
id "io.spring.dependency-management" version "1.0.8.RELEASE"
id 'org.springframework.boot' version '2.1.9.RELEASE'
}
apply plugin: 'java'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'jacoco'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
maven { url 'https://jitpack.io' }
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
}
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: springBootVersion
compile group: 'org.springframework.data', name: 'spring-data-r2dbc', version: springDataR2dbc
compile group: 'com.github.jasync-sql', name: 'jasync-mysql', version: '1.0.7'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-log4j2', version: springBootVersion
compile group: 'dev.miku', name: 'r2dbc-mysql', version: '0.8.0.RC1'
compile group: 'io.netty', name: 'netty-all', version: '4.1.42.Final'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion
//compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: springBootVersion
compile 'io.springfox:springfox-swagger2:3.0.0-SNAPSHOT'
compile 'io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT'
compile 'io.springfox:springfox-spring-webflux:3.0.0-SNAPSHOT'
compile group: 'org.springframework', name: 'spring-tx', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-core', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-webflux', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-web', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-orm', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-jdbc', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-jcl', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-expression', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-context', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-beans', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-aspects', version: springFrameworkVersion
compile group: 'org.springframework', name: 'spring-aop', version: springFrameworkVersion
compile group: 'io.projectreactor', name: 'reactor-core', version: '3.3.0.RELEASE'
testImplementation('org.junit.jupiter:junit-jupiter:5.5.2')
}
configurations {
compile.exclude module: 'spring-boot-starter-logging'
}
jacoco {
toolVersion = '0.8.4'
}
jacocoTestReport {
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it, exclude: ['**/Main*'])
})
}
reports {
xml.enabled = true
html.enabled = true
}
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
Interestingly, r2dbc has not yet fully supported mysql reactive driver. And Spring package manager sucks also, thus I should have added explicitly all Spring reactive dependencies. In addition, I've added support of junit 5.
Next, more challenging step was to add PUT/POST method for the
@RestController
.Here is an example with a problem ...
@PutMapping(path = "/vlan")
public void addVlan(@RequestBody VlanData vlanData) {
logger.debug("REQUESTED DATA: " + vlanData);
}
When you ran a
curl
command
curl -X PUT 127.0.0.1:6767/vlan -d '{"vlan_id":"9f8d4422-0aac-49f1-94b2-21b8302bebc2","vlan":"lbjmuspxig"}'
You've got some amazing error:
{"timestamp":1571234848606,"path":"/vlan","status":500,"error":"Internal Server Error","message":"In a WebFlux application, form data is accessed via ServerWebExchange.getFormData()."}
Basically for this reason I've written this blog. It took some time to figure out the cause. Having googled nothing that makes sence gotten. I started debugging and found a class:
AbstractMessageReaderArgumentResolver
with the following checking
if (mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) {
return Mono.error(new IllegalStateException("In a WebFlux application, form data is accessed via ServerWebExchange.getFormData()."));
}
That means, because I previous Java code, Put method was not specifying context type, as result ... such unplesant error.
Let's fix the problem!
@PutMapping(path = "/vlan", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public void addVlan(@RequestBody VlanData vlanData) {
logger.debug("REQUESTED DATA: " + vlanData);
}
Happy coding!!!
Comments