Problem Summary:
In my Spring Boot application, I'm facing an issue where fetch requests to 127.0.0.1 via JavaScript within a returned view are not behaving as expected in Chrome. The second fetch request should return a 304 status due to ETag matching, but it returns 200 instead. This issue does not occur when using the browser URL directly or with the localhost domain.
Detailed Description:
Context: My application uses ETag headers for caching responses. A 304 response is expected on the second request if the content hasn't changed. Specific Issue:
When I make a fetch request to 127.0.0.1 from JavaScript code within a Spring Boot returned view, the second request in Chrome incorrectly returns a 200 status, not the expected 304.
However, when the same URL (127.0.0.1) is entered directly in the browser, it behaves correctly, returning a 304 on the second request.
Requests to localhost work as expected, returning a 304 status on the second request in all scenarios.
This issue seems to be specific to Chrome; other browsers (Firefox, Safari) return the correct 304 status for both localhost and 127.0.0.1.
What I've Tried: Investigating the difference between localhost and 127.0.0.1, especially regarding how Chrome handles these domains.
Searching for Chrome-specific behaviors or settings that might affect how fetch requests are handled within JavaScript.
Questions:
Why does Chrome return a 200 status for fetch requests to 127.0.0.1 within a Spring Boot returned view, despite the server returning 304?
What is the significant difference in handling between direct browser URL entry and JavaScript fetch requests in Chrome?
How can I ensure consistent ETag-based response handling across different domains and request methods in Chrome?
Additional Context:
The issue is observed in a Spring Boot application, with JavaScript fetch requests within a returned view. I am experiencing this in Chrome version 120.0.6099.10 (arm64) Here is a snippet of the JavaScript code making the fetch request:
document.addEventListener('DOMContentLoaded', (event) => {
fetchData(1);
fetch("http://127.0.0.1:8080/api/templates/208")
.then(res => res.json())
.then(body => console.log(body));
});
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
chain.doFilter(request, response);
}
@Override
@Transactional(readOnly = true)
@Cacheable(value = "mockupTemplate", key = "#id")
public MockupTemplateReadResponse findOne(long id) {
return customRepository.getOne(id).orElseThrow(() -> new IllegalArgumentException("Id: " + id + " 는 존재하지 않는 아이디 입니다."));
}
@GetMapping("/{id}")
public ResponseEntity<MockupTemplateReadResponse> findMockupTemplate(@PathVariable Long id,
@RequestHeader(name = "If-None-Match", required = false) Optional<String> ifNoneMatch) {
MockupTemplateReadResponse response = service.findOne(id);
String eTag = response.getUpdateTime().toString();
if (ifNoneMatch.isPresent() && eTag.equals(ifNoneMatch.get())) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
return ResponseEntity.ok().eTag(eTag).body(response);
}