Search code examples
androidoauth-2.0robospice

Empty request body using SpringAndroidSpiceRequest


I'm writing an Android app, which is a client to my web application. I'm trying to use RoboSpice to perform network requests.

First of all I decided to test an API call to obtain an OAuth2 token. The following curl command can be called to obtain it from command line:

curl -X POST -d "grant_type=password&username=user&password=pass" http://testid:testsecret@localhost:8000/oauth2/token/

user and pass are the credentials for a registered user and testid and testsecret are the id and secret of a registered app in my web application. This call returns a JSON object with a token and other parameters.

I'm trying to do the same request using RoboSpice. Here's the code I wrote for the request:

public class OAuth2Request extends SpringAndroidSpiceRequest<String> {
    private final String user;
    private final String pass;

    public OAuth2Request(String user, String pass) {
        super(String.class);
        setRestTemplate(new RestTemplate());
        getRestTemplate().getMessageConverters().add(new StringHttpMessageConverter());

        this.user = user;
        this.pass = pass;
    }

    @Override
    public String loadDataFromNetwork() throws RestClientException {
        String client_id = "testid";
        String client_secret = "testsecret";
        HttpBasicAuthentication authHeader = new HttpBasicAuthentication(client_id, client_secret);
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setAuthorization(authHeader);
        requestHeaders.setUserAgent("AndroidNotesApp/1.0");

        String data = String.format("grant_type=password&username=%s&password=%s", this.user, this.pass);
        HttpEntity<String> requestEntity = new HttpEntity<>(data, requestHeaders);

        String url = "http://10.0.2.2:8000/oauth2/token/";
        return getRestTemplate().postForObject(url, requestEntity, String.class);
    }
}

The SpiceManager in my activity is declared like:

protected SpiceManager spiceManager = new SpiceManager(JacksonSpringAndroidSpiceService.class);

and the request is made by the following lines:

OAuth2Request req = new OAuth2Request(user, pass);
spiceManager.execute(req, new OAuth2RequestListener());

user and pass are Strings, which get their values from EditText views.

But when I try to run this request, I get an exception 400 BAD REQUEST. I set up logging in my django app to print the requests which come to /oauth2/token/, and I see, that POST parameters are empty in this request (I expect them to be the same as during the curl request, something like {'grant_type': 'password', 'password': 'pass', 'username': 'user'}).

Why are POST parameters empty in case of RoboSpice request? What am I doing wrong?

P.S. Just in case: the oauth2 authentication in my django web application is implemented using DjangoOAuthToolkit with DjangoRestFramework.

UPDATE: I decided to setup nginx proxy before my django web application to log the request body. The request body I get from the Android app is the following:

\x22grant_type=password&username=user&password=pass\x22

So the strange \x22 symbol is added in the beginning and in the end of the body (I believe it is a double-quote " symbol, but I'm not sure). Seems that these \x22 screw up POST parameter parsing in django. How can I get rid of these symbols?


Solution

  • I managed to solve my problem, so I'm posting an answer in case it helps someone.

    SpringAndroidSpiceRequest by default tries to map a java object into JSON, so when I tried to send a String in request body, it wrapped it in double quotes to make it a JSON string. I don't need to send a request body as a JSON string, and in order to do that I need to define additional message converters.

    Strangely, these lines in constructor don't seem to do anything

    setRestTemplate(new RestTemplate());
    getRestTemplate().getMessageConverters().add(new StringHttpMessageConverter());
    

    When I used debugger, it showed just one message converter, MappingJacksonHttpMessageConverter. So I decided to add my own message converters in loadDataFromNetwork method.

    I needed two message converters: FormHttpMessageConverter, which will process request and make a request POST body from MultiValueMap, and MappingJacksonHttpMessageConverter, which will process the JSON response into OAuth2Token POJO, which I also declared.

    I believe, that for simple testing of REST API with client (POST plain strings and receive plain strings) it'll be better to choose another implementation for SpiceRequest other than SpringAndroidSpiceRequest, but I decided to stick with it, as it'll be easier to implement the complete client for my web application.

    The complete code for OAuth2Request:

    public class OAuth2Request extends SpringAndroidSpiceRequest<OAuth2Token> {
        private final String user;
        private final String pass;
    
        public OAuth2Request(String user, String pass) {
            super(OAuth2Token.class);
    
            this.user = user;
            this.pass = pass;
        }
    
        @Override
        public OAuth2Token loadDataFromNetwork() throws RestClientException {
            String client_id = "testid";
            String client_secret = "testsecret";
            HttpBasicAuthentication authHeader = new HttpBasicAuthentication(client_id, client_secret);
            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setAuthorization(authHeader);
            requestHeaders.setUserAgent("AndroidNotesApp/1.0");
    
            MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
            data.add("grant_type", "password");
            data.add("username", this.user);
            data.add("password", this.pass);
            HttpEntity<?> requestEntity = new HttpEntity<>(data, requestHeaders);
    
            String url = "http://10.0.2.2:8000/oauth2/token/";
    
            getRestTemplate().getMessageConverters().clear();
            getRestTemplate().getMessageConverters().add(new FormHttpMessageConverter());
            getRestTemplate().getMessageConverters().add(new MappingJacksonHttpMessageConverter());
            return getRestTemplate().postForObject(url, requestEntity, OAuth2Token.class);
        }
    }