【问题标题】:Springboot @DeleteMapping respond 404, but response body is emptySpringboot @DeleteMapping 响应 404,但响应正文为空
【发布时间】:2022-02-03 10:26:30
【问题描述】:

@DeleteMapping 有问题。

情况如下。

  1. 如果我使用方法DELETE 请求/v1/cache/{cacheEntry}
    它以404 响应,但正文是空的。没有消息,没有 spring 默认的 json 404 响应消息。

  2. 如果我使用方法POST 请求/v1/cache/{cacheEntry}
    它以405 响应,正文在下方。 (此操作是正确的,不是错误。)

  3. 如果我将@DeleteMapping 更改为@PostMapping,并使用方法POST 请求/v1/cache/{cacheEntry},它会以代码200 响应成功。

{
    "timestamp": 1643348039913,
    "status": 405,
    "error": "Method Not Allowed",
    "message": "",
    "path": "/v1/cache/{cacheEntry}"
}

// 控制器


@Slf4j
@RestController
@RequestMapping("/v1/cache")
@RequiredArgsConstructor
public class CacheController {

    private final CacheService cacheService;

    @PostMapping("/{cacheEntry}")
    public CacheClearResponse clearCacheEntry(@PathVariable("cacheEntry") CacheChannels cacheEntry) {
        try {
            log.info("Cache entry :: " + cacheEntry);
            cacheService.evictCacheEntry(cacheEntry);
            return CacheClearResponse.builder()
                    .result(
                            RequestResult.builder()
                                    .code(9200)
                                    .message("SUCCESS")
                                    .build()
                    )
                    .common(
                            Common.builder().build()
                    )
                    .date(LocalDateTime.now())
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            return CacheClearResponse.builder()
                    .result(
                            RequestResult.builder()
                                    .code(9999)
                                    .message(sw.toString())
                                    .build()
                    )
                    .common(
                            Common.builder().build()
                    )
                    .date(LocalDateTime.now())
                    .build();
        }

    }
}
}

// 缓存服务

@Service
@RequiredArgsConstructor
public class CacheService {

    private final CacheManager cacheManager;

    public void evictCacheEntry(CacheChannels cacheEntry) {
        Cache cache = cacheManager.getCache(cacheEntry.getCacheName());
        if (cache != null) {
            cache.clear();
        }
    }

    public void evictCache(CacheChannels cacheEntry, String cacheKey) {
        Cache cache = cacheManager.getCache(cacheEntry.getCacheName());
        if (cache != null) {
            cache.evict(cacheKey);
        }
    }
}

// 枚举

@Getter
@AllArgsConstructor
public enum CacheChannels {
    CACHE_TEN_MIN(Names.CACHE_TEN_MIN, Duration.ofMinutes(10)),
    CACHE_HALF_HR(Names.CACHE_HALF_HR, Duration.ofMinutes(30)),
    CACHE_ONE_HR(Names.CACHE_ONE_HR, Duration.ofHours(1)),
    CACHE_THREE_HR(Names.CACHE_THREE_HR, Duration.ofHours(3)),
    CACHE_SIX_HR(Names.CACHE_SIX_HR, Duration.ofHours(6)),
    CACHE_ONE_DAY(Names.CACHE_ONE_DAY, Duration.ofDays(1));
    private final String cacheName;
    private final Duration cacheTTL;

    public static CacheChannels from(String value) {
        return Arrays.stream(values())
                .filter(cacheChannel -> cacheChannel.cacheName.equalsIgnoreCase(value))
                .findAny()
                .orElse(null);
    }

    public static class Names {

        public static final String CACHE_TEN_MIN = "cache10Minutes";
        public static final String CACHE_HALF_HR = "cache30Minutes";
        public static final String CACHE_ONE_HR = "cache1Hour";
        public static final String CACHE_THREE_HR = "cache3Hours";
        public static final String CACHE_SIX_HR = "cache6Hours";
        public static final String CACHE_ONE_DAY = "cache1Day";
    }
}

// 转换器

@Slf4j
public class StringToCacheChannelConverter implements Converter<String, CacheChannels> {
    @Override
    public CacheChannels convert(String source) {
        log.info("Convert Target: " + source);
        return CacheChannels.from(source);
    }
}

// 安全配置

@Configuration
@EnableWebSecurity
@Order(1)
public class APISecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.auth-token-header-name:Authorization}")
    private String apiKeyHeader;

    @Value("${spring.security.secret}")
    private String privateApiKey;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        APIKeyAuthFilter filter = new APIKeyAuthFilter(apiKeyHeader);
        filter.setAuthenticationManager(new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication)
                    throws AuthenticationException {
                String requestedApiKey = (String) authentication.getPrincipal();
                if (!privateApiKey.equals(requestedApiKey)) {
                    throw new BadCredentialsException("The API Key was not found or not the expected value");
                }

                authentication.setAuthenticated(true);
                return authentication;
            }
        });

        http
                .csrf().disable()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilter(filter)
                .authorizeRequests()
                    .antMatchers("/v1/cache/**")
                        .authenticated();
    }
}

// 过滤器

@Slf4j
public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {

    private String apiKeyHeader;

    public APIKeyAuthFilter(String apiKeyHeader) {
        this.apiKeyHeader = apiKeyHeader;
    }

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) {
        log.info("Check authenticated.");
        return httpServletRequest.getHeader(apiKeyHeader);
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
        return "N/A";
    }

}

// 网络配置

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToCacheChannelConverter());
    }

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new HiddenHttpMethodFilter();
    }
}

这可能是控制器已加载,端点已映射。
我尝试将@DeleteMapping 更改为@PostMapping 并成功响应POST 请求。

我错过了什么?

【问题讨论】:

  • deleteSomthing() 在做什么?也许找不到您要删除的内容,这正是预期的行为。能否请您添加相关代码?谢谢!
  • @JoãoDias 感谢您的回复和格式化。我更清楚地更新代码。
  • @Junghoon Kong 尝试二进制调试。意思是注释一半的代码 - 试试它是否有效。如果不这样做,则评论剩余部分的一半并重复直到它这样做。你可以尝试用catch Throwable + log注册@ControllerAdvice bean,看看是否有一些意外的异常。此处的示例baeldung.com/exception-handling-for-rest-with-spring 如果没有,那么我建议从头开始-仅从@RestController 开始-没有安全性,过滤器。作品?添加代码的其他部分。最后的安全性,这可能是这个问题的根源。

标签: java spring spring-boot http-delete


【解决方案1】:

我找到了收到 404 却没有任何消息的原因。 我的tomcat在远程服务器上。它配置了security-constraint 并为所有端点禁用了DELETE 方法。

我只是将它注释掉,它可以与 delete 方法一起正常工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-28
    • 1970-01-01
    • 2020-05-21
    • 1970-01-01
    • 2018-12-08
    • 2012-02-25
    • 2020-09-26
    相关资源
    最近更新 更多