Cant have RestController to accept application/octet-stream - java

I have a spring boot application with rest controller that has to accept binary stream at post endpoint and do the things with it.
So i have:
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public String parse(RequestEntity<InputStream> entity) {
return service.parse(entity.getBody());
}
When i try to test it with MockMvc i get org.springframework.web.HttpMediaTypeNotSupportedException.
I see in log:
Request:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /parse
Parameters = {}
Headers = [Content-Type:"application/octet-stream;charset=UTF-8", Content-Length:"2449"]
Body = ...skipped unreadable binary data...
Session Attrs = {}
Response:
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Accept:"application/json, application/*+json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I tried to add explicit header:
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE},
headers = "Accept=application/octet-stream")
Does not help.
The testing call is:
mvc.perform(post("/parse")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.content(bytes)
).andDo(print())
.andExpect(status().isOk());
How can i make it work without going to multipart-form?

I have analysed this issue and got to know that issue is in this method.
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public String parse(RequestEntity<InputStream> entity) {
return service.parse(entity.getBody());
}
Here method parameter is of type RequestEntity<InputStream> it should be HttpServletRequest
So here is the fix.
#PostMapping(value = "/upload",
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public String demo(HttpServletRequest httpServletRequest) {
ServletInputStream inputStream;
try {
inputStream = httpServletRequest.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
final List<String> list = new BufferedReader(new InputStreamReader(inputStream))
.lines().toList();
System.out.println(list);
return "Hello World";
}
Test Case
#Autowired
private MockMvc mockMvc;
#Test
public void shouldTestBinaryFileUpload() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders
.post("/upload")
.content("Hello".getBytes())
.contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(MockMvcResultMatchers
.status()
.isOk())
.andExpect(MockMvcResultMatchers
.content()
.bytes("Hello World".getBytes()));
}

Related

on a registration test, I get a 400 error instead of 200

the purpose is to test the registration of a user.
spring boot 2.6.7
the application works properly.
the registration test of a user has problems, I get an error 400 instead of 200.
AuthController.java
...
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignupRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Username is already taken!"));
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Email is already in use!"));
}
// Create new user's account
User user = authService.createUser(signUpRequest);
return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
}
AuthControllerTest.java
...
private static final String API = "/api/auth/signup";
...
#Test
void registerUser() {
String content = "User registered successfully!";
when(authService.createUser(any())).thenReturn(user);
when(userRepository.existsByUsername(any())).thenReturn(false);
when(userRepository.existsByEmail(any())).thenReturn(false);
//act
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.post(API)
.accept(MediaType.APPLICATION_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(content);
//assert
try {
mvc.perform(request)
.andExpect(status().isOk());
//.andExpect(new MessageResponse("User registered successfully!"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
the result of the test execution :
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/auth/signup
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"29"]
Body = User registered successfully!
Session Attrs = {}
Handler:
Type = com.acme.apitutorial.controller.AuthController
Method = com.acme.apitutorial.controller.AuthController#registerUser(SignupRequest)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status expected:<200> but was:<400>
Expected :200
Actual :400
any idea of the problem?
I have consulted several tutorials and source code, I do not see the error

Mock error UnfinishedStubbingException in a Junit test

I've created a test to try out my microservice. A simple get call that through 3 parameters returns me the message with the specifications of that user. I created it using Junit and Mockito. Below:
#Test
public void TestOk() throws Exception{
CarsRequest carsRequest = new CarsRequest();
carsRequest.setName(TestCostants.NAME);
carsRequest.setPlate(TestCostants.PLATE);
carsRequest.setPrice(TestCostants.PRICE);
Cars car = new Cars("BMW","TG35647", "15000","re",80000000,
null,null);
List<Cars> cars = new ArrayList<>();
cars.add(car);
Mockito.when(
service.getByPlate(carsRequest.getName(), carsRequest.getPlate(),
carsRequest.getPrice())).thenReturn(cars);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/resources/cars").accept(
MediaType.APPLICATION_JSON).header("Authorization","Basic //myAuth");
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println(result.getResponse());
String expected = "{\"name\"=\"BMW\", \"plate\"=\"TG35647\", " +
"\"price\"=\"15000\",\"brand\"=\"re\", \"kilometers\"=\"80000000\", \"revisiondate\"=\"null\", \"owner\"=\"null\"}";
JSONAssert.assertEquals(expected, result.getResponse()
.getContentAsString(), false);
});
clearly the constants of the variables that are passed conform to those of the message that must return me. When I try to start the test it gives me an error
This is the stacktrace I am reporting:
2021-03-29 12:45:07.906 [main] INFO o.s.s.l.SpringSecurityLdapTemplate - Ignoring PartialResultException
{
"#timestamp" : "2021-03-29T12:45:08.119+02:00",
"#version" : "1",
"message" : "[GET] http://localhost/resources/cars/ call resulted in the following error: Content type '' not supported",
"logger_name" : "my.project.car.controller.ExceptionHandlingController",
"thread_name" : "main",
"level" : "ERROR",
"level_value" : 40000
}
ErrorResponse: org.springframework.web.HttpMediaTypeNotSupportedException: Content type '' not supported
org.springframework.mock.web.MockHttpServletResponse#135f160e
MockHttpServletRequest:
HTTP Method = GET
Request URI = /resources/cars/
Parameters = {}
Headers = [Accept:"application/json", Authorization:"Basic //myAuth"]
Body = null
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = application/json
Body = {"esito":"KO","codiceEsito":"ERROR","descrizioneEsito":"Errore generico"}
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError:
Expected: name
but none found
;
Expected: plate
but none found
;
Expected: price
but none found
;
Expected: brand
but none found
;
Expected: kilometers
but none found
;
Expected: revisiondate
but none found
;
Expected: owner
but none found
;
Trying to go to Debug, I get this error back, but I don't know how to handle it at all
Method threw 'org.mockito.exceptions.misusing.UnfinishedStubbingException' exception. Cannot evaluate my.project.service.CarService$MockitoMock$1824138024.toString()
I guess the two errors are related but I don't know how to fix this.
EDIT:
As requested, I add my Car Service:
#Service
public class CarService {
#Autowired
CarRepository carRepository;
#Autowired
ObjectMapper objectMapper;
public List<Car> getByPlate(String name, String plate, String price) throws JsonProcessingException {
List<Car> car = new ArrayList<>();
for (Cache.Entry<String, Car> entry: carRepository.findAllByNameAndPlateAndPrice(name, plate,price)){
car.add(new Car(entry.getKey(), entry.getValue()));
System.out.println("Entry: " + objectMapper.writeValueAsString(entry));
}
return cars;
}
}
and my CarController
#RestController
#RequestMapping("/resources/")
public class CarController {
#Autowired
CarService carService;
#Autowired
ObjectMapper objectMapper;
#GetMapping(
value = "/cars",
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public CarsResponse getCars(#RequestBody CarsRequest request) throws IOException {
//some code
}
First of all, using GET requests with a body is the poor practice. Use POST requests instead.
Then, spring tells you about org.springframework.web.HttpMediaTypeNotSupportedException and in your controller method getCars() you describe the next:
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE}
But in your test you don't specify any content-type:
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/resources/cars").accept(
MediaType.APPLICATION_JSON).header("Authorization","Basic //myAuth");
Try to add content-type .contentType(MediaType.APPLICATION_JSON):
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/resources/cars")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic //myAuth");
Also, I don't see how you pass the body to the request. Try to add it .content(mapper.writeValueAsString(carsRequest)):
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/resources/cars")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic //myAuth")
.content(mapper.writeValueAsString(carsRequest));
where mapper is ObjectMapper. I assume you are using com.fasterxml.jackson.databind.ObjectMapper.

MockMVC test with Custom Authentication Principal

I am trying to write a test for an API endpoint but can not get past the security. At least that's what I understand is happening.
From what I understood in the docs (https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html) I think I am following the right way?
My Rest controller looks as following. I have an Authentication Principal which I believe is causing the problem.
#RestController
#RequestMapping("/api")
public class VehicleResource {
#GetMapping(value = "/status", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<StatusResponse> getStatus(#AuthenticationPrincipal CustomUser customUser) {
StatusResponse statusResponse = vehicleService.getStatus(customUser.getVin());
LOGGER.debug("Sent statusResponse: {}", statusResponse);
return new ResponseEntity<>(statusResponse, HttpStatus.OK);
}
My test is fairly simple:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = { MobileBackendApplication.class })
#AutoConfigureMockMvc
public class VehicleResourceTest {
#Autowired
private MockMvc mockMvc;
private AccountCredentials testCredentials;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
testCredentials = new AccountCredentials("test.user#test.com", "testPassword");
}
#Test
#WithMockCustomUser
public void getStatusTest() throws Exception {
mockMvc.perform(
get("/api/status").with(httpBasic(testCredentials.getUsername(), testCredentials.getPassword())))
.andExpect(status().isOk());
}
I have a MockCustomUser interface so I can pass along custom users:
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUser {
String username() default "test.user#bmw.com";
String password() default "testPassword";
String vin() default "123TEST456VIN789";
String pushNotificationId() default "testPushId";
}
With the WithMockCustomUserSecurityContextFactory:
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
CustomUser principal = new CustomUser(customUser.username(), customUser.password(), customUser.vin(),
new ArrayList<String>(Arrays.asList(customUser.pushNotificationId())), null,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password",
principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
When I run the test I receive a 401 Unauthorized instead of a 200. Other answers referred to maybe missing the Basic Authentication header, but I seem to be passing that along. Below is what is printed when I run the test.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/status
Parameters = {}
Headers = {Authorization=[Basic dGVzdC51c2VyQHNhbXBsZS5jb206dGVzdFBhc3N3b3Jk]}
Body = null
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Unauthorized
Headers = {Access-Control-Allow-Origin=[*], Access-Control-Allow-Methods=[POST, GET], Access-Control-Max-Age=[3600], Access-Control-Allow-Headers=[Origin, X-Requested-With, Content-Type, Accept, Authorization], Access-Control-Expose-Headers=[Authorization], WWW-Authenticate=[Basic realm="Realm"], X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []

RestTemplate handle [image/jpg] response content type in java

Sorry, i am newbie on java web development.
I got some task to fetch user profile picture from 3rd party company via HTTP rest(GET method). Their api only can be accessed using session id on the header parameter and the api will return some byte[] array looks like ’ÑÒBRSb¢ÂáTr²ñ#‚4“â3C etc.
How to handle rest response with content type image/jpg in Rest Template?
I do my best like this
private RestTemplate restTemplate;
public byte[] getProfilePic(){
String canonicalPath = "http://dockertest/bankingapp/customer/profpicFile";
String sessionId= "MTQ4NzE5Mz...etc";
HttpEntity<byte[]> request = new HttpEntity<byte[]>(null, getHeaders(true, "GET", null, canonicalPath, sessionId));
//getHeaders() will return HttpHeaders with those parameter
ResponseEntity<byte[]> response = null;
try {
response = this.restTemplate.exchange(uri, HttpMethod.GET, request, byte[].class);
} catch( HttpServerErrorException hse ){
throw hse;
}
return response;
}
This code will return an error
org.springframework.web.client.RestClientException: Could not extract
response: no suitable HttpMessageConverter found for response type
[[B] and content type [image/jpg]
Any suggestion or help will be appreciated!
Thank you
Update
Using stackoveflower suggestions i can manage to solve this.
private RestTemplate restTemplate;
public byte[] getProfilePic(){
String canonicalPath = "/mobile/customer/profpicFile";
String sessionId= "MTQ4NzE5Mz...etc";
HttpEntity<byte[]> request = new HttpEntity<byte[]>(null, getHeaders(true, "GET", null, canonicalPath, sessionId));
//getHeaders() will return HttpHeaders with those parameter
ResponseEntity<byte[]> response = null;
try {
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
response = this.restTemplate.exchange(uri, HttpMethod.GET, request, byte[].class).getBody();
return response;
} catch( HttpServerErrorException hse ){
throw hse;
}
return null;
}
Note about HttpMessageConverter, instead using list, i can directly add a ByteArrayHttpMessageConverter()
As said I guess you must use the right messageconverter
I would do in this way:
private RestTemplate restTemplate;
public byte[] getProfilePic(){
String canonicalPath = "http://dockertest/bankingapp/customer/profpicFile";
String sessionId= "MTQ4NzE5Mz...etc";
List<HttpMessageConverter> converters = new ArrayList<>(1);
converters.add(new ByteArrayHttpMessageConverter());
restTemplate.setMessageConverters(converters);
HttpEntity<byte[]> request = new HttpEntity<byte[]>(null, getHeaders(true, "GET", null, canonicalPath, sessionId));
//getHeaders() will return HttpHeaders with those parameter
ResponseEntity<byte[]> response = null;
try {
response = this.restTemplate.exchange(uri, HttpMethod.GET, request, byte[].class);
} catch( HttpServerErrorException hse ){
throw hse;
}
return response;
}
More information can be found here: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#setMessageConverters-java.util.List- and here https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/HttpMessageConverter.html and here https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/ByteArrayHttpMessageConverter.html
Thank you very much,this problem takes up my a lot of time. Now,it was resolved.
following:
#Configuration
#Slf4j
public class RestTemplateConfiguration implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RestTemplate restTemplate = (RestTemplate) applicationContext.getBean("restTemplate");
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
restTemplate.setUriTemplateHandler(new GetUriTemplateHandler());
}
}

HTTP 406 downloading a file with rest call

I followed this tutorial to implement rest API with Spring Boot for downloading files (xml format).
My controller class is as follows:
#RestController
public class RistoreController {
#Autowired
private RistoreService ristoreService;
#RequestMapping(
value = "/ristore/foundation/{trf}",
method = RequestMethod.GET,
produces = "application/xml")
public ResponseEntity<InputStream> getXMLById(#PathVariable("trf") String trf) throws IOException {
InputStream inputStream = ristoreService.findByTRF(trf);
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(inputStream);
}
}
I have service interface RistoreService autowired in the controller and Bean class for that service looks like this:
#Service
public class RistoreServiceBean implements RistoreService {
public InputStream findByTRF(String trf) throws IOException {
String filePath = "/Users/djiao/Box Sync/Work/Projects/RIStore/foundation/foundation_new/" + trf + ".xml";
File file = new File(filePath);
return new FileInputStream(file);
}
}
I tested the application using the following curl command:
curl -i -H "Accept: application/xml" http://localhost:8080/ristore/foundation/TRF133672_1455294493597
However, I got 406 error, "Not Acceptable". Something wrong with the file format?
Try to change the definition of the controller that way
#RequestMapping(value = "/ristore/foundation/{trf}", method = RequestMethod.GET, produces = "application/xml")
public ResponseEntity<InputStreamResource> downloadXMLFile(#PathVariable("trf") String trf)
throws IOException {
// Optinal headers configuration
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
// get the inputStream
InputStream xmlFileInputStream = ristoreService.findByTRF(trf);
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(xmlFileInputStream));
}
Then your service class would be :
#Service
public class RistoreServiceBean implements RistoreService {
public InputStream findByTRF(String trf) throws IOException {
String filePath = "/Users/djiao/Box Sync/Work/Projects/RIStore/foundation/foundation_new/" + trf + ".xml";
File file = new File(filePath);
return new FileInputStream(file);
}
}
406 Not Acceptable
The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.
That means that the inputstream you return must be considered as a resource as soon as you have a REST controller.
The following two lines in your code contradict each other:
.contentType(MediaType.parseMediaType("application/octet-stream"))
and
produces = "application/xml")

Categories