Soumen Dass
Soumen Dass

Reputation: 313

Springfox global response header

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

Answers (3)

Frame91
Frame91

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

wheeleruniverse
wheeleruniverse

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

Soumen Dass
Soumen Dass

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

Related Questions