简体   繁体   中英

How to make AngularJs http.put() and Spring controller work together (no more status 400 errors)?

I am learning AngularJs with Spring. I am converting an existing Spring MVC server to work with Angular-provided JSON.

Doing the GET was rather easy, cribbing from tutorial code. Doing a PUT is proving challenging. I mostly get status 400 (syntactically incorrect). Rather than thrash about I've decided to ask for advice.

Here is part my Spring controller:

@RequestMapping(value = "/json/{id}", method = RequestMethod.GET)
public @ResponseBody User getJEdit(@PathVariable("id") Long id) {
    [snip]
    User user = userService.get(id);
    [snip]
    return user;
}

@RequestMapping(value = "/json/{id}", method = RequestMethod.PUT)
public @ResponseBody User putJEdit(@RequestBody User userAttribute, @PathVariable("id") Long id) {
    [snip]
}

Here is the AngularJs controller code:

angular.module("myapp", [])
  .controller("MyController", function($scope, $http) {
    $scope.myDataGet = {};
    $scope.myDataGet.doClickGet = function(item, event) {
      var responsePromise = $http.get("/exchangeboard/admin/user/json/1");
      responsePromise.success(function(data, status, headers, config) {
        $scope.myDataGet.fromServer = data;
      });
      responsePromise.error(function(data, status, headers, config) {
        alert("AJAX failed!");
      });
    };
    $scope.myDataPut = {};
    $scope.myDataPut.doClickPut = function(item, event) {
      var responsePromise = $http.put("/exchangeboard/admin/user/json/1", 
        $scope.myDataGet.fromServer, {contentType: "application/x-www-form-urlencoded"});
      responsePromise.success(function(data, status, headers, config) {
        $scope.myDataPut.fromServer = data;
      });
      responsePromise.error(function(data, status, headers, config) {
        alert("AJAX failed!");
      });
    }
  });

The response from the GET works just fine (one line, broken for display):

{"id":1,"orgId":23,"name":"admin","password":"XXX",
 "role":"ROLE_ADMIN","fullName":"Jerome","phone":"",
 "email":"jerome@myemail.com","preferredContactMethod":"E",
 "inactiveDate":null,"orgName":"Org 2 receiver","donor":false,
 "adminRole":true,"inactiveDateString":""}

I turn around and send this right back to the server. I get this error:

<body>
  <h1>HTTP Status 400 - </h1>
  <HR size="1" noshade="noshade">
  <p><b>type</b> Status report</p>
  <p><b>message</b> <u></u></p><p><b>description</b> <u>The request sent by the client was syntactically incorrect.</u></p>
  <HR size="1" noshade="noshade">
  <h3>VMware vFabric tc Runtime 2.9.5.SR1/7.0.50.C.RELEASE</h3>
</body>

The server code putJEdit() isn't contacted -- no breakpoints are hit.

I think the answer is to have a correct content type, but which?

A second question: Suppose the server wants to validate things. I'm passing about a User object. I'm thinking I should be passing around something with a wrapper, so I can include errors and messages. Must this be home-grown? Or will something on the Spring side like ResponseEntity serve me?

A third question: Suppose I'm supposed to use something like User, and not a wrapper. What syntax do I use for sending a list or array of User objects?

Thanks much, Jerome.

EDIT:

Sorry about changing things up so much. I found another Spring MVC package I used for JSON communication and changed my problem to use ResponseEntity. This gives me a place for custom statuses and messages. I still don't have success, though. My Java controller section:

@RequestMapping(value = "/json/{id}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public ResponseEntity<User> getJEdit(@PathVariable("id") Long id) {
    [snip]
    return new ResponseEntity<User>(user, HttpStatus.OK);
}

@RequestMapping(value = "/json/{id}", method = RequestMethod.PUT, consumes = "application/json", produces="application/json")
@ResponseBody
public ResponseEntity<User> putJEdit(@RequestBody User userAttribute, @PathVariable("id") Long id) {
    [snip]
    return new ResponseEntity<User>(user, HttpStatus.OK);
}

My AngularJs module:

angular.module("myapp", []).controller("MyController", function($scope, $http) {
  $http.defaults.useXDomain = true;
  $scope.myDataGet = {};
  $scope.myDataGet.doClickGet = function(item, event) {
    var responsePromise = $http.get("/exchangeboard/admin/user/json/1");
    responsePromise.success(function(data, status, headers, config) {
      $scope.myDataGet.fromServer = data;
    });
    responsePromise.error(function(data, status, headers, config) {
      alert("AJAX failed!");
    });
  };
  $scope.myDataPut = {};
  $scope.myDataPut.doClickPut = function(item, event) {
    var responsePromise = $http.put("/exchangeboard/admin/user/json/1", $scope.myDataGet.fromServers, {headers: {'Content-Type': 'application/json'}});
    responsePromise.success(function(data, status, headers, config) {
      $scope.myDataPut.fromServer = data;
    });
    responsePromise.error(function(data, status, headers, config) {
      alert("AJAX failed!");
    });
  };
});

The GET always succeeds. This means my basic Spring communication is taking place OK. I consistently get "Request method 'PUT' not supported -- The specified HTTP method is not allowed for the requested resource."

In my server I do have a mapping:

INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
- Mapped "{[/admin/user/json/{id}],methods=[PUT],params=[],headers=[],consumes=[application/json],produces=[application/json],custom=[]}" 
onto public org.springframework.http.ResponseEntity<mystuff.domain.User> mystuff.controller.UserController.putJEdit(mystuff.domain.User,java.lang.Long)

So I don't know what I'm doing wrong. If I change everything to use POST instead of PUT I still get the same 405. Jerome.

First question

A response with HTTP status 400 is caused by the incorrect content type.

You can change it to

{"Content-Type": "application/json"}

Even better if you just omit a content type at all. According to $http documentation:

$httpProvider.defaults.headers.put (header defaults for PUT requests)

  • Content-Type: application/json

I bet that json-to-object conversion in your case is performed by Jackson in the classpath. When you send a request with application/x-www-form-urlencoded , MappingJackson2HttpMessageConverter does not process it. From the MappingJackson2HttpMessageConverter javadoc:

By default, this converter supports application/json and application/*+json . This can be overridden by setting the supportedMediaTypes property.

If you use Jackson 1.x , the behavior of MappingJacksonHttpMessageConverter toward content types is similar.

Second question

Using a wrapper is fine if you want to return some errors alongside User . Depending on your scenario, you may want to take a look at @ExceptionHandler and @ControllerAdvice too.

Third question

Just return @ResponseBody List<User> .

Man, I hate these details. My issues are caused by two things, one simple.

First, my User object, the details for which I didn't print earlier, have some helper functions like "isAdminRole()" that the Jackson JSON library, recommended for Spring use, translates into attribute names ("adminRole"). Inbound there is no setAdminRole() so the Jackson library throws an exception:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "adminRole" (Class mystuff.domain.User), not marked as ignorable

This gets reported out as a 405 error. A 405 is not always for CORS issues!

I got a tip from here and there to turn on debugging in log4j, the org.springframework.web set to DEBUG. Never would have seen this otherwise.

Secondly, my Angular controller uses both $scope.myDataGet.fromServer and fromServers. An obvious typo once you see it. Using the fromServers version meant that blank data was sent as JSON to the server, which rejected the revised version, and jQuery-based .ajax() calls, with a 400 error (invalid syntax).

Between the two of these I lost a day or two of work. I very much appreciate Constatine's responses and the magnaminous help of thousands of StackOverflow entries that let me understand I needed to turn on debug logging.

I have one example working here and now I can proceed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM