首页 > 解决方案 > Spring HttpHeaders 中可能存在的错误

问题描述

我发现我认为可能是 Spring 类HttpHeadersReadOnlyHttpHeaders. 在使用 Spring 提出 Jira 缺陷之前,我想确认这一点。这是我用来创建空HttpHeaders对象的代码片段:

HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);

然后我使用以下方法将标题添加到我的新对象:

myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip")

在这HttpHeaders.EMPTY不再是空的之后

HttpHeaders.EMPTY.size() == 1

HttpHeaders.EMPTY 的 javadoc 指出:

/**
* 空的 {@code HttpHeaders} 实例(不可变)。
*/
public static final HttpHeaders EMPTY

这里的问题是,当 'HttpHeaders.EMPTY' 在其他地方使用时,它会引入意外的标头。

考虑以下单元测试:

@Test
public void testUpdateEmptyHeaders() {
    assertEquals(0, HttpHeaders.EMPTY.size()); // **Success**
    HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY);
    myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
    assertEquals(0, HttpHeaders.EMPTY.size()); // **Assert Fails**
}

@Test
// This test will fail if run after the test above, but will be successful if run by itself
public void testEmptyHeaders() {
    assertEquals(0, HttpHeaders.EMPTY.size()); 
}

以下是单元测试的结果:

// testUpdateEmptyHeaders
08:39:28.450 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@2e222612, testMethod = testUpdateEmptyHeaders@AuditContextTest, testException = java.lang.AssertionError: expected:<0> but was:<1>

java.lang.AssertionError: 
Expected :0
Actual   :1

// testEmptyHeaders
08:39:28.482 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@2e222612, testMethod = testEmptyHeaders@AuditContextTest, testException = java.lang.AssertionError: expected:<0> but was:<1>

java.lang.AssertionError: 
Expected :0
Actual   :1

我觉得这是一个错误,因为HttpHeaders.EMPTY应该是不可变的。我还可以通过在 SpringHttpHeaders.javaReadOnlyHttpHeaders.java

标签: spring

解决方案


Yup you are right this might be bug for spring framework HttpHeaders

public static final HttpHeaders EMPTY

HttpHeaders.EMPTY This will return empty HttpHeaders instance (immutable). (and it is singleton)

Case :1 Let's take a look at HttpHeaders.Empty, which returns immutable object

    HttpHeaders head = HttpHeaders.EMPTY;
    
    System.out.println(System.identityHashCode(head));  //1338668845
    
    System.out.println(head.size());                    //0
    
    HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY); 
    
    System.out.println(System.identityHashCode(myHeaders));  //159413332
    
    myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip"); 
    
    head = HttpHeaders.EMPTY; 
    System.out.println(System.identityHashCode(head));       //1338668845
    System.out.println(head.size());                    //1
    System.out.println(head);                            //{Accept-Encoding=[gzip]}
    
    HttpHeaders head1 = HttpHeaders.EMPTY;
    System.out.println(head1);                          //{Accept-Encoding=[gzip]}
    System.out.println(System.identityHashCode(head1));   //1338668845

Conclusions :

1 : HttpHeaders.EMPTY is always returning the singleton object

2: The problem is when HttpHeaders.EMPTY is passed to writableHttpHeaders method internally the returned object is having relation with HttpHeaders.EMPTY singleton object, look at case 2

Case :2 return object fromwritableHttpHeaders reflects to HttpHeaders.EMPTY singleton object (internally and indirectly)

    HttpHeaders head = HttpHeaders.EMPTY;           
    
    System.out.println(System.identityHashCode(head));  //1338668845
    
    System.out.println(head.size());                //0
    
    HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY); 
    
    System.out.println(System.identityHashCode(myHeaders));  //159413332
    
    myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip"); 
    
    myHeaders.add("hello", "value");
    
    head = HttpHeaders.EMPTY; 
    System.out.println(System.identityHashCode(head));  //1338668845
    System.out.println(head.size());              //2
    System.out.println(head);                     //{Accept-Encoding=[gzip], hello=[value]}
    
    HttpHeaders head1 = HttpHeaders.EMPTY;
    System.out.println(head1);                    //{Accept-Encoding=[gzip], hello=[value]}
    System.out.println(System.identityHashCode(head1));   //1338668845
    
    myHeaders.remove("hello");
    
    System.out.println(System.identityHashCode(head));     //1338668845
    System.out.println(head.size());                 //1
    System.out.println(head);                       //{Accept-Encoding=[gzip]}
    
    System.out.println(head1);                        //{Accept-Encoding=[gzip]}
    System.out.println(System.identityHashCode(head1));    //1338668845
    

Conclusion :

1 : add and remove operations performed on myHeaders object are reflecting to HttpHeaders.EMPTY object

Case : 3 Suppose if we pass empty instance of the HttpHeaders object to writableHttpHeaders using constructor then there is no issue everything works pretty clear

    HttpHeaders head = HttpHeaders.EMPTY;
    
    System.out.println(System.identityHashCode(head));       //1338668845
    
    System.out.println(head.size());                     //0
    
    HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(new HttpHeaders()); 
    
    System.out.println(System.identityHashCode(myHeaders));       //1323165413
    
    myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip"); 
    
    myHeaders.add("hello", "value");
    
    head = HttpHeaders.EMPTY; 
    System.out.println(System.identityHashCode(head));           //1338668845
    System.out.println(head.size());                    //0
    System.out.println(head);                           //{}
    
    HttpHeaders head1 = HttpHeaders.EMPTY;
    System.out.println(head1);                           //{}
    System.out.println(System.identityHashCode(head1));   //1338668845

Case : 4 Even though indirectly immutable HttPHeaders.EMPTY can be modified, but still it throws an error if you try modifying directly

    HttpHeaders head = HttpHeaders.EMPTY;
    
    System.out.println(System.identityHashCode(head));
    
    System.out.println(head.size());
    
    HttpHeaders myHeaders = HttpHeaders.writableHttpHeaders(HttpHeaders.EMPTY); 
    
    System.out.println(System.identityHashCode(myHeaders));
    
    myHeaders.add(HttpHeaders.ACCEPT_ENCODING, "gzip"); 
    
    head = HttpHeaders.EMPTY; 
    System.out.println(System.identityHashCode(head));
    System.out.println(head.size());
    System.out.println(head);
    
    head.add("hello", "value");

Output :

1338668845
0
159413332
1338668845
1
{Accept-Encoding=[gzip]}
Exception in thread "main" java.lang.UnsupportedOperationException
at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:67)
at com.demo.NestedJsonParse.main(NestedJsonParse.java:40)

Final Conclusion : Yes it is bug you can raise a bug for spring projects spring-bug, immutable objects cannot change the state


推荐阅读