Skip to content

Commit 3410aea

Browse files
authored
Merge pull request #157 from xhaggi/fix/gh-155
[fix] broken HtmxResponse handling since 9cc7331
2 parents 95dcc2a + c61e55d commit 3410aea

File tree

8 files changed

+484
-116
lines changed

8 files changed

+484
-116
lines changed

htmx-spring-boot/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
<artifactId>spring-boot-starter-thymeleaf</artifactId>
4949
<scope>test</scope>
5050
</dependency>
51+
<dependency>
52+
<groupId>io.rest-assured</groupId>
53+
<artifactId>rest-assured</artifactId>
54+
<scope>test</scope>
55+
</dependency>
5156
<dependency>
5257
<groupId>com.google.code.findbugs</groupId>
5358
<artifactId>jsr305</artifactId>

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxExceptionHandlerExceptionResolver.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
*/
1919
public class HtmxExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver {
2020

21-
private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler;
21+
private final HtmxHandlerMethodHandler htmxHandlerMethodHandler;
2222

23-
public HtmxExceptionHandlerExceptionResolver(HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler) {
24-
this.handlerMethodAnnotationHandler = handlerMethodAnnotationHandler;
23+
public HtmxExceptionHandlerExceptionResolver(HtmxHandlerMethodHandler htmxHandlerMethodHandler) {
24+
this.htmxHandlerMethodHandler = htmxHandlerMethodHandler;
2525
}
2626

2727
@Override
@@ -31,10 +31,14 @@ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest reques
3131
ServletInvocableHandlerMethod exceptionHandlerMethod = this.getExceptionHandlerMethod(handlerMethod, exception, webRequest);
3232
if (exceptionHandlerMethod != null) {
3333
Method method = exceptionHandlerMethod.getMethod();
34-
handlerMethodAnnotationHandler.handleMethod(method, request, response);
34+
htmxHandlerMethodHandler.handleMethodAnnotations(method, request, response);
3535
}
3636

37-
return super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
37+
ModelAndView modelAndView = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
38+
39+
htmxHandlerMethodHandler.handleMethodArgument(request, response);
40+
41+
return modelAndView;
3842
}
3943

4044
}
Lines changed: 10 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,39 @@
11
package io.github.wimdeblauwe.htmx.spring.boot.mvc;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
53
import jakarta.servlet.http.HttpServletRequest;
64
import jakarta.servlet.http.HttpServletResponse;
75
import org.springframework.http.HttpHeaders;
86
import org.springframework.web.method.HandlerMethod;
97
import org.springframework.web.servlet.HandlerInterceptor;
8+
import org.springframework.web.servlet.ModelAndView;
109

1110
import java.lang.reflect.Method;
12-
import java.util.Collection;
13-
import java.util.HashMap;
14-
import java.util.stream.Collectors;
1511

1612
/**
1713
* HandlerInterceptor that adds htmx specific headers to the response.
1814
*/
1915
public class HtmxHandlerInterceptor implements HandlerInterceptor {
2016

21-
private final ObjectMapper objectMapper;
22-
private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler;
17+
private final HtmxHandlerMethodHandler htmxHandlerMethodHandler;
2318

24-
public HtmxHandlerInterceptor(ObjectMapper objectMapper, HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler) {
25-
this.objectMapper = objectMapper;
26-
this.handlerMethodAnnotationHandler = handlerMethodAnnotationHandler;
19+
public HtmxHandlerInterceptor(HtmxHandlerMethodHandler htmxHandlerMethodHandler) {
20+
this.htmxHandlerMethodHandler = htmxHandlerMethodHandler;
2721
}
2822

2923
@Override
30-
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
31-
32-
HtmxResponse htmxResponse = RequestContextUtils.getHtmxResponse(request);
33-
if (htmxResponse != null) {
34-
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER, htmxResponse.getTriggers());
35-
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE, htmxResponse.getTriggersAfterSettle());
36-
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP, htmxResponse.getTriggersAfterSwap());
37-
38-
if (htmxResponse.getReplaceUrl() != null) {
39-
response.setHeader(HtmxResponseHeader.HX_REPLACE_URL.getValue(), RequestContextUtils.createUrl(request, htmxResponse.getReplaceUrl(), htmxResponse.isContextRelative()));
40-
}
41-
if (htmxResponse.getPushUrl() != null) {
42-
response.setHeader(HtmxResponseHeader.HX_PUSH_URL.getValue(), RequestContextUtils.createUrl(request, htmxResponse.getPushUrl(), htmxResponse.isContextRelative()));
43-
}
44-
if (htmxResponse.getRetarget() != null) {
45-
response.setHeader(HtmxResponseHeader.HX_RETARGET.getValue(), htmxResponse.getRetarget());
46-
}
47-
if (htmxResponse.getReselect() != null) {
48-
response.setHeader(HtmxResponseHeader.HX_RESELECT.getValue(), htmxResponse.getReselect());
49-
}
50-
if (htmxResponse.getReswap() != null) {
51-
response.setHeader(HtmxResponseHeader.HX_RESWAP.getValue(), htmxResponse.getReswap().toHeaderValue());
52-
}
53-
}
24+
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
25+
htmxHandlerMethodHandler.handleMethodArgument(request, response);
5426
}
5527

5628
@Override
5729
public boolean preHandle(HttpServletRequest request,
5830
HttpServletResponse response,
5931
Object handler) {
6032

61-
if (handler instanceof HandlerMethod) {
62-
Method method = ((HandlerMethod) handler).getMethod();
63-
setVary(request, response);
64-
handlerMethodAnnotationHandler.handleMethod(method, request, response);
33+
setVary(request, response);
34+
35+
if (handler instanceof HandlerMethod handlerMethod) {
36+
htmxHandlerMethodHandler.handleMethodAnnotations(handlerMethod.getMethod(), request, response);
6537
}
6638

6739
return true;
@@ -73,35 +45,4 @@ private void setVary(HttpServletRequest request, HttpServletResponse response) {
7345
}
7446
}
7547

76-
private void setHeaderJsonValue(HttpServletResponse response, HtmxResponseHeader header, Object value) {
77-
try {
78-
response.setHeader(header.getValue(), objectMapper.writeValueAsString(value));
79-
} catch (JsonProcessingException e) {
80-
throw new IllegalArgumentException("Unable to set header " + header.getValue() + " to " + value, e);
81-
}
82-
}
83-
84-
private void addHxTriggerHeaders(HttpServletResponse response, HtmxResponseHeader headerName, Collection<HtmxTrigger> triggers) {
85-
if (triggers.isEmpty()) {
86-
return;
87-
}
88-
89-
// separate event names by commas if no additional details are available
90-
if (triggers.stream().allMatch(t -> t.getEventDetail() == null)) {
91-
String value = triggers.stream()
92-
.map(HtmxTrigger::getEventName)
93-
.collect(Collectors.joining(","));
94-
95-
response.setHeader(headerName.getValue(), value);
96-
return;
97-
}
98-
99-
// multiple events with or without details
100-
var triggerMap = new HashMap<String, Object>();
101-
for (HtmxTrigger trigger : triggers) {
102-
triggerMap.put(trigger.getEventName(), trigger.getEventDetail());
103-
}
104-
setHeaderJsonValue(response, headerName, triggerMap);
105-
}
106-
10748
}

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerMethodAnnotationHandler.java renamed to htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxHandlerMethodHandler.java

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,55 @@
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
77
import org.springframework.core.annotation.AnnotatedElementUtils;
8+
import org.springframework.http.HttpHeaders;
89

910
import java.lang.reflect.Method;
1011
import java.time.Duration;
12+
import java.util.Collection;
13+
import java.util.HashMap;
14+
import java.util.stream.Collectors;
1115

1216
/**
13-
* A handler for processing htmx annotations present on exception handler methods.
17+
* A handler for processing {@link HtmxResponse} and annotations present on handler methods.
1418
*
1519
* @since 3.6.2
1620
*/
17-
class HtmxHandlerMethodAnnotationHandler {
21+
class HtmxHandlerMethodHandler {
1822

1923
private final ObjectMapper objectMapper;
2024

21-
public HtmxHandlerMethodAnnotationHandler(ObjectMapper objectMapper) {
25+
public HtmxHandlerMethodHandler(ObjectMapper objectMapper) {
2226
this.objectMapper = objectMapper;
2327
}
2428

25-
public void handleMethod(Method method, HttpServletRequest request, HttpServletResponse response) {
29+
public void handleMethodArgument(HttpServletRequest request, HttpServletResponse response) {
30+
31+
HtmxResponse htmxResponse = RequestContextUtils.getHtmxResponse(request);
32+
if (htmxResponse != null) {
33+
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER, htmxResponse.getTriggers());
34+
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SETTLE, htmxResponse.getTriggersAfterSettle());
35+
addHxTriggerHeaders(response, HtmxResponseHeader.HX_TRIGGER_AFTER_SWAP, htmxResponse.getTriggersAfterSwap());
36+
37+
if (htmxResponse.getReplaceUrl() != null) {
38+
response.setHeader(HtmxResponseHeader.HX_REPLACE_URL.getValue(), RequestContextUtils.createUrl(request, htmxResponse.getReplaceUrl(), htmxResponse.isContextRelative()));
39+
}
40+
if (htmxResponse.getPushUrl() != null) {
41+
response.setHeader(HtmxResponseHeader.HX_PUSH_URL.getValue(), RequestContextUtils.createUrl(request, htmxResponse.getPushUrl(), htmxResponse.isContextRelative()));
42+
}
43+
if (htmxResponse.getRetarget() != null) {
44+
response.setHeader(HtmxResponseHeader.HX_RETARGET.getValue(), htmxResponse.getRetarget());
45+
}
46+
if (htmxResponse.getReselect() != null) {
47+
response.setHeader(HtmxResponseHeader.HX_RESELECT.getValue(), htmxResponse.getReselect());
48+
}
49+
if (htmxResponse.getReswap() != null) {
50+
response.setHeader(HtmxResponseHeader.HX_RESWAP.getValue(), htmxResponse.getReswap().toHeaderValue());
51+
}
52+
}
53+
}
54+
55+
public void handleMethodAnnotations(Method method, HttpServletRequest request, HttpServletResponse response) {
56+
2657
setHxLocation(request, response, method);
2758
setHxPushUrl(request, response, method);
2859
setHxRedirect(request, response, method);
@@ -36,6 +67,29 @@ public void handleMethod(Method method, HttpServletRequest request, HttpServletR
3667
setHxRefresh(response, method);
3768
}
3869

70+
private void addHxTriggerHeaders(HttpServletResponse response, HtmxResponseHeader headerName, Collection<HtmxTrigger> triggers) {
71+
if (triggers.isEmpty()) {
72+
return;
73+
}
74+
75+
// separate event names by commas if no additional details are available
76+
if (triggers.stream().allMatch(t -> t.getEventDetail() == null)) {
77+
String value = triggers.stream()
78+
.map(HtmxTrigger::getEventName)
79+
.collect(Collectors.joining(","));
80+
81+
response.setHeader(headerName.getValue(), value);
82+
return;
83+
}
84+
85+
// multiple events with or without details
86+
var triggerMap = new HashMap<String, Object>();
87+
for (HtmxTrigger trigger : triggers) {
88+
triggerMap.put(trigger.getEventName(), trigger.getEventDetail());
89+
}
90+
setHeaderJsonValue(response, headerName, triggerMap);
91+
}
92+
3993
private void setHxLocation(HttpServletRequest request, HttpServletResponse response, Method method) {
4094
HxLocation methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, HxLocation.class);
4195
if (methodAnnotation != null) {

htmx-spring-boot/src/main/java/io/github/wimdeblauwe/htmx/spring/boot/mvc/HtmxMvcAutoConfiguration.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
public class HtmxMvcAutoConfiguration implements WebMvcRegistrations, WebMvcConfigurer {
2525

2626
private final ObjectMapper objectMapper;
27-
private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler;
27+
private final HtmxHandlerMethodHandler handlerMethodHandler;
2828

2929
HtmxMvcAutoConfiguration() {
3030
this.objectMapper = JsonMapper.builder().build();
31-
this.handlerMethodAnnotationHandler = new HtmxHandlerMethodAnnotationHandler(this.objectMapper);
31+
this.handlerMethodHandler = new HtmxHandlerMethodHandler(this.objectMapper);
3232
}
3333

3434
@Override
@@ -38,7 +38,7 @@ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
3838

3939
@Override
4040
public void addInterceptors(InterceptorRegistry registry) {
41-
registry.addInterceptor(new HtmxHandlerInterceptor(objectMapper, handlerMethodAnnotationHandler));
41+
registry.addInterceptor(new HtmxHandlerInterceptor(handlerMethodHandler));
4242
}
4343

4444
@Override
@@ -54,7 +54,7 @@ public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handler
5454

5555
@Override
5656
public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
57-
return new HtmxExceptionHandlerExceptionResolver(handlerMethodAnnotationHandler);
57+
return new HtmxExceptionHandlerExceptionResolver(handlerMethodHandler);
5858
}
5959

6060
@Bean

0 commit comments

Comments
 (0)