I have a Spring Boot 1.3.6 application, built out of the box and using the embedded Tomcat server. The application has a single endpoint doing a very simple echo request.
Later I defined a corresponding client invoking that simple endpoint using AsyncRestTemplate, however if my client uses the Netty4ClientHttpRequestFactory the request fails, otherwise it succeeds.
My example below is in Kotlin, but it fails just the same in Java, so it does not have to do with the language I use to implement it.
The Server
#SpringBootApplication
open class EchoApplication {
companion object {
#JvmStatic fun main(args: Array<String>) {
SpringApplication.run(EchoApplication::class.java, *args)
}
}
#Bean
open fun objectMapper(): ObjectMapper {
return ObjectMapper().apply {
dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
registerModule(KotlinModule())
}
}
#Bean
open fun customConverters(): HttpMessageConverters {
return HttpMessageConverters(listOf(MappingJackson2HttpMessageConverter(objectMapper())))
}
}
My endpoint looks like this:
#RestController
class EchoController {
#RequestMapping(value="/foo", method = arrayOf(RequestMethod.PUT))
fun echo(#RequestBody order: Order): Order {
return order
}
}
And the order data class is
data class Order(val orderId: String)
Note: Since I use Kotlin, I also added the Kotlin Jackson Module to ensure proper constructor deserialization.
The Client
I then proceeded to create a client that invokes this endpoint.
If I do something like the following in my client, it works perfectly and I get a successful echo response.
val executor = TaskExecutorAdapter(Executors.newFixedThreadPool(2))
val restTemplate = AsyncRestTemplate(executor)
restTemplate.messageConverters = listOf(MappingJackson2HttpMessageConverter(mapper))
val promise = restTemplate.exchange(URI.create("http://localhost:8080/foo"), HttpMethod.PUT, HttpEntity(Order("b1254"), headers), Order::class.java)
promise.addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> {
override fun onSuccess(result: ResponseEntity<Order>) {
println(result.body)
}
override fun onFailure(ex: Throwable) {
ex.printStackTrace()
if(ex is HttpStatusCodeException){
println(ex.responseBodyAsString)
}
}
})
As mentioned above, the code above runs perfectly and prints a successful echo response.
The Problem
But if I decide to use the Netty client, then I get 400 Bad Request reporting I did not pass the body:
val nettyFactory = Netty4ClientHttpRequestFactory()
val restTemplate = AsyncRestTemplate(nettyFactory)
When I do this then I get a HttpMessageNotReadableException with a message saying "Required request body is missing".
I debugged the Spring Boot code and I see that when the ServletInputStream is read, it always return -1 as if it was empty.
In my gradle I added runtime('io.netty:netty-all:4.1.2.Final'), so I am using what, as of today, is the latest version of Netty. This Netty version has worked just fine when interacting with endpoints in other projects that I have that use regular Spring (i.e. not Spring Boot).
How come the SimpleClientHttpRequestFactory works fine, but the Netty4ClientHttpRequestFactory fails?
I thought it might had to do with the embedded Tomcat server, however, if I package this application as war and deploy it in an existing Tomcat server (i.e. not using the embedded one), the problem persists. So, I'm guessing is something related to Spring/Spring Boot.
Am I missing any configuration in my Spring Boot app? Any suggestions on how to make the Netty client work with Spring Boot?
Looks like there are problem in serialization on Client's side. Because this code works perfect:
restTemplate.exchange(
URI.create("http://localhost:8080/foo"),
HttpMethod.PUT,
HttpEntity("""{"orderId":"1234"}""", HttpHeaders().apply {
setContentType(MediaType.APPLICATION_JSON);
}),
Order::class.java
).addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> {
override fun onSuccess(result: ResponseEntity<Order>) {
println("Result: ${result.body}")
}
override fun onFailure(ex: Throwable) {
ex.printStackTrace()
if (ex is HttpStatusCodeException) {
println(ex.responseBodyAsString)
}
}
})
I need more precise look at restTemplate at his converters, but for now you can write this part this way:
val mapper = ObjectMapper()
restTemplate.exchange(
URI.create("http://localhost:8080/foo"),
HttpMethod.PUT,
HttpEntity(mapper.writeValueAsString(Order("HELLO")), HttpHeaders().apply {
setContentType(MediaType.APPLICATION_JSON);
}),
Order::class.java
).addCallback(object : ListenableFutureCallback<ResponseEntity<Order>> {
override fun onSuccess(result: ResponseEntity<Order>) {
println("Result: ${result.body}")
}
override fun onFailure(ex: Throwable) {
ex.printStackTrace()
if (ex is HttpStatusCodeException) {
println(ex.responseBodyAsString)
}
}
})
As you see, i don't use KotlinModule and this code works perfectly, so obviously problem in cofiguration AsyncRestTemplate itself.
My 2 cents. This is surely not the solution.
I configured the asyncRestTemplate with a AsyncHttpClientRequestInterceptor and it magically worked. No explanation, period!
public class AsyncClientLoggingInterceptor implements AsyncClientHttpRequestInterceptor {
#Override
public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request, byte[] body, AsyncClientHttpRequestExecution execution) throws IOException {
return execution.executeAsync(request, body);
}
}
Related
I want to add some custom auth headers to my request, when the dev-mode is activated. This should make the developement easier for me, since I don't have to add them on my self manually.
What I have found is the method annotation ServerRequestFilter which intercepts an ongoing request before hitting the controller level. The annotated function provides ContainerRequestContext argument where I can add my headers easily.
Now the problem: To know the custom headers value, I have to make an external rest call (I'm using the RestClient for that) to an API. Since I'm using the reactive library I get the exception org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException because of this call.
Since I'm using Kotlin, i tried to mark my method as suspendable. But this lead to a build error Method 'preCall$suspendImpl of class 'com.kaz.request.service.RequestInterceptor' cannot be static as it is annotated with '#org.jboss.resteasy.reactive.server.ServerRequestFilter'
Here my code:
#ApplicationScoped
class RequestInterceptor #Inject constructor(val authService: AuthService) {
#ServerRequestFilter
suspend fun preCall(requestContext: ContainerRequestContext) {
validateIdTokenHeader(requestContext)
}
private suspend fun validateIdTokenHeader(requestContext: ContainerRequestContext) {
val isTokenHeaderAbsent = requestContext.getHeaderString(Headers.X_ID_TOKEN) == null
val isDevModeEnabled = LaunchMode.current() == LaunchMode.DEVELOPMENT
if (isTokenHeaderAbsent && !isDevModeEnabled) {
throw AuthExceptions.ID_TOKEN_IS_ABSENT
} else {
injectDevUserIdToken(requestContext)
}
}
private suspend fun injectDevUserIdToken(requestContext: ContainerRequestContext) {
// This call is making the request and block
val idToken = authService.getIdToken("someHash")
requestContext.headers.putSingle(Headers.X_ID_TOKEN, idToken)
}
}
What I also tried to do is using Mutiny in my RestClient. I subscribed to the Uni and added the header when the result was available. But then I had the problem, that my controller/endpoint was already called before the header could be added to the request.
An endpoint could look like this:
#Path("hello/{id}")
#GET
suspend fun get(
//This header is what I want to add automatically, when dev mode is active.
#RestHeader(Headers.X_ID_TOKEN) idToken: UserIdToken,
#RestPath id: UUID,
#Context httpRequest: HttpServerRequest
): Response {
val request = RequestDTO(id, excludeFields, idToken.userId)
val envelope = service.findById(request)
return ResponseBuilder.build(httpRequest, envelope)
}
how about create a defer Uni and emit it on worker pool?
val idToken = Uni.createFrom().item { authService.getIdToken("someHash") }
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
or call the blocking code with the default io coroutines dispatcher
withContext(Dispatchers.IO){
authService.getIdToken("someHash")
}
and finally, maybe use Vertx.executeBlocking will be the simplest way
vertx.executeBlocking(Uni.createFrom().item {
authService.getIdToken("someHash")
})
Something in your implementation is blocking IO. So you can try to find it and wrap it with Uni or you can mark the method with #Blocking annotation, then the filters will be run on the worker thread.
Sometimes it makes sense not just to read the online documentation page but also the inline comments of the according class.
ServerRequestFilter.java says:
The return type of the method must be either be of type void, Response, RestResponse, Optional<Response>, Optional<RestResponse>, Uni<Void>, Uni<Response> or Uni<RestResponse>.
...
Uni<Void> should be used when filtering needs to perform a
non-blocking operation but the filter cannot abort processing. Note
that Uni<Void> can easily be produced using: Uni.createFrom().nullItem()
So the entrypoint function has to return an Uni as well, not just the RestClient. Changing it like the following will be enough
#ServerRequestFilter
fun preCall(requestContext: ContainerRequestContext) : Uni<Void> {
//Here I return a Uni<Void> back using Uni.createFrom().nullItem()
return validateIdTokenHeader(requestContext)
}
I am trying to access the POST API from my spring app to angular but little bit confused how to use and access the given API in my angular app.
Spring REST API
#RequestMapping(value = "/getWelcomeMessage", method = RequestMethod.POST)
public String getLoginWelcomeMessage() {
return details.getLoginWelcomeMessage();
}
The given API is fetching the welcome message details from my oracle DB and returning a string value. I am trying to access the given REST API in my angular code through services. I had define the post service as follows
export class LoginService {
constructor(private http : HttpClient) { }
welcomeMessageService(){
const headers = {'content-type':'application/text'}
return this.http.put("http://localhost:8080/API/getWelcomeMessage",null,
{'headers':headers});
}
}
As the post method requires three arguments URL, Body and header. But in my case my spring REST API doesn't contain any body and returning a string. So, I had define the body as null and change the header type to text as it is JASON by default.
At last, I am trying to access the given service method by injecting it in my component as follows-
export class LoginComponent implements OnInit {
message:string;
constructor(private loginService : LoginService) { }
ngOnInit(): void {
this.loginService.welcomeMessageService().subscribe(
response =>{
console.log(response);
this.message = response;
}
)
}
}
But when I am trying to assign the response to the string I am getting the error that string cannot be assigned to the object. I am little bit confused why this error is occurring as I had also changed the header type to string while defining my service but still getting the error.
It can be a great help if anybody guide me regarding this as I am new to angular and little bit confused with integration part of API with angular.
Use { responseType: 'text' } and also send an empty body not null
export class LoginService {
constructor(private http : HttpClient) { }
welcomeMessageService(){
return this.http.put("http://localhost:8080/API/getWelcomeMessage",{},
{ responseType: 'text' });
}
}
Maybe you have copied the function wrong but check also here
#RequestMapping(value = "/getWelcomeMessage", method = RequestMethod.POST)
public String getLoginWelcomeMessage() {
return details.getLoginWelcomeMessage();
}
This is a Post method not a put that you are trying to call
As for cors error add the following to the backend just above #Controller or #RestControler whatever you have
#CrossOrigin(value = {"http://localhost:4200"}, methods = {GET,POST,PUT,DELETE})
I am new to the Spring Integration project, now I need to create a flow with Java DSL and test it.
I came up with these flows. First one should run by cron and invoke second one, which invokes HTTP endpoint and translates XML response to POJO:
#Bean
IntegrationFlow pollerFlow() {
return IntegrationFlows
.from(() -> new GenericMessage<>(""),
e -> e.poller(p -> p.cron(this.cron)))
.channel("pollingChannel")
.get();
}
#Bean
IntegrationFlow flow(HttpMessageHandlerSpec bulkEndpoint) {
return IntegrationFlows
.from("pollingChannel")
.enrichHeaders(authorizationHeaderEnricher(user, password))
.handle(bulkEndpoint)
.transform(xmlTransformer())
.channel("httpResponseChannel")
.get();
}
#Bean
HttpMessageHandlerSpec bulkEndpoint() {
return Http
.outboundGateway(uri)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.errorHandler(new DefaultResponseErrorHandler());
}
Now I want to test flow and mock HTTP call, but struggling to mock HTTP handler, I tried to do it like that:
#ExtendWith(SpringExtension.class)
#SpringIntegrationTest(noAutoStartup = {"pollerFlow"})
#ContextConfiguration(classes = FlowConfiguration.class)
public class FlowTests {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Autowired
public DirectChannel httpResponseChannel;
#Autowired
public DirectChannel pollingChannel;
#Test
void test() {
final MockMessageHandler mockHandler = MockIntegration.mockMessageHandler()
.handleNextAndReply(message -> new GenericMessage<>(xml, message.getHeaders()));
mockIntegrationContext.substituteMessageHandlerFor("bulkEndpoint", mockHandler);
httpResponseChannel.subscribe(message -> {
assertThat(message.getPayload(), is(notNullValue()));
assertThat(message.getPayload(), instanceOf(PartsSalesOpenRootElement.class));
});
pollingChannel.send(new GenericMessage<>(""));
}
}
But I am always getting an error, that on line:
mockIntegrationContext.substituteMessageHandlerFor("bulkEndpoint", mockHandler);
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'bulkEndpoint' is expected to be of type 'org.springframework.integration.endpoint.IntegrationConsumer' but was actually of type 'org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler'
Am I doing something wrong here? I am assuming I have a problem with IntegrationFlow itself, or maybe my testing approach is a problem.
The error is correct. The bulkEndpoint is not an endpoint by itself. It is really a MessageHandler. The endpoint is created from the .handle(bulkEndpoint).
See docs: https://docs.spring.io/spring-integration/docs/current/reference/html/overview.html#finding-class-names-for-java-and-dsl-configuration and https://docs.spring.io/spring-integration/docs/current/reference/html/testing.html#testing-mocks.
So, to make it working you need to do something like this:
.handle(bulkEndpoint, e -> e.id("actualEndpoint"))
And then in the test:
mockIntegrationContext.substituteMessageHandlerFor("actualEndpoint", mockHandler);
You also probably need to think to not have that pollerFlow to be started when you test it sine you send the message into pollingChannel manually. So, there is no conflicts with what you'd like to test. For this reason you also add a id() into your e.poller(p -> p.cron(this.cron)) and use #SpringIntegrationTest(noAutoStartup) to have it stopped before your test. I see you try noAutoStartup = {"pollerFlow"}, but this is not going to help for static flows. You indeed need to have stopped an actual endpoint in this case.
I'm writing a kind of a wrapper around my request handlers to make them stream HTTP response. What I've got now is
Handler response wrapper:
#Component
public class ResponseBodyEmitterProcessor {
public ResponseBodyEmitter process(Supplier<?> supplier) {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
Executors.newSingleThreadExecutor()
.execute(() -> {
CompletableFuture<?> future = CompletableFuture.supplyAsync(supplier)
.thenAccept(result -> {
try {
emitter.send(result, MediaType.APPLICATION_JSON_UTF8);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
});
while (!future.isDone()) {
try {
emitter.send("", MediaType.APPLICATION_JSON_UTF8);
Thread.sleep(500);
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
}
});
return emitter;
}
}
Controller:
#RestController
#RequestMapping("/something")
public class MyController extends AbstractController {
#GetMapping(value = "/anything")
public ResponseEntity<ResponseBodyEmitter> getAnything() {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(process(() -> {
//long operation
}));
}
What I'm doing is just send empty string every half a second to keep a request alive. It's required for some tool to not shut it down by timeout. The problem here that I don't see any Content-Type header in a response. There's nothing at all, despite I return ResponseEntity from my controller method as it's said in this thread:
https://github.com/spring-projects/spring-framework/issues/18518
Looks like only TEXT_HTML media type is acceptable for streaming. Isn't there a way to stream json at all? I even manually mapped my dtos to json string using objectMapper, but still no luck. Tried with APPLICATION_JSON and APPLICATION_STREAM_JSON - doesn't work. Tried in different browsers - the same result.
I also manually set Content-Type header for ResponseEntity in the controller. Now there's the header in a response, but I'm not sure, if my data is actually streamed. In Chrome I can only see the result of my operation, not intermediate chars that I'm sending (changed them to "a" for test).
I checked the timing of request processing for two options:
Without emitter (just usual controller handler)
With emitter
As I understand Waiting status means: "Waiting for the first byte to appear". Seems like with emitter the first byte appears much earlier - this looks like what I need. Can I consider it as a proof that my solution works?
Maybe there's another way to do it in Spring? What I need is just to notify the browser that a request is still being processed by sending some useless data to it until the actual operation is done - then return the result.
Any help would be really appreciated.
Looking at the source of ResponseBodyEmitter#send it seems that the specified MediaType should have been set in the AbstractHttpMessageConverter#addDefaultHeaders method but only when no other contentType header is already present.
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
// ...
if (contentTypeToUse != null) {
headers.setContentType(contentTypeToUse);
}
}
// ...
}
I would suggest to set a break point there and have a look why the header is not applied. Maybe the #RestController sets a default header.
As a workaround you could try the set the contentType header via an annotation in the MVC controller.
E.g.
#RequestMapping(value = "/something", produces = MediaType.APPLICATION_JSON_UTF8)
I have a Spring Boot application that is running Netty for a REST API.
I have a WebExceptionHandler to handle exceptions that may occur, which is working. It builds appropriate responses and sends them.
The problem is, it also still logs the exception as an error. I want to change this to log it as an info instead (because we have tracking and alerts that operate differently based on info or error). It even logs things like 404's as errors.
It seems like exceptionCaught in a ChannelInboundHandler would help, but exceptionCaught is deprecated. I can't find anything that doesn't use this method, and I can't find anything referring to what (if anything) has replaced it).
I also tried using an #ControllerAdvice or #RestControllerAdvice with an #ExceptionHandler, but that is never called.
What is the correct way to intercept and handle the logging of the exception?
A minimal example implementation of our current set-up looks like this:
#RestController
class MyController {
#RequestMapping(method = [GET], value = ["endpoint"], produces = [APPLICATION_JSON_VALUE])
fun myEndpoint(): Mono<MyResponse> = createResponse()
}
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
class MyWebExceptionHandler : WebExceptionHandler {
// This does get called and sends the appropriate response back, but an error is logged somewhere outside of our code.
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> =
createErrorResponse();
}
// Tried using both, or just one at a time, no difference.
// It does get created.
#ControllerAdvice
#RestControllerAdvice
class MyExceptionHandler {
// Never called
#ExceptionHandler(Exception::class)
fun handle(ex: Exception) {
log.error(ex.message)
}
}
More information on how you are implementing your Exception Handler would be helpful.
here's a simple implementation which i follow to convert the exceptions and log them.
#ControllerAdvice
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionHandler.class);
private static String ERROR = "ERROR";
#ExceptionHandler(Exception.class)
ResponseEntity<Map<String, Map<String, String>>> exception(Exception e) {
Map<String,Map<String,String>> map = new HashMap<>(1);
Map<String,String> m = new HashMap<>(1);
m.put("message",e.getMessage());
map.put(ERROR, m);
LOG.info("some error " + e);
return new ResponseEntity<>(map, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Also don't forget to create a bean or add you ExceptionHandler class to the spring config.
#Bean
public DefaultExceptionHandler defaultExceptionHandler(){
return new DefaultExceptionHandler();
}