Skip to content
Commits on Source (4)
......@@ -25,12 +25,12 @@
<parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-parent</artifactId>
<version>1.4.23.Final</version>
<version>1.4.25.Final</version>
</parent>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>1.4.23.Final</version>
<version>1.4.25.Final</version>
<name>Undertow Core</name>
......
......@@ -263,7 +263,12 @@ public final class Undertow {
* Only shutdown the worker if it was created during start()
*/
if (internalWorker && worker != null) {
worker.shutdownNow();
worker.shutdown();
try {
worker.awaitTermination();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
worker = null;
}
xnio = null;
......
......@@ -19,6 +19,7 @@
package io.undertow.attribute;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import java.util.concurrent.TimeUnit;
......@@ -29,6 +30,8 @@ import java.util.concurrent.TimeUnit;
*/
public class ResponseTimeAttribute implements ExchangeAttribute {
private static final AttachmentKey<Long> FIRST_RESPONSE_TIME_NANOS = AttachmentKey.create(Long.class);
public static final String RESPONSE_TIME_MILLIS_SHORT = "%D";
public static final String RESPONSE_TIME_SECONDS_SHORT = "%T";
public static final String RESPONSE_TIME_MILLIS = "%{RESPONSE_TIME}";
......@@ -47,7 +50,17 @@ public class ResponseTimeAttribute implements ExchangeAttribute {
if(requestStartTime == -1) {
return null;
}
final long nanos = System.nanoTime() - requestStartTime;
final long nanos;
Long first = exchange.getAttachment(FIRST_RESPONSE_TIME_NANOS);
if(first != null) {
nanos = first;
} else {
nanos = System.nanoTime() - requestStartTime;
if(exchange.isResponseComplete()) {
//save the response time so it is consistent
exchange.putAttachment(FIRST_RESPONSE_TIME_NANOS, nanos);
}
}
if(timeUnit == TimeUnit.SECONDS) {
StringBuilder buf = new StringBuilder();
long milis = TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS);
......
......@@ -437,8 +437,10 @@ class HttpClientConnection extends AbstractAttachable implements Closeable, Clie
}
private void handleError(IOException exception) {
UndertowLogger.REQUEST_IO_LOGGER.ioException(exception);
safeClose(connection);
currentRequest.setFailed(exception);
currentRequest = null;
pendingResponse = null;
safeClose(connection);
}
public StreamConnection performUpgrade() throws IOException {
......
......@@ -137,6 +137,7 @@ public class ChunkedStreamSinkConduit extends AbstractStreamSinkConduit<StreamSi
}
this.state |= FLAG_FIRST_DATA_WRITTEN;
int oldLimit = src.limit();
boolean dataRemaining = false; //set to true if there is data in src that still needs to be written out
if (chunkleft == 0 && !chunkingSepBuffer.hasRemaining()) {
chunkingBuffer.clear();
putIntAsHexString(chunkingBuffer, src.remaining());
......@@ -149,6 +150,7 @@ public class ChunkedStreamSinkConduit extends AbstractStreamSinkConduit<StreamSi
chunkleft = src.remaining();
} else {
if (src.remaining() > chunkleft) {
dataRemaining = true;
src.limit(chunkleft + src.position());
}
}
......@@ -158,7 +160,7 @@ public class ChunkedStreamSinkConduit extends AbstractStreamSinkConduit<StreamSi
if (chunkingSize > 0 || chunkingSepSize > 0 || lastChunkBuffer != null) {
int originalRemaining = src.remaining();
long result;
if (lastChunkBuffer == null) {
if (lastChunkBuffer == null || dataRemaining) {
final ByteBuffer[] buf = new ByteBuffer[]{chunkingBuffer, src, chunkingSepBuffer};
result = next.write(buf, 0, buf.length);
} else {
......
......@@ -439,7 +439,9 @@ public class DeflatingStreamSinkConduit implements StreamSinkConduit {
if (additionalBuffer != null) {
remaining += additionalBuffer.remaining();
}
if(!exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) {
exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Integer.toString(remaining));
}
} else {
exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH);
}
......
......@@ -120,11 +120,11 @@ public class AsyncSenderImpl implements Sender {
throw UndertowMessages.MESSAGES.argumentCannotBeNull("callback");
}
if(!exchange.getConnection().isOpen()) {
callback.onException(exchange, this, new ClosedChannelException());
invokeOnException(callback, new ClosedChannelException());
return;
}
if(exchange.isResponseComplete()) {
throw UndertowMessages.MESSAGES.responseComplete();
invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete()));
}
if (this.buffer != null || this.fileChannel != null) {
throw UndertowMessages.MESSAGES.dataAlreadyQueued();
......@@ -185,11 +185,11 @@ public class AsyncSenderImpl implements Sender {
}
if(!exchange.getConnection().isOpen()) {
callback.onException(exchange, this, new ClosedChannelException());
invokeOnException(callback, new ClosedChannelException());
return;
}
if(exchange.isResponseComplete()) {
throw UndertowMessages.MESSAGES.responseComplete();
invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete()));
}
if (this.buffer != null) {
throw UndertowMessages.MESSAGES.dataAlreadyQueued();
......@@ -254,11 +254,11 @@ public class AsyncSenderImpl implements Sender {
}
if(!exchange.getConnection().isOpen()) {
callback.onException(exchange, this, new ClosedChannelException());
invokeOnException(callback, new ClosedChannelException());
return;
}
if(exchange.isResponseComplete()) {
throw UndertowMessages.MESSAGES.responseComplete();
invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete()));
}
if (this.fileChannel != null || this.buffer != null) {
throw UndertowMessages.MESSAGES.dataAlreadyQueued();
......@@ -299,11 +299,11 @@ public class AsyncSenderImpl implements Sender {
public void send(final String data, final Charset charset, final IoCallback callback) {
if(!exchange.getConnection().isOpen()) {
callback.onException(exchange, this, new ClosedChannelException());
invokeOnException(callback, new ClosedChannelException());
return;
}
if(exchange.isResponseComplete()) {
throw UndertowMessages.MESSAGES.responseComplete();
invokeOnException(callback, new IOException(UndertowMessages.MESSAGES.responseComplete()));
}
ByteBuffer bytes = ByteBuffer.wrap(data.getBytes(charset));
if (bytes.remaining() == 0) {
......
......@@ -798,12 +798,28 @@ public class SslConduit implements StreamSourceConduit, StreamSinkConduit {
}
return res;
}
} catch (SSLException e) {
try {
try {
//we make an effort to write out the final record
//this is best effort, there are no guarantees
doWrap(null, 0, 0);
flush();
} catch (Exception e2) {
UndertowLogger.REQUEST_LOGGER.debug("Failed to write out final SSL record", e2);
}
close();
} catch (Throwable ex) {
//we ignore this
UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doUnwrap", ex);
}
throw e;
} catch (RuntimeException|IOException|Error e) {
try {
close();
} catch (Throwable ex) {
//we ignore this
UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doUnwrap", e);
UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doUnwrap", ex);
}
throw e;
} finally {
......
......@@ -42,6 +42,7 @@ import io.undertow.util.AttachmentKey;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HexConverter;
import io.undertow.util.StatusCodes;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
......@@ -179,7 +180,7 @@ public class DigestAuthenticationMechanism implements AuthenticationMechanism {
return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
}
public AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exchange, final SecurityContext securityContext) {
private AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exchange, final SecurityContext securityContext) {
DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY);
Map<DigestAuthorizationToken, String> parsedHeader = context.getParsedHeader();
// Step 1 - Verify the set of tokens received to ensure valid values.
......@@ -229,7 +230,31 @@ public class DigestAuthenticationMechanism implements AuthenticationMechanism {
return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
// TODO - Validate the URI
if(parsedHeader.containsKey(DigestAuthorizationToken.DIGEST_URI)) {
String uri = parsedHeader.get(DigestAuthorizationToken.DIGEST_URI);
String requestURI = exchange.getRequestURI();
if(!exchange.getQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getQueryString();
}
if(!uri.equals(requestURI)) {
//it is possible we were given an absolute URI
//we reconstruct the URI from the host header to make sure they match up
//I am not sure if this is overly strict, however I think it is better
//to be safe than sorry
requestURI = exchange.getRequestURL();
if(!exchange.getQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getQueryString();
}
if(!uri.equals(requestURI)) {
//just end the auth process
exchange.setStatusCode(StatusCodes.BAD_REQUEST);
exchange.endExchange();
return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
}
} else {
return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
if (parsedHeader.containsKey(DigestAuthorizationToken.OPAQUE)) {
if (!OPAQUE_VALUE.equals(parsedHeader.get(DigestAuthorizationToken.OPAQUE))) {
......
......@@ -122,9 +122,6 @@ public class DefaultByteBufferPool implements ByteBufferPool {
local = threadLocalCache.get();
if (local != null) {
buffer = local.buffers.poll();
if (buffer != null) {
currentQueueLengthUpdater.decrementAndGet(this);
}
} else {
local = new ThreadLocalData();
synchronized (threadLocalDataList) {
......@@ -140,6 +137,9 @@ public class DefaultByteBufferPool implements ByteBufferPool {
}
if (buffer == null) {
buffer = queue.poll();
if (buffer != null) {
currentQueueLengthUpdater.decrementAndGet(this);
}
}
if (buffer == null) {
if (direct) {
......@@ -149,8 +149,10 @@ public class DefaultByteBufferPool implements ByteBufferPool {
}
}
if(local != null) {
if(local.allocationDepth < threadLocalCacheSize) { //prevent overflow if the thread only allocates and never frees
local.allocationDepth++;
}
}
buffer.clear();
return new DefaultPooledBuffer(this, buffer, leakDectionPercent == 0 ? false : (++count % 100 < leakDectionPercent));
}
......@@ -207,7 +209,7 @@ public class DefaultByteBufferPool implements ByteBufferPool {
DirectByteBufferDeallocator.free(buffer);
return;
}
} while (!currentQueueLengthUpdater.compareAndSet(this, size, currentQueueLength + 1));
} while (!currentQueueLengthUpdater.compareAndSet(this, size, size + 1));
queue.add(buffer);
}
......
......@@ -3,6 +3,7 @@ package io.undertow.server;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import io.undertow.UndertowLogger;
......@@ -87,11 +88,11 @@ public final class DirectByteBufferDeallocator {
private static Unsafe getUnsafe() {
if (System.getSecurityManager() != null) {
return new PrivilegedAction<Unsafe>() {
return AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
public Unsafe run() {
return getUnsafe0();
}
}.run();
});
}
return getUnsafe0();
}
......
......@@ -30,6 +30,7 @@ import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Handler that sets the peer address to the value of the X-Forwarded-For header.
......@@ -41,6 +42,10 @@ import java.util.Set;
*/
public class ProxyPeerAddressHandler implements HttpHandler {
private static final Pattern IP4_EXACT = Pattern.compile("(?:\\d{1,3}\\.){3}\\d{1,3}");
private static final Pattern IP6_EXACT = Pattern.compile("(?:[a-zA-Z0-9]{1,4}:){7}[a-zA-Z0-9]{1,4}");
private final HttpHandler next;
public ProxyPeerAddressHandler(HttpHandler next) {
......@@ -51,8 +56,15 @@ public class ProxyPeerAddressHandler implements HttpHandler {
public void handleRequest(HttpServerExchange exchange) throws Exception {
String forwardedFor = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_FOR);
if (forwardedFor != null) {
String remoteClient = mostRecent(forwardedFor);
//we have no way of knowing the port
exchange.setSourceAddress(InetSocketAddress.createUnresolved(mostRecent(forwardedFor), 0));
if(IP4_EXACT.matcher(forwardedFor).matches()) {
exchange.setSourceAddress(new InetSocketAddress(NetworkUtils.parseIpv4Address(remoteClient), 0));
} else if(IP6_EXACT.matcher(forwardedFor).matches()) {
exchange.setSourceAddress(new InetSocketAddress(NetworkUtils.parseIpv6Address(remoteClient), 0));
} else {
exchange.setSourceAddress(InetSocketAddress.createUnresolved(remoteClient, 0));
}
}
String forwardedProto = exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PROTO);
if (forwardedProto != null) {
......
......@@ -79,7 +79,6 @@ public class StuckThreadDetectionHandler implements HttpHandler {
private final Runnable stuckThreadTask = new Runnable() {
@Override
public void run() {
timerKey = null;
long thresholdInMillis = threshold * 1000L;
// Check monitored threads, being careful that the request might have
......
......@@ -125,6 +125,14 @@ public class DefaultAccessLogReceiver implements AccessLogReceiver, Runnable, Cl
calendar.add(Calendar.DATE, 1);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
currentDateString = df.format(new Date());
// if there is an existing default log file, use the date last modified instead of the current date
if (Files.exists(defaultLogFile)) {
try {
currentDateString = df.format(new Date(Files.getLastModifiedTime(defaultLogFile).toMillis()));
} catch(IOException e){
// ignore. use the current date if exception happens.
}
}
changeOverPoint = calendar.getTimeInMillis();
}
......
......@@ -18,10 +18,16 @@
package io.undertow.server.handlers.form;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import io.undertow.Handlers;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.ResponseCodeHandler;
import io.undertow.server.handlers.builder.HandlerBuilder;
/**
* Handler that eagerly parses form data. The request chain will pause while the data is being read,
......@@ -40,6 +46,13 @@ public class EagerFormParsingHandler implements HttpHandler {
private volatile HttpHandler next = ResponseCodeHandler.HANDLE_404;
private final FormParserFactory formParserFactory;
public static final HandlerWrapper WRAPPER = new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
return new EagerFormParsingHandler(handler);
}
};
public EagerFormParsingHandler(final FormParserFactory formParserFactory) {
this.formParserFactory = formParserFactory;
}
......@@ -48,6 +61,11 @@ public class EagerFormParsingHandler implements HttpHandler {
this.formParserFactory = FormParserFactory.builder().build();
}
public EagerFormParsingHandler(HttpHandler next) {
this();
this.next = next;
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
FormDataParser parser = formParserFactory.createParser(exchange);
......@@ -72,4 +90,33 @@ public class EagerFormParsingHandler implements HttpHandler {
this.next = next;
return this;
}
public static class Builder implements HandlerBuilder {
@Override
public String name() {
return "eager-form-parser";
}
@Override
public Map<String, Class<?>> parameters() {
return Collections.emptyMap();
}
@Override
public Set<String> requiredParameters() {
return Collections.emptySet();
}
@Override
public String defaultParameter() {
return null;
}
@Override
public HandlerWrapper build(Map<String, Object> config) {
return WRAPPER;
}
}
}
......@@ -68,9 +68,9 @@ public class Balancer {
private final int waitWorker;
/**
* value: number of attempts to send the request to the backend server. Default: "1"
* Maximum number of failover attempts to send the request to the backend server. Default: "1"
*/
private final int maxattempts;
private final int maxRetries;
private final int id;
private static final AtomicInteger idGen = new AtomicInteger();
......@@ -84,85 +84,50 @@ public class Balancer {
this.stickySessionRemove = b.isStickySessionRemove();
this.stickySessionForce = b.isStickySessionForce();
this.waitWorker = b.getWaitWorker();
this.maxattempts = b.getMaxattempts();
this.maxRetries = b.getMaxRetries();
UndertowLogger.ROOT_LOGGER.balancerCreated(this.id, this.name, this.stickySession, this.stickySessionCookie, this.stickySessionPath,
this.stickySessionRemove, this.stickySessionForce, this.waitWorker, this.maxattempts);
this.stickySessionRemove, this.stickySessionForce, this.waitWorker, this.maxRetries);
}
public int getId() {
return id;
}
/**
* Getter for name
*
* @return the name
*/
public String getName() {
return this.name;
}
/**
* Getter for stickySession
*
* @return the stickySession
*/
public boolean isStickySession() {
return this.stickySession;
}
/**
* Getter for stickySessionCookie
*
* @return the stickySessionCookie
*/
public String getStickySessionCookie() {
return this.stickySessionCookie;
}
/**
* Getter for stickySessionPath
*
* @return the stickySessionPath
*/
public String getStickySessionPath() {
return this.stickySessionPath;
}
/**
* Getter for stickySessionRemove
*
* @return the stickySessionRemove
*/
public boolean isStickySessionRemove() {
return this.stickySessionRemove;
}
/**
* Getter for stickySessionForce
*
* @return the stickySessionForce
*/
public boolean isStickySessionForce() {
return this.stickySessionForce;
}
/**
* Getter for waitWorker
*
* @return the waitWorker
*/
public int getWaitWorker() {
return this.waitWorker;
}
/**
* Getter for maxattempts
*
* @return the maxattempts
*/
public int getMaxRetries() {
return this.maxRetries;
}
@Deprecated
public int getMaxattempts() {
return this.maxattempts;
return this.maxRetries;
}
@Override
......@@ -173,10 +138,10 @@ public class Balancer {
.append(this.stickySessionPath).append("], remove: ")
.append(this.stickySessionRemove ? 1 : 0).append(", force: ")
.append(this.stickySessionForce ? 1 : 0).append(", Timeout: ")
.append(this.waitWorker).append(", Maxtry: ").append(this.maxattempts).toString();
.append(this.waitWorker).append(", Maxtry: ").append(this.maxRetries).toString();
}
static final BalancerBuilder builder() {
static BalancerBuilder builder() {
return new BalancerBuilder();
}
......@@ -189,7 +154,7 @@ public class Balancer {
private boolean stickySessionRemove = false;
private boolean stickySessionForce = true;
private int waitWorker = 0;
private int maxattempts = 1;
private int maxRetries = 1;
public String getName() {
return name;
......@@ -259,12 +224,34 @@ public class Balancer {
return this;
}
public int getMaxRetries() {
return this.maxRetries;
}
/**
* Maximum number of failover attempts to send the request to the backend server.
*
* @param maxRetries number of failover attempts
*/
public BalancerBuilder setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
/**
* @deprecated Use {@link BalancerBuilder#getMaxRetries()}.
*/
@Deprecated
public int getMaxattempts() {
return maxattempts;
return maxRetries;
}
/**
* @deprecated Use {@link BalancerBuilder#setMaxRetries(int)}.
*/
@Deprecated
public BalancerBuilder setMaxattempts(int maxattempts) {
this.maxattempts = maxattempts;
this.maxRetries = maxattempts;
return this;
}
......
......@@ -212,9 +212,8 @@ class MCMPHandler implements HttpHandler {
*
* @param exchange the http server exchange
* @param requestData the request data
* @throws IOException
*/
private void processConfig(final HttpServerExchange exchange, final RequestData requestData) throws IOException {
private void processConfig(final HttpServerExchange exchange, final RequestData requestData) {
// Get the node builder
List<String> hosts = null;
......@@ -236,7 +235,7 @@ class MCMPHandler implements HttpHandler {
node.setBalancer(value);
balancer.setName(value);
} else if (MAXATTEMPTS.equals(name)) {
balancer.setMaxattempts(Integer.parseInt(value));
balancer.setMaxRetries(Integer.parseInt(value));
} else if (STICKYSESSION.equals(name)) {
if ("No".equalsIgnoreCase(value)) {
balancer.setStickySession(false);
......
......@@ -35,7 +35,7 @@ class MCMPInfoUtil {
.append(" remove: ").append(toStringOneZero(balancer.isStickySessionRemove()))
.append(" force: ").append(toStringOneZero(balancer.isStickySessionForce()))
.append(" Timeout: ").append(balancer.getWaitWorker())
.append(" maxAttempts: ").append(balancer.getMaxattempts())
.append(" maxAttempts: ").append(balancer.getMaxRetries())
.append(NEWLINE);
}
......
......@@ -742,8 +742,14 @@ class ModClusterContainer implements ModClusterController {
}
@Override
public int getMaxRetries() {
return balancer.getMaxRetries();
}
@Override
@Deprecated
public int getMaxAttempts() {
return balancer.getMaxattempts();
return balancer.getMaxRetries();
}
}
......
......@@ -84,7 +84,7 @@ public interface ModClusterProxyTarget extends ProxyClient.ProxyTarget, ProxyCli
if(balancer == null) {
return 0;
}
return balancer.getMaxattempts() - 1;
return balancer.getMaxRetries();
}
}
......@@ -111,7 +111,7 @@ public interface ModClusterProxyTarget extends ProxyClient.ProxyTarget, ProxyCli
if(balancer == null) {
return 0;
}
return balancer.getMaxattempts() - 1;
return balancer.getMaxRetries();
}
@Override
......