webMagic学习系列:downloader模块学习
2021-03-03 23:28
标签:catch 包括 url tab ble 设计 配置 converter ntc 本篇主要剖析webmagic的downloader模块,对于httpclient模块涉及到的http相关的知识,例如:Request、Response以及重定向策略进行一定的分析。首先梳理了本模块的结构、然后对于执行流程进行了分析,最后对于其中涉及的设计模式:单例模式和相关算法进行了代码分析。 downloader涉及到的类和接口主要如下表所示: 首先来看具体的downloade代码: 可以看到主要的代码流程还是很清晰的,首先得到配置好的httpClient,这是通过 下面个用伪代码描述上面的流程: 可以看到downloade函数实际上关键的核心代码就是httpClinet的execute方法,其他的代码统一都可以抽象成准备工作。 httpClient初试化实际上涉及了一系列的参数配置,包括使用到的socket的参数配置,以及http一些连接配置,由于涉及到的参数非常多,对于socket的参数配置和httpClinet均使用到了Builder模式。具体的代码代码如下: 可以看到实际上就是根据站点来配置client参数的过程,也就是说,我们可以将一些自定义参数放置到Site实例中,这样就可以将参数填入了。这实际上也是我么常用的初始化策略,当参数众多时,我们抽象出相关的配置类,这样可以将参数集中管理起来,实现代码的结构化。 在第一节中我们提到,httpClinet使用了单例模式,下面我们看具体的实现过程: 可以看到代码的关键部分如下: 也就是代码判断了两次单例是否为空,第一次判断为空,然后加锁进行单例的判断,这个比较容易理解,但是第二次再次判断是为什么呢,我们设想如下情况: 当前单例未被创建,所以httpClient为null,线程一判断结果为空后还未加锁,此时进行了线程的切换,线程2得到了执行权,此时由于线程1为创建实例,所以线程2会创建一个实例出来。然后再切回线程1执行,由于之前线程1判断了httpClient为空,然后取得锁,此时仍进行了实例的创建。也就不满足单例模式了。所以第二次的再次判空时必要的。只有这样才能保证即使多线程也能创建唯一的实例。 webMagic学习系列:downloader模块学习 标签:catch 包括 url tab ble 设计 配置 converter ntc 原文地址:https://www.cnblogs.com/zhangshoulei/p/13270044.html摘要:
0x00:downloader的模块结构
类名称
作用
方法说明
备注
Downloader
定义downloader接口规范
downloade(r:Request,t:Task):Page
接口
AbstractDownloader
定义downloader状态接口
onSuccess(),onError(),@Overdide:downloade()
抽象类,
HttpClientDownloader
具体的下载接口
继承自AbstractDownloader
具体类
CustomRedirectStrategy
定义重定向策略
HttpClientGenerator
配置httpCliet的辅助类
getHttpClient(s:Site):HttpClient
HttpClientRequestContext
数据类
存储requestcontext和clinetcontext
HttpUriRequestConverter
配置Request的辅助类
convert(r:Request,s:Site,p:Proxy):Request
ox01:downloade的具体执行逻辑
@Override
public Page download(Request request, Task task) {
if (task == null || task.getSite() == null) {
throw new NullPointerException("task or site can not be null");
}
CloseableHttpResponse httpResponse = null;
CloseableHttpClient httpClient = getHttpClient(task.getSite());
Proxy proxy = proxyProvider != null ? proxyProvider.getProxy(task) : null;
HttpClientRequestContext requestContext = httpUriRequestConverter.convert(request, task.getSite(), proxy);
Page page = Page.fail();
try {
httpResponse = httpClient.execute(requestContext.getHttpUriRequest(), requestContext.getHttpClientContext());
page = handleResponse(request, request.getCharset() != null ? request.getCharset() : task.getSite().getCharset(), httpResponse, task);
onSuccess(request);
logger.info("downloading page success {}", request.getUrl());
return page;
} catch (IOException e) {
logger.warn("download page {} error", request.getUrl(), e);
onError(request);
return page;
} finally {
if (httpResponse != null) {
//ensure the connection is released back to pool
EntityUtils.consumeQuietly(httpResponse.getEntity());
}
if (proxyProvider != null && proxy != null) {
proxyProvider.returnProxy(proxy, page, task);
}
}
}
getClient()
方法得到的,这个方法具体涉及到设计模式中的单例,我们稍后再详细讲,然后根据传递过来的Request得到RequestContext和ClinetContext,根据执行httlClient的execute方法,这个方法就是具体的向服务端发送资源请求的方法,该方法会将服务器的资源封装到Response对象中。最后将Request和Response封装到Page中去,供后续的PageProcessor使用。fun download(r:Requst,t:Task):Page
httpClient = getClient(t.site())
context = convert(r,t.site(),proxy)
response = httpClient.execute(context.requestContext,context.clinetContext)
page = handle(r,response)
return page
0x02:初始化策略
private CloseableHttpClient generateClient(Site site) {
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setConnectionManager(connectionManager);
if (site.getUserAgent() != null) {
httpClientBuilder.setUserAgent(site.getUserAgent());
} else {
httpClientBuilder.setUserAgent("");
}
if (site.isUseGzip()) {
httpClientBuilder.addInterceptorFirst(new HttpRequestInterceptor() {
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
if (!request.containsHeader("Accept-Encoding")) {
request.addHeader("Accept-Encoding", "gzip");
}
}
});
}
//解决post/redirect/post 302跳转问题
httpClientBuilder.setRedirectStrategy(new CustomRedirectStrategy());
SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();
socketConfigBuilder.setSoKeepAlive(true).setTcpNoDelay(true);
socketConfigBuilder.setSoTimeout(site.getTimeOut());
SocketConfig socketConfig = socketConfigBuilder.build();
httpClientBuilder.setDefaultSocketConfig(socketConfig);
connectionManager.setDefaultSocketConfig(socketConfig);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(site.getRetryTimes(), true));
generateCookie(httpClientBuilder, site);
return httpClientBuilder.build();
}
ox03:单例模式
private CloseableHttpClient getHttpClient(Site site) {
if (site == null) {
return httpClientGenerator.getClient(null);
}
String domain = site.getDomain();
CloseableHttpClient httpClient = httpClients.get(domain);
if (httpClient == null) {
synchronized (this) {
httpClient = httpClients.get(domain);
if (httpClient == null) {
httpClient = httpClientGenerator.getClient(site);
httpClients.put(domain, httpClient);
}
}
}
return httpClient;
}
if(httpClient == null) {
synchronized(this) {
if(httpClinet == null) {
htttpClinet = httpClinetGenerator.getClinet();
}
}
}