转自:http://www.yeetrack.com/?p=779javascript
Http协议应该是互联网中最重要的协议。持续增加的web服务、可联网的家用电器等都在继承并拓展着Http协议,向着浏览器以外的方向发展。html
虽然jdk中的java.net包中提供了一些基本的方法,经过http协议来访问网络资源,可是大多数场景下,它都不够灵活和强大。HttpClient致力于填补这个空白,它能够提供有效的、最新的、功能丰富的包来实现http客户端。java
为了拓展,HttpClient即支持基本的http协议,还支持http-aware客户端程序,如web浏览器,Webservice客户端,以及利用or拓展http协议的分布式系统。web
一、HttpClient的范围/特性算法
二、HttpClient不能作的事情apache
HttpClient最基本的功能就是执行Http方法。一个Http方法的执行涉及到一个或者多个Http请求/Http响应的交 互,一般这个过程都会自动被HttpClient处理,对用户透明。用户只须要提供Http请求对象,HttpClient就会将http请求发送给目标 服务器,而且接收服务器的响应,若是http请求执行不成功,httpclient就会抛出异样。json
下面是个很简单的http请求执行的例子:windows
全部的Http请求都有一个请求行(request line),包括方法名、请求的URI和Http版本号。api
HttpClient支持HTTP/1.1这个版本定义的全部Http方法:GET
,HEAD
,POST
,PUT
,DELETE
,TRACE和
OPTIONS。对于每一种http方法,HttpClient都定义了一个相应的类:
HttpGet,
HttpHead,
HttpPost,
HttpPut,
HttpDelete,
HttpTrace和
HttpOpquertions。数组
Request-URI即统一资源定位符,用来标明Http请求中的资源。Http request URIs包含协议名、主机名、主机端口(可选)、资源路径、query(可选)和片断信息(可选)。
HttpClient提供URIBuilder
工具类来简化URIs的建立和修改过程。
上述代码会在控制台输出:
服务器收到客户端的http请求后,就会对其进行解析,而后把响应发给客户端,这个响应就是HTTP response.HTTP响应第一行是协议版本,以后是数字状态码和相关联的文本段。
一个Http消息能够包含一系列的消息头,用来对http消息进行描述,好比消息长度,消息类型等等。HttpClient提供了方法来获取、添加、移除、枚举消息头。
最有效的获取指定类型的消息头的方法仍是使用HeaderIterator
接口。
上述代码会在控制台输出:
HeaderIterator也提供很是便捷的方式,将Http消息解析成单独的消息头元素。
上述代码会在控制台输出:
Http消息能够携带http实体,这个http实体既能够是http请求,也能够是http响应的。Http实体,能够在某些 http请求或者响应中发现,但不是必须的。Http规范中定义了两种包含请求的方法:POST和PUT。HTTP响应通常会包含一个内容实体。固然这条 规则也有异常状况,如Head方法的响应,204没有内容,304没有修改或者205内容资源重置。
HttpClient根据来源的不一样,划分了三种不一样的Http实体内容。
当从Http响应中读取内容时,上面的三种区分对于链接管理器来讲是很是重要的。对于由应用程序建立并且只使用HttpClient发 送的请求实体,streamed和self-contained两种类型的不一样就不那么重要了。这种状况下,建议考虑如streamed流式这种不能重复 的实体,和能够重复的self-contained自我包含式实体。
一个实体是可重复的,也就是说它的包含的内容能够被屡次读取。这种屡次读取只有self contained(自包含)的实体能作到(好比ByteArrayEntity
或者StringEntity
)。
因为一个Http实体既能够表示二进制内容,又能够表示文本内容,因此Http实体要支持字符编码(为了支持后者,即文本内容)。
当须要执行一个完整内容的Http请求或者Http请求已经成功,服务器要发送响应到客户端时,Http实体就会被建立。
若是要从Http实体中读取内容,咱们能够利用HttpEntity
类的getContent
方法来获取实体的输入流(java.io.InputStream
),或者利用HttpEntity
类的writeTo(OutputStream)
方法来获取输出流,这个方法会把全部的内容写入到给定的流中。
当实体类已经被接受后,咱们能够利用HttpEntity
类的getContentType()
和getContentLength()
方法来读取Content-Type
和Content-Length
两个头消息(若是有的话)。因为Content-Type
包含mime-types的字符编码,好比text/plain或者text/html,HttpEntity
类的getContentEncoding()
方法就是读取这个编码的。若是头信息不存在,getContentLength()
会返回-1,getContentType()
会返回NULL。若是Content-Type
信息存在,就会返回一个Header
类。
当为发送消息建立Http实体时,须要同时附加meta信息。
为了确保系统资源被正确地释放,咱们要么管理Http实体的内容流、要么关闭Http响应。
请注意HttpEntity
的writeTo(OutputStream)
方法,当Http实体被写入到OutputStream后,也要确保释放系统资源。若是这个方法内调用了HttpEntity
的getContent()
方法,那么它会有一个java.io.InpputStream
的实例,咱们须要在finally中关闭这个流。
可是也有这样的状况,咱们只须要获取Http响应内容的一小部分,而获取整个内容并、实现链接的可重复性代价太大,这时咱们能够经过关闭响应的方式来关闭内容输入、输出流。
HttpClient推荐使用HttpEntity
的getConent()
方法或者HttpEntity
的writeTo(OutputStream)
方法来消耗掉Http实体内容。HttpClient也提供了EntityUtils
这个类,这个类提供一些静态方法能够更容易地读取Http实体的内容和信息。和以java.io.InputStream
流读取内容的方式相比,EntityUtils提供的方法能够以字符串或者字节数组的形式读取Http实体。可是,强烈不推荐使用EntityUtils
这个类,除非目标服务器发出的响应是可信任的,而且http响应实体的长度不会过大。
有些状况下,咱们但愿能够重复读取Http实体的内容。这就须要把Http实体内容缓存在内存或者磁盘上。最简单的方法就是把Http Entity转化成BufferedHttpEntity
,这样就把原Http实体的内容缓冲到了内存中。后面咱们就能够重复读取BufferedHttpEntity中的内容。
HttpClient提供了一些类,这些类能够经过http链接高效地输出Http实体内容。HttpClient提供的这几个类涵盖的常见的数据类型,如String,byte数组,输入流,和文件类型:StringEntity
,ByteArrayEntity
,InputStreamEntity
,FileEntity
。
InputStreamEntity
只能从下层的数据流中读取一次,因此它是不能重复的。推荐,经过继承
HttpEntity
这个自包含的类来自定义HttpEntity类,而不是直接使用
InputStreamEntity
这个类。
FileEntity
就是一个很好的起点(FileEntity就是继承的HttpEntity)。
不少应用程序须要模拟提交Html表单的过程,举个例子,登录一个网站或者将输入内容提交给服务器。HttpClient提供了UrlEncodedFormEntity
这个类来帮助实现这一过程。
UrlEncodedFormEntity
实例会使用所谓的Url编码的方式对咱们的参数进行编码,产生的结果以下:
通常来讲,推荐让HttpClient本身根据Http消息传递的特征来选择最合适的传输编码。固然,若是非要手动控制也是能够的,能够经过设置HttpEntity
的setChunked()
为true。请注意:HttpClient仅会将这个参数当作是一个建议。若是Http的版本(如http 1.0)不支持内容分块,那么这个参数就会被忽略。
最简单也是最方便的处理http响应的方法就是使用ResponseHandler
接口,这个接口中有handleResponse(HttpResponse response)
方法。使用这个方法,用户彻底不用关心http链接管理器。当使用ResponseHandler
时,HttpClient会自动地将Http链接释放给Http管理器,即便http请求失败了或者抛出了异常。
对于Http请求执行过程来讲,HttpClient
的接口有着必不可少的做用。HttpClient
接口没有对Http请求的过程作特别的限制和详细的规定,链接管理、状态管理、受权信息和重定向处理这些功能都单独实现。这样用户就能够更简单地拓展接口的功能(好比缓存响应内容)。
通常说来,HttpClient实际上就是一系列特殊的handler或者说策略接口的实现,这些handler(测试接口)负责着处 理Http协议的某一方面,好比重定向、认证处理、有关链接持久性和keep alive持续时间的决策。这样就容许用户使用自定义的参数来代替默认配置,实现个性化的功能。
HttpClient
已经实现了线程安全。因此但愿用户在实例化HttpClient时,也要支持为多个请求使用。
当一个CloseableHttpClient
的实例再也不被使用,而且它的做用范围即将失效,和它相关的链接必须被关闭,关闭方法能够调用CloseableHttpClient
的close()
方法。
最初,Http被设计成一种无状态的、面向请求-响应的协议。然而,在实际使用中,咱们但愿可以在一些逻辑相关的请求-响应中,保持状 态信息。为了使应用程序能够保持Http的持续状态,HttpClient容许http链接在特定的Http上下文中执行。若是在持续的http请求中使 用了一样的上下文,那么这些请求就能够被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map<String, Object>
功能相似。它实际上就是一个任意命名的值的集合。应用程序能够在Http请求执行前填充上下文的值,也能够在请求执行完毕后检查上下文。
HttpContext
能够包含任意类型的对象,所以若是在多线程中共享上下文会不安全。推荐每一个线程都只包含本身的http上下文。
在Http请求执行的过程当中,HttpClient会自动添加下面的属性到Http上下文中:
HttpConnection
的实例,表示客户端与服务器之间的链接HttpHost
的实例,表示要链接的目标服务器HttpRoute
的实例,表示所有的链接路由HttpRequest
的实例,表示Http请求。在执行上下文中,最终的HttpRequest对象会表明http消息的状态。Http/1.0和Http/1.1都默认使用相对的uri。可是若是使用了非隧道模式的代理服务器,就会使用绝对路径的uri。HttpResponse
的实例,表示Http响应java.lang.Boolean
对象,表示是否请求被成功的发送给目标服务器RequestConfig
对象,表示http request的配置信息java.util.List<Uri>
对象,表示Http响应中的全部重定向地址咱们可使用HttpClientContext
这个适配器来简化和上下文交互的过程。
同一个逻辑会话中的多个Http请求,应该使用相同的Http上下文来执行,这样就能够自动地在http请求中传递会话上下文和状态信息。
在下面的例子中,咱们在开头设置的参数,会被保存在上下文中,而且会应用到后续的http请求中。
HttpClient会被抛出两种类型的异常,一种是java.io.IOException
,当遇到I/O异常时抛出(socket超时,或者socket被重置);另外一种是HttpException
,表示Http失败,如Http协议使用不正确。一般认为,I/O错误时不致命、可修复的,而Http协议错误是致命了,不能自动修复的错误。
Http协议不能知足全部类型的应用场景,咱们须要知道这点。Http是个简单的面向协议的请求/响应的协议,当初它被设计用来支持静 态或者动态生成的内容检索,以前历来没有人想过让它支持事务性操做。例如,Http服务器成功接收、处理请求后,生成响应消息,而且把状态码发送给客户 端,这个过程是Http协议应该保证的。可是,若是客户端因为读取超时、取消请求或者系统崩溃致使接收响应失败,服务器不会回滚这一事务。若是客户端从新 发送这个请求,服务器就会重复的解析、执行这个事务。在一些状况下,这会致使应用程序的数据损坏和应用程序的状态不一致。
即便Http当初设计是不支持事务操做,可是它仍旧能够做为传输协议为某些关键程序提供服务。为了保证Http传输层的安全性,系统必须保证应用层上的http方法的幂等性。
HTTP/1.1规范中是这样定义幂等方法的,Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request。用其余话来讲,应用程序须要正确地处理同一方法屡次执行形成的影响。添加一个具备惟一性的id就能避免重复执行同一个逻辑请求,问题解 决。
请知晓,这个问题不仅是HttpClient才会有,基于浏览器的应用程序也会遇到Http方法不幂等的问题。
HttpClient默认把非实体方法get
、head
方法看作幂等方法,把实体方法post
、put
方法看作非幂等方法。
默认状况下,HttpClient会尝试自动修复I/O异常。这种自动修复仅限于修复几个公认安全的异常。
若是要自定义异常处理机制,咱们须要实现HttpRequestRetryHandler
接口。
有时候因为目标服务器负载太高或者客户端目前有太多请求积压,http请求不能在指定时间内执行完毕。这时候终止这个请求,释放阻塞I/O的进程,就显得很必要。经过HttpClient执行的Http请求,在任何状态下都能经过调用HttpUriRequest
的abort()
方法来终止。这个方法是线程安全的,而且能在任何线程中调用。当Http请求被终止了,本线程(即便如今正在阻塞I/O)也会经过抛出一个InterruptedIOException
异常,来释放资源。
HTTP协议拦截器是一种实现一个特定的方面的HTTP协议的代码程序。一般状况下,协议拦截器会将一个或多个头消息加入到接受或者发 送的消息中。协议拦截器也能够操做消息的内容实体—消息内容的压缩/解压缩就是个很好的例子。一般,这是经过使用“装饰”开发模式,一个包装实体类用于装 饰原来的实体来实现。一个拦截器能够合并,造成一个逻辑单元。
协议拦截器能够经过共享信息协做——好比处理状态——经过HTTP执行上下文。协议拦截器可使用Http上下文存储一个或者多个连续请求的处理状态。
一般,只要拦截器不依赖于一个特定状态的http上下文,那么拦截执行的顺序就无所谓。若是协议拦截器有相互依赖关系,必须以特定的顺序执行,那么它们应该按照特定的顺序加入到协议处理器中。
协议处理器必须是线程安全的。相似于servlets,协议拦截器不该该使用变量实体,除非访问这些变量是同步的(线程安全的)。
下面是个例子,讲述了本地的上下文时如何在连续请求中记录处理状态的:
上面代码在发送http请求时,会自动添加Count这个header,可使用wireshark抓包查看。
HttpClient会自动处理全部类型的重定向,除了那些Http规范明确禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 咱们可使用自定义的重定向策略来放松Http规范对Post方法重定向的限制。
HttpClient在请求执行过程当中,常常须要重写请求的消息。 HTTP/1.0和HTTP/1.1都默认使用相对的uri路径。一样,原始的请求可能会被一次或者屡次的重定向。最终结对路径的解释可使用最初的请求和上下文。URIUtils
类的resolve
方法能够用于将拦截的绝对路径构建成最终的请求。这个方法包含了最后一个分片标识符或者原始请求。
两个主机创建链接的过程是很复杂的一个过程,涉及到多个数据包的交换,而且也很耗时间。Http链接须要的三次握手开销很大,这一开销对于比较小的http消息来讲更大。可是若是咱们直接使用已经创建好的http链接,这样花费就比较小,吞吐率更大。
HTTP/1.1默认就支持Http链接复用。兼容HTTP/1.0的终端也能够经过声明来保持链接,实现链接复用。HTTP代理也可 以在必定时间内保持链接不释放,方便后续向这个主机发送http请求。这种保持链接不释放的状况其实是创建的持久链接。HttpClient也支持持久 链接。
HttpClient既能够直接、又能够经过多个中转路由(hops)和目标服务器创建链接。HttpClient把路由分为三种plain(明文 ),tunneled(隧道)和layered(分层)。隧道链接中使用的多个中间代理被称做代理链。
客户端直接链接到目标主机或者只经过了一个中间代理,这种就是Plain路由。客户端经过第一个代理创建链接,经过代理链 tunnelling,这种状况就是Tunneled路由。不经过中间代理的路由不可能时tunneled路由。客户端在一个已经存在的链接上进行协议分 层,这样创建起来的路由就是layered路由。协议只能在隧道—>目标主机,或者直接链接(没有代理),这两种链路上进行分层。
RouteInfo
接口包含了数据包发送到目标主机过程当中,通过的路由信息。HttpRoute
类继承了RouteInfo
接口,是RouteInfo
的具体实现,这个类是不容许修改的。HttpTracker
类也实现了RouteInfo
接口,它是可变的,HttpClient会在内部使用这个类来探测到目标主机的剩余路由。HttpRouteDirector
是个辅助类,能够帮助计算数据包的下一步路由信息。这个类也是在HttpClient内部使用的。
HttpRoutePlanner
接口能够用来表示基于http上下文状况下,客户端到服务器的路由计算策略。HttpClient有两个HttpRoutePlanner
的实现类。SystemDefaultRoutePlanner
这个类基于java.net.ProxySelector
,它默认使用jvm的代理配置信息,这个配置信息通常来自系统配置或者浏览器配置。DefaultProxyRoutePlanner
这个类既不使用java自己的配置,也不使用系统或者浏览器的配置。它一般经过默认代理来计算路由信息。
为了防止经过Http消息传递的信息不被未受权的第三方获取、截获,Http可使用SSL/TLS协议来保证http传输安全,这个协议是当前使用最广的。固然也可使用其余的加密技术。可是一般状况下,Http信息会在加密的SSL/TLS链接上进行传输。
Http链接是复杂,有状态的,线程不安全的对象,因此它必须被妥善管理。一个Http链接在同一时间只能被一个线程访问。HttpClient使用一个叫作Http链接管理器的特殊实体类来管理Http链接,这个实体类要实现HttpClientConnectionManager
接口。Http链接管理器在新建http链接时,做为工厂类;管理持久http链接的生命周期;同步持久链接(确保线程安全,即一个http链接同一时间只能被一个线程访问)。Http链接管理器和ManagedHttpClientConnection
的实例类一块儿发挥做用,ManagedHttpClientConnection
实 体类能够看作http链接的一个代理服务器,管理着I/O操做。若是一个Http链接被释放或者被它的消费者明确表示要关闭,那么底层的链接就会和它的代 理进行分离,而且该链接会被交还给链接管理器。这是,即便服务消费者仍然持有代理的引用,它也不能再执行I/O操做,或者更改Http链接的状态。
下面的代码展现了如何从链接管理器中取得一个http链接:
若是要终止链接,能够调用ConnectionRequest
的cancel()
方法。这个方法会解锁被ConnectionRequest
类get()
方法阻塞的线程。
BasicHttpClientConnectionManager
是个简单的链接管理器,它一次只能管理一个链接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。BasicHttpClientConnectionManager
会尽可能重用旧的链接来发送后续的请求,而且使用相同的路由。若是后续请求的路由和旧链接中的路由不匹配,BasicHttpClientConnectionManager
就会关闭当前链接,使用请求中的路由从新创建链接。若是当前的链接正在被占用,会抛出java.lang.IllegalStateException
异常。
相对BasicHttpClientConnectionManager
来讲,PoolingHttpClientConnectionManager
是 个更复杂的类,它管理着链接池,能够同时为不少线程提供http链接请求。Connections are pooled on a per route basis.当请求一个新的链接时,若是链接池有有可用的持久链接,链接管理器就会使用其中的一个,而不是再建立一个新的链接。
PoolingHttpClientConnectionManager
维护的链接数在每一个路由基础和总数上都有限制。默认,每一个路由基础上的链接不超过2个,总链接数不能超过20。在实际应用中,这个限制可能会过小了,尤为是当服务器也使用Http协议时。
下面的例子演示了若是调整链接池的参数:
当一个HttpClient的实例不在使用,或者已经脱离它的做用范围,咱们须要关掉它的链接管理器,来关闭掉全部的链接,释放掉这些链接占用的系统资源。
当使用了请求链接池管理器(好比PoolingClientConnectionManager
)后,HttpClient就能够同时执行多个线程的请求了。
PoolingClientConnectionManager
会根据它的配置来分配请求链接。若是链接池中的全部链接都被占用了,那么后续的请求就会被阻塞,直到有链接被释放回链接池中。为了防止永远阻塞的状况发生,咱们能够把http.conn-manager.timeout
的值设置成一个整数。若是在超时时间内,没有可用链接,就会抛出ConnectionPoolTimeoutException
异常。
即便HttpClient的实例是线程安全的,能够被多个线程共享访问,可是仍旧推荐每一个线程都要有本身专用实例的HttpContext。
下面是GetThread类的定义:
经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时,socket才能对I/O事件作出反应。当链接被管理器收回后,这个链接仍 然存活,可是却没法监控socket的状态,也没法对I/O事件作出反馈。若是链接被服务器端关闭了,客户端监测不到链接的状态变化(也就没法根据链接状 态的变化,关闭本地的socket)。
HttpClient为了缓解这一问题形成的影响,会在使用某个链接前,监测这个链接是否已通过时,若是服务器端关闭了链接,那么链接 就会失效。这种过期检查并非100%有效,而且会给每一个请求增长10到30毫秒额外开销。惟一一个可行的,且does not involve a one thread per socket model for idle connections的解决办法,是创建一个监控线程,来专门回收因为长时间不活动而被断定为失效的链接。这个监控线程能够周期性的调用ClientConnectionManager
类的closeExpiredConnections()
方法来关闭过时的链接,回收链接池中被关闭的链接。它也能够选择性的调用ClientConnectionManager
类的closeIdleConnections()
方法来关闭一段时间内不活动的链接。
Http规范没有规定一个持久链接应该保持存活多久。有些Http服务器使用非标准的Keep-Alive
头消息和客户端进行交互,服务器端会保持数秒时间内保持链接。HttpClient也会利用这个头消息。若是服务器返回的响应中没有包含Keep-Alive
头消息,HttpClient会认为这个链接能够永远保持。然而,不少服务器都会在不通知客户端的状况下,关闭必定时间内不活动的链接,来节省服务器资源。在某些状况下默认的策略显得太乐观,咱们可能须要自定义链接存活策略。
Http链接使用java.net.Socket
类来传输数据。这依赖于ConnectionSocketFactory
接口来建立、初始化和链接socket。这样也就容许HttpClient的用户在代码运行时,指定socket初始化的代码。PlainConnectionSocketFactory
是默认的建立、初始化明文socket(不加密)的工厂类。
建立socket和使用socket链接到目标主机这两个过程是分离的,因此咱们能够在链接发生阻塞时,关闭socket链接。
LayeredConnectionSocketFactory
是ConnectionSocketFactory
的拓展接口。分层socket工厂类能够在明文socket的基础上建立socket链接。分层socket主要用于在代理服务器之间建立安全socket。HttpClient使用SSLSocketFactory
这个类实现安全socket,SSLSocketFactory
实现了SSL/TLS分层。请知晓,HttpClient没有自定义任何加密算法。它彻底依赖于Java加密标准(JCE)和安全套接字(JSEE)拓展。
自定义的socket工厂类能够和指定的协议(Http、Https)联系起来,用来建立自定义的链接管理器。
HttpClient使用SSLSocketFactory
来建立ssl链接。SSLSocketFactory
容许用户高度定制。它能够接受javax.net.ssl.SSLContext
这个类的实例做为参数,来建立自定义的ssl链接。
除了信任验证和在ssl/tls协议层上进行客户端认证,HttpClient一旦创建起链接,就能够选择性验证目标域名和存储在X.509证书中的域名是否一致。这种验证能够为服务器信任提供额外的保障。X509HostnameVerifier
接口表明主机名验证的策略。在HttpClient中,X509HostnameVerifier
有三个实现类。重要提示:主机名有效性验证不该该和ssl信任验证混为一谈。
StrictHostnameVerifier
: 严格的主机名验证方法和 java 1.4,1.5,1.6验证方法相同。和IE6的方式也大体相同。这种验证方式符合RFC 2818通配符。The hostname must match either the first CN, or any of the subject-alts. A wildcard can occur in the CN, and in any of the subject-alts.BrowserCompatHostnameVerifier
: 这种验证主 机名的方法,和Curl及firefox一致。The hostname must match either the first CN, or any of the subject-alts. A wildcard can occur in the CN, and in any of the subject-alts.StrictHostnameVerifier
和BrowserCompatHostnameVerifier
方式惟一不一样的地方就是,带有通配符的域名(好比*.yeetrack.com),BrowserCompatHostnameVerifier
方式在匹配时会匹配全部的的子域名,包括 a.b.yeetrack.com .AllowAllHostnameVerifier
: 这种方式不对主机名进行验证,验证功能被关闭,是个空操做,因此它不会抛出javax.net.ssl.SSLException
异常。HttpClient默认使用BrowserCompatHostnameVerifier
的验证方式。若是须要,咱们能够手动执行验证方式。
尽管,HttpClient支持复杂的路由方案和代理链,它一样也支持直接链接或者只经过一跳的链接。
使用代理服务器最简单的方式就是,指定一个默认的proxy参数。
咱们也可让HttpClient去使用jre的代理服务器。
又或者,咱们也能够手动配置RoutePlanner
,这样就能够彻底控制Http路由的过程。
最初,Http被设计成一个无状态的,面向请求/响应的协议,因此它不能在逻辑相关的http请求/响应中保持状态会话。因为愈来愈多的系统使用http协议,其中包括http历来没有想支持的系统,好比电子商务系统。所以,http支持状态管理就很必要了。
当时的web客户端和服务器软件领先者,网景(netscape)公司,最早在他们的产品中支持http状态管理,而且制定了一些专有 规范。后来,网景经过发规范草案,规范了这一机制。这些努力促成 RFC standard track制定了标准的规范。可是,如今多数的应用的状态管理机制都在使用网景公司的规范,而网景的规范和官方规定是不兼容的。所以全部的浏览器开发这都 被迫兼容这两种协议,从而致使协议的不统一。
所谓的Http cookie就是一个token或者很短的报文信息,http代理和服务器能够经过cookie来维持会话状态。网景的工程师把它们称做“magic cookie”。
HttpClient使用Cookie
接口来表明cookie。简单说来,cookie就是一个键值对。通常,cookie也会包含版本号、域名、路径和cookie有效期。
SetCookie
接口能够表明服务器发给http代理的一个set-cookie响应头,在浏览器中,这个set-cookie响应头能够写入cookie,以便保持会话状态。SetCookie2
接口对SetCookie
接口进行了拓展,添加了Set-Cookie2
方法。
ClientCookie
接口继承了Cookie
接口,并进行了功能拓展,好比它能够取出服务器发送过来的原始cookie的值。生成头消息是很重要的,由于只有当cookie被指定为Set-Cookie
或者Set-Cookie2
时,它才须要包括一些特定的属性。
兼容网景的规范,可是不兼容官方规范的cookie,是版本0. 兼容官方规范的版本,将会是版本1。版本1中的Cookie可能和版本0工做机制有差别。
下面的代码,建立了网景版本的Cookie:
下面的代码,建立标准版本的Cookie。注意,标准版本的Cookie必须保留服务器发送过来的Cookie全部属性。
下面的代码,建立了Set-Cookie2
兼容cookie。
CookieSpec
接口表明了Cookie管理规范。Cookie管理规范规定了:
Set-Cookie
和Set-Cookie2
(可选)头消息的规则HttpClient有下面几种CookieSpec
规范:
咱们能够在建立Http client的时候指定Cookie测试,若是须要,也能够在执行http请求的时候,进行覆盖指定。
若是咱们要自定义Cookie测试,就要本身实现CookieSpec
接口,而后建立一个CookieSpecProvider
接口来新建、初始化自定义CookieSpec
接口,最后把CookieSpecProvider
注册到HttpClient中。一旦咱们注册了自定义策略,就能够像其余标准策略同样使用了。
HttpClient可使用任何存储方式的cookie store,只要这个cookie store实现了CookieStore
接口。默认的CookieStore经过java.util.ArrayList
简单实现了BasicCookieStore
。存在在BasicCookieStore
中的Cookie,当载体对象被当作垃圾回收掉后,就会丢失。若是必要,用户能够本身实现更为复杂的方式。
在Http请求执行过程当中,HttpClient会自动向执行上下文中添加下面的状态管理对象:
Lookup
对象 表明实际的cookie规范registry。在当前上下文中的这个值优先于默认值。CookieSpec
对象 表明实际的Cookie规范。CookieOrigin
对象 表明实际的origin server的详细信息。CookieStore
对象 表示Cookie store。这个属性集中的值会取代默认值。本地的HttpContext
对象能够用来在Http请求执行前,自定义Http状态管理上下文;或者测试 http请求执行完毕后上下文的状态。咱们也能够在不一样的线程中使用不一样的执行上下文。咱们在http请求层指定的cookie规范集和cookie store会覆盖在http Client层级的默认值。
HttpClient既支持HTTP标准规范定义的认证模式,又支持一些普遍使用的非标准认证模式,好比NTLM和SPNEGO。
任何用户认证的过程,都须要一系列的凭证来肯定用户的身份。最简单的用户凭证能够是用户名和密码这种形式。UsernamePasswordCredentials
这个类能够用来表示这种状况,这种凭据包含明文的用户名和密码。
这个类对于HTTP标准规范中定义的认证模式来讲已经足够了。
上述代码会在控制台输出:
NTCredentials
是微软的windows系统使用的一种凭据,包含username、password,还包括一系列其余的属性,好比用户所在的域名。在Microsoft Windows的网络环境中,同一个用户能够属于不一样的域,因此他也就有不一样的凭据。
上述代码输出:
AutoScheme
接口表示一个抽象的面向挑战/响应的认证方案。一个认证方案要支持下面的功能:
请注意:一个认证方案多是有状态的,由于它可能涉及到一系列的挑战/响应。
HttpClient实现了下面几种AutoScheme
:
凭证providers旨在维护一套用户的凭证,当须要某种特定的凭证时,providers就应该能产生这种凭证。认证的具体内容包 括主机名、端口号、realm name和认证方案名。当使用凭据provider的时候,咱们能够很模糊的指定主机名、端口号、realm和认证方案,不用写的很精确。由于,凭据 provider会根据咱们指定的内容,筛选出一个最匹配的方案。
只要咱们自定义的凭据provider实现了CredentialsProvider
这个接口,就能够在HttpClient中使用。默认的凭据provider叫作BasicCredentialsProvider
,它使用java.util.HashMap
对CredentialsProvider
进行了简单的实现。
上面代码输出:
HttpClient依赖AuthState
类去跟踪认证过程当中的状态的详细信息。在Http请求过程当中,HttpClient建立两个AuthState
实例:一个用于目标服务器认证,一个用于代理服务器认证。若是服务器或者代理服务器须要用户的受权信息,AuthScope
、AutoScheme
和认证信息就会被填充到两个AuthScope
实例中。经过对AutoState
的检测,咱们能够肯定请求的受权类型,肯定是否有匹配的AuthScheme
,肯定凭据provider根据指定的受权类型是否成功生成了用户的受权信息。
在Http请求执行过程当中,HttpClient会向执行上下文中添加下面的受权对象:
Lookup
对象,表示使用的认证方案。这个对象的值能够在本地上下文中进行设置,来覆盖默认值。CredentialsProvider
对象,表示认证方案provider,这个对象的值能够在本地上下文中进行设置,来覆盖默认值。AuthState
对象,表示目标服务器的认证状态,这个对象的值能够在本地上下文中进行设置,来覆盖默认值。AuthState
对象,表示代理服务器的认证状态,这个对象的值能够在本地上下文中进行设置,来覆盖默认值。AuthCache
对象,表示认证数据的缓存,这个对象的值能够在本地上下文中进行设置,来覆盖默认值。咱们能够在请求执行前,自定义本地HttpContext
对象来设置须要的http认证上下文;也能够在请求执行后,再检测HttpContext
的状态,来查看受权是否成功。
从版本4.1开始,HttpClient就会自动缓存验证经过的认证信息。可是为了使用这个缓存的认证信息,咱们必须在同一个上下文中执行逻辑相关的请求。一旦超出该上下文的做用范围,缓存的认证信息就会失效。
HttpClient默认不支持抢先认证,由于一旦抢先认证被误用或者错用,会致使一系列的安全问题,好比会把用户的认证信息以明文的方式发送给未受权的第三方服务器。所以,须要用户本身根据本身应用的具体环境来评估抢先认证带来的好处和带来的风险。
即便如此,HttpClient仍是容许咱们经过配置来启用抢先认证,方法是提早填充认证信息缓存到上下文中,这样,以这个上下文执行的方法,就会使用抢先认证。
从版本4.1开始,HttpClient就全面支持NTLMv一、NTLMv2和NTLM2认证。当人咱们能够仍旧使用外部的NTLM引擎(好比Samba开发的JCIFS库)做为与Windows互操做性程序的一部分。
相比Basic
和Digest
认证,NTLM认证要明显须要更多的计算开销,性 能影响也比较大。这也多是微软把NTLM协议设计成有状态链接的主要缘由之一。也就是说,NTLM链接一旦创建,用户的身份就会在其整个生命周期和它相 关联。NTLM链接的状态性使得链接持久性更加复杂,The stateful nature of NTLM connections makes connection persistence more complex, as for the obvious reason persistent NTLM connections may not be re-used by users with a different user identity. HttpClient中标准的链接管理器就能够管理有状态的链接。可是,同一会话中逻辑相关的请求,必须使用相同的执行上下文,这样才能使用用户的身份信 息。不然,HttpClient就会结束旧的链接,为了获取被NTLM协议保护的资源,而为每一个HTTP请求,建立一个新的Http链接。更新关于 Http状态链接的信息,点击此处。
因为NTLM链接是有状态的,通常推荐使用比较轻量级的方法来处罚NTLM认证(如GET、Head方法),而后使用这个已经创建的链接在执行相对重量级的方法,尤为是须要附件请求实体的请求(如POST、PUT请求)。
SPNEGO(Simple and Protected GSSAPI Megotiation Mechanism),当双方均不知道对方能使用/提供什么协议的状况下,可使用SP认证协议。这种协议在Kerberos认证方案中常用。It can wrap other mechanisms, however the current version in HttpClient is designed solely with Kerberos in mind.
SPNEGO认证方案兼容Sun java 1.5及以上版本。可是强烈推荐jdk1.6以上。Sun的JRE提供的类就已经几乎彻底能够处理Kerberos和SPNEGO token。这就意味着,须要设置不少的GSS类。SpnegoScheme
是个很简单的类,能够用它来handle marshalling the tokens and 读写正确的头消息。
最好的开始方法就是从示例程序中找到KerberosHttpClient.java
这个文件,尝试让它运行起来。运行过程有可能会出现不少问题,可是若是人品比较高可能会顺利一点。这个文件会提供一些输出,来帮咱们调试。
在Windows系统中,应该默认使用用户的登录凭据;固然咱们也可使用kinit
来覆盖这个凭据,好比$JAVA_HOME\bin\kinit testuser@AD.EXAMPLE.NET
,这在咱们测试和调试的时候就显得颇有用了。若是想用回Windows默认的登录凭据,删除kinit建立的缓存文件便可。
确保在krb5.conf文件中列出domain_realms
。这能解决不少没必要要的问题。
下面的这份文档是针对Windows系统的,可是不少信息一样适合Unix。
org.ietf.jgss
这个类有不少的配置参数,这些参数大部分都在krb5.conf/krb5.ini
文件中配置。更多的信息,参考此处。
下面是一个基本的login.conf文件,使用于Windows平台的IIS和JBoss Negotiation模块。
系统配置文件java.security.auth.login.config
能够指定login.conf
文件的路径。login.conf
的内容可能会是下面的样子:
若是没有手动指定,系统会使用默认配置。若是要手动指定,能够在java.security.krb5.conf
中设置系统变量,指定krb5.conf
的路径。krb5.conf
的内容多是下面的样子:
为了容许Windows使用当前用户的tickets,javax.security.auth.useSubjectCredsOnly
这个系统变量应该设置成false
,而且须要在Windows注册表中添加allowtgtsessionkey
这个项,并且要allow session keys to be sent in the Kerberos Ticket-Granting Ticket.
Windows Server 2003和Windows 2000 SP4,配置以下:
Windows XP SP2 配置以下:
HttpClient从4.2开始支持快速api。快速api仅仅实现了HttpClient的基本功能,它只要用于一些不须要灵活性的简单场景。例如,快速api不须要用户处理链接管理和资源释放。
下面是几个使用快速api的例子:
通常状况下,HttpClient的快速api不用用户处理链接管理和资源释放。可是,这样的话,就必须在内存中缓存这些响应消息。为了不这一状况,建议使用使用ResponseHandler来处理Http响应。
HttpClient的缓存机制提供一个与HTTP/1.1标准兼容的缓存层 – 至关于Java的浏览器缓存。HttpClient缓存机制的实现遵循责任链(Chain of Responsibility)设计原则,默认的HttpClient是没有缓存的,有缓存机制的HttpClient能够用来临时替代默认的 HttpClient,若是开启了缓存,咱们的请求结果就会从缓存中获取,而不是从目标服务器中获取。若是在Get请求头中设置了If-Modified-Since
或者If-None-Match
参数,那么HttpClient会自动向服务器校验缓存是否过时。
HTTP/1.1版本的缓存是语义透明的,意思是不管怎样,缓存都不该该修改客户端与服务器之间传输的请求/响应数据包。所以,在 existing compliant client-server relationship中使用带有缓存的HttpClient也应该是安全的。虽然缓存是客户端的一部分,可是从Http协议的角度来看,缓存机制是为 了兼容透明的缓存代理。
最后,HttpClient缓存也支持RFC 5861规定的Cache-Control拓展(stale-if-error'和
stale-while-revalidate`)。
当开启缓存的HttpClient执行一个Http请求时,会通过下面的步骤:
ByteArrayEntity
的BasicHttpResponse
对象,并将它返回给http请求。不然,HttpClient会向服务器从新校验缓存。HttpClient的缓存机制和RFC-2626文档规定是无条件兼容的。也就是说,只要指定了MUST
,MUST NOT
,SHOULD
或者SHOULD NOT
这些Http缓存规范,HttpClient的缓存层就会按照指定的方式进行缓存。即当咱们使用HttpClient的缓存机制时,HttpClient的缓存模块不会产生异常动做。
下面的例子讲述了如何建立一个基本的开启缓存的HttpClient。而且配置了最大缓存1000个Object对象,每一个对象最大占用8192字节数据。代码中出现的数据,只是为了作演示,而过不是推荐使用的配置。
有缓存的HttpClient继承了非缓存HttpClient的全部配置项和参数(包括超时时间,链接池大小等配置项)。若是须要对缓存进行具体配置,能够初始化一个CacheConfig
对象来自定义下面的参数:
Cache size
(缓存大小). 若是后台存储支持,咱们能够指定缓存的最大条数,和每一个缓存中存储的response的最大size。Public/private cacheing
(公用/私有 缓存). 默认状况下,缓存模块会把缓存当作公用的缓存,因此缓存机制不会缓存带有受权头消息或者指定Cache-Control:private
的响应。可是若是缓存只会被一个逻辑上的用户使用(和浏览器饿缓存相似),咱们可能但愿关闭缓存共享机制。Heuristic caching
(启发式缓存)。即便服务器没有明确设置缓存控制headers信 息,每一个RFC2616缓存也会存储必定数目的缓存。这个特征在HttpClient中默认是关闭的,若是服务器不设置控制缓存的header信息,可是 咱们仍然但愿对响应进行缓存,就须要在HttpClient中打开这个功能。激活启发式缓存,而后使用默认的刷新时间或者自定义刷新时间。更多启发式缓存 的信息,能够参考Http/1.1 RFC文档的13.2.2小节,13.2.4小节。Background validation
(后台校验)。HttpClient的缓存机制支持RFC5861的stale-while-revalidate
指令,它容许必定数目的缓存在后台校验是否过时。咱们可能须要调整能够在后台工做的最大和最小的线程数,以及设置线程在回收前最大的空闲时间。当没有足够线程来校验缓存是否过时时,咱们能够指定排队队列的大小。默认,HttpClient缓存机制将缓存条目和缓存的response放在本地程序的jvm内存中。这样虽然提供高性能,可是当咱们 的程序内存有大小限制的时候,这就会变得不太合理。由于缓存的生命中期很短,若是程序重启,缓存就会失效。当前版本的HttpClient使用 EhCache和memchached来存储缓存,这样就支持将缓存放到本地磁盘或者其余存储介质上。若是内存、本地磁盘、外地磁盘,都不适合你的应用程 序,HttpClient也支持自定义存储介质,只须要实现HttpCacheStorage
接口,而后在建立 HttpClient时,使用这个接口的配置。这种状况,缓存会存储在自定义的介质中,可是you will get to reuse all of the logic surrounding HTTP/1.1 compliance and cache handling. 通常来讲,能够建立出支持任何键值对指定存储(相似Java Map接口)的HttpCacheStorage
,用于进行原子更新。
最后,经过一些额外的工做,还能够创建起多层次的缓存结构;磁盘中的缓存,远程memcached中的缓存,虚拟内存中的缓存,L1/L2处理器中的缓存等。
在特定条件下,也许须要来定制HTTP报文经过线路传递,越过了可能使用的HTTP参数来处理非标准不兼容行为的方式。好比,对于Web爬虫,它可能须要强制HttpClient接受格式错误的响应头部信息,来抢救报文的内容。
一般插入一个自定义的报文解析器的过程或定制链接实现须要几个步骤:
提供一个自定义LineParser/LineFormatter接口实现。若是须要,实现报文解析/格式化逻辑。
提过一个自定义的 HttpConnectionFactory 实现。替换须要自定义的默认请求/响应解析器,请求/响应格式化器。若是须要,实现不一样的报文写入/读取代码。
为了建立新类的链接,提供一个自定义的ClientConnectionOperator接口实现。若是须要,实现不一样的套接字初始化代码。
若是它能够从给定的执行上下文中来得到,UserTokenHandler接口的默认实现是使用主类的一个实例来表明HTTP链接的状 态对象。UserTokenHandler将会使用基于如NTLM或开启的客户端认证SSL会话认证模式的用户的主链接。若是两者都不可用,那么就不会返 回令牌。
FutureRequestExecutionService用HttpRequestFutureTask(继承FutureTask)包装request。这个类容许你取消Task以及保持跟踪各项指标,如request duration。
futureRequestExecutionService的构造方法包括两个参数:httpClient实例和 ExecutorService实例。当配置两个参数的时候,您要使用的线程数等于最大链接数是很重要的。当线程比链接多的时候,链接可能会开始超时,因 为没有可用的链接。当链接多于线程时,futureRequestExecutionService不会使用全部的链接。
要安排一个请求,只需提供一个HttpUriRequest,HttpContext和ResponseHandler。由于request是由executor service处理的,而ResponseHandler的是强制性的。
预约的任务可能会被取消。若是任务还没有执行,但仅仅是排队等待执行,它根本就不会执行。若是任务在执行中且 mayInterruptIfRunning参数被设置为true,请求中的abort()函数将被调用;不然response会简单地忽略,但该请求将 被容许正常完成。任何后续调用task.get()会产生一个IllegalStateException。应当注意到,取消任务仅能够释放客户端的资 源。该请求可能其实是在服务器端正常处理。
不用手动调用task.get(),您也能够在请求完成时使用FutureCallback实例获取回调。这里采用的是和HttpAsyncClient相同的接口
FutureRequestExecutionService一般用于大量Web服务调用的应用程序之中。为了便于例如监视或配置调整,FutureRequestExecutionService跟踪了几个指标。
HttpRequestFutureTask会提供一些方法来得到任务时间:从被安排,开始,直到结束。此外,请求和任务持续时间也是 可用的。这些指标都汇集在FutureRequestExecutionService中的FutureRequestExecutionMetrics 实例,能够经过FutureRequestExecutionService.metrics()获取。