protected ThreadModel[] readWakabaPage(
      String url,
      ProgressListener listener,
      CancellableTask task,
      boolean checkModified,
      UrlPageModel urlModel)
      throws Exception {
    HttpResponseModel responseModel = null;
    WakabaReader in = null;
    HttpRequestModel rqModel =
        HttpRequestModel.builder()
            .setGET()
            .setCheckIfModified(checkModified)
            .setNoRedirect(wakabaNoRedirect())
            .build();
    try {
      responseModel =
          HttpStreamer.getInstance().getFromUrl(url, rqModel, httpClient, listener, task);
      if (responseModel.statusCode == 200) {
        in = getWakabaReader(responseModel.stream, urlModel);
        if (task != null && task.isCancelled()) throw new Exception("interrupted");
        return in.readWakabaPage();
      } else {
        if (responseModel.notModified()) return null;

        if (canCloudflare()) {
          String html = null;
          try {
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
            IOUtils.copyStream(responseModel.stream, byteStream);
            html = byteStream.toString("UTF-8");
          } catch (Exception e) {
          }
          if (html != null) {
            if (responseModel.statusCode == 403 && html.contains("CAPTCHA")) {
              throw CloudflareException.withRecaptcha(
                  CLOUDFLARE_RECAPTCHA_KEY,
                  getUsingUrl() + CLOUDFLARE_RECAPTCHA_CHECK_URL_FMT,
                  CLOUDFLARE_COOKIE_NAME,
                  getChanName());
            } else if (responseModel.statusCode == 503 && html.contains("Just a moment...")) {
              throw CloudflareException.antiDDOS(url, CLOUDFLARE_COOKIE_NAME, getChanName());
            }
          }
        }
        throw new HttpWrongStatusCodeException(
            responseModel.statusCode,
            responseModel.statusCode + " - " + responseModel.statusReason);
      }
    } catch (Exception e) {
      if (responseModel != null) HttpStreamer.getInstance().removeFromModifiedMap(url);
      throw e;
    } finally {
      IOUtils.closeQuietly(in);
      if (responseModel != null) responseModel.release();
    }
  }
 /**
  * Создать новый экземпляр cloudflare-исключения (обычная js-antiddos проверка, без рекапчи).
  *
  * @param url адрес, по которому вызвана проверка
  * @param cfCookieName название cloudflare-куки
  * @param chanName название модуля чана (модуль должен имплементировать {@link HttpChanModule})
  * @return созданный объект
  */
 public static CloudflareException antiDDOS(String url, String cfCookieName, String chanName) {
   CloudflareException e = new CloudflareException();
   e.url = url;
   e.recaptcha = false;
   e.publicKey = null;
   e.checkCaptchaUrlFormat = null;
   e.cfCookieName = cfCookieName;
   e.chanName = chanName;
   return e;
 }
 /**
  * Создать новый экземпляр cloudflare-исключения (проверка с рекапчей).
  *
  * @param publicKey открытый ключ рекапчи
  * @param checkUrlFormat строка-формат URL для проверки капчи (первый %s - challenge, второй %s -
  *     ответ на капчу)
  * @param cfCookieName название cloudflare-куки
  * @param chanName название модуля чана
  * @return созданный объект
  */
 public static CloudflareException withRecaptcha(
     String publicKey, String checkUrlFormat, String cfCookieName, String chanName) {
   CloudflareException e = new CloudflareException();
   e.url = null;
   e.recaptcha = true;
   e.publicKey = publicKey;
   e.checkCaptchaUrlFormat = checkUrlFormat;
   e.cfCookieName = cfCookieName;
   e.chanName = chanName;
   return e;
 }