I am trying to call my controller's delete method:
Spring:
#RestController
#RequestMapping("/api")
#CrossOrigin
public class Controller
{
#DeleteMapping("thing/item/{name}")
public void deleteThing(#PathVariable String name)
{
System.out.println("CALL"); // Never called!!!
}
}
Angular 7:
deleteTemplate(name: string) {
const url = `host/api/thing/item/${name}`;
return this.http.delete(url);
}
I've found something about including options:
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
deleteTemplate(name: string) {
const url = `${host}/api/thing/item/${name}`; // host is not relevant, same path with GET works.
return this.http.delete(url, this.httpOptions);
}
But I don't think that it's even needed since everything I am sending is in link itself (no json).
Every time its:
WARN 15280 --- [io-8443-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'DELETE' not supported]
How to fix this?
DELETE is sent with 405 error and preceeding OPTIONS (with 200 code). It reaches server and does this error.
EDIT
Above is a sample of code that is simplified and still doesn't work. I understand your comments, but host can be anything and doesn't matter here. Its localhost now and as mentioned - it works in sense of reaching endpoint.
To check if I am wrong I did this:
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
this.requestMappingHandlerMapping.getHandlerMethods()
.forEach((e, k) -> System.out.println(e + " OF " + k));
After startup, it printed everything I expected, including:
{GET /api/thing/item/{name}} OF public myPackage.Thing myPackage.Controller.getThing(java.lang.String)
{DELETE /api/thing/item/{name}} OF public void myPackage.Controller.deleteThing(java.lang.String)
Any ideas?
Goddamn. Turns out problem was in CSRF, mainly in hidden Angular documentation and misleading Spring filter logs.
It took me about 10h to make it work, but only in production (where Angular is on same host/port with Spring). I will try to make it work in dev, but that requires basically rewriting whole module supplied by Angular that is bound to its defaults.
Now as to the problem:
Everything starts with Spring Security when we want to use CSRF (and we do). Apparently CsrfFilter literally explodes if it can't match CSRF tokens it expects and falls back to /error. This all happens without ANY specific log message, but simple Request method 'DELETE' not supported, which from my question we know IT IS present.
This is not only for DELETE, but all action requests (non-GET/HEAD).
This all points to "How to setup CSRF with Angular?". Well, here we go:
https://angular.io/guide/http#security-xsrf-protection
And it WORKS by DEFAULT. Based on Cookie-Header mechanism for CSRF.
But wait, there's more!
Spring needs to bo configured for Cookie-Header mechanism. Happily we got:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().csrfTokenRepository(this.csrfRepo());
}
private CookieCsrfTokenRepository csrfRepo()
{
CookieCsrfTokenRepository repo = new CookieCsrfTokenRepository();
repo.setCookieHttpOnly(false);
return repo;
}
Which is built-in in spring and actually uses same cookie/header names as ones used by Angular. But what's up with repo.setCookieHttpOnly(false);? Well... another poor documented thingy. You just NEED to set it to false, otherwise Angular's side will not work.
Again - this only works for production builds, because Angular doesn't support external links. More at: Angular 6 does not add X-XSRF-TOKEN header to http request
So yeah... to make it work in dev localhost with 2 servers I'll need 1st to recreate https://angular.io/api/common/http/HttpClientXsrfModule mechanism to also intercept non-default requests.
EDIT
Based on JB Nizet comment I cooked up setup that works for both dev and production. Indeed proxy is awesome. With CSRF and SSL enabled (in my case), you also need to enable SSL on Angular CLI, otherwise Angular will not be able to use CSRF of SSL-enabled proxy backend and thus not work.
"serve": {
"options": {
"proxyConfig": "proxy.conf.json",
"sslKey": "localhost.key",
"sslCert": "localhost.crt",
"ssl": true
}
}
Note that Cert and Key will be auto-generated if not found :)
For Spring backend you don't need same cert. I use separate JKS there.
Cheers!
Add a slash / at the beginning of your path.
#DeleteMapping("/thing/item/{name}")
Related
I'm using Spring Boot 2.6.7 and org.springdoc:springdoc-openapi-ui:1.6.7 to run swagger ui with OpenApi 3 definition along with my backend.
My Setup
#GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public String getHello(HelloInput input) {
return "";
}
public class HelloInput {
public NestedInput nested;
// getter setter omitted
}
public class NestedInput {
public boolean a = true;
// getter setter omitted
}
This produces the following request when trying it out in swagger ui:
curl -X 'GET' \
'http://localhost:8080/api/hello?nested[a]=true' \
-H 'accept: application/json'
The Problem
However, when I execute this call I get the following Exception:
org.springframework.beans.InvalidPropertyException: Invalid property 'nested[a]' of bean class [com.proj.App.rest.explore.HelloInput]: Property referenced in indexed property path 'nested[a]' is neither an array nor a List nor a Map; returned value was [com.proj.App.rest.explore.NestedInput#68d1c7c4]
What do I need
The square braces notation causes this issue. The same call with dot notation does not cause exceptions:
curl -X 'GET' \
'http://localhost:8080/api/hello?nested.a.=true' \
-H 'accept: application/json'
Is there a way to either:
A: Configure Spring Boot in a way that it accepts the square brackets notation also for object properties and not just maps, lists etc.
OR
B: Configure Swagger UI in a way that it uses the dot notation when constructing the calls?
What did I already try
I already did a lot of research, but could not find other people with this problem.
I found a feature request for Spring to support squared brackets, but it was rejected: https://github.com/spring-projects/spring-framework/issues/20052
I found basically the same question, but the answers seem to be outdated, since the RelaxedDataBinder does not seem to be a part of Spring Boot 2.6.7 anymore: Customize Spring #RequestParam Deserialization for Maps and/or Nested Objects
Other than that, no one else seems to have this problem. Am I completely misunderstanding how Spring Boot handles Request Parameters and this problem occurs because I'm breaking some convention on how to handle GET Requests with many parameters?
I have the same problem and can't find any good solution. I have found only one more hack. So you can reformat request param json with dot-separated fields. It is a little bit inconvenient but works for me.
For example, by default for the controller in question, the request param input form will have json that will look like this:
{
"nested": {
"a": true
}
}
But you can use
{
"nested.a": true
}
to get what you want without any even more hacky changes(as I see this) to swagger-ui or spring components.
CAUTION: Hacky workaround for making swagger ui use dots instead of squared braces (probably breaks in a lot of scenarios)
Swagger Ui offers to define a requestInterceptor, that takes outbound requests and offers the user to change them.
When using Springdoc, swagger ui downloads a swagger-initializer.js for configuration of swagger ui.
This file is generated by an instance of a SwaggerIndexPageTransformer. The default instance already creates a requestInterceptor for CORS requests. I took that code and adapted it to change all requests with the squared braces notation to the dot notation.
For reference see AbstractSwaggerIndexTransformer#addCRSF
Workaround
First define a Configuration that offers a SwaggerIndexTransformer Bean:
#Configuration
public class OpenApiConfig {
#Bean
public SwaggerIndexTransformer swaggerIndexTransformer(
SwaggerUiConfigProperties a,
SwaggerUiOAuthProperties b,
SwaggerUiConfigParameters c,
SwaggerWelcomeCommon d) {
return new CustomSwaggerIndexTransformer(a, b, c, d);
}
}
Next implement the CustomSwaggerIndexTransformer, that now edits the swagger-initializer.js to add a requestInterceptor function, that replaces every [ with a . and removes all ].
public class CustomSwaggerIndexTransformer extends SwaggerIndexPageTransformer {
private static final String PRESETS = "presets: [";
...
#Override
protected String defaultTransformations(InputStream inputStream) throws IOException {
String html = super.defaultTransformations(inputStream);
html = addDottedRequests(html);
return html;
}
protected String addDottedRequests(String html) {
String interceptorFn = """
requestInterceptor: (request) => {
request.url = request.url.replaceAll("[", ".").replaceAll("]", "");
return request;
},
""" + PRESETS;
return html.replace(PRESETS, interceptorFn);
}
}
Limitations
This probably breaks working behavior when swagger ui uses squared braces for Maps, Arrays and Lists
This does not work when CORS is enabled for springdoc, since it also defines a requestInterceptor, in that case the javascript should somehow be incorporated into the CORS requestInterceptor function.
I have simple RestController in my application, with post mapping
#PostMapping("/bank/api/balance")
fun changeBalance(#RequestParam amount: String, #RequestParam action: String)
And I have test function for this controller.
private fun postBalanceAddRequest(amount: String): ResultActions {
return mockMvc.perform(MockMvcRequestBuilders.post(baseUrl).apply {
param("action", "add")
param("amount", amount)
})
}
Which I am using in this test
#Test
fun `add post request must return ok if request contains correct data`() {
Mockito.`when`(authenticationService.getCurrentUserUsername()).thenReturn("user")
Mockito.`when`(userService.userExist("user")).thenReturn(true)
postBalanceAddRequest(amount = "10")
.andExpect(MockMvcResultMatchers.status().isOk)
}
When I start this test I got 404 code, but it seems to me that there cannot be such a return code here.
when using MockMvc you don't need to provide the full url (including domain and port), the mockMvc will take care of it.
instead, you need just the uri. set your baseUrl to /bank/api/balance
Thanks for help, I just forgot to add a necessary controller. As for mockMvc, you can use short url, without domain and port
(I think it is the best way), but if you want you can use full url.
Problem
We're developing a Spring Boot service to upload data to different back end databases. The idea is that, in one multipart/form-data request a user will send a "model" (basically a file) and "modelMetadata" (which is JSON that defines an object of the same name in our code).
We got the below to work in the WebFlux annotated controller syntax, when the user sends the "modelMetadata" in the multipart form with the content-type of "application/json":
#PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
fun saveModel(#RequestPart("modelMetadata") monoModelMetadata: Mono<ModelMetadata>,
#RequestPart("model") monoModel: Mono<FilePart>,
#RequestHeader headers: HttpHeaders) : Mono<ResponseEntity<ModelMetadata>> {
return modelService.saveModel(monoModelMetadata, monoModel, headers)
}
But we can't seem to figure out how to do the same thing in Webflux's functional router definition. Below are the relevant code snippets we have:
#Bean
fun modelRouter() = router {
accept(MediaType.MULTIPART_FORM_DATA).nest {
POST(ROOT, handler::saveModel)
}
}
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
val monoModelPart = r.multipartData().map { multiValueMap ->
it["model"] // What do we do with this List<Part!> to get a Mono<FilePart>
it["modelMetadata"] // What do we do with this List<Part!> to get a Mono<ModelMetadata>
}
From everything we've read, we should be able to replicate the same functionality found in the annotation controller syntax with the router functional syntax, but this particular aspect doesn't seem to be well documented. Our goal was to move over to use the new functional router syntax since this is a new application we're developing and there are some nice forward thinking features/benefits as described here.
What we've tried
Googling to the ends of the Earth for a relevant example
this is a similar question, but hasn't gained any traction and doesn't relate to our need to create an object from one piece of the multipart request data
this may be close to what we need for uploading the file component of our multipart request data, but doesn't handle the object creation from JSON
Tried looking at the #RequestPart annotation code to see how things are done on that side, there's a nice comment that seems to hint at how they are converting the parts to objects, but we weren't able to figure out where that code lives or any relevant example of how to use an HttpMessageConverter on the ``
the content of the part is passed through an {#link HttpMessageConverter} taking into consideration the 'Content-Type' header of the request part.
Any and all help would be appreciated! Even just some links for us to better understand Part/FilePart types and there role in multipart requests would be helpful!
I was able to come up with a solution to this issue using an autowired ObjectMapper. From the below solution I could turn the modelMetadata and modelPart into Monos to mirror the #RequestPart return types, but that seems ridiculous.
I was also able to solve this by creating a MappingJackson2HttpMessageConverter and turning the metadataDataBuffer into a MappingJacksonInputMessage, but this solution seemed better for our needs.
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
return r.multipartData().flatMap {
// We're only expecting one Part of each to come through...assuming we understand what these Parts are
if (it.getOrDefault("modelMetadata", listOf()).size == 1 && it.getOrDefault("model", listOf()).size == 1) {
val modelMetadataPart = it["modelMetadata"]!![0]
val modelPart = it["model"]!![0] as FilePart
modelMetadataPart
.content()
.map { metadataDataBuffer ->
// TODO: Only do this if the content is JSON?
objectMapper.readValue(metadataDataBuffer.asInputStream(), ModelMetadata::class.java)
}
.next() // We're only expecting one object to be serialized from the buffer
.flatMap { modelMetadata ->
// Function was updated to work without needing the Mono's of each type
// since we're mapping here
modelService.saveModel(modelMetadata, modelPart, headers)
}
}
else {
// Send bad request response message
}
}
Although this solution works, I feel like it's not as elegant as the one alluded to in the #RequestPart annotation comments. Thus I will accept this as the solution for now, but if someone has a better solution please let us know and I will accept it!
Given an exchange using WebClient, filtered by a custom ExchangeFilterFunction:
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return next.exchange(request)
.doOnSuccess(response -> {
// ...
});
}
Trying to access the response body more than once using response.bodyToMono() will cause the underlying HTTP client connector to complain that only one receiver is allowed. AFAIK, there's no way to access the body's Publisher in order to cache() its signals (and I'm not sure it'd be a good idea, resource-wise), as well as no way to mutate or decorate the response object in a manner that allows access to its body (like it's possible with ServerWebExchange on the server side).
That makes sense, but I am wondering if there are any ways I could subscribe to the response body's publisher from a form of filter such as this one. My goal is to log the request/response being sent/received by a given WebClient instance.
I am new to reactive programming, so if there are any obvious no-nos here, please do explain :)
Only for logging you could add a wiretap to the HttpClient as desribed in this answer.
However, your question is also interesting in a more general sense outside of logging.
One possible way is to create a duplicate of the ClientResponse instance with a copy of the previous request body. This might go against reactive principles, but it got the job done for me and I don't see big downsides given the small size of the response bodies in my client.
In my case, I needed to do so because the server sending the request (outside of my control) uses the HTTP status 200 Ok even if requests fail. Therefore, I need to peek into the response body in order to find out if anything went wrong and what the cause was. In my case I evict a session cookie in the request headers from the cache if the error message indicates that the session expired.
These are the steps:
Get the response body as a Mono of a String (cf (1)).
Return a Mono.Error in case an error is detected (cf (2)).
Use the String of the response body to build a copy of the original response (cf (3)).
You could also use a dependency on the ObjectMapper to parse the String into an object for analysis.
Note that I wrote this in Kotlin but it should be easy enough to adapt to Java.
#Component
class PeekIntoResponseBodyExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return next.exchange(request)
.flatMap { response ->
// (1)
response.bodyToMono<String>()
.flatMap { responseBody ->
if (responseBody.contains("Error message")) {
// (2)
Mono.error(RuntimeException("Response contains an error"))
} else {
// (3)
val clonedResponse = response.mutate().body(responseBody).build()
Mono.just(clonedResponse)
}
}
}
}
}
I've recently started using the Play! framework (v2.0.4) for writing a Java web application. In the majority of my controllers I'm following the paradigm of suspending the HTTP request until the promise of a web service response has been fulfilled. Once the promise has been fulfilled, I return an AsyncResult. This is what most of my actions look like (with a bunch of code omitted):
public static Result myActionMethod() {
Promise<MyWSResponse> wsResponse;
// Perform a web service call that will return the promise of a MyWSResponse...
return async(wsResponse.map(new Function<MyWSResponse, Result>() {
#Override
public Result apply(MyWSResponse response) {
// Validate response...
return ok(myScalaViewTemplate.render(response.data()));
}
}));
}
I'm now trying to internationalise my app, but hit the following error when I try to render a template from an async method:
[error] play - Waiting for a promise, but got an error: There is no HTTP Context available from here.
java.lang.RuntimeException: There is no HTTP Context available from here.
at play.mvc.Http$Context.current(Http.java:27) ~[play_2.9.1.jar:2.0.4]
at play.mvc.Http$Context$Implicit.lang(Http.java:124) ~[play_2.9.1.jar:2.0.4]
at play.i18n.Messages.get(Messages.java:38) ~[play_2.9.1.jar:2.0.4]
at views.html.myScalaViewTemplate$.apply(myScalaViewTemplate.template.scala:40) ~[classes/:na]
at views.html.myScalaViewTemplate$.render(myScalaViewTemplate.template.scala:87) ~[classes/:na]
at views.html.myScalaViewTemplate.render(myScalaViewTemplate.template.scala) ~[classes/:na]
In short, where I've got a message bundle lookup in my view template, some Play! code is attempting to access the original HTTP request and retrieve the accept-languages header, in order to know which message bundle to use. But it seems that the HTTP request is inaccessible from the async method.
I can see a couple of (unsatisfactory) ways to work around this:
Go back to the 'one thread per request' paradigm and have threads block waiting for responses.
Figure out which language to use at Controller level, and feed that choice into my template.
I also suspect this might not be an issue on trunk. I know that there is a similar issue in 2.0.4 with regards to not being able to access or modify the Session object which has recently been fixed. However I'm stuck on 2.0.4 for the time being, so is there a better way that I can resolve this problem?
Gonna answer my own question here. A colleague of mine found what was ultimately a simple solution:
public static Result myActionMethod() {
final Context ctx = ctx(); // (1)
Promise<MyWSResponse> wsResponse;
// Perform a web service call that will return the promise of a MyWSResponse...
return async(wsResponse.map(new Function<MyWSResponse, Result>() {
#Override
public Result apply(MyWSResponse response) {
Context.current.set(ctx); // (2)
// Validate response...
return ok(myScalaViewTemplate.render(response.data()));
}
}));
}
Obtain a reference to the HTTP context at the beginning of the action
Restore it in the ThreadLocal once you're in the async block