/* * Copyright 2013-2014 the original author or authors. * * 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.springframework.xd.integration.reactor.net; import org.springframework.beans.factory.FactoryBean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import reactor.Environment; import reactor.core.Dispatcher; import reactor.io.buffer.Buffer; import reactor.io.codec.Codec; import reactor.io.codec.DelimitedCodec; import reactor.io.codec.LengthFieldCodec; import reactor.io.codec.StandardCodecs; import reactor.io.net.ChannelStream; import reactor.io.net.NetStreams; import reactor.io.net.ReactorPeer; import reactor.io.net.Spec; import reactor.io.net.codec.syslog.SyslogCodec; import reactor.io.net.impl.netty.http.NettyHttpServer; import reactor.io.net.impl.netty.tcp.NettyTcpServer; import reactor.io.net.impl.netty.udp.NettyDatagramServer; import java.net.URI; import java.util.HashMap; import java.util.Map; /** * @author Stephane Maldini */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ReactorPeerFactoryBean<IN, OUT> implements FactoryBean<ReactorPeer<IN, OUT, ?>> { private static final Map<String, Codec> DEFAULT_CODECS = new HashMap<String, Codec>(); static final String UDP = "udp"; static final String TCP = "tcp"; static { DEFAULT_CODECS.put("bytes", StandardCodecs.BYTE_ARRAY_CODEC); DEFAULT_CODECS.put("string", StandardCodecs.STRING_CODEC); DEFAULT_CODECS.put("syslog", new SyslogCodec()); } private final String transport; private final Environment environment; private final Map<String, Codec> codecs; private String host; private Dispatcher dispatcher; private int port; private String framing; private int lengthFieldLength; private Codec delegateCodec; public ReactorPeerFactoryBean(Environment env, String transport, Map<String, Codec> codecs) { this.codecs = (null == codecs ? DEFAULT_CODECS : codecs); this.transport = transport; this.environment = (null == env ? Environment.initializeIfEmpty().assignErrorJournal() : env); } /** * Set the name of the {@link reactor.core.Dispatcher} to use, which will be pulled from the current {@link * reactor.Environment}. * * @param dispatcher dispatcher name * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setDispatcher(String dispatcher) { Assert.notNull(environment, "Cannot lookup dispatcher from unassigned environment"); this.dispatcher = environment.getDispatcher(dispatcher); return this; } public ReactorPeerFactoryBean<IN, OUT> setDispatcher(Dispatcher dispatcher) { this.dispatcher = dispatcher; return this; } /** * Set the host to which this server will bind. * * @param host the host to bind to (defaults to {@code 0.0.0.0}) * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setHost(String host) { Assert.notNull(host, "Host cannot be null."); this.host = host; return this; } /** * Set the port to which this server will bind. * * @param port the port to bind to (defaults to {@code 3000}) * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setPort(int port) { Assert.isTrue(port > 0, "Port must be greater than 0"); this.port = port; return this; } /** * Set the type of {@link reactor.io.codec.Codec} to use to managing encoding and decoding of the data. <p> The * default options for codecs are: <ul> <li>{@code bytes} - Use the standard byte array codec.</li> <li>{@code * string} - Use the standard String codec.</li> <li>{@code syslog} - Use the standard Syslog codec.</li> </ul> * </p> * * @param codec the codec * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setCodec(String codec) { this.delegateCodec = codecs.get(codec); Assert.notNull(delegateCodec, "No Codec found for type " + codec); return this; } /** * Set the type of framing to use. <p> The options for framing are: <ul> <li>{@code linefeed} - Means use an * LF-delimited linefeed codec.</li> <li>{@code length} - Means use a length-field based codec where the initial * bytes of a message are the length of the rest of the message.</li> </ul> </p> * * @param framing type of framing * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setFraming(String framing) { Assert.isTrue("linefeed".equals(framing) || "length".equals(framing)); this.framing = framing; return this; } /** * Set the length of the length field if using length-field framing. * * @param lengthFieldLength {@code 2} for a {@code short}, {@code 4} for an {@code int} (the default), or {@code 8} * for a {@code long} * @return {@literal this} */ public ReactorPeerFactoryBean<IN, OUT> setLengthFieldLength(int lengthFieldLength) { this.lengthFieldLength = lengthFieldLength; return this; } @Override public ReactorPeer<IN, OUT, ?> getObject() throws Exception { if (TCP.equals(transport)) { return NetStreams.tcpServer(NettyTcpServer.class, new NetStreams.TcpServerFactory<IN, OUT>() { @Override public Spec.TcpServerSpec<IN, OUT> apply(Spec.TcpServerSpec<IN, OUT> spec) { commonSpecProperties(spec); //Add specifics to TCP return spec; } }); } else if (UDP.equals(transport)) { return NetStreams.udpServer(NettyDatagramServer.class, new NetStreams.UdpServerFactory<IN, OUT>() { @Override public Spec.DatagramServerSpec<IN, OUT> apply(Spec.DatagramServerSpec<IN, OUT> spec) { commonSpecProperties(spec); //Add specifics to UDP return spec; } }); } else { throw new IllegalArgumentException(transport + " not a value transport protocol type, only udp or tcp allowed"); } } @Override public Class<?> getObjectType() { return ReactorPeer.class; } @Override public boolean isSingleton() { return true; } private void commonSpecProperties(Spec.PeerSpec<IN, OUT, ?, ?, ?> spec) { spec .env(environment) .dispatcher(dispatcher) .listen(host, port); if (null != framing) { if ("linefeed".equals(framing)) { if(delegateCodec != null) { spec.codec(new DelimitedCodec(delegateCodec)); }else { // optimize for massive throughput by using lightweight codec in server spec.codec(new DelimitedCodec<>(false, (Codec<Buffer, IN, OUT>)StandardCodecs.PASS_THROUGH_CODEC)); } } else if ("length".equals(framing)) { if(delegateCodec != null) { spec.codec(new LengthFieldCodec(lengthFieldLength, delegateCodec)); }else{ spec.codec(new LengthFieldCodec<IN, OUT>(lengthFieldLength, (Codec<Buffer, IN, OUT>)StandardCodecs.PASS_THROUGH_CODEC)); } } } else if(delegateCodec != null){ spec.codec(delegateCodec); } } }