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.
Related
ServiceStack doesn't populate the original response in the WebServiceException's responseDTO property.
I'm running the code below which should always return a 404 response code with the ResponseStatus property of the TestResponse populated with "Some bad request" but it also seems like should return the original good response with it's output property populated from the request's input property. However I get null when I look at the WebServiceException responseDTO
public TestResponse Post(Test request)
{
var response = new TestResponse() { Output = request.Input };
throw new HttpError(response, (int)HttpStatusCode.BadRequest, "Some bad request");
}
public TestResponse Get(Test request)
{
try
{
using (var client = new JsonServiceClient("http://localhost:5000"))
{
var response = client.Post(request);
return response;
}
}
catch (WebServiceException ex)
{
throw;
}
}
In general I was expecting that the responseDTO property in the WebServiceException will contain the endpoint's DTO as long as it's passed in when throwing the HttpError but that doesn't seem to be the case. I see only default values and nulls for each property in the responseDTO.
When an Exception is thrown only the ResponseStatus is preserved, you can add any additional info to its Meta dictionary.
Alternatively you can return a failed HTTP Response:
public TestResponse Post(Test request)
{
var response = new TestResponse() { Output = request.Input };
base.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
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'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!
I have two REST services implemented with Dropwizard-0.8.
Both share an API dependency with following POJO:
public class Report{
private String text;
#JsonProperty("t")
public String getText()
{
return text;
}
public void setText(String tx)
{
text = tx;
}
}
My Server has a rest recourse:
#POST
#Consumes(MediaType.APPLICATION_JSON + ";charset=UTF-8")
#Produces(MediaType.TEXT_PLAIN + ";charset=UTF-8")
#Timed
public Response receive(Report dto) {
//do some stuff with dto
}
My Client has a method :
sendReport(report);
with:
private void sendReport(Report report) {
final String uri = "http://localhost:8080/.....";
Response response = null;
try {
response = client.target(uri).request().post(Entity.entity(report, MediaType.APPLICATION_JSON), Response.class);
final int status = response.getStatus();
if (status != Status.ACCEPTED.getStatusCode()) {
final StatusType statusInfo = response.getStatusInfo();
throw new SomeException();
}
}
catch (Exception e) {
LOGGER.error(e.getMessage());
}
finally {
if (response != null) {
response.close();
}
}
}
The Client is made in the Dropwizard application class with:
service.client = new JerseyClientBuilder(env).using(conf.getJerseyClient()).withProvider(JacksonJaxbJsonProvider.class).build(getName());
env.jersey().register(service);
Where 'service' is my rest class calling the 'sendReport' method.
Problem
When I call the rest service of my server from a browser or with curl etc it works perfectly as expected with following messagebody:
{"t":"some text for the server"}
But when I run my application to call the rest service I get a 400 "unable to process JSON".
Debugging and the log messages showed me that the application sends the following JSON to my server:
{"text":"some text for the server"}
Which leads to the error that Jackson cant find a property "text".
Why is the JerseyClient ignoring the JsonProperty annotation?
From what I understand you using Entity.entity from jersey which has no idea about the #JsonProperty annotation(which is from jackson library) . What you need to do is do serialisation using a jackson library and give it to post call .
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();