Return HTTP 204 on null with spring @RestControlle

2020-02-18 23:23发布

问题:

This returns 200 OK with Content-Length: 0

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       return null;
    }

}

Simply put I'd like it to return 204 No Content on null.

Is there a way to force spring-mvc/rest to return 204 on null not 200? I dont want to change every rest method to return ResponseEntity or something like that, only map null to 204

回答1:

Of course yes.

Option 1 :

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id, HttpServletResponse response) {
       Object object = getObject();
       if( null == object ){
          response.setStatus( HttpStatus.SC_NO_CONTENT);
       }
       return object ;
    }
}

Option 2 :

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       Object object = getObject();
       if ( null == object ){
          return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
       }

       return object ;
    }
}

Might have typos, but you get the concept.



回答2:

You can use the @ResponseStatus annotation. This way you can have a void method and you don't have to build a ResponseEntity.

@DeleteMapping(value = HERO_MAPPING)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long heroId) {
    heroService.delete(heroId);
}

BTW returning 200 when the object exists and 204 otherwise it's a bit unusual regarding API REST design. It's common to return a 404 (not found) when the requested object is not found. And this can be achieved using an ControllerAdvice.

In Spring REST it's better to handle Exceptions with a Exception handler instead of putting logic to decide the response status, etc. This is an example using the @ControllerAdvice annotation: http://www.jcombat.com/spring/exception-handling-in-spring-restful-web-service



回答3:

I solved this problem with a filter. It's global and simple.

package your.package.filter;

import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class NoContentFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(httpServletRequest, httpServletResponse);
        if (httpServletResponse.getContentType() == null ||
                httpServletResponse.getContentType().equals("")) {
            httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
        }
    }
}

and add the following in your web.xml

<filter>
    <filter-name>restNoContentFilter</filter-name>
    <filter-class>your.package.filter.NoContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>restNoContentFilter</filter-name>
    <url-pattern>/rest/*</url-pattern>
</filter-mapping>


回答4:

You can try this :

@RestController
public class RepoController {

    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public ResponseEntity<String> getDocument(@PathVariable long id) {

       if(noError) {
           ............
           return new ResponseEntity<String>(HttpStatus.OK); 
       }
       else {
           return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
       }
   }
}

Uou need to change HttpStatus.BAD_REQUEST with the equivalent for 204 code status



回答5:

Same answer but solved by AOP:

@Aspect
public class NoContent204HandlerAspect {

  @Pointcut("execution(public * xx.xxxx.controllers.*.*(..))")
  private void anyControllerMethod() {
  }

  @Around("anyControllerMethod()")
  public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {

    Object[] args = joinPoint.getArgs();

    Optional<HttpServletResponse> response = Arrays.asList(args).stream().filter(x -> x instanceof HttpServletResponse).map(x -> (HttpServletResponse)x).findFirst();

    if (!response.isPresent())
      return joinPoint.proceed();

    Object retVal = joinPoint.proceed();
    if (retVal == null)
      response.get().setStatus(HttpStatus.NO_CONTENT.value());

    return retVal;
  }
}


回答6:

Question is old but for those that needs a global answer and have Spring 4+, you can create a ResponseBodyAdvice that changes response code base on the controller response. The following exemple do it for all @RestController classes :

@ControllerAdvice(annotations = { RestController.class })
public class NullToNoContentResponseBodyAdvice
    implements ResponseBodyAdvice<Object>
{
    /**
     * {@inheritDoc}
     */
    @Override
    public Object beforeBodyWrite(final Object p_responseBodyObject, final MethodParameter p_methodParameter,
                                  final MediaType p_mediaType, final Class<? extends HttpMessageConverter<?>> p_class,
                                  final ServerHttpRequest p_serverHttpRequest,
                                  final ServerHttpResponse p_serverHttpResponse)
    {
        // ------------------------- DECLARE -------------------------- //

        if (p_responseBodyObject == null)
        {
            p_serverHttpResponse.setStatusCode(HttpStatus.NO_CONTENT);
        }

        // Always return object unchanged or it will break response
        return p_responseBodyObject;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(final MethodParameter p_methodParameter, final Class<? extends HttpMessageConverter<?>> p_class)
    {
        return AbstractGenericHttpMessageConverter.class.isAssignableFrom(p_class);
    }
}