Taken from ThumbSnap
ThumbSnap offers a free API that can be used to upload and host images and videos programmatically. This is the main API method used to upload images to ThumbSnap
Required fields:
media - Binary image/video data. Should be sent as an HTTP POST formatted as multipart/form-data
key - The API key provided by ThumbSnap Upon successfully posting an image, a JSON-formatted document will be returned with a 'url' which is the full URL to the uploaded photo's page at ThumbSnap. This URL must be linked to any ThumbSnap-hosted thumbnails or images used by your application.
Sample Response A successful image upload will return a JSON document as follows:
{ "data": { "id": "soLHmGdX", "url": "https://thumbsnap.com/soLHmGdX", "media": "https://thumbsnap.com/i/soLHmGdX.png", "thumb": "https://thumbsnap.com/t/soLHmGdX.jpg", "width": 224, "height": 224 }, "success": true, "status": 200 }
I want to use this service in Spring boot powered application, I have converted this response:
{
"data": {
"id": "soLHmGdX",
"url": "https://thumbsnap.com/soLHmGdX",
"media": "https://thumbsnap.com/i/soLHmGdX.png",
"thumb": "https://thumbsnap.com/t/soLHmGdX.jpg",
"width": 224,
"height": 224
},
"success": true,
"status": 200
}
to DTO as:
public record ThumbSnapResponseDTO(
@JsonProperty(value = "data")
DataDTO data,
boolean success,
int status
) {
public record DataDTO(
String id,
String url,
String media,
String thumb,
int width,
int height
) {}
}
Next there is Service class witch have following logic:
@Slf4j
@Service
@RequiredArgsConstructor
public class ThumbSnapServiceImpl implements ThumbSnapService {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@Async
@Override
public Future<ThumbSnapResponseDTO> uploadImage(byte[] imageData, String filename) throws IOException {
HttpHeaders headers = new HttpHeaders();
// headers.setContentType(MediaType.MULTIPART_FORM_DATA); // First tried this
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // Second this also does not work
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("key", THUMB_SNAP_API_KEY);
body.add("media", new ByteArrayResource(imageData) {
@Override
public String getFilename() {
return filename;
}
});
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
restTemplate.getMessageConverters().add(new MultiPartMessageConverter(objectMapper));
ResponseEntity<ThumbSnapResponseDTO> response =
restTemplate.exchange(
THUMB_SNAP_BASE_URL,
HttpMethod.POST,
requestEntity,
ThumbSnapResponseDTO.class
);
CompletableFuture<ThumbSnapResponseDTO> future = new CompletableFuture<>();
log.info("REST RESPONSE {} ", response.getBody());
future.complete(response.getBody());
return future;
}
}
last but not least there is Controller method where this Service is actually used:
// ThumbSnap
private final ThumbSnapService thumbSnapService;
@Override
@PostMapping("/{portfolioId}/upload")
public ResponseEntity<?> saveToThumbSnap(
@PathVariable(name = "portfolioId") Long portfolioId,
@RequestPart("media") MultipartFile file
) throws ExecutionException, InterruptedException, IOException {
// Logign
String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
long size = file.getSize();
log.info("File is {} - - and size is {} ", fileName, size);
// Get Data as bytes
byte[] imageData = file.getBytes();
Future<ThumbSnapResponseDTO> response = thumbSnapService.uploadImage(imageData, fileName);
ThumbSnapResponseDTO result = response.get();
var portfolio = portfolioService.findById(portfolioId).orElseThrow(
() -> new EntityNotFoundException(String.format("Record not found with id = %s", portfolioId))
);
// Check if Image Uploaded Successfully
if (!result.success())
throw new RuntimeException("Unable to upload image CONTROLLER");
var portfolioItemImage = new PortfolioItemImage();
//set img URL
portfolioItemImage.setImgUrl(result.data().url());
// Save mapping
portfolioItemImage.setPortfolio(portfolio);
return ResponseEntity.status(HttpStatus.OK).body(
portfolioItemImageMapper.asDTO(portfolioItemImageService.save(portfolioItemImage))
);
}
with that said when I try it out in PostMan REST Client for sending post request to above endpoint I get error:
{
"code": "EXECUTION",
"message": "org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: \"No API key specified. Upload stopped\""
}
The ScreenShot is below:
I have checked API_KEY a zillion times, but in-vain.
Is there any problem with following code:
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("key", THUMB_SNAP_API_KEY);
body.add("media", new ByteArrayResource(imageData) {
@Override
public String getFilename() {
return filename;
}
});
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
restTemplate.getMessageConverters().add(new MultiPartMessageConverter(objectMapper));
ResponseEntity<ThumbSnapResponseDTO> response =
restTemplate.exchange(
THUMB_SNAP_BASE_URL,
HttpMethod.POST,
requestEntity,
ThumbSnapResponseDTO.class
);
Or there is any other way to accomplish this. How can I send request correctly can you please suggest a solution to this.
As for as the above code is concerned, every thing looks fine except @PostMapping
. You have to change that line as follows:
@PostMapping(path = "/{userId}/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
The consume attribute is used to specify the types of media that the method can process in the request body. The MediaType.MULTIPART_FORM_DATA_VALUE
value indicates that the method can accept data in multipart/form data format, which is commonly used for file upload operations, where as the value MediaType.APPLICATION_JSON_VALUE
indicates that the method can return data in application/json
format.
And now It should work as intended/expected.