/**
* 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();
}
}
}