+++ /dev/null
-package de.juplo.demo;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.servlet.HandlerInterceptor;
-
-import java.io.IOException;
-
-
-@RequiredArgsConstructor
-public class BackendVersionInterceptor implements HandlerInterceptor
-{
- public final static String BACKEND_VERSION = "X-Backend-Version";
-
-
- private final String backendVersion;
-
-
- @Override
- public boolean preHandle(
- HttpServletRequest request,
- HttpServletResponse response,
- Object handler) throws IOException
- {
- String requstedVersion = request.getHeader(BACKEND_VERSION);
- if (requstedVersion != null && !requstedVersion.equals(backendVersion))
- {
- response.sendError(HttpStatus.GONE.value());
- return false;
- }
- else
- {
- return true;
- }
- }
-}
String projectVersion;
- @Bean
- public BackendVersionInterceptor backendVersionInterceptor(
- @Value("${build.version}") String projectVersion)
- {
- return new BackendVersionInterceptor(projectVersion);
- }
-
@Bean
public String from(
@Value("${application.name}") String applicationName,
private final String from;
- @PostMapping("{to}")
- public GreetingTO greet(
+ @RequestableByHumans
+ @PostMapping("/greet/{to}")
+ public MessageTo greet(
@PathVariable String to,
@RequestBody String greeting)
{
log.info("Greeting from {} to {}: {}", from, to, message);
- return GreetingTO.of(message, from, to);
+ return MessageTo.of(message, from, to);
+ }
+
+ @PostMapping("/acknowledge/{to}")
+ public MessageTo acknowledge(
+ @PathVariable String to,
+ @RequestBody String acknowledgment)
+ {
+ String message = acknowledgment
+ .replaceAll(PLACEHOLDER_FROM, from)
+ .replaceAll(PLACEHOLDER_TO, to)
+ .trim();
+
+ log.info("Acknowledgement from {} to {}: {}", from, to, message);
+
+ return MessageTo.of(message, from, to);
}
}
public class DemoWebMvcConfigurer implements WebMvcConfigurer
{
@Autowired
- BackendVersionInterceptor backendVersionInterceptor;
+ RequestTypeInterceptor requestTypeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry)
{
- registry.addInterceptor(backendVersionInterceptor);
+ registry.addInterceptor(requestTypeInterceptor);
}
}
+++ /dev/null
-package de.juplo.demo;
-
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-
-import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
-
-
-@Data
-@AllArgsConstructor(staticName = "of")
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(NON_EMPTY)
-public class GreetingTO
-{
- private String greeting;
- private String from;
- private String to;
-}
--- /dev/null
+package de.juplo.demo;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
+
+
+@Data
+@AllArgsConstructor(staticName = "of")
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(NON_EMPTY)
+public class MessageTo
+{
+ private String message;
+ private String from;
+ private String to;
+}
--- /dev/null
+package de.juplo.demo;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.io.IOException;
+
+
+@Component
+@Slf4j
+public class RequestTypeInterceptor implements HandlerInterceptor
+{
+ public final static String REQUEST_TYPE = "X-Request-Type";
+
+ public enum RequestType { HUMAN, MACHINE };
+
+
+ @Override
+ public boolean preHandle(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler) throws IOException
+ {
+ // Only instances of HandlerMethod are intercepted for the type of the request
+ if (!(handler instanceof HandlerMethod))
+ {
+ log.trace("Ignoring handler of type {}", handler);
+ return true;
+ }
+
+ String requestTypeHeader = request.getHeader(REQUEST_TYPE);
+ if (requestTypeHeader == null)
+ {
+ sendBadRequest(response, "Ignoring request without header");
+ return false;
+ }
+
+ try
+ {
+ RequestType requestType = RequestType.valueOf(requestTypeHeader.toUpperCase());
+
+ if (requestType == RequestType.MACHINE)
+ {
+ log.debug("Accepting machine-request for {}", handler);
+ return true;
+ }
+
+ if (((HandlerMethod)handler).getMethod().getAnnotation(RequestableByHumans.class) != null)
+ {
+ log.info("Accepting human request for {}", handler);
+ return true;
+ }
+ else
+ {
+ sendForbidden(response, "Human requests are not allowed for " + handler);
+ return false;
+ }
+ }
+ catch (IllegalArgumentException e)
+ {
+ log.warn("Invalid RequestType: {}", e.toString());
+ }
+
+ sendBadRequest(response, requestTypeHeader);
+ return false;
+ }
+
+ void sendForbidden(
+ HttpServletResponse response,
+ String message) throws IOException
+ {
+ log.warn("Forbidden: {}", message);
+ response.sendError(HttpStatus.FORBIDDEN.value(), message);
+ }
+
+ void sendBadRequest(
+ HttpServletResponse response,
+ String message) throws IOException
+ {
+ log.warn("Bad request: {}", message);
+ response.sendError(HttpStatus.BAD_REQUEST.value(), message);
+ }
+}
--- /dev/null
+package de.juplo.demo;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequestableByHumans
+{
+}
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
-import static de.juplo.demo.BackendVersionInterceptor.BACKEND_VERSION;
+import static de.juplo.demo.RequestTypeInterceptor.REQUEST_TYPE;
+import static de.juplo.demo.RequestTypeInterceptor.RequestType.HUMAN;
+import static de.juplo.demo.RequestTypeInterceptor.RequestType.MACHINE;
import static org.springframework.http.HttpHeaders.ACCEPT;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@Test
- void getWelcomeResponseIsOkIfBackendHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+ void getWelcomeResponseIsOkIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
{
- mvc.perform(get("/")).andExpect(status().isOk());
+ mvc
+ .perform(get("/"))
+ .andExpect(status().isOk());
}
@Test
- void getWelcomeResponseIsOkIfBackendHeaderDoesMatch(@Autowired MockMvc mvc) throws Exception
+ void getWelcomeResponseIsOkIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
{
mvc
.perform(get("/")
- .header(BACKEND_VERSION, projectVersion))
+ .header(REQUEST_TYPE, MACHINE))
.andExpect(status().isOk());
}
@Test
- void getWelcomeResponseIsGoneIfBackendHeaderDoesNotMatch(@Autowired MockMvc mvc) throws Exception
+ void getWelcomeResponseIsOkIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
{
mvc
.perform(get("/")
- .header(BACKEND_VERSION, "FOO"))
- .andExpect(status().isGone());
+ .header(REQUEST_TYPE, HUMAN))
+ .andExpect(status().isOk());
}
@Test
- void getGreetingIfBackendHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+ void expectedResultsForGreetIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
{
mvc
- .perform(post("/peter")
+ .perform(post("/greet/peter")
.header(ACCEPT, "application/json")
- .header(BACKEND_VERSION, projectVersion)
+ .header(REQUEST_TYPE, MACHINE)
.content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
.andExpect(status().isOk())
- .andExpect(content().json("{\"greeting\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+ .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
}
@Test
- void getGreetingIfBackendHeaderDoesMatch(@Autowired MockMvc mvc) throws Exception
+ void expectedResultsForGreetIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
{
mvc
- .perform(post("/peter")
+ .perform(post("/greet/peter")
.header(ACCEPT, "application/json")
- .header(BACKEND_VERSION, "FOO")
+ .header(REQUEST_TYPE, HUMAN)
.content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
- .andExpect(status().isGone());
+ .andExpect(status().isOk())
+ .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
}
@Test
- void getGreetingIfBackendHeaderDoesNotMatch(@Autowired MockMvc mvc) throws Exception
+ void badRequestForGreetIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
{
mvc
- .perform(post("/peter")
+ .perform(post("/greet/peter")
.header(ACCEPT, "application/json")
.content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ void expectedResultsForAcknowlegeIfRequestIsOfTypeMachine(@Autowired MockMvc mvc) throws Exception
+ {
+ mvc
+ .perform(post("/acknowledge/peter")
+ .header(ACCEPT, "application/json")
+ .header(REQUEST_TYPE, MACHINE)
+ .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
.andExpect(status().isOk())
- .andExpect(content().json("{\"greeting\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+ .andExpect(content().json("{\"message\": \"Hello peter, nice to meet you. I'm FOO@BAR.\"}"));
+ }
+
+ @Test
+ void isForbiddenForAcknowlegeIfRequestIsOfTypeHuman(@Autowired MockMvc mvc) throws Exception
+ {
+ mvc
+ .perform(post("/acknowledge/peter")
+ .header(ACCEPT, "application/json")
+ .header(REQUEST_TYPE, HUMAN)
+ .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ void badRequestForAcknowlegeIfRequestTypeHeaderIsNotSet(@Autowired MockMvc mvc) throws Exception
+ {
+ mvc
+ .perform(post("/acknowledge/peter")
+ .header(ACCEPT, "application/json")
+ .content("Hello TO_NAME, nice to meet you. I'm FROM_NAME."))
+ .andExpect(status().isBadRequest());
}
}