Return all available mappings on RequestMethod GET - spring

I'm learning about spring boot and having a REST Server with JPA.
For my RestControllers, I wanted to have the behavior on the base page that when someone goes to the base page they would be able to see all the available RequestMappings under the base page.
#RestController
#RequestMapping("/food")
public class FoodRestController {
#RequestMapping(value = "/all", method = RequestMethod.GET)
#ResponseBody
public Iterable<Food> printAllFoods() {
return foodRepository.findAll();
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<?> addFood(#RequestBody Food f) {
foodRepository.save(f);
HttpHeaders httpHeaders = new HttpHeaders();
return new ResponseEntity<Food>(f, httpHeaders, HttpStatus.CREATED);
}
So for the above going to "localhost:8080/food" would give a page showing something like not a valid endpoint, possible endpoints are localhost:8080/food/all or localhost:8080/food/add.
I could just have a RequestMapping with GET and return it as a body but it would be a manually typed response. Wanted to see if Spring offers anything like this

You can also use Swagger . It is actually a documentation framework. It also builds around a beautiful UI to try out the APIs that are available along with the documentation.

The SpringBoot Actuator already has a functionality that does something like this. Add a dependency on the SpringBoot Actuator, start up your application and point your browser to:
http://[yourHostAndPort]/mappings
This will produce something like the following (assuming JSON), which contains ALL mappings that are a part of your project (Spring endpoints too!).
{
...
"{[/refresh],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()"
},
"{[/restart],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.cloud.context.restart.RestartMvcEndpoint.invoke()"
},
"{[/configprops],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
},
"{[/env],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}": {
"bean": "endpointHandlerMapping",
"method": "public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map<java.lang.String, java.lang.String>)"
},
...
}
This snippet shows a small handful of the mappings that are available from some other Actuator endpoints.
I understand that your requirements are a bit different, so if this setup isn't exactly what you need, you should be able to create your own endpoint(s) that do something similar, just by browsing the SpringBoot Actuator source code. The specific file that does the majority of the work for the mappings endpoint is org.springframework.boot.actuate.endpoint.RequestMappingEndpoint.java.

Related

Error Response body changed after Boot 3 upgrade

I have the following Controller endpoint in my project:
#GetMapping(value = "/{id}")
public FooDto findOne(#PathVariable Long id) {
Foo model = fooService.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return toDto(model);
}
My application retrieved the following response when it couldn't find a Foo with the provided id:
{
"timestamp": "2023-01-06T08:43:12.161+00:00",
"status": 404,
"error": "Not Found",
"path": "/foo/99"
}
However, after upgrading to Boot 3, the response body changed to:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "/foo/99"
}
I couldn't find any related information in the Spring Boot 3.0 Migration Guide nor in the Upgrading to Spring Framework 6.x one.
Spring Web 6 introduced support for the "Problem Details for HTTP APIs" specification, RFC 7807.
With this, the ResponseStatusException now implements the ErrorResponse interface and extends the ErrorResponseException class.
Having a quick look at the javadocs, we can see that all these are backed by the RFC 7807 formatted ProblemDetail body, which, as you can imagine, has the fields of the new response you're getting, and also uses the application/problem+json media type in the response.
Here is a reference to how Spring now treats Error Responses, which naturally goes in the direction of using Problem Details spec across the board.
Now, normally, if you were simply relying on Boot's Error Handling mechanism without any further change, you would still see the same response as before. My guess is that you are using a #ControllerAdvice extending ResponseEntityExceptionHandler. With that, you are enabling RFC 7807 (as per the Spring docs here)
So, that is why your ResponseStatusException has changed its body content.
Configuring the Problem Detail response body to include previous fields
If you need to stick to the pre-existing fields (at least until you fully migrate to the Problem Detail based approach) or if you simply want to add custom fields to the error response, you can override the createResponseEntity method in the #ControlAdvice class extending ResponseEntityExceptionHandler as follows:
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> createResponseEntity(#Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
if (body instanceof ProblemDetail) {
ProblemDetail problemDetail = ((ProblemDetail) body);
problemDetail.setProperty("error", problemDetail.getTitle());
problemDetail.setProperty("timestamp", new Date());
if (request instanceof ServletWebRequest) {
problemDetail.setProperty("path", ((ServletWebRequest) request).getRequest()
.getRequestURI());
}
}
return new ResponseEntity<>(body, headers, statusCode);
}
}
Note: I'm using new Date() instead of Java Time simply because that's what Boot's DefaultErrorAttributes class uses. Also, I'm not using Boot's ErrorAttributes for simplicity.
Note that defining the path field is a little bit tricky because problemDetail.getInstance() returns null at this stage; the framework sets it up later in the HttpEntityMethodProcessor.
Of course, this solution is suitable for a servlet stack, but it should help figure out how to proceed in a reactive stack as well.
With this, the response will look as follows:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "/foo/99",
"error": "Not Found",
"path": "/foo/99",
"timestamp": "2023-01-06T10:00:20.509+00:00"
}
Of course, it has duplicated fields. You can completely replace the response body in the method if you prefer.
Configuring Boot to also use the Problem Detail spec
Now, to be consistent across the board in your application, note that Boot now provides the spring.mvc.problemdetails.enabled property to use the Problem Details in its Error Handling mechanism (which is disabled by default to avoid breaking changes, as its associated issue):
spring.mvc.problemdetails.enabled=true

Resolving POST /** request URL to full request URL using micrometer

With the micro-service architecture I have written a generic POST request handler which is consumed by all the micro-services. The post mapping in spring look like this:
#RestController
#RequestMapping(value = "/v1/", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class V1Controller {
#PostMapping(path = "/**")
public #ResponseBody Json post () {}
}
Now while I am consuming the metrics for this endpoint using micrometer I am only getting /v1/ as the endpoint in the metrics while I am sending the full URL like /v1/demo/foo from the calling service. I tried lot of the combination but it is not working. I have also added the WebMvcTagsProvider where I am listing to request and resolving the POST api calls.
#Bean
#SuppressWarnings("unchecked")
public WebMvcTagsProvider webMvcTagsProvider(ObjectMapper objectMapper) {
return new DefaultWebMvcTagsProvider() {
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
if ("POST".equals(request.getMethod())) {
Tag uriTag = Tag.of("uri", String.valueOf(request.getRequestURI()));
return Tags.of(WebMvcTags.method(request), uriTag, WebMvcTags.exception(exception), WebMvcTags.status(response));
}
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.exception(exception), WebMvcTags.status(response));
}
};
}
Still it is resolving to /v1/ URL in the metrics. I tried googling alot but didn't find any solution. Thanks in advance.
The build in Spring Boot RequestMapping based metrics match on the annotations and add those as tags.
This is to avoid a tag explosion. Imagine a #RequestMapping for a path like user/{userId}, you would want to group all those calls together (user/1, user/2, user/3).
You'll want to create your own Timer in your post method that set that url tags, etc there.
If you decide to reuse the same metric name as the built in Spring Boot metric, you'll want to disable that one as well, so you don't double count those requests.

Spring boot application does not allow get request

I followed tutorial from here (https://medium.com/echoenergy/how-to-use-java-high-level-rest-client-with-spring-boot-to-talk-to-aws-elasticsearch-9e12571df93e) to create a springboot- elastic search application.
I was able to do a successful POST and PUT method but GET request fails for
me ( using PostMan).
GET fails with following exception
{
"timestamp": "2019-03-09T10:45:18.496+0000",
"status": 405,
"error": "Method Not Allowed",
"message": "Request method 'GET' not supported",
"path": "/api/v1/profiles/464d06e8-ef57-49f3-ac17-bd51ba7786e2"
}
But I correctly added the corresponding get method in the controller
#RestController("/api/v1/profiles")
public class ProfileController {
private ProfileService service;
#Autowired
public ProfileController(ProfileService service) {
this.service = service;
}
#PostMapping
public ResponseEntity createProfile(
#RequestBody ProfileDocument document) throws Exception {
return
new ResponseEntity(service.createProfile(document), HttpStatus.CREATED);
}
#GetMapping("/{id}")
public ProfileDocument findById(#PathVariable String id) throws Exception {
return service.findById(id);
}
}
In the response, I can see that it allows only PUT and POST. But I could not find any config file in the server to explicitly add http methods other than the controller
Can someone please help
The issue with your controller that I can see is, there's no #RequestMapping("/api/v1/profiles") at controller class level. It should be like
#RestController
#RequestMapping("/api/v1/profiles")
You cannot specify the request path in #RestController's value field. It means (as per javadocs);
The value may indicate a suggestion for a logical component name, to
be turned into a Spring bean in case of an autodetected component.
Hope this helps.

Response MIME type for Spring Boot actuator endpoints

I have updated a Spring Boot application from 1.4.x to 1.5.1 and the Spring Actuator endpoints return a different MIME type now:
For example, /health is now application/vnd.spring-boot.actuator.v1+json instead simply application/json.
How can I change this back?
The endpoints return a content type that honours what the client's request says it can accept. You will get an application/json response if the client send an Accept header that asks for it:
Accept: application/json
In response to the comment of https://stackoverflow.com/users/2952093/kap (my reputation is to low to create a comment): when using Firefox to check endpoints that return JSON I use the Add-on JSONView. In the settings there is an option to specify alternate JSON content types, just add application/vnd.spring-boot.actuator.v1+jsonand you'll see the returned JSON in pretty print inside your browser.
As you noticed the content type for actuators have changed in 1.5.x.
If you in put "application/json" in the "Accept:" header you should get the usual content-type.
But if you don't have any way of modifying the clients, this snippet returns health (without details) and original content-type (the 1.4.x way).
#RestController
#RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#Inject
HealthEndpoint healthEndpoint;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Health > health() throws IOException {
Health health = healthEndpoint.health();
Health nonSensitiveHealthResult = Health.status(health.getStatus()).build();
if (health.getStatus().equals(Status.UP)) {
return ResponseEntity.status(HttpStatus.OK).body(nonSensitiveHealthResult);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(nonSensitiveHealthResult);
}
}
}
Configuration (move away existing health)
endpoints.health.path: internal/health
Based on the code in https://github.com/spring-projects/spring-boot/issues/2449 (which also works fine but completely removes the new type) I came up with
#Component
public class ActuatorCustomizer implements EndpointHandlerMappingCustomizer {
static class Fix extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Object attribute = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (attribute instanceof LinkedHashSet) {
#SuppressWarnings("unchecked")
LinkedHashSet<MediaType> lhs = (LinkedHashSet<MediaType>) attribute;
if (lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)) {
lhs.add(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON);
}
}
return true;
}
}
#Override
public void customize(EndpointHandlerMapping mapping) {
mapping.setInterceptors(new Object[] {new Fix()});
}
}
which puts the new vendor-mediatype last so that it will use application/json for all actuator endpoints when nothing is specified.
Tested with spring-boot 1.5.3
Since SpringBoot 2.0.x the suggested solution in implementing the EndpointHandlerMappingCustomizer doesn't work any longer.
The good news is, the solution is simpler now.
The Bean EndpointMediaTypes needs to be provided. It is provided by the SpringBoot class WebEndpointAutoConfiguration by default.
Providing your own could look like this:
#Configuration
public class ActuatorEndpointConfig {
private static final List<String> MEDIA_TYPES = Arrays
.asList("application/json", ActuatorMediaType.V2_JSON);
#Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
}
To support application/vnd.spring-boot.actuator.v1+json in Firefox's built in JSON viewer, you can install this addon: json-content-type-override. It will convert content types that contain "json" to "application/json".
Update: Firefox 58+ has built-in support for these mime types, and no addon is needed anymore. See https://bugzilla.mozilla.org/show_bug.cgi?id=1388335

Swagger 2.0 support in Grails 3+ (3.0.11) not working

I am running Grails 3.0.11 and want to create Swagger-documentation for my REST endpoints. I added the SwaggyDoc-plugin to the dependencies in my build.gradle script by adding:
compile "org.grails.plugins:swaggydoc:0.26.0".
In IntelliJ I see the Swaggydoc-dependency added to my list of libraries.
After starting my Grails-application via the grails run-app command and opening my application by entering http://localhost:8080/api/ I get an 404 error telling the page does not exist.
Do I need to configure something more or to run something special to generate documentation? I already tried to open a ticket in the Git-project and contacting the author with no succes.
Update1: there seems to be a Grails 3-plugin (found at Versioneye?) which I added:
compile "org.grails.plugins:swaggydoc-grails3:0.26.0"
It does work half, by default some sort of Pet-demo is visible and it is failing on constraints in a domain and enums. Doesn't seem to work very well actually.
Update2: As pointed out by Dilip Krishnan I tried to use SpringFox, first I added the dependencies to my Gradle build file:
compile("io.springfox:springfox-swagger2:2.3.1")
compile("io.springfox:springfox-swagger-ui:2.3.1")
Then I added a new class called ApiDocumentationConfiguration with the following code:
#Configuration
#EnableSwagger2
public class ApiDocumentationConfiguration {
#Bean
public Docket documentation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
#Bean
public UiConfiguration uiConfig() {
return UiConfiguration.DEFAULT;
}
private ApiInfo metadata() {
return new ApiInfoBuilder()
.title("My awesome API")
.description("Some description")
.version("1.0")
.contact("my-email#domain.org")
.build();
}
}
My Grails resources file contains the following code:
beans = {
apiDocumentationConfiguration(ApiDocumentationConfiguration)
}
Last step was starting the application and trying to load the end point which shows the Swagger front end:
http://localhost:8080/swagger-ui.html
It behind the scenes tries to load an other end point (containing the JSON I guess?) which loads
http://localhost:8080/v2/api-docs
This does show JSON data and I get end points for things like a basic error controller, health mvc, metrics mvc et cetera. But not my own annotated user controller which is annotated like follows:
#Api(value = "users", description = "Endpoint for user management")
class UserController {
// GET all users
#ApiOperation(value = "doStuff", nickname = "doStuff", response = User.class)
def index() {
respond User.list()
}
}
Seems I am almost there, but still missing something, is my annotation wrong or doesn't it scan my controllers?
Update3: in contact with one of the authors of SpringFox (Dilip Krishnan) to add support for Grails 3+ to SpringFox, see ticket. The reason it doesn't currently work is because SpringFox looks at MVC annotation, an adapter needs to be written to retrieve the endpoints from the controllers in Grails.
I have successfully used swaggydocs in both 2.4.x projects and 3.1.4.
In order to make it work in 3.x (tested on 3.1.4) you have to add
compile "org.grails.plugins:swaggydoc-grails3:0.26.0"
to gradle dependencies section. That makes swaggy available in your project.
Then add annotations to your controllers
#Api("test methods")
class TestController {
#ApiOperation(value = "some method description")
#ApiResponses([
#ApiResponse(code = 405, message = "Bad method. Only POST is allowed"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 400, message = "Invalid request json")
])
def testGetMethod() {
render([status: "OK"] as JSON)
}
Then mark your methods allowedMethods as follows
class TestController {
static allowedMethods = [testGetMethod: "GET", testPostMethod: "POST"]
NOTE this is really important - otherwise swaggy will mark every your method as GET. Swaggy doesn't respect neither httpMethod in ApiOperation annotation nor http method in url mappings.
Finally add your controller to urlmappings as swaggy checks url mappings to look for URLS. Note camelCase!
//NOTE small camelCase
//swaggy won't see urls correctly if you start controller name with capital letter
"/api/test/"(controller: "test", action: "testGetMethod")
"/api/test/"(controller: "test", action: "testPostMethod")
You can also add some api info in application.yml
swaggydoc:
contact: rafal#pydyniak.pl
description: sample swaggy app
You can find sample app (with dummy methods but point was to make swaggy work) at my github https://github.com/RafalPydyniak/Swaggy-example.
Also there are some more docs on how to use this api on http://rahulsom.github.io/swaggydoc/. I just wanted to show you how to install it (as it's quite tricky to make everything work)
Hope it helps!
I followed the same 2 steps:
1) add swagger2 dependencies
2) provide configuation
The problem is that sprinfox does not scan grails url mappings (see https://github.com/springfox/springfox/issues/1169#issuecomment-252259284)
To fix that I added standard spring annotations:
#Controller()
#RequestMapping(value="/path")
on the controller and
#RequestMapping(value="/resource")
on the method. After that sprinfox started to pick up my documentation.

Resources