Reputation: 313
In my spring boot rest API, I'm sending back a unique request id header "x-request-id" for every response (irrespective of the method) for every endpoint. I can add this using something like this:
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "Successful status response",
responseHeaders = {
@ResponseHeader(
name = "x-request-id",
description = "auto generated unique request id",
response = String.class)})
})
This works fine and I can see it in the Swagger UI. However, doing this for every endpoint is a tedious + maintenance problem. I'm looking to do this globally but the Springfox documentation only shows about global response message using .globalResponseMessage option - I can't find anything for global response headers.
Upvotes: 4
Views: 4277
Reputation: 3789
I know I'm late to the party here, but I did find a way to globally add a header to every response using reflection (might not be required but turned out to be the easiest way for me to get EVERY response. You can also check for all ApiResponses
annotations but some were added implicitly and therefore left out with that approach).
@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 10)
public class RequestIdResponseHeaderPlugin implements OperationBuilderPlugin {
@Override
public boolean supports(DocumentationType documentationType) {
return true;
}
@Override
public void apply(OperationContext operationContext) {
try {
// we use reflection here since the operationBuilder.build() method would lead to different operation ids
// and we only want to access the private field 'responseMessages' to add the request-id header to it
Field f = operationContext.operationBuilder().getClass().getDeclaredField("responseMessages");
f.setAccessible(true);
Set<ResponseMessage> responseMessages = (Set<ResponseMessage>) f.get(operationContext.operationBuilder());
responseMessages.forEach(message -> {
int code = message.getCode();
Map<String, Header> map = new HashMap<>();
map.put("my-header-name", new Header(null, null, new ModelRef("string")));
ResponseMessage responseMessage = new ResponseMessageBuilder().code(code).headersWithDescription(map).build();
operationContext.operationBuilder().responseMessages(Collections.singleton(responseMessage));
});
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
Found this way after looking into the method responseMessages()
of the operation-builder. It internally merges response-headers based on the status-code and the logic itself will simply add headers to existing response-headers.
Hope it helps someone since it does not require you to annotate every single endpoint.
Upvotes: 1
Reputation: 1625
I updated my Docket configuration to include the Global header on every API. Hope this helps.
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.contact(new Contact("My Support", null, "My Email"))
.description("My Description")
.licenseUrl("My License")
.title("My Title")
.termsOfServiceUrl("My Terms and Conditions")
.version("My Version")
.build())
.globalOperationParameters(Collections.singletonList(new ParameterBuilder()
.name("x-request-id")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build()))
.select()
.paths(PathSelectors.regex("/user*))
.build()
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class);
Upvotes: 0
Reputation: 313
Ended up creating an annotation to handle this:
package com.abc.xyz.api.docs.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ResponseHeader;
import com.abc.xyz.api.constants.ApiConstants;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "Successful status response",
responseHeaders = {
@ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
@ApiResponse(
code = 401,
message = "Successful status response",
responseHeaders = {
@ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
@ApiResponse(
code = 403,
message = "Successful status response",
responseHeaders = {
@ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
@ApiResponse(
code = 404,
message = "Successful status response",
responseHeaders = {
@ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
}
)
public @interface RequestIdMethod {};
With this, I can add this as a marker annotation in front of my methods:
@RequestMapping(value = "/heartbeat", method = RequestMethod.GET)
@RequestIdMethod
public Heartbeat checkHeartbeat() {
return new Heartbeat(status);
}
It is not great because I need to repeat the entire @ApiResponse annotation block for every http return code (obviously there could be other return codes but I only covered the default codes shown by Springfox). Would have been better if there was a way to parameterize the entire @ApiResponse block.
Upvotes: 3