当前位置: 首页 > news >正文

个人简介网站怎么做北京到安阳的火车票

个人简介网站怎么做,北京到安阳的火车票,长沙网站排名优化,各种手艺培训班在上一章#xff0c;我们梳理了 URL 相关错误。实际上#xff0c;对于一个 HTTP 请求而言#xff0c;URL 固然重要#xff0c;但是为了便于用户使用#xff0c;URL 的长度有限#xff0c;所能携带的信息也因此受到了制约。 如果想提供更多的信息#xff0c;Header 往往…在上一章我们梳理了 URL 相关错误。实际上对于一个 HTTP 请求而言URL 固然重要但是为了便于用户使用URL 的长度有限所能携带的信息也因此受到了制约。 如果想提供更多的信息Header 往往是不二之举。不言而喻Header 是介于 URL 和 Body 之外的第二大重要组成它提供了更多的信息以及围绕这些信息的相关能力例如 Content-Type 指定了我们的请求或者响应的内容类型便于我们去做解码。虽然 Spring 对于 Header 的解析大体流程和 URL 相同但是 Header 本身具有自己的特点。例如Header 不像 URL 只能出现在请求中。所以Header 处理相关的错误和 URL 又不尽相同。接下来我们看看具体的案例。 案例 1接受 Header 使用错 Map 类型 在 Spring 中解析 Header 时我们在多数场合中是直接按需解析的。例如我们想使用一个名为 myHeaderName 的 Header我们会书写代码如下 RequestMapping(path /hi, method RequestMethod.GET) public String hi(RequestHeader(myHeaderName) String name){//省略 body 处理 }; 定义一个参数标记上 RequestHeader指定要解析的 Header 名即可。但是假设我们需要解析的 Header 很多时按照上面的方式很明显会使得参数越来越多。在这种情况下我们一般都会使用 Map 去把所有的 Header 都接收到然后直接对 Map 进行处理。于是我们可能会写出下面的代码 RequestMapping(path /hi1, method RequestMethod.GET) public String hi1(RequestHeader() Map map){return map.toString(); }; 粗略测试程序你会发现一切都很好。而且上面的代码也符合针对接口编程的范式即使用了 Map 这个接口类型。但是上面的接口定义在遇到下面的请求时就会超出预期。请求如下 GET http://localhost:8080/hi1 myheader: h1 myheader: h2 这里存在一个 Header 名为 myHeader不过这个 Header 有两个值。此时我们执行请求会发现返回的结果并不能将这两个值如数返回。结果示例如下 {myheaderh1, hostlocalhost:8080, connectionKeep-Alive, user-agentApache-HttpClient/4.5.12 (Java/11.0.6), accept-encodinggzip,deflate} 如何理解这个常见错误及背后原理接下来我们就具体解析下。 案例解析 实际上当我们看到这个测试结果大多数同学已经能反应过来了。对于一个多值的 Header在实践中通常有两种方式来实现一种是采用下面的方式 Key: value1,value2 而另外一种方式就是我们测试请求中的格式 Key:value1 Key:value2 对于方式 1我们使用 Map 接口自然不成问题。但是如果使用的是方式 2我们就不能拿到所有的值。这里我们可以翻阅代码查下 Map 是如何接收到所有请求的。 对于一个 Header 的解析主要有两种方式分别实现在 RequestHeaderMethodArgumentResolver 和 RequestHeaderMapMethodArgumentResolver 中它们都继承于 AbstractNamedValueMethodArgumentResolver但是应用的场景不同我们可以对比下它们的 supportsParameter()来对比它们适合的场景 在上图中左边是 RequestHeaderMapMethodArgumentResolver 的方法。通过比较可以发现对于一个标记了 RequestHeader 的参数如果它的类型是 Map则使用 RequestHeaderMapMethodArgumentResolver否则一般使用的是 RequestHeaderMethodArgumentResolver。 在我们的案例中很明显参数类型定义为 Map所以使用的自然是RequestHeaderMapMethodArgumentResolver。接下来我们继续查看它是如何解析 Header 的关键代码参考 resolveArgument() Override public Object resolveArgument(MethodParameter parameter, Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, Nullable WebDataBinderFactory binderFactory) throws Exception {Class? paramType parameter.getParameterType();if (MultiValueMap.class.isAssignableFrom(paramType)) {MultiValueMapString, String result;if (HttpHeaders.class.isAssignableFrom(paramType)) {result new HttpHeaders();}else {result new LinkedMultiValueMap();}for (IteratorString iterator webRequest.getHeaderNames(); iterator.hasNext();) {String headerName iterator.next();String[] headerValues webRequest.getHeaderValues(headerName);if (headerValues ! null) {for (String headerValue : headerValues) {result.add(headerName, headerValue);}}}return result;}else {MapString, String result new LinkedHashMap();for (IteratorString iterator webRequest.getHeaderNames(); iterator.hasNext();) {String headerName iterator.next();//只取了一个“值”String headerValue webRequest.getHeader(headerName);if (headerValue ! null) {result.put(headerName, headerValue);}}return result;} } 针对我们的案例这里并不是 MultiValueMap所以我们会走入 else 分支。这个分支首先会定义一个 LinkedHashMap然后将请求一一放置进去并返回。其中第 29 行是去解析获取 Header 值的实际调用在不同的容器下实现不同。例如在 Tomcat 容器下它的执行方法参考MimeHeaders#getValue public MessageBytes getValue(String name) {for (int i 0; i count; i) {if (headers[i].getName().equalsIgnoreCase(name)) {return headers[i].getValue();}}return null; } 当一个请求出现多个同名 Header 时我们只要匹配上任何一个即立马返回。所以在本案例中只返回了一个 Header 的值。 其实换一个角度思考这个问题毕竟前面已经定义的接收类型是 LinkedHashMap它的 Value 的泛型类型是 String也不适合去组织多个值的情况。上不管是结合代码还是常识本案例的代码都不能获取到 myHeader 的所有值。 问题修正 现在我们要修正这个问题。在案例解析部分其实我已经给出了答案。 在 RequestHeaderMapMethodArgumentResolver 的 resolveArgument() 中假设我们的参数类型是 MultiValueMap我们一般会创建一个 LinkedMultiValueMap然后使用下面的语句来获取 Header 的值并添加到 Map 中去 String[] headerValues webRequest.getHeaderValues(headerName) 参考上面的语句不用细究我们也能看出我们是可以获取多个 Header 值的。另外假设我们定义的是 HttpHeaders也是一种 MultiValueMap我们会直接创建一个 HttpHeaders 来存储所有的 Header。 有了上面的解析我们可以得出这样一个结论要完整接收到所有的 Header能直接使用 Map 而应该使用 MultiValueMap。我们可以采用以下两种方式来修正这个问题 //方式 1 RequestHeader() MultiValueMap map //方式 2 RequestHeader() HttpHeaders map 重新运行测试你会发现结果符合预期 [myheader:h1, h2, host:localhost:8080, connection:Keep-Alive, user-agent:Apache-HttpClient/4.5.12 (Java/11.0.6), accept-encoding:gzip,deflate] 对比来说方式 2 更值得推荐因为它使用了大多数人常用的 Header 获取方法例如获取 Content-Type 直接调用它的 getContentType() 即可诸如此类非常好用。 反思这个案例我们为什么会犯这种错误呢追根溯源还是在于我们很少看到一个 Header 有多个值的情况从而让我们疏忽地用错了接收类型。 案例 2错认为 Header 名称首字母可以一直忽略大小写 在 HTTP 协议中Header 的名称是无所谓大小写的。在使用各种框架构建 Web 时我们都会把这个事实铭记于心。我们可以验证下这个想法。例如我们有一个 Web 服务接口如下 RequestMapping(path /hi2, method RequestMethod.GET) public String hi2(RequestHeader(MyHeader) String myHeader){return myHeader; }; 然后我们使用下面的请求来测试这个接口是可以获取到对应的值的 GET http://localhost:8080/hi2 myheader: myheadervalue 另外结合案例 1我们知道可以使用 Map 来接收所有的 Header那么这种方式下是否也可以忽略大小写呢这里我们不妨使用下面的代码来比较下 RequestMapping(path /hi2, method RequestMethod.GET) public String hi2(RequestHeader(MyHeader) String myHeader, RequestHeader MultiValueMap map){return myHeader compare with : map.get(MyHeader); }; 再次运行之前的测试请求我们得出下面的结果 myheadervalue compare with : null 综合来看直接获取 Header 是可以忽略大小写的但是如果从接收过来的 Map 中获取 Header 是不能忽略大小写的。稍微不注意我们就很容易认为 Header 在任何情况下都可以不区分大小写来获取值。 那么针对这个案例如何去理解 案例解析 我们知道对于RequestHeader(MyHeader) String myHeader的定义Spring 使用的是 RequestHeaderMethodArgumentResolver 来做解析。解析的方法参考 RequestHeaderMethodArgumentResolver#resolveName protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {String[] headerValues request.getHeaderValues(name);if (headerValues ! null) {return (headerValues.length 1 ? headerValues[0] : headerValues);}else {return null;} } 从上述方法的关键调用request.getHeaderValues(name)去按图索骥我们可以找到查找 Header 的最根本方法即 org.apache.tomcat.util.http.ValuesEnumerator#findNext private void findNext() {nextnull;for(; pos size; pos ) {MessageBytes n1headers.getName( pos );if( n1.equalsIgnoreCase( name )) {nextheaders.getValue( pos );break;}}pos; } 在上述方法中name 即为查询的 Header 名称可以看出这里是忽略大小写的。 而如果我们用 Map 来接收所有的 Header我们来看下这个 Map 最后存取的 Header 和获取的方法有没有忽略大小写。 有了案例 1 的解析针对当前的类似案例结合具体的代码我们很容易得出下面两个结论。 1. 存取 Map 的 Header 是没有忽略大小写的 参考案例 1 解析部分贴出的代码可以看出在存取 Header 时需要的 key 是遍历 webRequest.getHeaderNames() 的返回结果。而这个方法的执行过程参考org.apache.tomcat.util.http.NamesEnumerator#findNext private void findNext() {nextnull;for(; pos size; pos ) {nextheaders.getName( pos ).toString();for( int j0; jpos ; j ) {if( headers.getName( j ).equalsIgnoreCase( next )) {// duplicate.nextnull;break;}}if( next!null ) {// its not a duplicatebreak;}}// next time findNext is called it will try the// next elementpos; } 这里返回结果并没有针对 Header 的名称做任何大小写忽略或转化工作。 2. 从 Map 中获取的 Header 也没有忽略大小写 这点可以从返回是 LinkedHashMap 类型看出LinkedHashMap 的 get() 未忽略大小写。 接下来我们看下怎么解决。 问题修正 就从接收类型 Map 中获取 Header 时注意下大小写就可以了修正代码如下 RequestMapping(path /hi2, method RequestMethod.GET) public String hi2(RequestHeader(MyHeader) String myHeader, RequestHeader MultiValueMap map){return myHeader compare with : map.get(myHeader); }; 另外你可以思考一个问题如果我们使用 HTTP Headers 来接收请求那么从它里面获取 Header 是否可以忽略大小写呢 这点你可以通过它的构造器推测出来其构造器代码如下 public HttpHeaders() {this(CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap(8, Locale.ENGLISH))); } 可以看出它使用的是 LinkedCaseInsensitiveMap而不是普通的 LinkedHashMap。所以这里是可以忽略大小写的我们不妨这样修正 RequestMapping(path /hi2, method RequestMethod.GET) public String hi2(RequestHeader(MyHeader) String myHeader, RequestHeader HttpHeaders map){return myHeader compare with : map.get(MyHeader); }; 再运行下程序结果已经符合我们的预期了 myheadervalue compare with : [myheadervalue] 通过这个案例我们可以看出在实际使用时虽然 HTTP 协议规范可以忽略大小写但是不是所有框架提供的接口方法都是可以忽略大小写的。这点你一定要注意 案例 3试图在 Controller 中随意自定义 CONTENT_TYPE 等 和开头我们提到的 Header 和 URL 不同Header 可以出现在返回中。正因为如此一些应用会试图去定制一些 Header 去处理。例如使用 Spring Boot 基于 Tomcat 内置容器的开发中存在下面这样一段代码去设置两个 Header其中一个是常用的 CONTENT_TYPE另外一个是自定义的命名为 myHeader。 RequestMapping(path /hi3, method RequestMethod.GET) public String hi3(HttpServletResponse httpServletResponse){httpServletResponse.addHeader(myheader, myheadervalue);httpServletResponse.addHeader(HttpHeaders.CONTENT_TYPE, application/json);return ok; }; 运行程序测试下访问 GET http://localhost:8080/hi3 我们会得到如下结果 GET http://localhost:8080/hi3  HTTP/1.1 200 myheader: myheadervalue Content-Type: text/plain;charsetUTF-8 Content-Length: 2 Date: Wed, 17 Mar 2021 08:59:56 GMT Keep-Alive: timeout60 Connection: keep-alive 可以看到 myHeader 设置成功了但是 Content-Type 并没有设置成我们想要的application/json而是text/plain;charsetUTF-8。为什么会出现这种错误 案例解析 首先我们来看下在 Spring Boot 使用内嵌 Tomcat 容器时尝试添加 Header 会执行哪些关键步骤。 第一步我们可以查看 org.apache.catalina.connector.Response#addHeader 方法代码如下 private void addHeader(String name, String value, Charset charset) {//省略其他非关键代码char ccname.charAt(0);if (ccC || ccc) {//判断是不是 Content-Type如果是不要把这个 Header 作为 header 添加到 org.apache.coyote.Responseif (checkSpecialHeader(name, value))return;}getCoyoteResponse().addHeader(name, value, charset); } 参考代码及注释正常添加一个 Header 是可以添加到 Header 集里面去的但是如果这是一个 Content-Type则事情会变得不一样。它并不会如此做而是去做另外一件事即通过 Response#checkSpecialHeader 的调用来设置 org.apache.coyote.Response#contentType 为 application/json关键代码如下 private boolean checkSpecialHeader(String name, String value) {if (name.equalsIgnoreCase(Content-Type)) {setContentType(value);return true;}return false; } 最终我们获取到的 Response 如下 从上图可以看出Headers 里并没有 Content-Type而我们设置的 Content-Type 已经作为 coyoteResponse 成员的值了。当然也不意味着后面一定不会返回我们可以继续跟踪后续执行。 在案例代码返回 ok 后我们需要对返回结果进行处理执行方法为 RequestResponseBodyMethodProcessor#handleReturnValue关键代码如下 Override public void handleReturnValue(Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage createInputMessage(webRequest);ServletServerHttpResponse outputMessage createOutputMessage(webRequest);//对返回值(案例中为“ok”)根据返回类型做编码转化处理writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } 而在上述代码的调用中writeWithMessageConverters 会根据返回值及类型做转化同时也会做一些额外的事情。它的一些关键实现步骤参考下面几步 1. 决定用哪一种 MediaType 返回 参考下面的关键代码 //决策返回值是何种 MediaType MediaType selectedMediaType null;MediaType contentType outputMessage.getHeaders().getContentType();boolean isContentTypePreset contentType ! null contentType.isConcrete();//如果 header 中有 contentType则用其作为选择的 selectedMediaType。if (isContentTypePreset) {selectedMediaType contentType;}//没有则根据“Accept”头、返回值等核算用哪一种else {HttpServletRequest request inputMessage.getServletRequest();ListMediaType acceptableTypes getAcceptableMediaTypes(request);ListMediaType producibleTypes getProducibleMediaTypes(request, valueType, targetType);//省略其他非关键代码 ListMediaType mediaTypesToUse new ArrayList();for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}//省略其他关键代码 for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType mediaType;break;}//省略其他关键代码 } ​这里我解释一下上述代码是先根据是否具有 Content-Type 头来决定返回的 MediaType通过前面的分析它是一种特殊的 Header在 Controller 层并没有被添加到 Header 中去所以在这里只能根据返回的类型、请求的 Accept 等信息协商出最终用哪种 MediaType。 实际上这里最终使用的是 MediaType#TEXT_PLAIN。这里还需要补充说明下没有选择 JSON 是因为在都支持的情况下TEXT_PLAIN 默认优先级更高参考代码 WebMvcConfigurationSupport#addDefaultHttpMessageConverters 可以看出转化器是有优先顺序的所以用上述代码中的 getProducibleMediaTypes() 遍历 Converter 来收集可用 MediaType 也是有顺序的。 2. 选择消息转化器并完成转化 决定完 MediaType 信息后即可去选择转化器并执行转化关键代码如下 for (HttpMessageConverter? converter : this.messageConverters) {GenericHttpMessageConverter genericConverter (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter?) converter : null);if (genericConverter ! null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {//省略其他非关键代码if (body ! null) {//省略其他非关键代码if (genericConverter ! null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}//省略其他非关键代码} } 如代码所示即结合 targetTypeString、valueTypeString、selectedMediaTypeMediaType#TEXT_PLAIN三个信息来决策可以使用哪种消息 Converter。常见候选 Converter 可以参考下图 最终本案例选择的是 StringHttpMessageConverter在最终调用父类方法 AbstractHttpMessageConverter#write 执行转化时会尝试添加 Content-Type。具体代码参考 AbstractHttpMessageConverter#addDefaultHeaders protected void addDefaultHeaders(HttpHeaders headers, T t, Nullable MediaType contentType) throws IOException {if (headers.getContentType() null) {MediaType contentTypeToUse contentType;if (contentType null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {contentTypeToUse getDefaultContentType(t);}else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {MediaType mediaType getDefaultContentType(t);contentTypeToUse (mediaType ! null ? mediaType : contentTypeToUse);}if (contentTypeToUse ! null) {if (contentTypeToUse.getCharset() null) {//尝试添加字符集Charset defaultCharset getDefaultCharset();if (defaultCharset ! null) {contentTypeToUse new MediaType(contentTypeToUse, defaultCharset);}}headers.setContentType(contentTypeToUse);}}//省略其他非关键代码 } 结合案例参考代码我们可以看出我们使用的是 MediaType#TEXT_PLAIN 作为 Content-Type 的 Header毕竟之前我们添加 Content-Type 这个 Header 并没有成功。最终运行结果也就不出意外了即Content-Type: text/plain;charsetUTF-8。 通过案例分析可以总结出虽然我们在 Controller 设置了 Content-Type但是它是一种特殊的 Header所以在 Spring Boot 基于内嵌 Tomcat 开发时并不一定能设置成功最终返回的 Content-Type 是根据实际的返回值及类型等多个因素来决定的。 问题修正 针对这个问题如果想设置成功我们就必须让其真正的返回就是 JSON 类型这样才能刚好生效。而且从上面的分析也可以看出返回符合预期也并非是在 Controller 设置的功劳。不过围绕目标我们也可以这样去修改下 1. 修改请求中的 Accept 头约束返回类型 参考代码如下 GET http://localhost:8080/hi3 Accept:application/json 即带上 Accept 头这样服务器在最终决定 MediaType 时会选择 Accept 的值。具体执行可参考方法 AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes。 2. 标记返回类型 主动显式指明类型修改方法如下 RequestMapping(path /hi3, method RequestMethod.GET, produces {application/json}) 即使用 produces 属性来指明即可。这样的方式影响的是可以返回的 Media 类型一旦设置下面的方法就可以只返回一个指明的类型了。参考 AbstractMessageConverterMethodProcessor#getProducibleMediaTypes protected ListMediaType getProducibleMediaTypes(HttpServletRequest request, Class? valueClass, Nullable Type targetType) {SetMediaType mediaTypes (SetMediaType) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (!CollectionUtils.isEmpty(mediaTypes)) {return new ArrayList(mediaTypes);}//省略其他非关键代码 } 上述两种方式一个修改了 getAcceptableMediaTypes 返回值一个修改了 getProducibleMediaTypes这样就可以控制最终协商的结果为 JSON 了。从而影响后续的执行结果。 不过这里需要额外注意的是虽然我们最终结果返回的 Content-Type 头是 JSON 了但是对于内容的加工仍然采用的是 StringHttpMessageConverter感兴趣的话你可以自己去研究下原因。 重点回顾 通过这节课的学习我们了解到了在 Spring 解析 Header 中的一些常见错误及其背后的深层原因。这里带你回顾下重点 1. 要完整接收到所有的 Header不能直接使用 Map 而应该使用 MultiValueMap。常见的两种方式如下 //方式 1 RequestHeader() MultiValueMap map //方式 2专用于Header的MultiValueMap子类型 RequestHeader() HttpHeaders map 深究原因Spring 在底层解析 Header 时如果接收参数是 Map则当请求的 Header 是多 Value 时只存下了其中一个 Value。 2.在 HTTP 协议规定中Header 的名称是无所谓大小写的。但是这并不意味着所有能获取到 Header 的途径最终得到的 Header 名称都是统一大小写的。 3.不是所有的 Header 在响应中都能随意指定虽然表面看起来能生效但是最后返回给客户端的仍然不是你指定的值。例如在 Tomcat 下CONTENT_TYPE 这个 Header 就是这种情况。 以上即为这一讲的核心知识点希望你以后在解析 Header 时会更有信心。
http://www.zqtcl.cn/news/35406/

相关文章:

  • 做网站店铺怎样打理WordPress怎么输入代码
  • 顺德公益网站制作网站总是跳转dede58
  • 江西医疗网站备案前置审批每月网站流量
  • 网站建设花多少钱西安知名网站开发的公司
  • 网站内链结构是什么怎么办
  • 无锡模板网站wordpress压缩图片
  • 国外企业招聘网站wordpress emoji表情
  • python 做网站我们是谁 网站运营
  • 企业百度网站怎么做中国移动手机支付网站
  • 成都网站建设推荐安徽秒搜科技相亲网站做期货现货贵金属的人
  • 嘉兴公司的网站设计网站不备案会怎...
  • 怎么制作钓鱼网站链接建设网站的相关技术指标
  • 模板手机网站建设公司排名如何制作手机免费网站模板下载
  • 广告网站定制企业网站只做英文
  • 网站关键字怎么做在线做原型的网站
  • 静态动漫网站模板企业网站的缺点
  • 个人网站开发工具品牌建设有待加强
  • 没有网站怎么做推广me域名公司网站
  • 扬州市住房和建设局网站网站 权限
  • 北京微信网站搭建多少钱工具网站有哪些
  • 做ppt的网站网站建站销售提成
  • 网站做好后怎么做seo广告推广策划方案
  • php网站建设案例教程视频专门做顶账房的网站
  • 网站主机服务器郴州网站优化
  • 李宁运动服网站建设规划书网站建设秋实
  • 聊城做网站推广网站推广优化网址
  • 网站开发树形图外网建筑设计网站
  • 秦皇岛做网站的公司宏杰zkeys网站模板
  • 做一个网站要注意什么哈尔滨公共资源交易网建设工程
  • 做网站别名解析的目的是什么wordpress管理后台添加导航栏