Web开发方面、专注于Web
5月16
我正在使用旧版Spring应用程序,并且想要迁移到Spring Boot。我的意图是使用spring-boot-starter-data- jpa。因此,我在pom.xml(管理所有spring-boot-dependencies)中添加了以下部分:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
</parent>
但这搞砸了我暂时需要保留的某些依赖项。我目前正在使用Selenium依赖项(版本2.53.0;从另一个项目中过渡添加),但是spring-
boot正在获取的依赖项3.9.1。

我想排除3.9.1依赖关系,但exclusion过滤器无法按预期工作。

不能乱<excludes>然后尝试弄清楚您需要再次包含什么(在弄清楚要排除的内容之后)。只需覆盖《Spring
Boot参考指南》中此处解释的版本即可。

假设您将spring-boot-starter- parent用作父项,则只需将a添加<selenium.version>到您的<properties>部分中即可指定所需的版本。

<properties>
  <selenium.version>2.53.0</selenium.version>
</properties>
这将使Spring Boot使用所需的版本。
1月2
在SpringBoot中使用单元测试时,出现以下报错,意思是创建名为‘serverEndpointExporter’的bean时出错。
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
[20240102 14:42:43][main][ERROR][org.springframework.boot.SpringApplication:854] Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/nine/rivers/bms/system/config/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:765)
  at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)
  at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:121)
  at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
  at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
  at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
  at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
  at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
  at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
  at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:350)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:355)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$7(ClassBasedTestDescriptor.java:350)
  at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
  at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
  at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
  at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
  at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
  at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
  at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743)
  at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:742)
  at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:349)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$4(ClassBasedTestDescriptor.java:270)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:269)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
  at java.util.Optional.orElseGet(Optional.java:267)
  at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
  at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
  at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
  at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
  at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
  at java.util.ArrayList.forEach(ArrayList.java:1259)

问题分析
通过报错提示可以看出,在创建命名为 ‘serverEndpointExporter’ 的bean时出错,而这个bean是webscoket中的,而webscoket是需要依赖tomcat等容器才能启动的,恰巧运行单元测试不需要依赖tomcat容器,两者互相矛盾这就是出现该问题的原因了。

问题解决
使用tomcat容器运行单元测试,在注解 @SpringbootTest 中追加以下内容即可:
@SpringbootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
12月25
前后端通过websocket通信,但是需要鉴权,可以利用websocket的子协议来传输token,代码如下:
var ws = new WebSocket("ws://localhost/ws",[token]);
在后面加上[这里是token内容]。这样请求头就会携带:
Sec-WebSocket-Protocol:token value
参数是一个数组。如果数组有多个值,后端也会接受到逗号隔开的值。
后端可以获取这个token进行鉴权。
重点来了!
如果传递了token参数,后端响应的时候,也必须带上这个token响应!否则前端接收不到数据!
我竟然忘记前端传过来的token是加密后的,首先解密后去校验,通过后再把原加密token放在响应头即可, 如果不一致也会导致前端接收不到消息。
如下是java的例子“:
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setHeader("Sec-WebSocket-Protocol", token);
12月19
咱话不多说,正如标题所见,实践开始。

首先打开cmd,win键+r打开运行,输入cmd打开控制台。

在控制台中输入msg / server:IP地址 * "要发送的内容。"   注:(这里的引号要加,且*号左右必须空一格)

例如:我的ipv4地址是192.168.90.1**    注:(用ipconfig命令可查)
7月17
Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹下面。由于临时 /tmp 目录下的文件,在长时间(10天)没有使用的情况下,系统执行了 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow...8090 文件被清理,然而在上传的时候,undertow 服务器需要创建/tmp/undertow...8090/undertow...upload 临时文件,但是调用 Files.createFile(...) 的时候就会发现找不到父目录,才导致了以上的错误。

具体错误日志(参考)
undertow

java.nio.file.NoSuchFileException: /tmp/undertow.17753558642503713859.8085/undertow7370242804103803588upload

Tomcat

The temporary upload location [/tmp/tomcat.7957874575370093230.8088/work/Tomcat/localhost/ROOT] is not valid

重现方法
找到类 io.undertow.server.handlers.form.MultiPartParserDefinition
定位到如下代码

@Override
public void beginPart(final HeaderMap headers) {
    this.currentFileSize = 0;
    this.headers = headers;
    final String disposition = headers.getFirst(Headers.CONTENT_DISPOSITION);
    if (disposition != null) {
        if (disposition.startsWith("form-data")) {
            currentName = Headers.extractQuotedValueFromHeader(disposition, "name");
            fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename");
            if (fileName != null && fileSizeThreshold == 0) {
                try {
                    if (tempFileLocation != null) {
                        file = Files.createTempFile(tempFileLocation, "undertow", "upload");
                    } else {
                        file = Files.createTempFile("undertow", "upload");
                    }
                    createdFiles.add(file);
                    fileChannel = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

在 createdFiles.add(file); 处打断点,复制file的 path 的值找到该文件并将其删除;放开断点,错误重现;
6月30
在做javaweb项目时,关于统一错误页面在开发的过程中就做过编码,并且一直都很有效,像500,404,403等常规错误码都能得到有效处理,但是400却不行,而且还暴露tomcat的版本信息,这是很严重的安全漏洞
解决方法百度和测试很多,总结如下:

问题产生:
根据rfc规范,url中不允许有 |,{,}等特殊字符,但在实际生产中还是有些url有可能携带有这些字符,特别是|还是较为常见的。在tomcat升级到7以后,对url字符的检查都变严格了,如果出现这类字符,服务器tomcat将直接返回400状态码。

方法一:降低tomcat的版本
开发人员增加一项设置,允许配置在url可以出现的特殊字符,但也仅限于|,{,}三种,见:http://tomcat.apache.org/tomcat-8.0-doc/config/systemprops.html#Other
该项设置在以下版本的tomcat中有效:

- 8.5.x for 8.5.12 onwards

- 8.0.x for 8.0.42 onwards

- 7.0.x for 7.0.76 onwards
- 这个只是允许出现一些特殊字符,并没有说是全部特殊字符。
好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符.否则会报400错误,
tomcat请求中包含特殊字符 [] | {} 发送get请求失败:
原因:
这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有"[]"、""不在RFC3986中的保留字段中,所以会报这个错。

方法二:修改server.xml
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;" redirectPort="8443" />
方法三:修改server.xml(推荐方法)
在Host里加入:
<Valve className="org.apache.catalina.valves.ErrorReportValve"  showReport="false" showServerInfo="false" />
方法三 修改后只会显示出HTTP 400 错误,不会打印堆栈和tomcat版本号信息
2月22

nginx目录穿越漏洞

10:59Web开发  From: 本站原创
漏洞原理
nginx在配置别名(Alias)的时候忘记加/,则可能实现目录穿越
配置中设置目录别名时/files配置为/home/的别名,那么当我们访问
/files…/时,nginx实际处理的路径时/home/…/,从而实现了穿越目录

location /file{
autoindex on;
alias /home/;
}

所以修复这个漏洞也就是将上面的/file/闭合成这样,就不支持../这些回上一层目录的操作了。
12月1

浅谈HTTP中Get与Post的区别

15:57Web开发  From: 本站原创

Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的4个操作。到这里,大家应该有个大概的了解了,GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。

  1.根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的

  (1).所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。

  * 注意:这里安全的含义仅仅是指是非修改信息。

  (2).幂等的意味着对同一URL的多个请求应该返回同样的结果。这里我再解释一下幂等这个概念:

幂等(idempotent、idempotence)是一个数学或计算机学概念,常见于抽象代数中。
  幂等有一下几种定义:
  对于单目运算,如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。比如绝对值运算就是一个例子,在实数集中,有abs(a)
=abs(abs(a))
  对于双目运算,则要求当参与运算的两个值是等值的情况下,如果满足运算结果与参与运算的两个值相等,则称该运算幂等,如求两个数的最大值的函数,有在在实数集中幂等,即max(x,x) 
= x

看完上述解释后,应该可以理解GET幂等的含义了。

  但在实际应用中,以上2条规定并没有这么严格。引用别人文章的例子:比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。从根本上说,如果目标是当用户打开一个链接时,他可以确信从自身的角度来看没有改变资源即可。

  2.根据HTTP规范,POST表示可能修改变服务器上的资源的请求。继续引用上面的例子:还是新闻以网站为例,读者对新闻发表自己的评论应该通过POST实现,因为在评论提交后站点的资源已经不同了,或者说资源被修改了。

 

  上面大概说了一下HTTP规范中GET和POST的一些原理性的问题。但在实际的做的时候,很多人却没有按照HTTP规范去做,导致这个问题的原因有很多,比如说:

  1.很多人贪方便,更新资源时用了GET,因为用POST必须要到FORM(表单),这样会麻烦一点。

  2.对资源的增,删,改,查操作,其实都可以通过GET/POST完成,不需要用到PUT和DELETE。

  3.另外一个是,早期的Web MVC框架设计者们并没有有意识地将URL当作抽象的资源来看待和设计,所以导致一个比较严重的问题是传统的Web MVC框架基本上都只支持GET和POST两种HTTP方法,而不支持PUT和DELETE方法。

   * 简单解释一下MVC:MVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

  以上3点典型地描述了老一套的风格(没有严格遵守HTTP规范),随着架构的发展,现在出现REST(Representational State Transfer),一套支持HTTP规范的新风格,这里不多说了,可以参考《RESTful Web Services》。

 

  说完原理性的问题,我们再从表面现像上面看看GET和POST的区别

  1.GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

  POST把提交的数据则放置在是HTTP包的包体中。

  2."GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据,IIS4中最大为80KB,IIS5中为100KB"??!

  以上这句是我从其他文章转过来的,其实这样说是错误的,不准确的:

  (1).首先是"GET方式提交的数据最多只能是1024字节",因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。

  注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。[见参考资料5]

  (2).理论上讲,POST是没有大小限制的HTTP协议规范也没有进行大小限制,说“POST数据量存在80K/100K的大小限制”是不准确的,POST数据是没有限制的,起限制作用的是服务器的处理程序的处理能力。

  对于ASP程序,Request对象处理每个表单域时存在100K的数据长度限制。但如果使用Request.BinaryRead则没有这个限制。

  由这个延伸出去,对于IIS 6.0,微软出于安全考虑,加大了限制。我们还需要注意:

     1).IIS 6.0默认ASP POST数据量最大为200KB,每个表单域限制是100KB。
     2).IIS 6.0默认上传文件的最大大小是4MB。
     3).IIS 6.0默认最大请求头是16KB。
  IIS 6.0之前没有这些限制。[见参考资料5]

  所以上面的80K,100K可能只是默认值而已(注:关于IIS4和IIS5的参数,我还没有确认),但肯定是可以自己设置的。由于每个版本的IIS对这些参数的默认值都不一样,具体请参考相关的IIS配置文档。

  3.在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用Request.Form。在JSP中,用request.getParameter(\"XXXX\")来获取,虽然jsp中也有request.getQueryString()方法,但使用起来比较麻烦,比如:传一个test.jsp?name=hyddd&password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。在PHP中,可以用$_GET和$_POST分别获取GET和POST中的数据,而$_REQUEST则可以获取GET和POST两种请求中的数据。值得注意的是,JSP中使用request和PHP中使用$_REQUEST都会有隐患,这个下次再写个文章总结。

  4.POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击。

  总结一下,Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,在FORM(表单)中,Method默认为"GET",实质上,GET和POST只是发送机制不同,并不是一个取一个发!

  纯属hyddd个人总结,如有错漏请指出。:>

 

参考资料

[1].http://hi.baidu.com/liuzd003/blog/item/7bfecbfa6ea94ed8b58f318c.html

[2].http://www.blogjava.net/onlykeke/archive/2006/08/23/65285.aspx

[3].http://baike.baidu.com/view/2067025.htm

[4].http://www.chxwei.com/article.asp?id=373

[5].http://blog.csdn.net/somat/archive/2004/10/29/158707.aspx

12月1

Http协议是什么?

15:54Web开发  From: 本站原创

一、什么是Http协议

  超文本传输协议的简称,用于定义客户端与web服务器通迅的格式。

  关于【标准的HTTP协议是无状态的】,请参见:https://www.heckjj.com/post/628/

二、Http的版本区别

  1.0:客户端与web服务器建立连接后,只能获得一个web资源

  1.1:允许客户端与web服务器建立连接后,在一个连接上获取多个web资源

三、Http协议格式

1.请求:客户端向服务器发出的请求

  请求行(常为GET/POST请求方式)

    GET: (默认的请求方式),在URL地址后附带的参数,但是有限制的,其数据容量通常不能超过1K。对应的查——用于查询资源信息

    POST: 可以在请求的实体内容中向服务器发送数据,Post方式的特点:传送的数据量无限制。对应改——用于更新资源信息

  GET与POST的区别请参见https://www.heckjj.com/post/630/
  请求头:头值
  请求体
  

HTTP请求中的常用消息头

  accept:浏览器通过这个头告诉服务器,它所支持的数据类型
  Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
  Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
  Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
  Host:浏览器通过这个头告诉服务器,想访问哪台主机
  If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
  Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的  防盗链
  Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接

2.响应:服务端向客户端送回的数据

  响应行( 协议/版本 状态码/状态码的解析 ) (状态行,处理的结果)
  响应头(key/value格式):对数据的描述以及告知客户端如何处理送回的数据

HTTP响应中的常用响应头(消息头)
  Location: 服务器通过这个头,来告诉浏览器跳到哪里
  Server:服务器通过这个头,告诉浏览器服务器的型号
  Content-Encoding:服务器通过这个头,告诉浏览器,数据的压缩格式
  Content-Length: 服务器通过这个头,告诉浏览器回送数据的长度
  Content-Language: 服务器通过这个头,告诉浏览器语言环境
  Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
  Refresh:服务器通过这个头,告诉浏览器定时刷新

  Content-Disposition: 服务器通过这个头,告诉浏览器以下载方式打数据
  Transfer-Encoding:服务器通过这个头,告诉浏览器数据是以分块方式回送的
  Expires: -1  控制浏览器不要缓存
  Cache-Control: no-cache  
  Pragma: no-cache
  空行
  响应体

常见状态码:

  HTTP/1.1 200 OK HTTP版本1.1 状态码200 解码表示ok(2开头的都是ok
       404(客户端的错误,资源找不到)(4打头客户端错误
       405 不支持该请求(没有重写相关的方法doGet() doPost())
       500 请求的资源找到了,但服务器内部出现了错误(5开头服务器错误
       503 服务器暂时不可用
       302 重定向。带一个响应头Location(3开头都是中转相关
       304 未修改 Not Modified。告诉浏览器最后修改时间是正确最新的,节省传输成本
         (Last-Modifired (Since))当然针对的是HTML静态页面

12月1
引言:
最近在好好了解http,发现对介绍http的第一句话【http协议是无状态的,无连接的】就无法理解了:无状态的【状态】到底指的是什么?!
找了很多资料不仅没有发现有一针见血正面回答这个问题的,而且有些解释还充斥了各种错误,看着看着就觉得心里憋着一股浊气吐不出来
于是在看了很多资料之后,我一口吐出浊气,大声正面提出这个问题:http协议无状态中的【状态】到底指的是什么?!
然后开始不断探索解决这个问题。。。
最终很高兴的是我找到了让人满意的答案,先卖个关子,各位如果着急可以直接拉到最下查看

正文:http协议无状态中的【状态】到底指的是什么呢?

先来看这句话的另外两个概念:(标准的http协议是无状态的,无连接的)
标准的http协议指的是不包括cookies, session,application的http协议,他们都不属于标准协议,虽然各种网络应用提供商,实现语言、web容器等,都默认支持它
无连接指的是什么
每一个访问都是无连接,服务器挨个处理访问队列里的访问,处理完一个就关闭连接,这事儿就完了,然后处理下一个新的
无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接

对于【无状态】,我看到很多隔着一层磨砂玻璃一样的模糊说法(官方或者教程里的说法),看着非常难受(但其实算是对的)(后来我发现我为什么觉得它看着难受了,因为他们引入了很多新的,而且明显是一个可能用在很多地方的广义名词,这些词最大的作用就是,混淆概念,下面我标注了)
协议对于事务处理没有记忆能力【事物处理】【记忆能力】
对同一个url请求没有上下文关系【上下文关系】
每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况【无直接联系】【受直接影响】
服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器【状态】

我必须得到确切而具体的解释!

这几点给了我下一步思考的方向:
【服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器 】这里的客户端的状态是不是确切地指服务器没有保存客户的信息呢?但显然不是啊
【HTTP无状态的特性严重阻碍了这些应用程序的实现,毕竟交互是需要承前启后的,简单的购物车程序也要知道用户到底在之前选择了什么商品】我对此质疑为什么无状态就不能实现购物车呢?服务器就不能存储东西了么?
【 每次的请求都是独立的,<它的执行情况和结果>与<前面的请求>和<之后的请求>是无直接关系的】我觉得这个说法比较靠谱,但是所谓的不同请求间的没有关系,是指的请求内容没有关系,还是只是指请求本身没有关系?
请求内容没有关系只可能是服务器上不存有用户数据才可能啊,但是显然是存有的啊
请求本身没有关系,这又有什么意义呢,每一次的请求有什么价值?

根据这个方向我做了一个模拟访问实验:假如没有cookie没有session,只有http的时候,那当一个注册用户访问这个购物网站的时候,会发生这些事情:
前提情况:
服务器肯定为每个注册用户建立了数据表,记录用户的数据
http是无连接的
第一步需要登录
用户通过http把用户的用户名和密码发送给服务器,服务器把他们跟自己存有的用户资料对比,如果一致,则返回信息登录成功
然后用户点击某一商品页
这个动作相当于输入一个商品页的网址
假如商品页比较机密不对外公开,需要是用户才能访问
而虽然http能传送用户名和密码,而且刚才也输入了,还验证成功了,但是因为服务器既不会记得你登录的状态,你的客户端也不会存储你刚才输入的用户名和密码
所以因为这一次访问因为无法确定你的身份,只能访问失败
这时候如果要解决这个问题,而且没有cookie没有session,那就只能你在访问网址的同时继续带上你的用户名和密码(继续输入咯)其实就像我现在的APP一样
假设上一步的问题解决了,就是每次访问的时候都会手动输入用户名和密码,然后现在的情况是:你已经选了几件商品在你的购物车中,你想再添加一件商品,于是你点击某个商品旁边的加号
这个动作也相当于输入一个网址,网址的内容是发送一个请求,往你的购物车中加入这个商品
系统首先用你传来的用户名和密码验证你的身份,然后访问你的数据库,在其中的购物车属性下加一条数据,就是这个商品的数据
操作结束后,返回操作成功,并结束访问
OK,实验结束,看似没有cookie没有session也能凑合解决问题,其实两个操作都有很大的问题
你每访问一次需要权限的内容都需要在客户端输入用户名和密码,这一项的繁琐就不必赘述了
你的每一次操作都要与系统底层的数据库进行交互
多次少量的访问存在非常大的性能浪费。非常容易就能想到肯定是一次大量的操作更加有效率,于是就想到了缓存区
你的非重要琐碎数据也被写进数据库中,跟你的主要数据放在一起
一次次添加和删除购物车其实只是跟你这次浏览,或者叫这次会话有关,是临时的数据,跟用户的主要信息无关,它们没什么价值,纯粹的冗余数据(不排除现在有的公司觉得这种数据也有非常大的价值可以让它们巧妙的利用),用什么存放这些临时的数据,我们也很容易想到缓存区

经过这个模拟访问实验,结合前面的思考方向,我们知道了三点:
服务器上肯定存有用户的数据,你提交的增删改查它也能够处理,所以这句话中【服务器中没有保存客户端的状态】的状态并不是指用户的数据,我们的猜测不对
我们的质疑对了,无状态能实现购物车,可以通过服务器上存有的用户数据来实现
但是,使用上面这种方式实现购物车,存在三个比较大的问题。由此,我们不禁会想,这三个问题的解决是不是跟我们不确切了解的【状态】一词有关?于是,接下来我们来通过解决这三个问题来把【状态】的意义探寻下去

由上所述,我们可以在http的基础上增加一些机制来解决上面出现的三个问题
在用户端增加一个记录本是非常有必要的,正好官方加入的cookie机制跟这个一样,它的用处也确实是上面讨论的那样,一般就是用来标识访问者的身份
在服务器增加一个缓存区能同时解决后两个问题
有了这个缓存区作为一个数据缓冲,就不用一次次地访问数据库,浪费大量计算机资源,而是在最后统一归入数据库
有了这个缓存区,你就不用把临时的数据放到数据库中了,只需要在你们交流告一段落之后,再把数据整理,把有用的数据归入数据库
这里就自然引申出了一个重要的概念:会话,它作为一个缓冲存储区被从数据库中分离出来,理由并不生硬,它有其独特的重要且不可替代的作用。这个东西恰好跟官方加入的session机制一样
另外说一个非常具有迷惑性的容易让人对session的主要作用产生偏离的理解:认为session存在的价值就是给访问者分配一个sessionID代替用户名和密码,
为什么非常具有迷惑性,因为session确实做了这件事,而且也起到了很大的作用,所以它是对的,但是只对一半,而且没有涉及问题的本质,这种情况是最危险的(看似很有说服力,把你说服了,所以你很难有动力继续找下去,但是真实情况跟它有偏差,但是偏差不大,所以又很难把你说服回来,只有隐隐的不对劲,这个时候你离真实最近,也离真实最远)
那就顺便说说它为什么是对的,也就是用session做的另一件有用的事:
给每个session一个ID,一方面用来方便自己查询,另一方面把这个ID给用户,用户下一次访问的时候就可以不用用户名和密码,而是直接使用这个ID来表明自己的身份
首先,这个ID安全吗?这个ID比直接传用户名和密码安全吗?
你很容易会想到,本来用户名和密码的组合还特地设置地比较复杂,你这换一组数字就代替了,是不是太不安全了?
我们知道http协议本身是完全不加密的,如果使用用户名和密码,第一次访问是放在http头中,后边自动保存了密码就会放在cookie中,这些都完全没有加密,它的安全性基本为0,就是裸奔了,只要被窃取,那就丢失了
所以,就这个意义来讲,sessionID的安全性跟使用用户名和密码没什么区别
但是其实,虽然http本身不能加密,但是有些软件什么的,能在应用层面手动给你加密,比如QQ就会使用户名密码加临时验证码联合哈希,sessionID加一个时间戳简单加密也是非常常用的方法
而且因为sessionID本身有有效期,即使丢了,也可能很快失效,造成的损失可能没那么大,而用户名跟密码丢了,那就大了
所以总结就是:
不严格加密的sessionID和用户名和密码一样,都不太安全
但是相比较来说,sessionID要安全一些
而使用https是完全安全的
然后,使用sessionID有哪些好处
方便直接根据ID查询用户对应的session
加密的时候计算量小
安全性不会降低,甚至还更高一些

OK,通过独立地解决纯http机制会产生的问题,我们探讨了cookie和session机制的本质。而且想到:【使用http协议,服务器中不会保存客户端的状态】所产生的问题通过增加cookie和session机制解决了,是不是就意味着这个【状态】跟cookie和session的关系非常紧密?所以这个无状态指的是【没有对 本次会话 设置一个缓存区,记录这次会话的状态,缓存区包括服务器端和用户端】但好像还是没有点破关键(主要是觉得跟前面那些官方对状态的说法不太吻合,甚至没有对应关系)

忽然我想到一个问题:一个有状态的http是什么样的?
很难直接想象有状态的http是什么样,因为http这种机制是天然无状态的
那就类比一下吧,另一个天然有状态的机制叫TCP
如果有状态的意思是它的每次请求是有联系的,那么有状态的TCP的样子是:假如一份数据分了三份TCP包发送,那这个包上面会标明这是第几个包,会标明这个包跟那几个包是有联系的,有什么联系
但好像这个有状态的TCP跟我们想要的有状态的HTTP没有关系,因为即使每次http请求之间互相有联系,它也不能解决上面提到的http无状态的问题
诶,等等,好像能类比:
假如每个http连接都有一个签名,于是第一次登陆成功之后,服务器就知道了这个签名是允许登陆的,于是之后所有同样签名的http连接都能登陆,这里利用了同一个用户发出的http连接之间的同主人关系,这里解决了一个保持登录状态的问题
同样,来尝试利用这个【每次http请求之间互相有联系】来解决上面碰到的那个问题【每一次操作都要与系统底层的数据库进行交互】,但想了半天确实无法进行下去
不过我灵机一动,从另一个角度来想,好像解决了这个问题:
只有【每次http请求之间互相有联系】这个条件,无法解决【每一次操作都要与系统底层的数据库进行交互】
因为很明显,要解决【每一次操作都要与系统底层的数据库进行交互】就必须在服务器端开辟一块缓存区
不过如果你思考一下如何实现【每次http请求之间互相有联系】,你就会发现,它也需要在服务器端开辟一块缓存区
所以【在服务器端开辟一块缓存区】才是真正的条件,也就是说,它确实等价于【有状态】
而且我也找到了这个【在服务器端开辟一块缓存区】的条件跟前面那些官方对状态的说法对应的点,那就是:
通过在服务器端开辟一块缓存区,存储、记忆、共享一些临时数据,你就可以:
协议对于事务处理有记忆能力【事物处理】【记忆能力】
对同一个url请求有上下文关系【上下文关系】
每次的请求都是不独立的,它的执行情况和结果与前面的请求和之后的请求是直接关系的【不独立】【直接关系】
服务器中保存客户端的状态【状态】
所以,这个状态,加上前面说的客户端也有cookie,就是指,客户端和服务器在临时会话中产生的数据!而前面也说道了,使用缓存区保存临时会话中的数据是多么重要
所以状态不仅包括不同URL访问之间的关系,还有对其他URL访问的数据记录,还有一些其他的东西,所以更确切地说,状态应该是【实现了这些东西所凭借的后面的缓存空间】中的客户的临时数据
cookie和session应该是完全实现了有状态这个功能

一种常见的对状态的误解:
有人在解释HTTP的无状态时,把它跟有连接对立,说是两种方式,也就是如果想不无状态,就必须有连接,但其实不然
有连接和无连接以及之后的Keep-Alive都是指TCP连接
有状态和无状态可以指TCP也可以指HTTP
TCP一直有状态,HTTP一直无状态,但是应用为了有状态,就给HTTP加了cookie和session机制,让使用http的应用也能有状态,但http还是无状态
开始TCP是有连接,后来TCP无连接,再后来也就是现在TCP是Keep-Alive,有点像有连接。
分页: 1/12 第一页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]