Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use RestTemplate with object as data and application/x-www-form-urlencoded content type?

I need to post an object (e.g. not a MultiValueMap) via a RestTemplate with the content type application/x-www-form-urlencoded. When I try to do so ...

HttpHeaders headers = new HttpHeaders();
HttpEntity request;

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED)

// data is some generic type
request = new HttpEntity<>(data, headers);

// clazz is the Class<T> being returned
restTemplate.exchange(url, method, request, clazz)

... I get the following error:

org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.whatever.MyRequestPayload] and content type [application/x-www-form-urlencoded]

Here is what I see within restTemplate.getMessageConverters():

message converters

Why don't I want to provide a MultiValueMap? Two reasons:

  1. this is general purpose code which is used to send requests to multiple endpoints, so adding an overload specifically for x-www-form-urlencoded will only complicate things
  2. it doesn't seem like I should have to -- I just don't know which HttpMessageConverter needs to be used to support converting objects to a x-www-form-urlencoded string
like image 421
Josh M. Avatar asked Jun 12 '19 14:06

Josh M.


People also ask

How do you send data in application X www form Urlencoded?

To use it, we need to select the x-www-form-urlencoded tab in the body of their request. We need to enter the key-value pairs for sending the request body to the server, and Postman will encode the desired data before sending it. Postman encodes both the key and the value.

How do I send a POST request with X www form Urlencoded body?

setRequestMethod("POST"); connection. setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection. setRequestProperty("Content-Length", "" + Integer. toString(urlParameters.

Which annotations access application X www form Urlencoded parameters?

Now notice, we have used @ModelAttribute above to map the incoming application/x-www-form-urlencoded or multipart/form-data directly to the Student model class. The @ModelAttribute is an annotation that binds a method parameter or method return value to a named model attribute and then exposes it to a web view.


2 Answers

I ended up having to write a custom HTTP message converter which takes any object and writes it out as www-form-urlencoded content to the request body:

Usage

RestTemplate template = new RestTemplate(...);

template.getMessageConverters().add(new ObjectToUrlEncodedConverter(mapper));

ObjectToUrlEncodedConverter

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;

public class ObjectToUrlEncodedConverter implements HttpMessageConverter
{
    private static final String Encoding = "UTF-8";

    private final ObjectMapper mapper;

    public ObjectToUrlEncodedConverter(ObjectMapper mapper)
    {
        this.mapper = mapper;
    }

    @Override
    public boolean canRead(Class clazz, MediaType mediaType)
    {
        return false;
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType)
    {
        return getSupportedMediaTypes().contains(mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes()
    {
        return Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED);
    }

    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException
    {
        throw new NotImplementedException();
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException
    {
        if (o != null)
        {
            String body = mapper
                .convertValue(o, UrlEncodedWriter.class)
                .toString();

            try
            {
                outputMessage.getBody().write(body.getBytes(Encoding));
            }
            catch (IOException e)
            {
                // if UTF-8 is not supporter then I give up
            }
        }
    }

    private static class UrlEncodedWriter
    {
        private final StringBuilder out = new StringBuilder();

        @JsonAnySetter
        public void write(String name, Object property) throws UnsupportedEncodingException
        {
            if (out.length() > 0)
            {
                out.append("&");
            }

            out
                .append(URLEncoder.encode(name, Encoding))
                .append("=");

            if (property != null)
            {
                out.append(URLEncoder.encode(property.toString(), Encoding));
            }
        }

        @Override
        public String toString()
        {
            return out.toString();
        }
    }
}
like image 177
Josh M. Avatar answered Sep 22 '22 13:09

Josh M.


Reason: there is no converter can convert your java object into request body in x-www-form-urlencoded format.

Solution1: create this kind of converter, as what @Josh M. posts.

Solution2: convert your java object into MultiValueMap, and there is already a converter named FormHttpMessageConverter in spring boot which will convert MultiValueMap into request body in x-www-form-urlencoded format automatically.

So in solution2, all you need is to convert your java object into MultiValueMap:

        MultiValueMap<String, String> bodyPair = new LinkedMultiValueMap();
        bodyPair.add(K1, V1);
        bodyPair.add(K1, V2);
        bodyPair.add(K2, V2);
        ...

K1, V1, K2, V2, ..., means the field names and corresponding values in your java object. All the fields you declared in your java class need to be added. If there are too many fields, consider using Java Reflection.

like image 22
puppylpg Avatar answered Sep 21 '22 13:09

puppylpg