Skip to content

Conversation

@hannahbast
Copy link
Member

@hannahbast hannahbast commented Oct 15, 2025

There is now a runtime parameter service-max-redirects that limits the number of redirects that will be followed for a single SERVICE request. The default value is 1, that is, at most one redirect will be followed. To disable following redirects altogether, set the value to 0. In particular, this fixes the problem discussed in #2434

There is now a runtime parameter `service-max-redirects` (default: 1)
that limits the number of redirects that will be followed for a single
`SERVICE` request. To disable following redirects, set the parameter to
0.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for following HTTP redirects in SERVICE requests by introducing a configurable maximum redirect limit. The implementation handles redirect status codes (301, 302, 307, 308) and follows the Location header up to a specified limit.

Key changes:

  • Added service-max-redirects runtime parameter (default: 1) to control redirect behavior
  • Extended HTTP client response structure to include Location header
  • Implemented redirect following logic in Service.cpp with proper error handling

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/global/RuntimeParameters.h Added serviceMaxRedirects_ parameter declaration
src/global/RuntimeParameters.cpp Registered the new runtime parameter
src/util/http/HttpClient.h Added location_ field to HttpOrHttpsResponse struct
src/util/http/HttpClient.cpp Extract Location header from HTTP response
src/engine/Service.cpp Implemented redirect following logic with configurable limits
test/util/HttpClientTestHelpers.h Updated test helper to support location parameter
test/ServiceTest.cpp Removed trailing periods from error messages

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

const size_t maxRedirects =
getRuntimeParameter<&RuntimeParameters::serviceMaxRedirects_>();
size_t redirectCount = 0;
while (redirectCount <= maxRedirects) {
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop condition allows one more redirect than intended. When maxRedirects is 1, this allows 2 redirects (when redirectCount is 0 and 1). Change to redirectCount < maxRedirects to respect the exact limit.

Suggested change
while (redirectCount <= maxRedirects) {
while (redirectCount < maxRedirects) {

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first request is not a redirect. The second request is the first redirect. And so on ...


// If the loop exited because we exceeded the maximum number of redirects,
// throw a corresponding error.
if (redirectCount > maxRedirects) {
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition will never be true due to the loop condition above. The loop exits when redirectCount <= maxRedirects is false, meaning redirectCount equals maxRedirects + 1. Change to redirectCount == maxRedirects + 1 or adjust the loop condition.

Suggested change
if (redirectCount > maxRedirects) {
if (redirectCount == maxRedirects + 1) {

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not true, this condition is only true when there are redirects in every iteration, until the while condition is violated

@codecov
Copy link

codecov bot commented Oct 15, 2025

Codecov Report

❌ Patch coverage is 91.30435% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.45%. Comparing base (91e7da9) to head (4e4cb79).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/util/http/HttpClient.cpp 89.74% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2435   +/-   ##
=======================================
  Coverage   91.45%   91.45%           
=======================================
  Files         462      462           
  Lines       46907    46925   +18     
  Branches     5240     5247    +7     
=======================================
+ Hits        42899    42916   +17     
  Misses       2511     2511           
- Partials     1497     1498    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Collaborator

@RobinTF RobinTF left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Service.cpp is the right place to add this functionality. This should be moved to HttpClient.cpp where HttpClientImpl<T>::sendRequest gets another parameter that allows it to follow redirects. This way different parts of the code could benefit from this behaviour.

@hannahbast
Copy link
Member Author

I don't think Service.cpp is the right place to add this functionality. This should be moved to HttpClient.cpp where HttpClientImpl<T>::sendRequest gets another parameter that allows it to follow redirects. This way different parts of the code could benefit from this behaviour.

Good point, I will try this

std::string_view target, ad_utility::SharedCancellationHandle handle,
std::string_view requestBody, std::string_view contentTypeHeader,
std::string_view acceptHeader) {
std::string_view acceptHeader, [[maybe_unused]] size_t maxRedirects) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is unused this can be removed.


// If it is a redirect, but there is no `Location` header, we cannot
// proceed and throw an error.
if (response.location_.empty()) {
Copy link
Collaborator

@RobinTF RobinTF Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should comment that we explicitly don't want to change the request method here, even if browser would do it for 301 and 302.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinTF I don't understand this comment, please explain

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have a look at the notes on https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301 and https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302 .
Changing the request type to GET for 301 and 302 is technically allowed and is the standard behaviour for browsers, but this is of course not what we want here. So we should make it explicit that we just follow location headers and don't change behaviour based on status codes (technically the permanent redirects should be stored somewhere to avoid the extra step, but that's completely overkill for what we need here).

Copy link
Member Author

@hannahbast hannahbast Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinTF By "make it explicit" you mean: Write it in the comment and the description? Or check it in the code and throw (or ignore) if it happens?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment should suffice

std::string finalUrl =
absl::StrCat("http://localhost:", okServer.getPort());

// Create `redirectServer` that points to `okServer`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating multiple servers that all run in their own threads. Couldn't you just create a single server, that returns a status code based on GET parameters? Like http://localhost/301?redirect=/200 conceptually? This way you can easily chain them by extending the parameters without having to write much more code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinTF Is this important? It's just a test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not important from a functional perspective, but it avoids the overhead of starting and stopping servers which AFAIK takes rather long for a unit test.
Also I believe it would reduce the amount of code in the test.

Copy link
Member Author

@hannahbast hannahbast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinTF Thank your for your comments. I have addressed some of them, I am not sure about the others, see my comments there. Can I merge this now, once everything is green?

PS: Before my last changes, the partial test coverage of 90% in HttpTest was due to a branch that was already untested before.

@sparql-conformance
Copy link

Overview

Number of Tests Passed ✅ Failed ❌ Intended ⚠️ Not tested
522 439 16 67 0

Conformance check passed ✅

No test result changes.

Details: https://qlever.dev/sparql-conformance-ui?cur=4e4cb79f8c8520e082a68562d9d20f431627e06f&prev=cba5c74fc0f3c411929d1d02063885cf77f8c0af

@sonarqubecloud
Copy link

Copy link
Collaborator

@RobinTF RobinTF left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with your proposals, but I just realized that we should properly handle relative URLs, otherwise we end up having to deal with this issue again in the future.

}

// Follow the redirect.
currentUrl = Url{response.location_};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to consider still:
The Location header can be a relative URL. In that case it needs to be resolved relative to the previous URL, which is not the case here, so this currently might break on some redirects that don't post the whole URL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobinTF Yeah, I had it in the code, than removed it again, but should include it again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants