I'm new to this AOP technique and is having problems with getting the json body inside the methods in Aspect class.
Here is my controller method:
#PostMapping("/startDMS")
#ResponseBody
public String getDmsInitialisePageValidToken(HttpServletRequest request, #RequestBody JSONObject requestJson) {
logger.debug("/initialiseView");
return "{\"SUCCESS\" :\"DMS initialisation.\"}";
}
And here is my aspect methods:
#Around("tokenValidationPointCut()")
public Object validateToken(ProceedingJoinPoint pjp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
try {
JSONObject requestJson = null;
Object[] requestParams = pjp.getArgs();
System.out.println("Array Object Length: " + requestParams.length);
if (requestParams != null && requestParams.length > 0) {
System.out.println("Object2: " + requestParams[1]);
for (Object object : requestParams) {
if (JSONObject.class.equals(object.getClass())) {
requestJson = (JSONObject) object;
System.out.println("Json Object: " + requestJson);
break;
}
}
}
//rest of the code
if(Constants.TOKEN_VALIDATION_STATUS.TOKEN_VALIDATION_SUCCESS.equals(jsonTknObject.getTokenValidation().getStatus())) {
return pjp.proceed();
}
}catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return loginViews.getTokenValidationError(request);
}
#Pointcut("execution(* com.dms.controller.*.*ValidToken(..))")
public void tokenValidationPointCut() {
System.out.println("This method is tokenValidationPointCut.");
}
I'm using Postman to do the testing, and I send the Json as body. But in the code, where I'm printing the received json, the output is like this:
20:20:52,570 INFO [stdout] (default task-4) Array Object Length: 2
20:20:52,570 INFO [stdout] (default task-4) Object2: {}
20:20:52,570 INFO [stdout] (default task-4) Json Object: {}
Here is the json string:
{
"JSON_INITIATE": {
"initialisation": {
"status": "REQUEST",
"token": "abcd1234",
"user_id": "1234",
"user_role":"GA",
"request_type":"DMS_INITIALISATION"
}
}
}
Thank you in advance for your help!
I found a workaround solution for this issue. Instead of receiving the object as JSONObject, I tried with String and it works fine.
public String getDmsInitialisePageValidToken(HttpServletRequest request, #RequestBody String requestJson) {
//code here
}
Only problem is I have to write an additional line of code to convert the String to JSONObject.
Still wondering why the first method with JSONObject didn't work!
Related
I am using Spring boot and following libraries in client and server,
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2"
}
}
// Spring Cloud Sleuth
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-sleuth', version: '2.0.1.RELEASE'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-zipkin', version: '2.0.1.RELEASE'
Based upon spring documentation, "https://cloud.spring.io/spring-cloud-sleuth/"
Run this app and then hit the home page. You will see traceId and spanId populated in the logs. If this app calls out to another one (e.g. with RestTemplate) it will send the trace data in headers and if the receiver is another Sleuth app you will see the trace continue there.
How will this work with Spring5 web client?
It will work in the same way. It's enough to inject a bean of WebClient or WebClientBuilder type. Check out this sample https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/java/io/spring/cloud/sleuth/docs/service1/Service2Client.java
/**
* #author Marcin Grzejszczak
*/
#Component
class Service2Client {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final WebClient webClient;
private final String serviceAddress;
private final Tracer tracer;
Service2Client(WebClient webClient,
#Value("${service2.address:localhost:8082}") String serviceAddress,
Tracer tracer) {
this.webClient = webClient;
this.serviceAddress = serviceAddress;
this.tracer = tracer;
}
public String start() throws InterruptedException {
log.info("Hello from service1. Setting baggage foo=>bar");
Span span = tracer.currentSpan();
String secretBaggage = ExtraFieldPropagation.get("baggage");
log.info("Super secret baggage item for key [baggage] is [{}]", secretBaggage);
if (StringUtils.hasText(secretBaggage)) {
span.annotate("secret_baggage_received");
span.tag("baggage", secretBaggage);
}
String baggageKey = "key";
String baggageValue = "foo";
ExtraFieldPropagation.set(baggageKey, baggageValue);
span.annotate("baggage_set");
span.tag(baggageKey, baggageValue);
log.info("Hello from service1. Calling service2");
String response = webClient.get()
.uri("http://" + serviceAddress + "/foo")
.exchange()
.block()
.bodyToMono(String.class).block();
Thread.sleep(100);
log.info("Got response from service2 [{}]", response);
log.info("Service1: Baggage for [key] is [" + ExtraFieldPropagation.get("key") + "]");
return response;
}
#NewSpan("first_span")
String timeout(#SpanTag("someTag") String tag) {
try {
Thread.sleep(300);
log.info("Hello from service1. Calling service2 - should end up with read timeout");
String response = webClient.get()
.uri("http://" + serviceAddress + "/readtimeout")
.retrieve()
.onStatus(httpStatus -> httpStatus.isError(), clientResponse -> {
throw new IllegalStateException("Exception!");
})
.bodyToMono(String.class)
.block();
log.info("Got response from service2 [{}]", response);
return response;
} catch (Exception e) {
log.error("Exception occurred while trying to send a request to service 2", e);
throw new RuntimeException(e);
}
}
}
I want to implement a custom Content-Type validation filter so that a custom error model on a 415 Unsupported Media Type can be provided.
Something like this:
public class ValidateContentTypeFilterAttribute : ActionFilterAttribute
{
private const string JsonMimeType = "application/json";
public override void OnActionExecuting(ActionExecutingContext context)
{
string requestMethod = context.HttpContext.Request.Method.ToUpper();
if (requestMethod == WebRequestMethods.Http.Post || requestMethod == WebRequestMethods.Http.Put)
{
if (request.ContentType != JsonMimeType)
{
// "Unsupported Media Type" HTTP result.
context.Result = new HttpUnsupportedMediaTypeResult();
return;
}
}
}
}
The problem is that the MVC pipeline seems to be "catching" unsupported or invalid Content-Type values before executing any custom filters. Even the 'application/xml' content type will be refused.
Where would this be configured?
My MVC configuration consists of not much more than this:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Include;
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.Converters.Add(new SquidJsonConverter());
})
.AddMvcOptions(options =>
{
options.Filters.Add(typeof(ValidateAntiForgeryTokenAttribute));
options.Filters.Add(typeof(ValidateContentTypeFilterAttribute));
options.Filters.Add(typeof(ValidateAcceptFilterAttribute));
options.Filters.Add(typeof(ValidateModelFilterAttribute));
});
...
}
Action filters are too late in the processing pipeline for what you are trying to achieve here.
The filter execution order for an "incoming" request is the following:
Authorization filters' OnAuthorization.. method invocation
Resource filters' OnResourceExecuting.. method invocation Model
Model binding happens (this is the place where the content type check is
made)
Action filters' OnActionExecuting.. method invocation
Action execution happens
You could instead create a resource filter. An example:
public class CustomResourceFilter : IResourceFilter
{
private readonly string jsonMediaType = "application/json";
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (context.HttpContext.Request.Method == "PUT" || context.HttpContext.Request.Method == "POST")
{
if (!string.Equals(
MediaTypeHeaderValue.Parse(context.HttpContext.Request.ContentType).MediaType,
jsonMediaType,
StringComparison.OrdinalIgnoreCase))
{
context.Result = new JsonResult(new { Error = "An error message here" }) { StatusCode = 415 };
}
}
}
}
If you would like to modify all types of UnsupportedMediaTypeResult responses, then you could write a Result filter instead.
The filter pipeline for outgoing response is:
Action filters' OnActionExecuted... method invocation
Result filters' OnResultExecuting.. method invocation
Result filters' OnResultExecuted.. method invocation
Resource filters' OnResourceExecuted.. method invocation
An example with a Result filter:
public class CustomResultFilter : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result as UnsupportedMediaTypeResult;
if (result != null)
{
context.Result = new JsonResult(new { Error = "An error message here" }) { StatusCode = 415 };
}
}
}
I want to upload files using spring-boot, and I have configured the properties right, and I also ensure the controller is correct, but the strange thing is the controller executed twice when I tried to upload a file larger exceed the limitation, what I expect is an error json message, and what I got is no response under the Postman.
Here is my controller,
#RestController
public class FileUploadController implements HandlerExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadController.class);
private static final String UPLOAD_PATH = "upload";
#ResponseBody
#RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = "multipart/form-data", produces = "application/json;charset=UTF-8")
public String upload(final MultipartFile file) {
try {
final Result<String> result = new Result<>();
if (file.isEmpty()) {
result.setSuccess(false);
result.setMessage("file is empty");
return Constants.OBJECT_MAPPER.writeValueAsString(result);
}
final File outputFile = new File(UPLOAD_PATH, UUID.randomUUID().toString());
FileUtils.writeByteArrayToFile(outputFile, file.getBytes());
result.setSuccess(true);
result.setMessage(outputFile.toString());
return Constants.OBJECT_MAPPER.writeValueAsString(result);
} catch (final Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ExceptionResultBuilder.build(ex);
}
}
#Override
public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) {
final ModelAndView modelAndView = new ModelAndView();
modelAndView.setView(new MappingJackson2JsonView());
final Map<String, Object> map = new HashMap<>();
map.put("success", false);
if (ex instanceof MultipartException) {
// if (LOGGER.isDebugEnabled()) {
LOGGER.info(ex.getMessage(), ex);
// }
final Throwable rootCause = ((MultipartException) ex).getRootCause();
if (rootCause instanceof SizeLimitExceededException) {
map.put("message", "request too large");
} else if (rootCause instanceof FileSizeLimitExceededException) {
map.put("message", "file too large");
} else {
map.put("message", "其他异常: " + rootCause.getMessage());
}
} else {
LOGGER.error(ex.getMessage(), ex);
}
modelAndView.addAllObjects(map);
return modelAndView;
}
}
and this is my property snippet for file uploading,
# MULTIPART (MultipartProperties)
multipart.enabled=true
multipart.max-file-size=5Mb
multipart.max-request-size=10Mb
If I tried to upload a file a bit larger than 5M, I will get the result like below under Postman, (the file size is 5208k)
enter image description here
and if I tried to upload a file between 5M and 10M, I will get this error, (the file size is 9748k)
enter image description here
I debugged into the controller and found that the resolveException method executed twice in a single upload.
Does anybody give me some tip?
The latest code list here, and I still got the same result,
#RestController
#ControllerAdvice
public class FileUploadController {
private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadController.class);
private static final String UPLOAD_PATH = "upload";
#RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = "multipart/form-data", produces = "application/json;charset=UTF-8")
public HttpEntity<?> upload(final MultipartFile file) {
try {
final Result<String> result = new Result<>();
if (file == null || file.isEmpty()) {
result.setSuccess(false);
result.setMessage("上传的文件为空");
return new ResponseEntity<Result<?>>(result, HttpStatus.OK);
}
final File outputFile = new File(UPLOAD_PATH, UUID.randomUUID().toString());
FileUtils.writeByteArrayToFile(outputFile, file.getBytes());
result.setSuccess(true);
result.setMessage(outputFile.toString());
return new ResponseEntity<Result<?>>(result, HttpStatus.OK);
} catch (final Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ExceptionResultBuilder.build(ex);
}
}
#ExceptionHandler(MultipartException.class)
public HttpEntity<?> multipartExceptionHandler(final MultipartException exception) {
LOGGER.error(exception.getMessage(), exception);
try {
final Result<String> result = new Result<>();
result.setSuccess(false);
final Throwable rootCause = ((MultipartException) exception).getRootCause();
if (rootCause instanceof SizeLimitExceededException) {
result.setMessage("请求过大");
} else if (rootCause instanceof FileSizeLimitExceededException) {
result.setMessage("文件过大");
} else {
result.setMessage("未知错误");
}
return new ResponseEntity<Result<?>>(result, HttpStatus.OK);
} catch (final Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ExceptionResultBuilder.build(ex);
}
}
}
I just go the same error and fix it by add the flowing code to my controller, good luck
#ExceptionHandler({ MultipartException.class, FileSizeLimitExceededException.class,
SizeLimitExceededException.class })
public ResponseEntity<Attachment> handleUploadrException(HttpServletRequest request, Throwable ex) {
Attachment result = new Attachment();
result.setDescription(ex.getMessage());
HttpStatus status = getStatus(request);
return new ResponseEntity<Attachment>(result, status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
I have configured an inbound HTTP gateway, which takes POST requests (JSON) and does homework to return a JSON response, the request and response payload is the same POJO.
I created beans for Json converters as follows
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
return builder;
}
#Bean
public List<HttpMessageConverter<?>> getConverters(){
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build()));
return converters;
}
And then I wired them up to the gate way definition in same Java Config class, snippet as follows:
#Bean
public HttpRequestHandlingMessagingGateway gateway(){
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
RequestMapping requestMapping = new RequestMapping();
requestMapping.setMethods(HttpMethod.POST);
requestMapping.setPathPatterns("/appliance/v1/status");
requestMapping.setConsumes("application/json");
requestMapping.setProduces("application/json");
gateway.setRequestMapping(requestMapping);
gateway.setRequestChannel(requestChannel());
gateway.setReplyChannel(replyChannel());
gateway.setMessageConverters(getConverters());
return gateway;
}
And the POJO for which I intend transform is pretty straight forward
public class ApplianceStatus {
private String gatewayId;
private String applianceId;
private boolean running;
public String getGatewayId() {
return gatewayId;
}
public void setGatewayId(String gatewayId) {
this.gatewayId = gatewayId;
}
public String getApplianceId() {
return applianceId;
}
public void setApplianceId(String applianceId) {
this.applianceId = applianceId;
}
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
}
However a POST Request to with Content-Type header set to application/json returns 400, the JSON I send is
{
"gatewayId": 1,
"applianceId": 123,
"running": false
}
I get the response
{
"timestamp" : 1434615561240,
"status" : 400,
"error" : "Bad Request",
"exception" : "org.springframework.http.converter.HttpMessageNotReadableException",
"message" : "Bad Request",
"path" : "/appliance/v1/status"
}
and In logs
2015-06-18 14:55:30.501 DEBUG 3447 --- [tp1023996917-22] .w.s.m.a.ResponseStatusExceptionResolver : Resolving exception from handler [gateway]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of byte[] out of START_OBJECT token
at [Source: HttpInputOverHTTP#55c2d2c5; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of byte[] out of START_OBJECT token
at [Source: HttpInputOverHTTP#55c2d2c5; line: 1, column: 1]
2015-06-18 14:55:30.501 DEBUG 3447 --- [tp1023996917-22] .w.s.m.s.DefaultHandlerExceptionResolver : Resolving exception from handler [gateway]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not deserialize instance of byte[] out of START_OBJECT token
at [Source: HttpInputOverHTTP#55c2d2c5; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of byte[] out of START_OBJECT token
at [Source: HttpInputOverHTTP#55c2d2c5; line: 1, column: 1]
The problem was due to request payload type not set on gateway
gateway.setRequestPayloadType(ApplianceStatus.class);
That solved it.
I'm trying to upload a file using JavaFX using the HttpRequest. For this purpose I have written the following function.
function uploadFile(inputFile : File) : Void {
// check file
if (inputFile == null or not(inputFile.exists()) or inputFile.isDirectory()) {
return;
}
def httpRequest : HttpRequest = HttpRequest {
location: urlConverter.encodeURL("{serverUrl}");
source: new FileInputStream(inputFile)
method: HttpRequest.POST
headers: [
HttpHeader {
name: HttpHeader.CONTENT_TYPE
value: "multipart/form-data"
}
]
}
httpRequest.start();
}
On the server side, I am trying to handle the incoming data using the Apache Commons FileUpload API using a Jersey REST service. The code used to do this is a simple copy of the FileUpload tutorial on the Apache homepage.
#Path("Upload")
public class UploadService {
public static final String RC_OK = "OK";
public static final String RC_ERROR = "ERROR";
#POST
#Produces("text/plain")
public String handleFileUpload(#Context HttpServletRequest request) {
if (!ServletFileUpload.isMultipartContent(request)) {
return RC_ERROR;
}
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
}
catch (FileUploadException e) {
e.printStackTrace();
return RC_ERROR;
}
...
}
}
However, I get a exception at items = upload.parseRequest(request);:
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I guess I have to add a manual boundary info to the InputStream. Is there any easy solution to do this? Or are there even other solutions?
Have you tried just using the InputStream from HttpServletRequest like so
InputStream is = httpRequest.getInputStream();
BufferedInputStream in = new BufferedInputStream(is);
//Write out bytes
out.close();
is.close();