I'm developing a simple REST service using Spring. I've a entity and a controller to that. My problem is that I can't use Post function from browser, it's just works from terminal. The Get function works fine from browser and terminal, but Post function just works from terminal, but I must that it works from browser.
For the code below, if I navigate to:
http://localhost:8080/cities
the result is ok, all records are returned.
Get method:
#RestController
public class CityController {
...
#GetMapping(value = "/cities", produces = "application/json; charset=UTF-8")
List<City> all() {
return repository.findAll();
}
}
For the Post method, just works from terminal if I write something like:
curl -X POST localhost:8080/cities -H 'Content-type:application/json'
-d '{"name":"test", "state":"test"}'
Result is ok, record is created.
But, from browser, if I tries add a new record with:
http://localhost:8080/cities?name=test&state=test
nothing happens, and no error occurs.
Post method:
#PostMapping(path = "/cities", consumes = "application/json", produces = "application/json")
City newCity(#RequestBody City city) {
return repository.save(city);
}
Entity:
#Entity
public class City {
#Id #GeneratedValue(strategy=GenerationType.AUTO) Long id;
private String name;
private String state;
public City() {
}
public City(String name, String state) {
this.name = name;
this.state = state;
}
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Typing http://localhost:8080/cities?name=test&state=test into a browser is still going to send it as a GET.
To send as a POST, you have a few options:
Use a browser plugin as others have mentioned.
Create an HTML form.
Use JavaScript.
Option 1 is great for debugging and testing, but is no way appropriate for a production quality web site. You cannot reasonably expect your visitors to install or use a browser add-on to interact with your site.
Option 2 is the most traditional design. You would need to serve a HTML file from your application (it can be a static HTML file or use a template framework such as Thmyeleaf or Freemarker). The HTML would need a form element that is configured to use POST and point it back to your endpoint. Keep in mind your endpoint would need to accept form encoded data, not just JSON.
Option 3 could be implemented in several ways. You could have a HTML file that uses embedded JavaScript to call your endpoint, or you could use some framework like Angular or React.
Lots of options, and it's hard to say which one is best without knowing what exactly you're trying to accomplish.
Related
I have an objects in java backend that i want to change status property on:
#Entity
public class Employee {
#Id #GeneratedValue long id;
private String name;
private String status;
}
I want to set the property status to "Checked" for a targeted object chosen with id with this putmapping:
#PutMapping("/api/users/id")
public Employee changeStatus(#RequestParam Long id)
{
Employee newEmployee = userRepository.getById(id);
newEmployee.setStatus("Checked");
return userRepository.save(newEmployee);
}
I want to do it from my frontend through :
public changeStatus(id: number): Observable<any>
{
return this.http.put<any>(${this.apiServerUrl}/users/id, id)
}
Nothing happens in backend, no errors or anything shows up. What am i missing? I suspect i do something wrong in the frontend call, but i cant figure it out.
Backend-frontend connections seems to work because i can get all data from my backend and see it in frontend with theese two endpoints
Backend:
#RequestMapping("/api/users") public List<Employee> getUsers()
{
return (List<Employee>) userRepository.findAll();
}
Frontend:
public getUsers(): Observable<any>
{
return this.http.get<any>(${this.apiServerUrl}/users)
}
Thanks in advance!
Are you subscribing to this call on the FE ? the angular HttpClient returns an Observable, if you are not subscribing, i.e. if you are not doing something like this:
getUsers().subscribe((data) => {
console.log('whatever');
})
The http request will never be send, that's how observable works, they only get "executed" when someone is "listening"
Seeing how you try to pass id as pathVariable your endpoint should also accept it as such. For that refactor your endpoint. Frontend looks fine.
#PutMapping("/api/users/{id}")
public Employee changeStatus(#PathVariable Long id)
{
Employee newEmployee = userRepository.getById(id);
newEmployee.setStatus("Checked");
return userRepository.save(newEmployee);
}
Remove one extra id from url.
public changeStatus(id: number): Observable<any>
{
const url = this.apiServerUrl + /users/ + id;
return this.http.put<any>(url);
}
Otherwise try to get rid of any types and use strong typing - that's the benefit TypeScript gives us.
Let's assume the following example.
The POJO class:
#XmlRootElement
public class User {
private String id;
private String email;
private String password;
private String firstName;
private String lastName;
// getters and setters
}
The resource class:
#Path("user")
public class UserResource {
private UserRepository userRepository = new UserRepositoryStub();
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public User createUser(User user) {
return userRepository.create(user);
}
#GET
#Path("{objectId}")
#Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public Response getManagedObject(#PathParam("objectId") String objectId) {
if (objectId == null) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
User user = userRepository.findUser(objectId);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
// Possible, but seems that not nice solution
// user.setPassword(null);
return Response.ok().entity(user).build();
}
}
In this simple example I want that the GET request {url}/user/12345 doesn't return password field. I've commented one solution I don't like.
In general while working on the API I want to have configurations of visibility of POJO's fields for every request. Is there an elegant way of achieving that?
Create a TransferObject i.e TO or DTO which holds the fields that you want the user to show in JSON response. You can use #JsonIgnore on the field and the JSON parser wont parse that field and thus wont be included in response.
the general practice is to have a service layer in between. you then have a dto object that is an io object for the outside world that is converted to your resource/entity/repository/whatever object. you need to provide conversion/mapper/whatever between those 2 types of objects and you don't set the password when going in dto to resource direction. same thing is usually done for ids in rest interfaces. you don't want anyone to update a resource and by providing an id in the input object to update a different object. this is how things are usually done even though it means extra code, this is usually trivial. can be simplified using a config using Dozer framework or something similar.
from a design point of view resource/persistence layer should only contain atomic operations. what happens if you need to do several of those for a single resource? you'd have to put it in a single method in the resource class. this way you'll be mixing the rest/io logic with what should be in the service layer. easier to make a mistake and harder to write isolated unit tests for
Assuming that you want the POST method (un-marshaling) to include the password -
but not the GET method (marshaling) - and you are using JAXB, you can write an XmlAdapter.
Its primer use is to convert between mappable and unmappable classes, but it can do the trick here.
public class PasswordAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String v) throws Exception {
return v;
}
#Override
public String marshal(String v) throws Exception {
return "***";
}
}
Then specify that adapter for the password property:
class User {
//...
#XmlJavaTypeAdapter(PasswordAdapter.class);
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
I need to have my Java server receive a PUT request to create a new user from an id and a json body, the URI needs to be like:
/usermanagement/user/$id { "name":john, "type":admin }
Given that I've made a simple Java class and can later convert the JSON to a POJO using Jackson, here's my problem:
How do I specify the PUT request to accept both the id and the JSON body as parameters? So far I've got:
#PUT
#Path("{id}")
#Consumes(MediaType.APPLICATION_JSON)
public String createUser(#PathParam("id") int id){
User user = new User();
User.setId(id);
return SUCCESS_MSG;
}
And this works, but I've had no luck adding the JSON body and having the function parse it. I've tried:
public String createUser(#PathParam("id") int id, String body){
return body;
}
It should return the same input JSON when testing in Postman, however it always returns a "resource not available" error.
I feel there's something obvious that I'm missing here?
As per REST API conventions, a POST method on a uri like /usermanagement/users is what is needed. PUT method is used for updating an existing resource. You can go through this wonderful article on how to design pragmatic RESTful API. http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api.
If you are trying to create a new user, why give it an ID? You have to POST the data such as user name, lastname, email, ... and let the backend generate an ID (like an auto-incremented id, or some UUUID) for this new resource.
For example, in my app, I use a json body for a POST request like below:
{
"loginId": "ravi.sharma",
"firstName": "Ravi",
"lastName": "Sharma",
"email": "myemail#email.com",
"contactNo": "919100000001",
"..." : ".."
}
Moreover, your response should return HTTP-201, after successful creation, and it should contain a location header, pointing to the newly created resource.
Instead of Using String body, use a Class with Member variables name and Type Like this.
public class User {
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
(This works in Spring Boot Web out-of-box. incase of Spring MVC, you might need to add Jackson dependency): On your Controller , Add #RequestBody Annotation, then Jackson will take care of the un-marshaling of JSON String to User Object.
public String createUser(#PathParam("id") int id, #RequestBody User user){
When creating or listing and item using REST api I return also the whole resource path.
For example creating a person record returns http://service:9000/person/1234in response. In order to get schema, host & port part like http://service:9000, I extract it from URL obtained by HttpServletRequest.getRequestURL().
Example (not the production code but conceptually same):
#RequestMapping(value = "/person", method = RequestMethod.PUT)
public Object putPerson(
#RequestParam(value = "name") String name,
HttpServletRequest req) {
long id = createPerson(name);
String uriStart = RestUtils.getSchemeHostPortUrlPart(req
.getRequestURL().toString());
String uri = uriStart + "/person/" + id;
Person person = new Person(name, id, uri);
return person; //Serialized to json by Spring & Jackson
}
//Simple bean-like class
public class Person {
//Getter methods for uri, name & id
}
Since this is quite a boiler plate code which repeats in every method I was wondering if Spring does not have support for this which eluded me when reading it's documentation.
By this I mean accessing either URL without neededn HttpServletRequest or even better its schema, host, port part only.
The documentation provides a lot of examples for constructing URIs using a UriComponentsBuilder.
Furthermore I recommend to take a look at Spring HATEOAS if you want to take your REST API to the next level.
BTW: PUT means that you place what you send (request body) to the location to which you send it (URL). If nothing is there something new is created, otherwise what exists is updated (replaced).
This is not what is happening in your example. The proper way would be to either POST to /person or PUT to the person's own URL, e.g. /person/1234, provided you have the ID beforehand.
You can construct the URI in an interceptor (that's executed previous to controller methods) and put it as an attribute and use it in the controller method.
I believe it is quite simple. Look at this example:
#RequestMapping(value = "/person", method = RequestMethod.PUT)
public Object putPerson(#RequestParam(value = "name") String name, HttpServletRequest req) {
long id = createPerson(name);
Person person = new Person(id, name, req);
return person; //Serialized to json by Spring & Jackson
}
public class JsonResponse {
private String url;
public JsonResponse(HttpServletRequest request) {
url = request.getRequestURI() + "?" + request.getQueryString();
}
public final String url() {
return url;
}
}
public class User extends JsonResponse {
private Long id;
private String name;
public User(Long id, String name, HttpServletRequest request) {
super(request);
this.id = id;
this.name = name;
}
// Getter, Setter
}
You can use org.springframework.web.servlet.support.ServletUriComponentsBuilder like this:
String uri = ServletUriComponentsBuilder.fromCurrentRequest()
.replacePath("/person/{id}")
.buildAndExpand(id)
.toUriString();
I have read through the majority of posts on StackOverflow concerning this issue, and have tried numerous fixes, with nothing ultimately solving my problem. Spring throws an HttpMediaTypeNotSupportedException: Content type 'application/json' not supported
I have a controller that is defined like so:
#RequestMapping(method = RequestMethod.POST, value = "/update")
public #ResponseBody JSONDomainResponse update(#RequestBody Model inModel)
Where the model looks like so:
public class Model implements Serializable {
private static final long serialVersionUID = 2738522159847487651L;
private String id;
private BigDecimal offset;
#JsonCreator
public Model(#JsonProperty("id") String id, #JsonProperty("offset") BigDecimal offset) {
this.id = id;
this.offset = offset;
}
public String getID() {
return id;
}
public BigDecimal getOffset() {
return offset;
}
public void setID(String id) {
this.id = id;
}
public void setOffset(BigDecimal offset) {
this.offset = offset;
}
}
The AJAX call I am attempting to use looks like this:
$.ajax({
type : 'POST',
url : '/update',
contentType : "application/json",
data : JSON.stringify({"id":"test", "offset":300})
});
I have the <mvc:annotation-driven/> configuration in my context.xml file, and I have verified that MappingJacksonHttpMessageConverter's canRead() method returns true for my model and the JSON Media Type.
I do also have the Jackson core and mapper jars specified in my classpath.
I have noticed that removing the Model parameter from the controller signature makes it so I actually reach the controller with my post, which leads me to believe there is some problem with my Model. Since there is minimal information logged, however, I can't really tell what the problem can be.
Thanks in advance.
I finally found the solution to the problem, which was trivial but entirely non-obvious.
It turns out the Model object I was attempting to deserialize was in an improperly named package, and when Spring was unable to locate the Model, it simply swallows the generated exception and returns false. I discovered this through debugging deep in Spring-MVC land, particularly the StdDeserializerProvider class.
For others out there receiving this error, I would highly recommend writing some code to verify what is happening in this class, for example:
#Test
public void testThatCanConvertUpdateModel() {
MappingJacksonHttpMessageConverter conv = new MappingJacksonHttpMessageConverter();
assertTrue(conv.canRead(YourModel.class, MediaType.APPLICATION_JSON));
}