I am developing my first Spring Boot application and i ran into a weird problem. The configuration is very basic:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""
xsi:schemaLocation=".0.0 .0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>.pawsec</groupId>
<artifactId>kitchen</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>kitchen</name>
<description>The Kitchen restaurant system</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.pawsec</groupId>
<artifactId>mon</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>
We have some Javascript code on a page calling these two services. When the controller returns a Guy object in the first method, we get an empty response:
{data: "", status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: ""
headers: {}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object
When we return a List of Guy objects from the second method, however, we get the full Json structure
back:
{data: Array(3), status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: Array(3)
0: {guyId: 1, name: "Walter Sobchak", age: 45}
1: {guyId: 2, name: "Jeffrey Lebowski", age: 42}
2: {guyId: 3, name: "Theodore Donald Kerabatsos", age: 39}
length: 3
: Array(0)
headers: {content-type: "application/json;charset=UTF-8", cache-control: "private", expires: "Thu, 01 Jan 1970 00:00:00 GMT"}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object
The controller looks like this:
package .pawsec.kitchen.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import .pawsec.kitchen.model.Guy;
@RestController
public class GuyController {
@RequestMapping(value="/get/guy/{guyId}", method=RequestMethod.GET,
headers={"Accept=application/json"})
public Guy getGuy(@PathVariable("guyId") int guyId) {
Guy someGuy = new Guy(guyId, "Walter Sobchak", 45);
return someGuy;
}
@RequestMapping(value="/get/guys", method=RequestMethod.GET,
headers={"Accept=application/json"})
public List<Guy> getGuys() {
Guy walter = new Guy(1, "Walter Sobchak", 45);
Guy theDude = new Guy(2, "Jeffrey Lebowski", 42);
Guy donny = new Guy(3, "Theodore Donald Kerabatsos", 39);
List<Guy> guys = new ArrayList<Guy>();
guys.add(walter);
guys.add(theDude);
guys.add(donny);
return guys;
}
}
Strangely, if i call these two services from a browser, i get the correct Json structure for both the calls.
When i run a mvn dependency:tree, the expected Jackson dependencies that e with a basic Boot Project are there.
This is what the JavaScript code looks like:
return dispatch => {
dispatch(fetchMenuStart());
const url = ':8443/get/guy/1';
const headers = {
headers: {
'Content-Type': 'application/json'
}
}
axios.get(url, headers)
.then(res => {
console.log(res);
dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
})
.catch(error => {
console.log("error", error);
const errorMsg = 'There was an error fetching the menu';
dispatch(fetchMenuFail(errorMsg));
});
};
Can anyone suggest what might be causing this or steps to test to figure out the issue?
New javascript example code:
const doesNotWork = ':8443/get/guy/1';
const doesWork = ':8443/get/guys';
const headers = {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
}
axios.get(doesNotWork, headers)
.then(res => {
console.log(res);
})
.catch(error => {
console.log("error", error);
const errorMsg = 'There was an error fetching the menu';
});
I am developing my first Spring Boot application and i ran into a weird problem. The configuration is very basic:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>.pawsec</groupId>
<artifactId>kitchen</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>kitchen</name>
<description>The Kitchen restaurant system</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.pawsec</groupId>
<artifactId>mon</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>
We have some Javascript code on a page calling these two services. When the controller returns a Guy object in the first method, we get an empty response:
{data: "", status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: ""
headers: {}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object
When we return a List of Guy objects from the second method, however, we get the full Json structure
back:
{data: Array(3), status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: Array(3)
0: {guyId: 1, name: "Walter Sobchak", age: 45}
1: {guyId: 2, name: "Jeffrey Lebowski", age: 42}
2: {guyId: 3, name: "Theodore Donald Kerabatsos", age: 39}
length: 3
: Array(0)
headers: {content-type: "application/json;charset=UTF-8", cache-control: "private", expires: "Thu, 01 Jan 1970 00:00:00 GMT"}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object
The controller looks like this:
package .pawsec.kitchen.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import .pawsec.kitchen.model.Guy;
@RestController
public class GuyController {
@RequestMapping(value="/get/guy/{guyId}", method=RequestMethod.GET,
headers={"Accept=application/json"})
public Guy getGuy(@PathVariable("guyId") int guyId) {
Guy someGuy = new Guy(guyId, "Walter Sobchak", 45);
return someGuy;
}
@RequestMapping(value="/get/guys", method=RequestMethod.GET,
headers={"Accept=application/json"})
public List<Guy> getGuys() {
Guy walter = new Guy(1, "Walter Sobchak", 45);
Guy theDude = new Guy(2, "Jeffrey Lebowski", 42);
Guy donny = new Guy(3, "Theodore Donald Kerabatsos", 39);
List<Guy> guys = new ArrayList<Guy>();
guys.add(walter);
guys.add(theDude);
guys.add(donny);
return guys;
}
}
Strangely, if i call these two services from a browser, i get the correct Json structure for both the calls.
When i run a mvn dependency:tree, the expected Jackson dependencies that e with a basic Boot Project are there.
This is what the JavaScript code looks like:
return dispatch => {
dispatch(fetchMenuStart());
const url = 'https://boot.ourpany.:8443/get/guy/1';
const headers = {
headers: {
'Content-Type': 'application/json'
}
}
axios.get(url, headers)
.then(res => {
console.log(res);
dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
})
.catch(error => {
console.log("error", error);
const errorMsg = 'There was an error fetching the menu';
dispatch(fetchMenuFail(errorMsg));
});
};
Can anyone suggest what might be causing this or steps to test to figure out the issue?
New javascript example code:
const doesNotWork = 'https://boot.exmpledomain.:8443/get/guy/1';
const doesWork = 'https://boot.exmpledomain.:8443/get/guys';
const headers = {
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
}
axios.get(doesNotWork, headers)
.then(res => {
console.log(res);
})
.catch(error => {
console.log("error", error);
const errorMsg = 'There was an error fetching the menu';
});
Share
Improve this question
edited Feb 26, 2019 at 14:21
Mats Andersson
asked Feb 18, 2019 at 16:08
Mats AnderssonMats Andersson
3972 gold badges8 silver badges21 bronze badges
26
- 2 did you set the content-type as application/json in the ajax call? – Amardeep Bhowmick Commented Feb 18, 2019 at 16:13
- 2 If you're getting the right response from the browser but incorrect from the js code, obviously there is some issue with the js code. Could you please add the js code snippet that you're using? – Madhu Bhat Commented Feb 18, 2019 at 16:15
- 1 ..maybe show us "some Javascript code". – xerx593 Commented Feb 18, 2019 at 18:36
- 1 @MatsAndersson, Since you get the proper response when call this via the browser. The problem is in your front-end code. Can you add the full front end code? Also, do add the code for the other request too – Phenomenal One Commented Feb 25, 2019 at 10:32
- 2 @MatsAndersson - If it works in the browser, your problem is not the backend. The moment the JSON goes on the wire, there's not type Guy, there's no "custom beans" and so on. The json is plain text and your JS frontend should know how to work with that. Can you add interceptors for the request and responses and paste the output on both? github./axios/axios#interceptors – hovanessyan Commented Mar 3, 2019 at 7:39
6 Answers
Reset to default 1Could you please try changing the header to accept in the javascript
return dispatch => {
dispatch(fetchMenuStart());
const url = 'https://boot.ourpany.:8443/get/guy/1';
const headers = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
axios.get(url, headers)
.then(res => {
console.log(res);
dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
})
.catch(error => {
console.log("error", error);
const errorMsg = 'There was an error fetching the menu';
dispatch(fetchMenuFail(errorMsg));
});
};
I have finally solved this issue by disabling CORS, with the following class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Profile("devel")
@Configuration
public class WebConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
I have also added the @Profile
annotation to disable CORS only on development time.
By the way, the reason for the issue seems to be explained in:
https://chromium.googlesource./chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md#Protecting-JSON
When returning an object, it is interpreted as a non-empty JSON object (such as {"key": "value"}
). When returning a list, the same text is wrapped in squared brackets and it passes the protection.
If you use spring, you should use ResponseEntity
instead of directly returning the object:
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
This is how I write my controllers:
@RestController
@RequestMapping(USERS)
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private LdapUserDetailsManager userDetailsService;
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> list(PagedResourcesAssembler<User> pageAssembler, @PageableDefault(size = 20) Pageable pageable, UserDTO condition) {
Page<User> page = userService.findAll(pageable, condition);
PagedResources<?> resources = pageAssembler.toResource(page, new UserResourceAssembler());
return ResponseEntity.ok(resources);
}
@GetMapping(value = CoreHttpPathStore.PARAM_ID, produces= MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UserResource> get(@PathVariable("id") Long id) {
User user = userService.get(id);
UserResource resource = new UserResourceAssembler().toResource(user);
return ResponseEntity.ok(resource);
}
private void preProcessEntity(@RequestBody UserDTO entity) {
if (null != entity.getPassword()) {
userDetailsService.changePassword(entity.getOldPassword(), entity.getPassword());
}
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
Long create(@RequestBody User user) {
userService.insert(user);
return user.getId();
}
@PutMapping(CoreHttpPathStore.PARAM_ID)
@ResponseStatus(HttpStatus.NO_CONTENT)
void modify(@PathVariable("id") Long id, @RequestBody UserDTO user) {
user.setId(id);
preProcessEntity(user);
userService.updateIgnore(user);
}
@DeleteMapping(CoreHttpPathStore.PARAM_ID)
@ResponseStatus(HttpStatus.NO_CONTENT)
void delete(@PathVariable("id") Long id) {
userService.delete(id);
}
@DeleteMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
void bulkDelete(@RequestBody Long[] ids) {
userService.delete(ids);
}
}
As your javascript is on a different domain from the spring-boot service, you need to configure CORS.
This could be done globally adding @CrossOrigin
like this :
@RestController
@CrossOrigin
public class GuyController {
OK everyone, thank you so much for your efforts. It turns out that the solution suggested by @mpromonet (adding CrossOrigin annotation on the controller) solves this problem. I am still very curious to know why a method returning List works and one returning a Guy does not if this is a cross-origin issue. It does not seem logical and it makes the issue a lot harder to figure out.
You have to add @ResponseBody
annotation before your method.
@ResponseBody
public Guy ....