/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.component.undertow; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import javax.net.ssl.SSLContext; import io.undertow.client.ClientRequest; import io.undertow.client.UndertowClient; import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.DefaultByteBufferPool; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; import io.undertow.util.HttpString; import org.apache.camel.AsyncCallback; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.TypeConverter; import org.apache.camel.http.common.cookie.CookieHandler; import org.apache.camel.impl.DefaultAsyncProducer; import org.apache.camel.util.URISupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.OptionMap; import org.xnio.Xnio; import org.xnio.XnioWorker; import org.xnio.ssl.XnioSsl; /** * The Undertow producer. * * The implementation of Producer is considered as experimental. The Undertow * client classes are not thread safe, their purpose is for the reverse proxy * usage inside Undertow itself. This may change in the future versions and * general purpose HTTP client wrapper will be added. Therefore this Producer * may be changed too. */ public class UndertowProducer extends DefaultAsyncProducer { private static final Logger LOG = LoggerFactory.getLogger(UndertowProducer.class); private UndertowClient client; private final UndertowEndpoint endpoint; private final OptionMap options; private DefaultByteBufferPool pool; private XnioSsl ssl; private XnioWorker worker; public UndertowProducer(final UndertowEndpoint endpoint, final OptionMap options) { super(endpoint); this.endpoint = endpoint; this.options = options; } @Override public UndertowEndpoint getEndpoint() { return endpoint; } @Override public boolean process(final Exchange camelExchange, final AsyncCallback callback) { final URI uri; final HttpString method; try { final String exchangeUri = UndertowHelper.createURL(camelExchange, getEndpoint()); uri = UndertowHelper.createURI(camelExchange, exchangeUri, getEndpoint()); method = UndertowHelper.createMethod(camelExchange, endpoint, camelExchange.getIn().getBody() != null); } catch (final URISyntaxException e) { camelExchange.setException(e); callback.done(true); return true; } final String pathAndQuery = URISupport.pathAndQueryOf(uri); final UndertowHttpBinding undertowHttpBinding = endpoint.getUndertowHttpBinding(); final CookieHandler cookieHandler = endpoint.getCookieHandler(); final Map<String, List<String>> cookieHeaders; if (cookieHandler != null) { try { cookieHeaders = cookieHandler.loadCookies(camelExchange, uri); } catch (final IOException e) { camelExchange.setException(e); callback.done(true); return true; } } else { cookieHeaders = Collections.emptyMap(); } final ClientRequest request = new ClientRequest(); request.setMethod(method); request.setPath(pathAndQuery); final HeaderMap requestHeaders = request.getRequestHeaders(); // Set the Host header final Message message = camelExchange.getIn(); final String host = message.getHeader(Headers.HOST_STRING, String.class); requestHeaders.put(Headers.HOST, Optional.ofNullable(host).orElseGet(() -> uri.getAuthority())); final Object body = undertowHttpBinding.toHttpRequest(request, camelExchange.getIn()); final TypeConverter tc = endpoint.getCamelContext().getTypeConverter(); final ByteBuffer bodyAsByte = tc.tryConvertTo(ByteBuffer.class, body); // As tryConvertTo is used to convert the body, we should do null check // or the call bodyAsByte.remaining() may throw an NPE if (body != null && bodyAsByte != null) { requestHeaders.put(Headers.CONTENT_LENGTH, bodyAsByte.remaining()); } for (final Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) { requestHeaders.putAll(HttpString.tryFromString(entry.getKey()), entry.getValue()); } if (LOG.isDebugEnabled()) { LOG.debug("Executing http {} method: {}", method, pathAndQuery); } final UndertowClientCallback clientCallback = new UndertowClientCallback(camelExchange, callback, getEndpoint(), request, bodyAsByte); // when connect succeeds or fails UndertowClientCallback will // get notified on a I/O thread run by Xnio worker. The writing // of request and reading of response is performed also in the // callback client.connect(clientCallback, uri, worker, ssl, pool, options); // the call above will proceed on Xnio I/O thread we will // notify the exchange asynchronously when the HTTP exchange // ends with success or failure from UndertowClientCallback return false; } @Override protected void doStart() throws Exception { super.doStart(); // as in Undertow tests pool = new DefaultByteBufferPool(true, 17 * 1024); final Xnio xnio = Xnio.getInstance(); worker = xnio.createWorker(options); final SSLContext sslContext = getEndpoint().getSslContext(); if (sslContext != null) { ssl = new UndertowXnioSsl(xnio, options, sslContext); } final CamelContext camelContext = getEndpoint().getCamelContext(); client = UndertowClient.getInstance(camelContext.getApplicationContextClassLoader()); LOG.debug("Created worker: {} with options: {}", worker, options); } @Override protected void doStop() throws Exception { super.doStop(); if (worker != null && !worker.isShutdown()) { LOG.debug("Shutting down worker: {}", worker); worker.shutdown(); } } }