Hello!

Summary

Please add support for sending application/x-www-form-urlencoded request bodies using the @HttpExchange / @PostExchange abstraction.

Currently, it is not possible to annotate a method argument which is POJO in an @HttpExchange interface to send form-encoded data in the request body.


Example

@HttpExchange("/token")
interface AuthClient {
    @PostExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    fun getToken(
        @RequestBody form: MyForm
    ): TokenResponse
}
data class MyForm(
  val login: String,
  val password: String,
}
authClient.getToken(
  MyForm("login", "pwd")
}

This code leads to org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=MyForm


Currently to avoid limitations we can use custom org.springframework.http.codec.HttpMessageWriter

class FormUrlencodedWriter : HttpMessageWriter<Any> {

    private val supportedMediaType = MediaType.APPLICATION_FORM_URLENCODED

    override fun canWrite(elementType: ResolvableType, mediaType: MediaType?): Boolean {
        return supportedMediaType == mediaType
    }

    override fun getWritableMediaTypes(): MutableList<MediaType> {
        return mutableListOf(supportedMediaType)
    }

    override fun write(
        inputStream: Publisher<out Any>,
        elementType: ResolvableType,
        mediaType: MediaType?,
        message: ReactiveHttpOutputMessage,
        hints: MutableMap<String, Any>
    ): Mono<Void> {
        return Flux.from(inputStream).next().flatMap { value ->
            val form = LinkedMultiValueMap<String, String>()

            value.javaClass.declaredFields.forEach { field: Field ->
                field.isAccessible = true
                val fieldValue = field.get(value)
                if (fieldValue != null) {
                    val annotation = field.getDeclaredAnnotation(FormProperty::class.java)
                    if (annotation != null) {
                        form.add(annotation.name, fieldValue.toString())
                    } else {
                        form.add(field.name, fieldValue.toString())
                    }
                }
            }

            val encoded = UriComponentsBuilder.newInstance()
                .queryParams(form)
                .build()
                .toUri()
                .rawQuery

            val bytes = encoded.toByteArray(UTF_8)
            message.headers.contentType = supportedMediaType
            message.headers.contentLength = bytes.size.toLong()

            message.writeWith(Mono.just(message.bufferFactory().wrap(bytes)))
        }
    }
}

and add it to WebClient

@Bean
fun exchangeStrategies(): ExchangeStrategies = ExchangeStrategies.builder()
    .codecs { config -> config.customCodecs().register(FormUrlencodedWriter()) }
    .build()

@Bean
fun webClient(exchangeStrategies: ExchangeStrategies): WebClient = WebClient.builder()
    .exchangeStrategies(exchangeStrategies)
    .baseUrl("http://localhost:8081")
    .build()

I understand that if use org.springframework.util.MultiValueMap as @RequestBody type then everything works fine but POJO support might be good enhancement

This request is similar to https://github.com/OpenFeign/feign-form/ which design was nice