企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# httpclient6 <div><div><div style="font-family:Verdana, Arial, Tahoma, sans-serif;font-size:large;font-weight:bold;text-align:left;"><span style="font-size:19px;">第六章 高级主题</span></div><div style="font-family:Verdana, Arial, Tahoma, sans-serif;font-size:9.5pt;"><h3 style="font-family:Verdana, Arial, Tahoma, sans-serif;">6.1 自定义客户端连接</h3><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">在特定条件下,也许需要来定制HTTP报文通过线路传递,越过了可能使用的HTTP参数来处理非标准不兼容行为的方式。比如,对于Web爬虫,它可能需要强制HttpClient接受格式错误的响应头部信息,来抢救报文的内容。</p><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">通常插入一个自定义的报文解析器的过程或定制连接实现需要几个步骤:</p><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">提供一个自定义LineParser/LineFormatter接口实现。如果需要,实现报文解析/格式化逻辑。</p><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">class MyLineParser extends BasicLineParser {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">@Override</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">public Header parseHeader(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final CharArrayBuffer buffer) throws ParseException {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">try {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return super.parseHeader(buffer);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">} catch (ParseException ex) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">// 压制ParseException异常</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return new BasicHeader("invalid", buffer.toString());</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div></blockquote><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">提过一个自定义的OperatedClientConnection实现。替换需要自定义的默认请求/响应解析器,请求/响应格式化器。如果需要,实现不同的报文写入/读取代码。</p><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">class MyClientConnection extends DefaultClientConnection {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">@Override</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">protected HttpMessageParser createResponseParser(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final SessionInputBuffer buffer,</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final HttpResponseFactory responseFactory,</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final HttpParams params) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return new DefaultResponseParser(buffer,</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">new MyLineParser(),responseFactory,params);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div></blockquote><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">为了创建新类的连接,提供一个自定义的ClientConnectionOperator接口实现。如果需要,实现不同的套接字初始化代码。</p><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">class MyClientConnectionOperator extends</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">DefaultClientConnectionOperator {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">public MyClientConnectionOperator(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final SchemeRegistry sr) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">super(sr);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">@Override</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">public OperatedClientConnection createConnection() {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return new MyClientConnection();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div></blockquote><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">为了创建新类的连接操作,提供自定义的ClientConnectionManager接口实现。</p><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">class MyClientConnManager extends SingleClientConnManager {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">public MyClientConnManager(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final HttpParams params,</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final SchemeRegistry sr) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">super(params, sr);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">@Override</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">protected ClientConnectionOperator createConnectionOperator(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">final SchemeRegistry sr) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return new MyClientConnectionOperator(sr);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div></blockquote><h3 style="font-family:Verdana, Arial, Tahoma, sans-serif;">6.2 有状态的HTTP连接</h3><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HTTP 规范假设session状态信息通常是以HTTP cookie格式嵌入在HTTP报文中的,因此HTTP连接通常是无状态的,这个假设在现实生活中通常是不对的。也有一些情况,当HTTP连接使用特定的 用户标识或特定的安全上下文来创建时,因此不能和其它用户共享,只能由该用户重用。这样的有状态的HTTP连接的示例就是NTLM认证连接和使用客户端证 书认证的SSL连接。</div><h4 style="font-family:Verdana, Arial, Tahoma, sans-serif;">6.2.1 用户令牌处理器</h4><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpClient 依赖UserTokenHandler接口来决定给定的执行上下文是否是用户指定的。如果这个上下文是用户指定的或者如果上下文没有包含任何资源或关于当 前用户指定详情而是null,令牌对象由这个处理器返回,期望唯一地标识当前的用户。用户令牌将被用来保证用户指定资源不会和其它用户来共享或重用。</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;"><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">如 果它可以从给定的执行上下文中来获得,UserTokenHandler接口的默认实现是使用主类的一个实例来代表HTTP连接的状态对象。 UserTokenHandler将会使用基于如NTLM或开启的客户端认证SSL会话认证模式的用户的主连接。如果二者都不可用,那么就不会返回令牌。</p><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">如果默认的不能满足它们的需要,用户可以提供一个自定义的实现:</div><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">DefaultHttpClient httpclient = new DefaultHttpClient();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">httpclient.setUserTokenHandler(new UserTokenHandler() {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">public Object getUserToken(HttpContext context) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">return context.getAttribute("my-token");</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">});</div></blockquote><h4 style="font-family:Verdana, Arial, Tahoma, sans-serif;">6.2.2 用户令牌和执行上下文</h4><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">在HTTP请求执行的过程中,HttpClient添加了下列和用户标识相关的对象到执行上下文中:</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;"><p style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">'http.user-token':对象实例代表真实的用户标识,通常期望Principle接口的实例。</p><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">我们可以在请求被执行后,通过检查本地HTTP上下文的内容,发现是否用于执行请求的连接是有状态的。</div><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">DefaultHttpClient httpclient = new DefaultHttpClient();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpContext localContext = new BasicHttpContext();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpGet httpget = new HttpGet("http://localhost:8080/");</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpResponse response = httpclient.execute(httpget, localContext);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpEntity entity = response.getEntity();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">if (entity != null) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">entity.consumeContent();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">Object userToken = localContext.getAttribute(ClientContext.USER_TOKEN);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">System.out.println(userToken);</div></blockquote><h5 style="font-family:Verdana, Arial, Tahoma, sans-serif;">6.2.2.1 持久化有状态的连接</h5><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">请注意带有状态对象的持久化连接仅当请求被执行时,相同状态对象被绑定到执行上下文时可以被重用。所以,保证相同上下文重用于执行随后的相同用户,或用户令牌绑定到之前请求执行上下文的HTTP请求是很重要的。</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;"><blockquote style="background-color:#f9f9ff;font-family:Verdana, Arial, Tahoma, sans-serif;"><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">DefaultHttpClient httpclient = new DefaultHttpClient();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpContext localContext1 = new BasicHttpContext();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpGet httpget1 = new HttpGet("http://localhost:8080/");</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpResponse response1 = httpclient.execute(httpget1, localContext1);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpEntity entity1 = response1.getEntity();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">if (entity1 != null) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">entity1.consumeContent();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">Principal principal = (Principal) localContext1.getAttribute(</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">ClientContext.USER_TOKEN);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpContext localContext2 = new BasicHttpContext();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">localContext2.setAttribute(ClientContext.USER_TOKEN, principal);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpGet httpget2 = new HttpGet("http://localhost:8080/");</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpResponse response2 = httpclient.execute(httpget2, localContext2);</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">HttpEntity entity2 = response2.getEntity();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">if (entity2 != null) {</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">entity2.consumeContent();</div><div style="color:inherit;font-family:inherit;font-size:inherit;font-style:inherit;">}</div></blockquote></div></div></div></div></div><br></div>