Kotlin Spring - 405 response - spring

I am trying to go through that tutorial:
https://kotlinlang.org/docs/jvm-spring-boot-add-db-support.html#add-messages-to-database-via-http-request
when I am sending post request I am getting:
{
"timestamp": "2023-01-09T05:35:30.982+00:00",
"status": 405,
"error": "Method Not Allowed",
"path": "/"
}
My code:
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
#RestController
class MessageController(val service: MessageService) {
#GetMapping("/")
fun index(): List<Message> = service.findMessages()
#PostMapping()
fun post(#RequestBody message: Message) {
service.save(message)
}
}
data class Message(val id: String?, val text: String)
#Service
class MessageService(val db: JdbcTemplate) {
fun findMessages(): List<Message> = db.query("select * from messages") { response, _ ->
Message(response.getString("id"), response.getString("text"))
}
fun save(message: Message){
val id = message.id ?: UUID.randomUUID().toString()
db.update("insert into messages values ( ?, ? )",
id, message.text)
}
}
My request:
POST http://localhost:8080/
Content-Type: application/json
{
"text": "Hello!"
}

You are missing the path in the #PostMapping() annotation - see how you have it in the #GetMapping("/")

Related

PATCH endpoint 404 on Upgrade to Springboot Version 3

I'm in the process of migrating my Kotlin API to Springboot version 3.
I'm having a problem where certain endpoints aren't getting hit, for example::
a GET to localhost:8081/search/hello
works just fine, here is the GET endpoint::
#PreAuthorize("hasRole('ROLE_MYROLE')")
#GetMapping("/search/{somephrase}")
suspend fun getSomething(#PathVariable("somephrase") phrase: String): someResponse {
return myService.getPhrase(phrase)
}
a PATCH to localhost:8081/update/resourceId
does not, and here is my patch endpoint::
#PreAuthorize("hasRole('ROLE_MYROLE')")
#PatchMapping("/update/{resourceId}")
suspend fun updateSomething(
#PathVariable("resourceId") resourceId: Long,
#RequestBody updateJson: JsonNode
): UpdateResponse {
return recommendationService.update(updateJson, resourceId)
}
Here is the reponse I get::
{
"timestamp": "2023-02-12 09:59:50",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/search"
}
and this in the console::
: [preHandle] PATCH /error - operationId: 00000000000000000000000000000000 |
Here is my Spring Security Config::
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.DefaultSecurityFilterChain
#Configuration
#EnableWebSecurity
class ResourceServerConfiguration {
#Bean
fun configure(http: HttpSecurity): DefaultSecurityFilterChain? {
http.authorizeHttpRequests()
.requestMatchers("/csrf").permitAll()
.requestMatchers("/**").authenticated()
.and()
.csrf()
.disable()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter { AuthAwareTokenConverter().convert(it) }
return http.build()
}
}
and here is my token converter (it just adds a couple of extra claims to the token)
import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
class AuthAwareTokenConverter : Converter<Jwt, AbstractAuthenticationToken> {
private val jwtGrantedAuthoritiesConverter: Converter<Jwt, Collection<GrantedAuthority>> =
JwtGrantedAuthoritiesConverter()
override fun convert(jwt: Jwt): AbstractAuthenticationToken {
val claims = jwt.claims
val principal = findPrincipal(claims)
val authorities = extractAuthorities(jwt)
return AuthAwareAuthenticationToken(jwt, principal, authorities)
}
private fun findPrincipal(claims: Map<String, Any?>): String {
return if (claims.containsKey(CLAIM_USERNAME)) {
claims[CLAIM_USERNAME] as String
} else if (claims.containsKey(CLAIM_USER_ID)) {
claims[CLAIM_USER_ID] as String
} else {
claims[CLAIM_CLIENT_ID] as String
}
}
private fun extractAuthorities(jwt: Jwt): Collection<GrantedAuthority> {
val authorities = mutableListOf<GrantedAuthority>().apply { addAll(jwtGrantedAuthoritiesConverter.convert(jwt)!!) }
if (jwt.claims.containsKey(CLAIM_AUTHORITY)) {
#Suppress("UNCHECKED_CAST")
val claimAuthorities = (jwt.claims[CLAIM_AUTHORITY] as Collection<String>).toList()
authorities.addAll(claimAuthorities.map(::SimpleGrantedAuthority))
}
return authorities.toSet()
}
companion object {
const val CLAIM_USERNAME = "user_name"
const val CLAIM_USER_ID = "user_id"
const val CLAIM_CLIENT_ID = "client_id"
const val CLAIM_AUTHORITY = "authorities"
}
}
class AuthAwareAuthenticationToken(
jwt: Jwt,
private val aPrincipal: String,
authorities: Collection<GrantedAuthority>
) : JwtAuthenticationToken(jwt, authorities) {
override fun getPrincipal(): String {
return aPrincipal
}
}
What I've tried::
Simplifying the PATCH endpoint to simply return a "hello" String to rule out an issue with the service.
The result is the same, so I think the point of failure must be my Spring Security configuration.
Any help/pointers would be much appreciated!
Interesting! I appear to have found the solution.
I added an extra forward slash to the end of the path in my mapping.
So I changed this::
#PatchMapping("/update/{resourceId}")
to this::
#PatchMapping("/update/{resourceId}/")
and it works!
Oddly SpringBoot version 2.X worked just fine with or without the forward slash. But SpringBoot version 3.X requires it.

How to advise (AOP) spring webflux web handlers to catch and transform reactive error

[UPDATE 2021-10-11] Added MCVE
https://github.com/SalathielGenese/issue-spring-webflux-reactive-error-advice
For reusability concerns, I run my validation on the service layer, which returns Mono.error( constraintViolationException )...
So that my web handlers merely forward the unmarshalled domain to the service layer.
So far, so great.
But how do I advise (AOP) my web handlers so that it returns HTTP 422 with the formatted constraint violations ?
WebExchangeBindException only handle exceptions thrown synchronously (I don't want synchronous validation to break the reactive flow).
My AOP advice trigger and error b/c :
my web handler return Mono<DataType>
but my advice return a ResponseEntity
And if I wrap my response entity (from the advice) into a Mono<ResponseEntity>, I an HTTP 200 OK with the response entity serialized :(
Code Excerpt
#Aspect
#Component
class CoreWebAspect {
#Pointcut("withinApiCorePackage() && #annotation(org.springframework.web.bind.annotation.PostMapping)")
public void postMappingWebHandler() {
}
#Pointcut("within(project.package.prefix.*)")
public void withinApiCorePackage() {
}
#Around("postMappingWebHandler()")
public Object aroundWebHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
final var proceed = proceedingJoinPoint.proceed();
if (proceed instanceof Mono<?> mono) {
try {
return Mono.just(mono.toFuture().get());
} catch (ExecutionException exception) {
if (exception.getCause() instanceof ConstraintViolationException constraintViolationException) {
return Mono.just(getResponseEntity(constraintViolationException));
}
throw exception.getCause();
}
}
return proceed;
} catch (ConstraintViolationException constraintViolationException) {
return getResponseEntity(constraintViolationException);
}
}
private ResponseEntity<Set<Violation>> getResponseEntity(final ConstraintViolationException constraintViolationException) {
final var violations = constraintViolationException.getConstraintViolations().stream().map(violation -> new Violation(
stream(violation.getPropertyPath().spliterator(), false).map(Node::getName).collect(toList()),
violation.getMessageTemplate().replaceFirst("^\\{(.*)\\}$", "$1"))
).collect(Collectors.toSet());
return status(UNPROCESSABLE_ENTITY).body(violations);
}
#Getter
#AllArgsConstructor
private static class Violation {
private final List<String> path;
private final String template;
}
}
From observation (I haven't found any proof in the documentation), Mono.just() on response is automatically translated into 200 OK regardless of the content. For that reason, Mono.error() is needed. However, its constructors require Throwable so ResponseStatusException comes into play.
return Mono.error(new ResponseStatusException(UNPROCESSABLE_ENTITY));
Request:
curl -i --request POST --url http://localhost:8080/welcome \
--header 'Content-Type: application/json' \
--data '{}'
Response (formatted):
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Content-Length: 147
{
"error": "Unprocessable Entity",
"message": null,
"path": "/welcome",
"requestId": "7a3a464e-1",
"status": 422,
"timestamp": "2021-10-13T16:44:18.225+00:00"
}
Finally, 422 Unprocessable Entity is returned!
Sadly, the required List<Violation> as a body can be passed into ResponseStatusException only as a String reason which ends up with an ugly response:
return Mono.error(new ResponseStatusException(UNPROCESSABLE_ENTITY, violations.toString()));
Same request
Response (formatted):
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Content-Length: 300
{
"timestamp": "2021-10-13T16:55:30.927+00:00",
"path": "/welcome",
"status": 422,
"error": "Unprocessable Entity",
"message": "[IssueSpringWebfluxReactiveErrorAdviceApplication.AroundReactiveWebHandler.Violation(template={javax.validation.constraints.NotNull.message}, path=[name])]",
"requestId": "de92dcbd-1"
}
But there is a solution defining the ErrorAttributes bean and adding violations into the body. Start with a custom exception and don't forget to annotate it with #ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) to define the correct response status code:
#Getter
#RequiredArgsConstructor
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public class ViolationException extends RuntimeException {
private final List<Violation> violations;
}
Now define the ErrorAttributes bean, get the violations and add it into the body:
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
Throwable error = getError(request);
if (error instanceof ViolationException) {
ViolationException violationException = (ViolationException) error;
errorAttributes.put("violations", violationException .getViolations());
}
return errorAttributes;
}
};
}
And finally, do this in your aspect:
return Mono.error(new ViolationException(violations));
And test it out:
Same request
Response (formatted):
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Content-Length: 238
{
"timestamp": "2021-10-13T17:07:07.668+00:00",
"path": "/welcome",
"status": 422,
"error": "Unprocessable Entity",
"message": "",
"requestId": "a80b54d9-1",
"violations": [
{
"template": "{javax.validation.constraints.NotNull.message}",
"path": [
"name"
]
}
]
}
The tests will pass. Don't forget some classes are newly from the reactive packages:
org.springframework.boot.web.reactive.error.ErrorAttributes
org.springframework.boot.web.reactive.error.DefaultErrorAttributes
org.springframework.web.reactive.function.server.ServerRequest
How about replacing the aspect with a #ControllerAdvice containing an #ExceptionHandler? But let us clean up the main application class, extracting the inner classes from it into an extra class:
package name.genese.salathiel.issuespringwebfluxreactiveerroradvice;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import static org.springframework.boot.SpringApplication.run;
#SpringBootApplication
#EnableAspectJAutoProxy
public class IssueSpringWebfluxReactiveErrorAdviceApplication {
public static void main(String[] args) {
run(IssueSpringWebfluxReactiveErrorAdviceApplication.class, args);
}
}
package name.genese.salathiel.issuespringwebfluxreactiveerroradvice;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.StreamSupport.stream;
#ControllerAdvice
public class ConstraintViolationExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<List<Violation>> handleException(ConstraintViolationException constraintViolationException) {
final List<Violation> violations = constraintViolationException.getConstraintViolations().stream()
.map(violation -> new Violation(
violation.getMessageTemplate(),
stream(violation.getPropertyPath().spliterator(), false)
.map(Path.Node::getName)
.collect(Collectors.toList())
)).collect(Collectors.toList());
return ResponseEntity.unprocessableEntity().body(violations);
}
#Getter
#RequiredArgsConstructor
static class Violation {
private final String template;
private final List<String> path;
}
}
Now your tests both pass.
BTW, not being a Spring user, I got the idea from this answer.

Spring WebFlux Route always returns 404

I am working on a simple project which uses Spring Boot 2 with Spring WebFlux using Kotlin.
I wrote test for my handler function (in which I mock the dependencies using Mockito).
However, it seems like my route function does not trigger the handler, as all of my requests return HTTP 404 NOT FOUND (even though the route is correct).
I have looked at various other projects to find out what how these tests are supposed to be written (here, here), but the problem persists.
The code is as follows (and can also be found on GitHub):
UserRouterTest
#ExtendWith(SpringExtension::class, MockitoExtension::class)
#Import(UserHandler::class)
#WebFluxTest
class UserRouterTest {
#MockBean
private lateinit var userService: UserService
#Autowired
private lateinit var userHandler: UserHandler
#Test
fun givenExistingCustomer_whenGetCustomerByID_thenCustomerFound() {
val expectedCustomer = User("test", "test")
val id = expectedCustomer.userID
`when`(userService.getUserByID(id)).thenReturn(Optional.ofNullable(expectedCustomer))
val router = UserRouter().userRoutes(userHandler)
val client = WebTestClient.bindToRouterFunction(router).build()
client.get()
.uri("/users/$id")
.accept(MediaType.ALL)
.exchange()
.expectStatus().isOk
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody(User::class.java)
}
}
User
#Entity
class User(var username : String, var password: String) {
#Id
val userID = UUID.randomUUID()
}
UserRepository
#Repository
interface UserRepository : JpaRepository<User, UUID>{
}
UserService
#Service
class UserService(
private val userRepository: UserRepository
) {
fun getUserByID(id: UUID): Optional<User> {
return Optional.of(
try {
userRepository.getOne(id)
} catch (e: EntityNotFoundException) {
User("test", "test")
}
)
}
fun addUser(user: User) {
userRepository.save(user)
}
}
UserHandler
#Component
class UserHandler(
private val userService: UserService
) {
fun getUserWithID(request: ServerRequest): Mono<ServerResponse> {
val id = try {
UUID.fromString(request.pathVariable("userID"))
} catch (e: IllegalArgumentException) {
return ServerResponse.badRequest().syncBody("Invalid user id")
}
val user = userService.getUserByID(id).get()
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(user))
}
}
UserRouter
#Configuration
class UserRouter {
#Bean
fun userRoutes(userHandler: UserHandler) = router {
contentType(MediaType.APPLICATION_JSON_UTF8).nest {
GET("/users/{userID}", userHandler::getUserWithID)
GET("") { ServerResponse.ok().build() }
}
}
}
EDIT
To route based on the presence of one or more query parameter (regardless of their values), we can do the following:
UserRouter
#Configuration
class UserRouter {
#Bean
fun userRoutes(userHandler: UserHandler) = router {
GET("/users/{userID}", userHandler::getUserWithID)
(GET("/users/")
and queryParam("username") { true }
and queryParam("password") { true }
)
.invoke(userHandler::getUsers)
}
}
Note that GET("/users/?username={username}", userHandler::getUsersWithUsername) does not work.
The way the router is configured - contentType(MediaType.APPLICATION_JSON_UTF8).nest - will only match requests that have this content type, so you would have to either remove the contentType prerequisite or change the test to include it
client.get()
.uri("/users/$id")
.accept(MediaType.ALL)
.header("Content-Type", "application/json;charset=UTF-8")
.exchange()
.expectStatus().isOk
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody(User::class.java)

Using WebClient to propagate request headers received in a Spring Webflux applications to downstream services

I have two kinds of Webflux applications, annotation-based and route-based. These applications are called with a set of headers, some of which (Open Tracing) I need to propagate in downstream calls using WebClient.
If these were normal Spring WebMvc applications I would use a Filter to keep the selected headers in a ThreadLocal, access it in a RestTemplate interceptor to send them to subsequent services and clear the ThreadLocal.
What's the proper way to replicate this behaviour in WebFlux applications?
I solved it using Project Reactor's Context to store the headers in a WebFilter. Then they are gotten in the WebClient's ExchangeFilterFunction. Here's the whole solution:
WebFilter
class OpenTracingFilter(private val openTracingHeaders: Set<String>) : WebFilter {
private val logger = LoggerFactory.getLogger(javaClass)
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
return chain.filter(exchange)
.subscriberContext { ctx ->
var updatedContext = ctx
exchange.request.headers.forEach {
if (openTracingHeaders.contains(it.key.toLowerCase())) {
logger.debug("Found OpenTracing Header - key {} - value {}", it.key, it.value[0])
updatedContext = updatedContext.put(it.key, it.value[0])
}
}
updatedContext
}
}
}
OpenTracingExchangeFilterFunction
class OpenTracingExchangeFilterFunction(private val headers: Set<String>) : ExchangeFilterFunction {
private val logger = LoggerFactory.getLogger(javaClass)
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
logger.debug("OpenTracingExchangeFilterFunction - filter()")
return OpenTracingClientResponseMono(request, next, headers)
}
}
OpenTracingClientResponseMono
class OpenTracingClientResponseMono(private val request: ClientRequest,
private val next: ExchangeFunction,
private val headersToPropagate: Set<String>) : Mono<ClientResponse>() {
private val logger = LoggerFactory.getLogger(javaClass)
override fun subscribe(subscriber: CoreSubscriber<in ClientResponse>) {
val context = subscriber.currentContext()
val requestBuilder = ClientRequest.from(request)
requestBuilder.headers { httpHeaders ->
headersToPropagate.forEach {
if(context.hasKey(it)) {
logger.debug("Propagating header key {} - value{}", it, context.get<String>(it))
httpHeaders[it] = context.get<String>(it)
}
}
}
val mutatedRequest = requestBuilder.build()
next.exchange(mutatedRequest).subscribe(subscriber)
}
}
OpenTracingConfiguration
#Configuration
class OpenTracingConfiguration(private val openTracingConfigurationProperties: OpenTracingConfigurationProperties) {
#Bean
fun webClient(): WebClient {
return WebClient.builder().filter(openTracingExchangeFilterFunction()).build()
}
#Bean
fun openTracingFilter(): WebFilter {
return OpenTracingFilter(openTracingConfigurationProperties.headers)
}
#Bean
fun openTracingExchangeFilterFunction(): OpenTracingExchangeFilterFunction {
return OpenTracingExchangeFilterFunction(openTracingConfigurationProperties.headers)
}
}
OpenTracingConfigurationProperties
#Configuration
#ConfigurationProperties("opentracing")
class OpenTracingConfigurationProperties {
lateinit var headers: Set<String>
}
application.yml
opentracing:
headers:
- x-request-id
- x-b3-traceid
- x-b3-spanid
- x-b3-parentspanid
- x-b3-sampled
- x-b3-flags
- x-ot-span-context
I needed to pass x-request-id header to a downstream service in my application. Achieved this by adding WebFilter that writes x-request-id to a reactor context
class ContextWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val headers = exchange.request.headers
val xRequestId = headers[X_REQUEST_ID]?.firstOrNull() ?: ""
val requestId = xRequestId.ifBlank { UUID.randomUUID().toString() }
return chain
.filter(exchange)
.contextWrite { it.put(X_REQUEST_ID, requestId) }
}
companion object {
const val X_REQUEST_ID = "X-REQUEST-ID"
}
}
and updating WebClient with ExchangeFilterFunction that updates outgoing request
WebClient.builder()
.filter(
ExchangeFilterFunction.ofRequestProcessor { request ->
Mono.deferContextual { context ->
val xRId = context.getOrDefault<String>("X-REQUEST-ID", "")
logger.debug("Set X-REQUEST-ID={} as a header to outgoing call", xRId)
Mono.just(
ClientRequest.from(request)
.header("X-REQUEST-ID", xRId)
.build()
)
}
}
)
.baseUrl("http://localhost:8080")
.build()

Retrieve path variable on Spring Boot WebFlux (functional approach)

Let's say I have this router definition:
#Component
class PersonRouter(private val handler: PersonHandler) {
#Bean
fun router(): RouterFunction<ServerResponse> = router {
("/api/people" and accept(MediaType.APPLICATION_JSON_UTF8)).nest {
GET("/{id}") { handler.findById(it) }
}
}
And then this handler:
#Component
#Transactional
class PersonHandler(private val repository: PersonRepository) {
private companion object : KLogging()
#Transactional(readOnly = true)
fun findById(req: ServerRequest): Mono<ServerResponse> {
logger.info { "${req.method()} ${req.path()}" }
val uuid = ? // req.pathContainer().elements().last().value()
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(repository.findById(uuid)))
.switchIfEmpty(ServerResponse.notFound().build())
}
}
How do I access the identifier (what would be a #PathVariable id: String on a typical #RestController) from ServerRequest without doing black magic with regular expressions, string-heavy-lifting, and such things?
Ah! Found it!
It is by doing: req.pathVariable("id")
It was there all the time...in the official Spring Framework (Web Reactive) documentation!

Resources