【问题标题】:Apache HttpClient timeoutApache HttpClient 超时
【发布时间】:2011-10-09 11:49:46
【问题描述】:

有没有办法为HttpClient的整个执行指定超时?

我尝试了以下方法:

httpClient.getParams().setParameter("http.socket.timeout", timeout * 1000);
httpClient.getParams().setParameter("http.connection.timeout", timeout * 1000);
httpClient.getParams().setParameter("http.connection-manager.timeout", new Long(timeout * 1000));
httpClient.getParams().setParameter("http.protocol.head-body-timeout", timeout * 1000);

它实际上工作得很好,除非远程主机发回数据——即使是一字节/秒——它将永远继续读取!但我想在最多 10 秒内中断连接,无论主机是否响应。

【问题讨论】:

  • @RealMan 的回答对我有用。
  • 如果是异步的,有超时。但它是同步的,所以没有办法按照HttpClient 的方式。

标签: java timeout apache-httpclient-4.x


【解决方案1】:

对于较新版本的 httpclient(例如 http 组件 4.3 - https://hc.apache.org/httpcomponents-client-4.3.x/index.html):

int CONNECTION_TIMEOUT_MS = timeoutSeconds * 1000; // Timeout in millis.
RequestConfig requestConfig = RequestConfig.custom()
    .setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS)
    .setConnectTimeout(CONNECTION_TIMEOUT_MS)
    .setSocketTimeout(CONNECTION_TIMEOUT_MS)
    .build();

HttpPost httpPost = new HttpPost(URL);
httpPost.setConfig(requestConfig);

【讨论】:

  • 添加了单位,我讨厌它只是说setTimeout,应该是setTimeoutMs,确实是毫秒。
  • 此设置是否允许整个请求生命周期> CONNECTION_TIMEOUT_MS?您在请求的多个阶段指定了相同的超时时间。
  • 我认为这不是原始问题的正确答案。连接请求超时状态的文档。 返回从连接管理器请求连接时使用的超时时间(以毫秒为单位) 这是不是执行请求的总时间,只是为了从连接管理器获取连接。因此,如果服务器返回 1 btye/s ,正如 OP 要求的那样,这很容易超过连接请求超时。
  • 同意,这不是给定问题的解决方案。如果请求不断从套接字读取数据包,则请求永远不会超时。问题是关于硬超时。
【解决方案2】:

目前没有办法设置那种最大请求持续时间:基本上你想说我不在乎任何特定的请求阶段是否超时,但是整个请求的持续时间不得超过 15 秒(例如)。

最好的办法是运行一个单独的计时器,当它到期时,获取 HttpClient 实例使用的连接管理器并关闭连接,这应该会终止链接。让我知道这是否适合你。

【讨论】:

  • 是的,可能不得不这样做:定时器应该在一个线程中自己运行。
  • 这值得一个功能请求吗?这似乎是一个常见的用例
  • 在这里您可以找到此类解决方案的实现,请参阅baeldung.com/httpclient-timeout#hard_timeout
【解决方案3】:

按照 Femi 的建议,工作正常。谢谢!

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    public void run() {
        if(getMethod != null) {
            getMethod.abort();
        }
    }
}, timeout * 1000);

【讨论】:

  • 需要一个锁,以避免 NPE。 getMethod 可以在检查和调用 abort 之间变为 null!
  • @TonyBenBrahim 你能举例说明如何添加这个锁吗?
  • @Turbo: synchronized(foo){ ... getMethod=null; } synchronized(foo){ if (getMethod!=null){ getMethod.abort(); } }
  • @TonyBenBrahim 对此表示感谢。我是同步新手。 getMethod=null; 部分只是使用HttpClient 的方法的一个例子吗?也就是说,getMethod 不会故意设置为null,但在该方法退出时可能会变为null,因此它应该与Timer 线程同步(在同一个对象上)。
  • 此解决方案不适用于 prod。请记住,每个计时器都会旋转一个线程。每分钟 1000/req 将每分钟旋转 1000 个线程。这将继续堆积,直到您因内存不足而死亡
【解决方案4】:

定时器是邪恶的! 使用计时器或执行器或任何其他为每个请求创建线程/可运行对象的机制是一个非常糟糕的主意。请明智地思考,不要这样做。否则,您将很快遇到或多或少真实环境的各种内存问题。想象一下 1000 req/min 意味着 1000 个线程或工作线程/分钟。可怜的GC。我提出的解决方案只需要 1 个看门狗线程,可以节省资源时间和精力。 基本上你做了 3 个步骤。

  1. 将请求放入缓存中。
  2. 完成后从缓存中删除请求。
  3. 中止未在您的限制范围内完成的请求。

您的缓存和看门狗线程可能如下所示。

import org.apache.http.client.methods.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;

public class RequestCache {

private static final long expireInMillis = 300000;
private static final Map<HttpUriRequest, Long> cache = new ConcurrentHashMap<>();
private static final ScheduledExecutorService exe = Executors.newScheduledThreadPool(1);

static {
    // run clean up every N minutes
    exe.schedule(RequestCache::cleanup, 1, TimeUnit.MINUTES);
}

public static void put(HttpUriRequest request) {
    cache.put(request, System.currentTimeMillis()+expireInMillis);
}

public static void remove(HttpUriRequest request) {
    cache.remove(request);
}

private static void cleanup() {
    long now = System.currentTimeMillis();
    // find expired requests
    List<HttpUriRequest> expired = cache.entrySet().stream()
            .filter(e -> e.getValue() > now)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

    // abort requests
    expired.forEach(r -> {
        if (!r.isAborted()) {
            r.abort();
        }
        cache.remove(r);
      });
    }
  }

以及下面的sudo代码如何使用缓存

import org.apache.http.client.methods.*;

public class RequestSample {

public void processRequest() {
    HttpUriRequest req = null;
    try {
        req = createRequest();

        RequestCache.put(req);

        execute(req);

    } finally {
        RequestCache.remove(req);
    }
  }
}

【讨论】:

    【解决方案5】:

    在其他答案的基础上,我的解决方案是使用 HttpRequestInterceptor 将 abort runnable 添加到每个请求中。我还把Timer 换成了ScheduledExecutorService

    public class TimeoutInterceptor implements HttpRequestInterceptor {
    
    private int requestTimeout = 1 * DateTimeConstants.MILLIS_PER_MINUTE;
    
    private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    
    public TimeoutInterceptor() {  }
    
    public TimeoutInterceptor(final int requestTimeout) {
        this.requestTimeout = requestTimeout;
    }
    
    @Override
    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
        if (request instanceof AbstractExecutionAwareRequest) {
            final AbstractExecutionAwareRequest abortableRequest = (AbstractExecutionAwareRequest) request;
            setAbort(abortableRequest);
        } else if (request instanceof HttpRequestWrapper) {
            HttpRequestWrapper wrapper = (HttpRequestWrapper) request;
            this.process(wrapper.getOriginal(), context);
        }
    
    }
    
    /**
     * @param abortableRequest
     */
    private void setAbort(final AbstractExecutionAwareRequest abortableRequest) {
        final SoftReference<AbstractExecutionAwareRequest> requestRef = new SoftReference<AbstractExecutionAwareRequest>(abortableRequest);
    
        executorService.schedule(new Runnable() {
    
            @Override
            public void run() {
                AbstractExecutionAwareRequest actual = requestRef.get();
                if (actual != null && !actual.isAborted()) {
                    actual.abort();
                }
            }
        }, requestTimeout, TimeUnit.MILLISECONDS);
    
    }
    
    public void setRequestTimeout(final int requestTimeout) {
        this.requestTimeout = requestTimeout;
    }
    }
    

    【讨论】:

    • 这是可行的解决方案,但您需要为每个请求创建一个可运行的。这很快就变成了真正的大容量环境中的一个问题。每分钟 1000 个请求将导致每分钟 1000 个可运行。 Runnables 将被排队。确实会遇到各种导致服务失败的问题。
    【解决方案6】:

    在 HttpClient 4.3 版本中,您可以使用下面的示例.. 说 5 秒

    int timeout = 5;
    RequestConfig config = RequestConfig.custom()
      .setConnectTimeout(timeout * 1000)
      .setConnectionRequestTimeout(timeout * 1000)
      .setSocketTimeout(timeout * 1000).build();
    CloseableHttpClient client = 
      HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    HttpGet request = new HttpGet("http://localhost:8080/service"); // GET Request
    response = client.execute(request);
    

    【讨论】:

      【解决方案7】:

      这也行:

      HttpClient client = new DefaultHttpClient();
      HttpConnectionParams.setConnectionTimeout(client.getParams(), timeout * 1000);
      HttpConnectionParams.setSoTimeout(client.getParams(), timeout * 1000);
      

      【讨论】:

      • 实际上,这正是他在问题中所做的,所以这显然对他不起作用(不幸的是我也不适合)。
      猜你喜欢
      • 2011-08-26
      • 1970-01-01
      • 2019-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-19
      相关资源
      最近更新 更多