Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpHeaders constructor should not be public or should not mutate original HttpHeaders #28336

Closed
ryoheinagao opened this issue Apr 14, 2022 · 4 comments
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply

Comments

@ryoheinagao
Copy link

Problem

  1. When I use HttpHeaders, its constructors confuse me, because instance initialized from original header has same hashmap of original. For example, test code below will be failed. Calling a method of copy instance may affect original instance.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;

public class HeaderTest {

    @Test
    void test() {
        var original = new HttpHeaders();
        var copy = new HttpHeaders(original);

        copy.add("key", "value");

        Assertions.assertFalse(original.containsKey("key"));
    }
}
  1. The javadoc says that the constructor I mentioned is for internal use in the spring fremework, but users can access it.
    /**
    * Construct a new, empty instance of the {@code HttpHeaders} object.
    * <p>This is the common constructor, using a case-insensitive map structure.
    */
    public HttpHeaders() {
    this(CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)));
    }
    /**
    * Construct a new {@code HttpHeaders} instance backed by an existing map.
    * <p>This constructor is available as an optimization for adapting to existing
    * headers map structures, primarily for internal use within the framework.
    * @param headers the headers map (expected to operate with case-insensitive keys)
    * @since 5.1
    */
    public HttpHeaders(MultiValueMap<String, String> headers) {
    Assert.notNull(headers, "MultiValueMap must not be null");
    this.headers = headers;
    }

Suggetion

So I suggest this confusional constructor is package-private not to be accessed from external the library or support immutableness of its hashmap field.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 14, 2022
@sbrannen
Copy link
Member

Hi @ryoheinagao,

Thanks for opening your first issue for the Spring Framework.

What is your concrete use case for creating an instance of HttpHeaders from an existing instance of HttpHeaders and not wanting modifications to write through to the original instance?

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue in: web Issues in web modules (web, webmvc, webflux, websocket) labels Apr 14, 2022
@sbrannen sbrannen changed the title HttpHeaders's constructor should prohibit accessed from external or be immutable HttpHeaders copy constructor should not be public or should be immutable Apr 14, 2022
@sbrannen sbrannen changed the title HttpHeaders copy constructor should not be public or should be immutable HttpHeaders constructor should not be public or should not mutate original HttpHeaders Apr 14, 2022
@ryoheinagao
Copy link
Author

ryoheinagao commented Apr 18, 2022

@sbrannen Thank you for your comment.

For example, most of header's value are constant but some value(secret token or user's location info) are vary in each request. And constant pairs are common among multiple implementation classes or methods. In this case, I want to create an instance from constant header, and then I want to add these temporary key-value. The pseudo code is below.

In the case of sharing key-value between implementation class or there are many constant value, I think instantiation from existing HttpHeaders is effective.

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

public class SomeRepository {

    private static final HttpHeaders STATIC_HEADER = new HttpHeaders();

    static {
        STATIC_HEADER.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
        // please assume this header has many key-value pairs and is shared among methods or classes
    }

    String findSomePrivateValue(String userToken,){
        var header = new HttpHeaders(STATIC_HEADER);
        header.add("X-Secret", userToken);
        // rest operations etc...
    }
    
    String findUserAround(String userLocation){
        var header = new HttpHeaders(STATIC_HEADER);
        header.add("X-Location", userLocation);
        // rest operations etc...
    }
}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Apr 18, 2022
@ryoheinagao
Copy link
Author

What do you think my suggestion?

@bclozel
Copy link
Member

bclozel commented Jul 17, 2023

Thanks for the suggestion, but the behavior of that constructor is well documented and well defined. Unlike collection-related constructors (like HashMap or ArrayList), this is not about copying entries to a new collection, but creating a new HttpHeaders instance backed by an existing map. Making that method package private will break existing integrations it's made for (not all live in the same package).

I'm declining this issue as a result.

@bclozel bclozel closed this as not planned Won't fix, can't repro, duplicate, stale Jul 17, 2023
@bclozel bclozel added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

4 participants