Preface
The deployment of test , Deployment pre release , Everything is ready for testing , Go to production .
Release production
Flash back
What???
Roll back now
Start troubleshooting
Code as like as two peas at the back end. , No APP Let's talk about it . can APP There's no release on the end .
…… An investigation
Turned out to be APP End pack , Test and pre release packages Header It's all over the place
Authorization
, What's going on in productionauthorization
. It's case , Change it quickly .
official account :『 Liu Zhihang 』, Record the skills in work study 、 Development and source notes ; From time to time to share some of the life experience . You are welcome to guide !
background
The homepage interface can only be accessed by login , Because the home page to show access to user accounts some information . Here, we use unified interception , from Header In order to get token after , Use token Get user information .
And now to change to users who are not logged in, you can also view the propaganda copy in the home page information and so on , Only the account information is not displayed .
Original process
The whole process is in the code ThreadLocal Underlying principle There is an introduction in it . Part of the code is omitted here .
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
LocalUserUtils.remove();
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Does the request method have a comment
boolean assignableFrom = handler.getClass().isAssignableFrom(HandlerMethod.class);
if (!assignableFrom) {
return true;
}
CheckToken checkToken = null;
if (handler instanceof HandlerMethod) {
checkToken = ((HandlerMethod) handler).getMethodAnnotation(CheckToken.class);
}
// There is no comment Let it go
if (checkToken == null) {
return true;
}
// from Header In order to get Authorization
String authorization = request.getHeader("Authorization");
log.info("header authorization : {}", authorization);
if (StringUtils.isBlank(authorization)) {
log.error(" from Header In order to get Authorization Failure ");
throw CustomExceptionEnum.NOT_HAVE_TOKEN.throwCustomException();
}
// Other code omissions
return true;
}
}
From the code, we can see that the general process here is as follows :
- It's using interceptors to intercept requests
- If the method does not CheckToken Let's go of the notes
- Yes CheckToken annotation , From request Of header In order to get Authorization
The new demand
I just want to get rid of the annotations , It is then retrieved from the request parameters token that will do . Get the original logic , If you can't get it, you will only return the information such as propaganda copy .
from Header For information
Get a request header directly headerName
@PostMapping("/getAuthorizationByKey")
public String getAuthorizationByKey(@RequestHeader("Authorization") String authorization) {
log.info(" obtain Authorization --->{}", authorization);
return authorization;
}
Use Map Get all request headers
@PostMapping("/getAuthorizationByMap")
public String getAuthorizationByMap(@RequestHeader Map<String, String> map) {
String authorization = map.get("Authorization");
log.info(" obtain Authorization --->{}", authorization);
return authorization;
}
Use MultiValueMap Get request header
@PostMapping("/getAuthorizationByMultiValueMap")
public String getAuthorizationByMultiValueMap(@RequestHeader MultiValueMap<String, String> map) {
List<String> authorization = map.get("Authorization");
log.info(" obtain Authorization --->{}", authorization);
return "SUCCESS";
}
Use HttpHeaders Get request header
@PostMapping("/getAuthorizationByHeaders")
public String getAuthorizationByHeaders(@RequestHeader HttpHeaders headers) {
List<String> authorization = headers.get("Authorization");
log.info(" obtain Authorization --->{}", authorization);
return "SUCCESS";
}
Use HttpServletRequest obtain
@PostMapping("/getAuthorizationByServlet")
public String getAuthorizationByServlet(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
log.info(" obtain Authorization --->{}", authorization);
return authorization;
}
The test file
After testing, these are OK , Finally choose to use Map receive Header , And then from Map In order to get Authorization.
PS: There may be small partners testing, but , Find accepted header Inside name It's all lowercase , Can continue reading .
The source code is at the end of the article , You can also pay attention to the official account , send out headerName/4 obtain .
You think it's over if you get here , That's naive .
see , The scene described at the beginning of the article appears , Roll back quickly , Then check the problem , Finally, it was determined to be yes Header Of name Case problem .
reflection
- Before APP That's what Duan said , So why is it normal to use interceptors ?
- Are all the above ways the same ?
- Do not rule out tomcat Found that the original will be converted to lowercase , And why ?
Simulation investigation
Environment configuration
Simulation production first uses the same container configuration , The built-in tomcat Containers , And use undertow Containers .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Why not use interceptors to pass lowercase
- Use lowercase for modification
authorization
- debug The breakpoint
A magical scene appeared , It's actually in lowercase , however request.getHeader("Authorization"); But you can get it authorization
- F7 Keep following
io.undertow.servlet.spec.HttpServletRequestImpl#getHeader
The first 190 That's ok , from HeaderMap Gets the first element in the
io.undertow.util.HeaderMap#getFirst
The first 297 That's ok , adopt getEntry Method to get header
Keep tracking , Found in io.undertow.util.HeaderMap#getEntry(java.lang.String)
Method 77~79 We got it when we got it header Information . Then take a look at this source code .
On a closer look, it turns out that 77 That's ok final int hc = HttpString.hashCodeOf(headerName);
In obtaining name Of hashCode when , It doesn't matter whether it's case or not , It's all the same hashCode. The code is as follows
higher Method :
private static int higher(byte b) {
return b & (b >= 'a' && b <= 'z' ? 0xDF : 0xFF);
}
What does this mean
- If b Is a lowercase character
b & 0xDF
- If b Is a capital character, then
b & 0xFF
contrast ASCII surface , Difference between upper and lower case letters 32 and 0xFF(255) and 0xDF(223) The same difference 32, So the problem is fixed .header Of name Whether it's uppercase or lowercase , Will find the same value .
Of course you can do the same
So who's up there ,Header Used in name That's it. .
Use Map Why is case sensitive
Uppercase is passed in
HttpServlet
-> DispatcherServlet#doDispatch
-> AbstractHandlerMethodAdapter#handle
-> RequestMappingHandlerAdapter#handleInternal
-> RequestMappingHandlerAdapter#invokeHandlerMethod
-> ServletInvocableHandlerMethod#invokeAndHandle
-> InvocableHandlerMethod#invokeForRequest ( Parse parameter values )
-> InvocableHandlerMethod#getMethodArgumentValues
-> RequestHeaderMapMethodArgumentResolver#resolveArgument
As shown in the figure String headerName = iterator.next();
name Case sensitive LinkedHashMap in , The corresponding Controller Method .
So there's the problem I've had .
Of course, in theory APP Clients should not test and pre release with uppercase , Production uses lowercase .
The source code read above is just Spring Yes Header To deal with ,Spring stay HttpServlet
On request ,Spring No request for Header Of name Case conversion , Just getting the corresponding value When , No case sensitive fetch .
Container pair header To deal with
undertow Handling of containers
- Processing of request parameters
Here we find undertow There is no case conversion operation on the request parameters .
- from HttpServletRequest obtain header
debug It was found that io.undertow.servlet.spec.HttpServletRequestImpl#getHeader
, This process is the above investigation process .
- from Headers In order to get header
adopt debug Find out jetty It's called org.springframework.http.HttpHeaders#get
, And then call org.springframework.util.MultiValueMapAdapter#get
, And then call org.springframework.util.LinkedCaseInsensitiveMap#get
Case insensitive
- from MultiValueMap obtain header
This piece of debug The discovery was made directly from LinkedHashMap
Acquired , So it's case sensitive .
tomcat Handling of containers
- Processing of request parameters
And if not excluded , The embedded tomcat The container passes both uppercase and lowercase , All received are lowercase , What is the situation ?
adopt debug It was found that it was not excluded tomcat It uses , When receiving a request, the org.apache.coyote.http11.Http11Processor
.
stay Http11Processor#service
In the method
class 284 The bank is responsible for processing the analysis header
Get into org.apache.coyote.http11.Http11InputBuffer#parseHeaders
Method
The first 589 That's ok (Download Sources after ), read parseHeader
Method
Discovery will request header Of name Convert to lowercase
- from HttpServletRequest obtain header
When using tomcat When the container , call org.apache.catalina.connector.RequestFacade#getHeader
, org.apache.catalina.connector.Request#getHeader
, org.apache.coyote.Request#getHeader
org.apache.tomcat.util.http.MimeHeaders#getHeader
Last call org.apache.tomcat.util.http.MimeHeaders#getValue
obtain header
Case judgment is also ignored here
- from Headers obtain header
adopt debug Find out tomcat What the container calls down is org.springframework.http.HttpHeaders#get
, And then call org.springframework.util.MultiValueMapAdapter#get
, And then call org.springframework.util.LinkedCaseInsensitiveMap#get
Case insensitive
- from MultiValueMap obtain header
This piece of debug The discovery was made directly from LinkedHashMap
Acquired , So it's case sensitive .
jetty Handling of containers
- Processing of request parameters
If replaced jetty If the container
stay org.eclipse.jetty.server.HttpConnection
You will find that both uppercase and lowercase are converted to humps .
Source code can be read org.eclipse.jetty.http.HttpParser#parseFields
Will be converted to hump nomenclature .
- from HttpServletRequest obtain header
adopt debug Find out jetty It's called org.eclipse.jetty.server.Request#getHeader
jetty In obtaining header when , Would call org.eclipse.jetty.http.HttpFields#get
It turns out that the case is ignored in the acquisition
- from Headers obtain header
adopt debug Find out jetty What the container calls down is org.springframework.http.HttpHeaders#get
, And then call org.springframework.util.MultiValueMapAdapter#get
, And then call org.springframework.util.LinkedCaseInsensitiveMap#get
Case insensitive
- from MultiValueMap obtain
Also called org.springframework.util.MultiValueMapAdapter#get
Then it is case insensitive . And from the Headers Get the same in .
summary
Q&A
Q: Why interceptors get Authorization Can be case insensitive ?
A: Get from interceptor Authorization In fact, from HttpServletRequest
In order to get , No matter what you use here tomcat Or use undertow perhaps jetty obtain Header Yes, they are all ignored headerName Upper and lower case . Specific can read the above source analysis .
Q: So much to get Header What's the difference in the way you do it ?
A:
Different containers can be implemented in different ways , Here is a list
undertow | tomcat | jetty | |
---|---|---|---|
Request parameter case conversion | unchanged | A lowercase letter | hump |
Get a request header directly headerName | Ignore case , Can't be empty | Ignore case , Can't be empty | Ignore case , Can't be empty |
Use Map Get all request headers | Map Of key And incoming headerName Case consistency , Consistent access | Map Of key It's all lowercase , Lowercase is required headerName obtain | Map Of key It's the hump nomenclature , Only by naming humps can we get |
Use MultiValueMap Get request header | It's actually from LinkedHashMap In order to get , Case sensitive | It's actually from LinkedHashMap In order to get , Case sensitive | from LinkedCaseInsensitiveMap obtain , Case insensitive |
Use HttpHeaders Get request header | from LinkedCaseInsensitiveMap obtain , Case insensitive | from LinkedCaseInsensitiveMap obtain , Case insensitive | from LinkedCaseInsensitiveMap obtain , Case insensitive |
Use HttpServletRequest obtain | Use HttpString.hashCodeOf(headerName) Case ignored | call MimeHeaders#getValue Case ignored | HttpFields#get Case ignored |
Find through the table , Even if different containers are in use HttpHeaders Get the request header is called Spring Of LinkedCaseInsensitiveMap
obtain header, And case is ignored internally , It is recommended here .
Also use HttpServletRequest It's also recommended to get it .
Conclusion
This paper mainly analyzes a problem in production , And then you start to explore why , At the beginning, I found that it was Spring Why , Because use Map Reception time , headerName What is the format .
Write on your own demo I found that , Original and Spring It doesn't matter much , It's the container . Different containers are handled in different ways . So summarize the relevant articles , For your reference , deficiencies , Welcome to correct .
Related information
- This article source address :https://github.com/liuzhihang/header-demo