| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.jboss.as.test.integration.web.access.log; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.PrintWriter; | ||
| import javax.json.Json; | ||
| import javax.json.stream.JsonGenerator; | ||
| import javax.servlet.ServletException; | ||
| import javax.servlet.annotation.WebServlet; | ||
| import javax.servlet.http.HttpServlet; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import javax.servlet.http.HttpServletResponse; | ||
| import javax.ws.rs.core.MediaType; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| @WebServlet("simple") | ||
| public class SimpleServlet extends HttpServlet { | ||
|
|
||
| @Override | ||
| protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { | ||
| final PrintWriter writer = response.getWriter(); | ||
| response.setContentType(MediaType.APPLICATION_JSON); | ||
| try (JsonGenerator generator = Json.createGenerator(writer)) { | ||
| generator.writeStartObject(); | ||
|
|
||
| generator.writeStartObject("parameters"); | ||
|
|
||
| request.getParameterMap().forEach((key, values) -> { | ||
| if (values == null) { | ||
| generator.writeNull(key); | ||
| } else if (values.length > 1) { | ||
| generator.writeStartArray(key); | ||
| for (String value : values) { | ||
| generator.write(value); | ||
| } | ||
| generator.writeEnd(); | ||
| } else { | ||
| final String value = values[0]; | ||
| if (value == null) { | ||
| generator.writeNull(key); | ||
| } else { | ||
| generator.write(key, value); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| generator.writeEnd(); // end parameters | ||
|
|
||
| generator.writeEnd(); // end main | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,117 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.extension.undertow; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.function.Function; | ||
|
|
||
| import io.undertow.attribute.ExchangeAttribute; | ||
| import io.undertow.server.HttpServerExchange; | ||
|
|
||
| /** | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class AccessLogAttribute implements Comparable<AccessLogAttribute> { | ||
| private final String key; | ||
| private final ExchangeAttribute exchangeAttribute; | ||
| private final Function<String, Object> valueConverter; | ||
|
|
||
| private AccessLogAttribute(final String key, final ExchangeAttribute exchangeAttribute, final Function<String, Object> valueConverter) { | ||
| this.key = key; | ||
| this.exchangeAttribute = exchangeAttribute; | ||
| this.valueConverter = valueConverter; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new attribute. | ||
| * | ||
| * @param key the key for the attribute | ||
| * @param exchangeAttribute the exchange attribute which resolves the value | ||
| * | ||
| * @return the new attribute | ||
| */ | ||
| static AccessLogAttribute of(final String key, final ExchangeAttribute exchangeAttribute) { | ||
| return new AccessLogAttribute(key, exchangeAttribute, null); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Creates a new attribute. | ||
| * | ||
| * @param key the key for the attribute | ||
| * @param exchangeAttribute the exchange attribute which resolves the value | ||
| * @param valueConverter the converter used to convert the | ||
| * {@linkplain ExchangeAttribute#readAttribute(HttpServerExchange) string} into a different | ||
| * type | ||
| * | ||
| * @return the new attribute | ||
| */ | ||
| static AccessLogAttribute of(final String key, final ExchangeAttribute exchangeAttribute, final Function<String, Object> valueConverter) { | ||
| return new AccessLogAttribute(key, exchangeAttribute, valueConverter); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the key used for the structured log output. | ||
| * | ||
| * @return the key | ||
| */ | ||
| String getKey() { | ||
| return key; | ||
| } | ||
|
|
||
| /** | ||
| * Resolves the value for the attribute. | ||
| * | ||
| * @param exchange the exchange to resolve the value from | ||
| * | ||
| * @return the value of the attribute | ||
| */ | ||
| Object resolveAttribute(final HttpServerExchange exchange) { | ||
| if (valueConverter == null) { | ||
| return exchangeAttribute.readAttribute(exchange); | ||
| } | ||
| return valueConverter.apply(exchangeAttribute.readAttribute(exchange)); | ||
| } | ||
|
|
||
| @Override | ||
| public int compareTo(final AccessLogAttribute o) { | ||
| return key.compareTo(o.key); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(key); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(final Object obj) { | ||
| if (this == obj) { | ||
| return true; | ||
| } | ||
| if (!(obj instanceof AccessLogAttribute)) { | ||
| return false; | ||
| } | ||
| final AccessLogAttribute other = (AccessLogAttribute) obj; | ||
| return key.equals(other.key); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return getClass().getSimpleName() + "{key=" + key + ", exchangeAttribute=" + exchangeAttribute + | ||
| ", valueConverter=" + valueConverter + "}"; | ||
| } | ||
| } |
| @@ -0,0 +1,170 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.wildfly.extension.undertow; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import java.util.function.Supplier; | ||
|
|
||
| import io.undertow.predicate.Predicate; | ||
| import io.undertow.predicate.Predicates; | ||
| import org.jboss.as.controller.AbstractAddStepHandler; | ||
| import org.jboss.as.controller.AbstractRemoveStepHandler; | ||
| import org.jboss.as.controller.AttributeDefinition; | ||
| import org.jboss.as.controller.OperationContext; | ||
| import org.jboss.as.controller.OperationFailedException; | ||
| import org.jboss.as.controller.PathAddress; | ||
| import org.jboss.as.controller.PathElement; | ||
| import org.jboss.as.controller.PersistentResourceDefinition; | ||
| import org.jboss.as.controller.PropertiesAttributeDefinition; | ||
| import org.jboss.as.controller.SimpleAttributeDefinition; | ||
| import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; | ||
| import org.jboss.as.controller.SimpleResourceDefinition; | ||
| import org.jboss.as.controller.capability.DynamicNameMappers; | ||
| import org.jboss.as.controller.capability.RuntimeCapability; | ||
| import org.jboss.dmr.ModelNode; | ||
| import org.jboss.dmr.ModelType; | ||
| import org.jboss.dmr.Property; | ||
| import org.jboss.msc.service.ServiceBuilder; | ||
| import org.jboss.msc.service.ServiceController; | ||
| import org.xnio.XnioWorker; | ||
|
|
||
| /** | ||
| * The resource definition for the {@code setting=console-access-log} resource. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class ConsoleAccessLogDefinition extends PersistentResourceDefinition { | ||
| private static final RuntimeCapability<Void> CONSOLE_ACCESS_LOG_CAPABILITY = RuntimeCapability.Builder.of( | ||
| Capabilities.CAPABILITY_CONSOLE_ACCESS_LOG, true, EventLoggerService.class) | ||
| .setDynamicNameMapper(DynamicNameMappers.GRAND_PARENT) | ||
| .build(); | ||
|
|
||
| static final SimpleAttributeDefinition INCLUDE_HOST_NAME = SimpleAttributeDefinitionBuilder.create("include-host-name", ModelType.BOOLEAN, true) | ||
| .setAllowExpression(true) | ||
| .setDefaultValue(new ModelNode(true)) | ||
| .setRestartAllServices() | ||
| .build(); | ||
|
|
||
| static final PropertiesAttributeDefinition METADATA = new PropertiesAttributeDefinition.Builder("metadata", true) | ||
| .setAllowExpression(true) | ||
| .setRestartAllServices() | ||
| .build(); | ||
|
|
||
| static final Collection<AttributeDefinition> ATTRIBUTES = Arrays.asList( | ||
| ExchangeAttributeDefinitions.ATTRIBUTES, | ||
| INCLUDE_HOST_NAME, | ||
| AccessLogDefinition.WORKER, | ||
| AccessLogDefinition.PREDICATE, | ||
| METADATA | ||
| ); | ||
| static final ConsoleAccessLogDefinition INSTANCE = new ConsoleAccessLogDefinition(); | ||
|
|
||
|
|
||
| private ConsoleAccessLogDefinition() { | ||
| super(new SimpleResourceDefinition.Parameters(PathElement.pathElement(Constants.SETTING, "console-access-log"), | ||
| UndertowExtension.getResolver("console-access-log")) | ||
| .setAddHandler(AddHandler.INSTANCE) | ||
| .setRemoveHandler(RemoveHandler.INSTANCE) | ||
| .addCapabilities(CONSOLE_ACCESS_LOG_CAPABILITY) | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<AttributeDefinition> getAttributes() { | ||
| return ATTRIBUTES; | ||
| } | ||
|
|
||
| private static class AddHandler extends AbstractAddStepHandler { | ||
| static final AddHandler INSTANCE = new AddHandler(); | ||
|
|
||
| private AddHandler() { | ||
| super(ATTRIBUTES); | ||
| } | ||
|
|
||
| @Override | ||
| protected void performRuntime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException { | ||
| final PathAddress address = context.getCurrentAddress(); | ||
| final PathAddress hostAddress = address.getParent(); | ||
| final PathAddress serverAddress = hostAddress.getParent(); | ||
|
|
||
| final String worker = AccessLogDefinition.WORKER.resolveModelAttribute(context, model).asString(); | ||
| final ModelNode properties = METADATA.resolveModelAttribute(context, model); | ||
| final Map<String, Object> metadata = new LinkedHashMap<>(); | ||
| if (properties.isDefined()) { | ||
| for (Property property : properties.asPropertyList()) { | ||
| metadata.put(property.getName(), property.getValue().asString()); | ||
| } | ||
| } | ||
|
|
||
| Predicate predicate = null; | ||
| final ModelNode predicateNode = AccessLogDefinition.PREDICATE.resolveModelAttribute(context, model); | ||
| if (predicateNode.isDefined()) { | ||
| predicate = Predicates.parse(predicateNode.asString(), getClass().getClassLoader()); | ||
| } | ||
|
|
||
| final boolean includeHostName = INCLUDE_HOST_NAME.resolveModelAttribute(context, model).asBoolean(); | ||
|
|
||
| final String serverName = serverAddress.getLastElement().getValue(); | ||
| final String hostName = hostAddress.getLastElement().getValue(); | ||
|
|
||
| final ServiceBuilder<?> serviceBuilder = context.getServiceTarget() | ||
| .addService(CONSOLE_ACCESS_LOG_CAPABILITY.getCapabilityServiceName(address)); | ||
|
|
||
| final Supplier<Host> hostSupplier = serviceBuilder.requires( | ||
| context.getCapabilityServiceName(Capabilities.CAPABILITY_HOST, Host.class, serverName, hostName)); | ||
| final Supplier<XnioWorker> workerSupplier = serviceBuilder.requires( | ||
| context.getCapabilityServiceName(Capabilities.REF_IO_WORKER, XnioWorker.class, worker)); | ||
|
|
||
| // Get the list of attributes to log | ||
| final Collection<AccessLogAttribute> attributes = parseAttributes(context, model); | ||
|
|
||
| final EventLoggerService service = new EventLoggerService(attributes, predicate, metadata, includeHostName, hostSupplier, | ||
| workerSupplier); | ||
| serviceBuilder.setInstance(service) | ||
| .setInitialMode(ServiceController.Mode.ACTIVE) | ||
| .install(); | ||
| } | ||
|
|
||
| private Collection<AccessLogAttribute> parseAttributes(final OperationContext context, final ModelNode model) throws OperationFailedException { | ||
| final Collection<AccessLogAttribute> attributes = new ArrayList<>(); | ||
| final ModelNode attributesModel = ExchangeAttributeDefinitions.ATTRIBUTES.resolveModelAttribute(context, model); | ||
| for (AttributeDefinition valueType : ExchangeAttributeDefinitions.ATTRIBUTES.getValueTypes()) { | ||
| attributes.addAll(ExchangeAttributeDefinitions.resolveAccessLogAttribute(valueType, context, attributesModel)); | ||
| } | ||
| return attributes; | ||
| } | ||
| } | ||
|
|
||
| private static class RemoveHandler extends AbstractRemoveStepHandler { | ||
|
|
||
| static final RemoveHandler INSTANCE = new RemoveHandler(); | ||
|
|
||
| @Override | ||
| protected void performRuntime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException { | ||
| final PathAddress address = context.getCurrentAddress(); | ||
| context.removeService(CONSOLE_ACCESS_LOG_CAPABILITY.getCapabilityServiceName(address)); | ||
| } | ||
|
|
||
| @Override | ||
| protected void recoverServices(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException { | ||
| AddHandler.INSTANCE.performRuntime(context, operation, model); | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.extension.undertow; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| import io.undertow.predicate.Predicate; | ||
| import io.undertow.server.ExchangeCompletionListener; | ||
| import io.undertow.server.HttpHandler; | ||
| import io.undertow.server.HttpServerExchange; | ||
| import org.wildfly.event.logger.EventLogger; | ||
|
|
||
| /** | ||
| * An HTTP handler that writes exchange attributes to an event logger. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class EventLoggerHttpHandler implements HttpHandler { | ||
|
|
||
| private final HttpHandler next; | ||
| private final ExchangeCompletionListener exchangeCompletionListener = new AccessLogCompletionListener(); | ||
| private final Predicate predicate; | ||
| private final Collection<AccessLogAttribute> attributes; | ||
| private final EventLogger eventLogger; | ||
|
|
||
| /** | ||
| * Creates a new instance of the HTTP handler. | ||
| * | ||
| * @param next the next handler in the chain to invoke to invoke after this handler executes | ||
| * @param predicate the predicate used to determine if this handler should execute | ||
| * @param attributes the attributes which should be logged | ||
| * @param eventLogger the event logger | ||
| */ | ||
| EventLoggerHttpHandler(final HttpHandler next, final Predicate predicate, | ||
| final Collection<AccessLogAttribute> attributes, final EventLogger eventLogger) { | ||
| this.next = next; | ||
| this.predicate = predicate; | ||
| this.attributes = attributes; | ||
| this.eventLogger = eventLogger; | ||
| } | ||
|
|
||
| @Override | ||
| public void handleRequest(final HttpServerExchange exchange) throws Exception { | ||
| exchange.addExchangeCompleteListener(exchangeCompletionListener); | ||
| next.handleRequest(exchange); | ||
| } | ||
|
|
||
| private class AccessLogCompletionListener implements ExchangeCompletionListener { | ||
| @Override | ||
| public void exchangeEvent(final HttpServerExchange exchange, final NextListener nextListener) { | ||
| try { | ||
| if (predicate == null || predicate.resolve(exchange)) { | ||
| final Map<String, Object> data = new LinkedHashMap<>(); | ||
| for (AccessLogAttribute attribute : attributes) { | ||
| data.put(attribute.getKey(), attribute.resolveAttribute(exchange)); | ||
| } | ||
| eventLogger.log(data); | ||
| } | ||
| } finally { | ||
| nextListener.proceed(); | ||
| } | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,105 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.extension.undertow; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.CopyOnWriteArraySet; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.function.Function; | ||
| import java.util.function.Supplier; | ||
|
|
||
| import io.undertow.predicate.Predicate; | ||
| import io.undertow.predicate.Predicates; | ||
| import io.undertow.server.HttpHandler; | ||
| import org.jboss.msc.Service; | ||
| import org.jboss.msc.service.StartContext; | ||
| import org.jboss.msc.service.StartException; | ||
| import org.jboss.msc.service.StopContext; | ||
| import org.wildfly.event.logger.EventLogger; | ||
| import org.wildfly.event.logger.JsonEventFormatter; | ||
| import org.wildfly.event.logger.StdoutEventWriter; | ||
| import org.wildfly.extension.undertow.logging.UndertowLogger; | ||
| import org.xnio.XnioWorker; | ||
|
|
||
| /** | ||
| * A service which creates an asynchronous {@linkplain EventLogger event logger} which writes to {@code stdout} in JSON | ||
| * structured format. | ||
| * | ||
| * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> | ||
| */ | ||
| class EventLoggerService implements Service { | ||
| private final Set<AccessLogAttribute> attributes; | ||
| private final boolean includeHostName; | ||
| private final Map<String, Object> metadata; | ||
| private final Predicate predicate; | ||
| private final Supplier<Host> host; | ||
| private final Supplier<XnioWorker> worker; | ||
|
|
||
| /** | ||
| * Creates a new service. | ||
| * | ||
| * @param predicate the predicate that determines if the request should be logged | ||
| * @param metadata a map of metadata to be prepended to the structured output | ||
| * @param includeHostName {@code true} to include the host name in the structured JSON output | ||
| * @param host the host service supplier | ||
| * @param worker the worker service supplier for the | ||
| * {@linkplain EventLogger#createAsyncLogger(String, Executor) async logger} | ||
| */ | ||
| EventLoggerService(final Collection<AccessLogAttribute> attributes, final Predicate predicate, final Map<String, Object> metadata, | ||
| final boolean includeHostName, final Supplier<Host> host, final Supplier<XnioWorker> worker) { | ||
| this.attributes = new CopyOnWriteArraySet<>(attributes); | ||
| this.predicate = predicate == null ? Predicates.truePredicate() : predicate; | ||
| this.metadata = metadata; | ||
| this.includeHostName = includeHostName; | ||
| this.host = host; | ||
| this.worker = worker; | ||
| } | ||
|
|
||
| @Override | ||
| @SuppressWarnings("Convert2Lambda") | ||
| public void start(final StartContext context) throws StartException { | ||
| final Host host = this.host.get(); | ||
| // Create the JSON event formatter | ||
| final JsonEventFormatter.Builder formatterBuilder = JsonEventFormatter.builder() | ||
| .setIncludeTimestamp(false); | ||
| if (includeHostName) { | ||
| formatterBuilder.addMetaData("hostName", host.getName()); | ||
| } | ||
| if (metadata != null && !metadata.isEmpty()) { | ||
| formatterBuilder.addMetaData(metadata); | ||
| } | ||
| final JsonEventFormatter formatter = formatterBuilder.build(); | ||
| final EventLogger eventLogger = EventLogger.createAsyncLogger("web-access", | ||
| StdoutEventWriter.of(formatter), worker.get()); | ||
| UndertowLogger.ROOT_LOGGER.debugf("Adding console-access-log for host %s", host.getName()); | ||
| host.setAccessLogHandler(new Function<HttpHandler, HttpHandler>() { | ||
| @Override | ||
| public HttpHandler apply(final HttpHandler httpHandler) { | ||
| return new EventLoggerHttpHandler(httpHandler, predicate, attributes, eventLogger); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public void stop(final StopContext context) { | ||
| final Host host = this.host.get(); | ||
| UndertowLogger.ROOT_LOGGER.debugf("Removing console-access-log for host %s", host.getName()); | ||
| host.setAccessLogHandler(null); | ||
| } | ||
| } |
| @@ -0,0 +1,58 @@ | ||
| /* | ||
| * Copyright 2019 Red Hat, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.wildfly.extension.undertow; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| import org.jboss.as.subsystem.test.KernelServices; | ||
| import org.jboss.as.subsystem.test.KernelServicesBuilder; | ||
| import org.junit.Test; | ||
|
|
||
| /** | ||
| * This is the barebone test example that tests subsystem | ||
| * | ||
| * @author <a href="mailto:tomaz.cerar@redhat.com">Tomaz Cerar</a> | ||
| */ | ||
| public class UndertowSubsystem80TestCase extends AbstractUndertowSubsystemTestCase { | ||
|
|
||
| private final String virtualHostName = "some-server"; | ||
| private final int flag = 1; | ||
|
|
||
| @Override | ||
| protected String getSubsystemXml() throws IOException { | ||
| return readResource("undertow-8.0.xml"); | ||
| } | ||
|
|
||
| @Override | ||
| protected String getSubsystemXsdPath() throws Exception { | ||
| return "schema/wildfly-undertow_8_0.xsd"; | ||
| } | ||
|
|
||
| protected KernelServices standardSubsystemTest(String configId, boolean compareXml) throws Exception { | ||
| return super.standardSubsystemTest(configId, false); | ||
| } | ||
|
|
||
| @Test | ||
| public void testRuntime() throws Exception { | ||
| setProperty(); | ||
| KernelServicesBuilder builder = createKernelServicesBuilder(RUNTIME).setSubsystemXml(getSubsystemXml()); | ||
| KernelServices mainServices = builder.build(); | ||
| testRuntime(mainServices, virtualHostName, flag); | ||
| testRuntimeOther(mainServices); | ||
| testRuntimeLast(mainServices); | ||
| } | ||
| } |
| @@ -0,0 +1,114 @@ | ||
| <!-- | ||
| ~ Copyright 2019 Red Hat, Inc. | ||
| ~ | ||
| ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| ~ you may not use this file except in compliance with the License. | ||
| ~ You may obtain a copy of the License at | ||
| ~ | ||
| ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
| ~ | ||
| ~ Unless required by applicable law or agreed to in writing, software | ||
| ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| ~ See the License for the specific language governing permissions and | ||
| ~ limitations under the License. | ||
| --> | ||
|
|
||
| <subsystem xmlns="urn:jboss:domain:undertow:9.0" default-server="some-server" default-servlet-container="myContainer" default-virtual-host="default-virtual-host" instance-id="some-id" statistics-enabled="true"> | ||
| <byte-buffer-pool name="test" thread-local-cache-size="45" buffer-size="1000" direct="false" leak-detection-percent="50" max-pool-size="1000"/> | ||
| <buffer-cache buffer-size="1025" buffers-per-region="1054" max-regions="15" name="default"/> | ||
| <buffer-cache buffer-size="1025" buffers-per-region="1054" max-regions="15" name="extra"/> | ||
| <server default-host="other-host" name="some-server" servlet-container="myContainer"> | ||
| <ajp-listener disallowed-methods="FOO TRACE" allow-unescaped-characters-in-url="true" max-parameters="5000" name="ajp-connector" no-request-timeout="10000" receive-buffer="5000" redirect-socket="ajps" request-parse-timeout="2000" resolve-peer-address="true" secure="true" send-buffer="50000" socket-binding="ajp" tcp-backlog="500" tcp-keep-alive="true" max-ajp-packet-size="10000"/> | ||
| <http-listener always-set-keep-alive="${prop.smth:false}" certificate-forwarding="true" name="default" proxy-address-forwarding="${prop.smth:false}" redirect-socket="ajp" resolve-peer-address="true" socket-binding="http" proxy-protocol="true"/> | ||
| <http-listener max-cookies="100" max-headers="30" max-parameters="30" max-post-size="100000" name="second" redirect-socket="https-non-default" require-host-http11="true" socket-binding="http-2" url-charset="windows-1250"/> | ||
| <http-listener max-cookies="100" max-headers="30" max-parameters="30" max-post-size="100000" name="no-redirect" socket-binding="http-3" url-charset="windows-1250" worker="non-default"/> | ||
| <https-listener disallowed-methods="" max-buffered-request-size="50000" max-connections="100" name="https" record-request-start-time="true" require-host-http11="true" resolve-peer-address="true" security-realm="UndertowRealm" socket-binding="https-non-default" verify-client="REQUESTED"/> | ||
| <https-listener certificate-forwarding="true" allow-unescaped-characters-in-url="true" enabled-cipher-suites="ALL:!MD5:!DHA" enabled-protocols="SSLv3, TLSv1.2" name="https-2" proxy-address-forwarding="true" read-timeout="-1" security-realm="UndertowRealm" socket-binding="https-2" write-timeout="-1"/> | ||
| <https-listener disallowed-methods="" max-buffered-request-size="50000" max-connections="100" name="https-3" record-request-start-time="true" resolve-peer-address="true" socket-binding="https-3" ssl-context="TestContext" rfc6265-cookie-validation="true" proxy-protocol="true"/> | ||
| <!--<https-listener disallowed-methods="" max-buffered-request-size="50000" max-connections="100" name="https-4" record-request-start-time="true" resolve-peer-address="true" socket-binding="https-4" />--> <!-- this one must fail--> | ||
| <host alias="localhost,some.host" default-response-code="503" default-web-module="something.war" name="default-virtual-host"> | ||
| <location handler="welcome-content" name="/"> | ||
| <filter-ref name="limit-connections"/> | ||
| <filter-ref name="headers" priority="${some.priority:10}"/> | ||
| <filter-ref name="404-handler"/> | ||
| <filter-ref name="static-gzip" predicate="path-suffix('.js')"/> | ||
| </location> | ||
| <access-log directory="${jboss.server.server.dir}" pattern="REQ %{i,test-header}" predicate="not path-suffix(*.css)" prefix="access" rotate="false"/> | ||
| <console-access-log predicate="not path-suffix(*.css)" worker="default"> | ||
| <attributes> | ||
| <authentication-type/> | ||
| <date-time date-format="yyyy-MM-dd'T'HH:mm:ss" key="timestamp"/> | ||
| <query-parameter> | ||
| <name value="test"/> | ||
| </query-parameter> | ||
| <request-header key-prefix="requestHeader"> | ||
| <name value="Content-Type"/> | ||
| <name value="Content-Encoding"/> | ||
| </request-header> | ||
| <response-code/> | ||
| <response-time time-unit="MICROSECONDS"/> | ||
| </attributes> | ||
| <metadata> | ||
| <property name="@version" value="1"/> | ||
| <property name="host" value="${jboss.host.name:localhost}"/> | ||
| </metadata> | ||
| </console-access-log> | ||
| <single-sign-on cookie-name="SSOID" domain="${prop.domain:myDomain}" http-only="true" path="/path" secure="true"/> | ||
| </host> | ||
| <host alias="www.mysite.com,${prop.value:default-alias}" default-response-code="501" default-web-module="something-else.war" disable-console-redirect="true" name="other-host" queue-requests-on-start="false"> | ||
| <location handler="welcome-content" name="/"> | ||
| <filter-ref name="limit-connections"/> | ||
| <filter-ref name="headers"/> | ||
| <filter-ref name="static-gzip" predicate="path-suffix('.js') or path-suffix('.css') or path-prefix('/resources')"/> | ||
| <filter-ref name="404-handler"/> | ||
| <filter-ref name="mod-cluster"/> | ||
| </location> | ||
| <filter-ref name="headers"/> | ||
| <http-invoker http-authentication-factory="factory" path="services"/> | ||
| </host> | ||
| </server> | ||
| <servlet-container default-buffer-cache="extra" default-encoding="utf-8" default-session-timeout="100" directory-listing="true" eager-filter-initialization="true" ignore-flush="true" name="myContainer" proactive-authentication="${prop.pro:false}" use-listener-encoding="${prop.foo:false}" disable-session-id-reuse="${prop.foo:true}" disable-file-watch-service="${prop.foo:true}" file-cache-metadata-size="50" file-cache-max-file-size="5000" file-cache-time-to-live="1000" default-cookie-version="1"> | ||
| <jsp-config check-interval="${prop.check-interval:20}" disabled="${prop.disabled:false}" display-source-fragment="${prop.display-source-fragment:true}" dump-smap="${prop.dump-smap:true}" error-on-use-bean-invalid-class-attribute="${prop.error-on-use-bean-invalid-class-attribute:true}" generate-strings-as-char-arrays="${prop.generate-strings-as-char-arrays:true}" java-encoding="${prop.java-encoding:utf-8}" keep-generated="${prop.keep-generated:true}" mapped-file="${prop.mapped-file:true}" modification-test-interval="${prop.modification-test-interval:1000}" optimize-scriptlets="${prop.optimise-scriptlets:true}" recompile-on-fail="${prop.recompile-on-fail:true}" scratch-dir="${prop.scratch-dir:/some/dir}" smap="${prop.smap:true}" source-vm="${prop.source-vm:1.7}" tag-pooling="${prop.tag-pooling:true}" target-vm="${prop.target-vm:1.7}" trim-spaces="${prop.trim-spaces:true}" x-powered-by="${prop.x-powered-by:true}"/> | ||
| <session-cookie comment="session cookie" domain="example.com" http-only="true" max-age="1000" name="MYSESSIONCOOKIE" secure="true"/> | ||
| <websockets deflater-level="0" dispatch-to-worker="false" per-message-deflate="false"/> | ||
| <mime-mappings> | ||
| <mime-mapping name="txt" value="text/plain"/> | ||
| </mime-mappings> | ||
| <welcome-files> | ||
| <welcome-file name="index.seam"/> | ||
| </welcome-files> | ||
| <crawler-session-management session-timeout="2" user-agents=".*googlebot.*"/> | ||
| </servlet-container> | ||
| <handlers> | ||
| <file case-sensitive="false" directory-listing="true" follow-symlink="true" name="welcome-content" path="${jboss.home.dir}" safe-symlink-paths="/path/to/folder /second/path"/> | ||
| <reverse-proxy connection-idle-timeout="60" connections-per-thread="30" max-retries="10" name="reverse-proxy"> | ||
| <host instance-id="myRoute" name="server1" outbound-socket-binding="ajp-remote" path="/test" scheme="ajp" ssl-context="TestContext"/> | ||
| <host instance-id="myRoute" name="server2" outbound-socket-binding="ajp-remote" path="/test" scheme="ajp" ssl-context="TestContext"/> | ||
| </reverse-proxy> | ||
| </handlers> | ||
| <filters> | ||
| <request-limit max-concurrent-requests="15000" name="limit-connections" queue-size="100"/> | ||
| <response-header header-name="MY_HEADER" header-value="someValue" name="headers"/> | ||
| <gzip name="static-gzip"/> | ||
| <error-page code="404" name="404-handler" path="/opt/data/404.html"/> | ||
| <mod-cluster advertise-frequency="1000" advertise-path="/foo" advertise-protocol="ajp" | ||
| advertise-socket-binding="advertise-socket-binding" broken-node-timeout="1000" | ||
| cached-connections-per-thread="10" connection-idle-timeout="10" | ||
| failover-strategy="DETERMINISTIC" health-check-interval="600" | ||
| management-access-predicate="method[GET]" management-socket-binding="test3" | ||
| max-request-time="1000" max-retries="10" name="mod-cluster" | ||
| security-key="password" ssl-context="TestContext" max-ajp-packet-size="10000" /> | ||
| <filter class-name="io.undertow.server.handlers.HttpTraceHandler" module="io.undertow.core" name="custom-filter"/> | ||
| <expression-filter expression="dump-request" name="requestDumper"/> | ||
| <rewrite name="redirects" redirect="true" target="'/foo/'"/> | ||
| </filters> | ||
| <application-security-domains> | ||
| <application-security-domain enable-jacc="true" http-authentication-factory="elytron-factory" name="other" override-deployment-config="true" enable-jaspi="false" integrated-jaspi="false"> | ||
| <single-sign-on client-ssl-context="my-ssl-context" cookie-name="SSOID" domain="${prop.domain:myDomain}" http-only="true" key-alias="my-key-alias" key-store="my-key-store" path="/path" secure="true"> | ||
| <credential-reference alias="my-credential-alias" store="my-credential-store" type="password"/> | ||
| </single-sign-on> | ||
| </application-security-domain> | ||
| <application-security-domain security-domain="elytron-domain" name="domain-ref" /> | ||
| </application-security-domains> | ||
| </subsystem> |