/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.http.impl; import io.vertx.core.Handler; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpMethod; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import java.util.concurrent.TimeoutException; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ abstract class HttpClientRequestBase implements HttpClientRequest { private static final Logger log = LoggerFactory.getLogger(HttpClientRequestImpl.class); protected final HttpClientImpl client; protected final io.vertx.core.http.HttpMethod method; protected final String uri; protected final String path; protected final String host; protected final int port; protected final String query; protected final boolean ssl; private Handler<Throwable> exceptionHandler; private long currentTimeoutTimerId = -1; private long currentTimeoutMs; private long lastDataReceived; protected Throwable exceptionOccurred; private Object metric; HttpClientRequestBase(HttpClientImpl client, boolean ssl, HttpMethod method, String host, int port, String uri) { this.client = client; this.uri = uri; this.method = method; this.host = host; this.port = port; this.path = uri.length() > 0 ? HttpUtils.parsePath(uri) : ""; this.query = HttpUtils.parseQuery(uri); this.ssl = ssl; } Object metric() { return metric; } void metric(Object metric) { this.metric = metric; } protected abstract Object getLock(); protected abstract void doHandleResponse(HttpClientResponseImpl resp, long timeoutMs); protected abstract void checkComplete(); protected String hostHeader() { if ((port == 80 && !ssl) || (port == 443 && ssl)) { return host; } else { return host + ':' + port; } } @Override public String absoluteURI() { return (ssl ? "https://" : "http://") + hostHeader() + uri; } public String query() { return query; } public String path() { return path; } public String uri() { return uri; } @Override public io.vertx.core.http.HttpMethod method() { return method; } @Override public HttpClientRequest exceptionHandler(Handler<Throwable> handler) { synchronized (getLock()) { if (handler != null) { checkComplete(); this.exceptionHandler = handler; } else { this.exceptionHandler = null; } return this; } } Handler<Throwable> exceptionHandler() { synchronized (getLock()) { return exceptionHandler; } } @Override public HttpClientRequest setTimeout(long timeoutMs) { synchronized (getLock()) { cancelOutstandingTimeoutTimer(); currentTimeoutMs = timeoutMs; currentTimeoutTimerId = client.getVertx().setTimer(timeoutMs, id -> handleTimeout(timeoutMs)); return this; } } public void handleException(Throwable t) { synchronized (getLock()) { cancelOutstandingTimeoutTimer(); exceptionOccurred = t; if (exceptionHandler != null) { exceptionHandler.handle(t); } else { log.error(t); } } } void handleResponse(HttpClientResponseImpl resp) { synchronized (getLock()) { // If an exception occurred (e.g. a timeout fired) we won't receive the response. if (exceptionOccurred == null) { long timeoutMS = currentTimeoutMs; cancelOutstandingTimeoutTimer(); try { doHandleResponse(resp, timeoutMS); } catch (Throwable t) { handleException(t); } } } } private void cancelOutstandingTimeoutTimer() { if (currentTimeoutTimerId != -1) { client.getVertx().cancelTimer(currentTimeoutTimerId); currentTimeoutTimerId = -1; currentTimeoutMs = 0; } } private void handleTimeout(long timeoutMs) { if (lastDataReceived == 0) { timeout(timeoutMs); } else { long now = System.currentTimeMillis(); long timeSinceLastData = now - lastDataReceived; if (timeSinceLastData >= timeoutMs) { timeout(timeoutMs); } else { // reschedule lastDataReceived = 0; setTimeout(timeoutMs - timeSinceLastData); } } } private void timeout(long timeoutMs) { handleException(new TimeoutException("The timeout period of " + timeoutMs + "ms has been exceeded while executing " + method + " " + uri + " for host " + host)); } void handleResponseEnd() { } void dataReceived() { synchronized (getLock()) { if (currentTimeoutTimerId != -1) { lastDataReceived = System.currentTimeMillis(); } } } }