INTSAMPLES-63 - Polish Tcp-Client-Server Sample
For reference see: https://jira.springsource.org/browse/INTSAMPLES-63 * Update README.md * Code clean-up (E.g. convert spaces to tabs) * Make sample executable via Maven
This commit is contained in:
@@ -19,36 +19,37 @@ import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
|
||||
/**
|
||||
* @author: ceposta
|
||||
* @author Christian Posta
|
||||
* @author Gunnar Hillert
|
||||
*/
|
||||
public class CustomOrder {
|
||||
private int number;
|
||||
private String sender;
|
||||
private String message;
|
||||
private int number;
|
||||
private String sender;
|
||||
private String message;
|
||||
|
||||
public CustomOrder(int number, String sender) {
|
||||
this.number = number;
|
||||
this.sender = sender;
|
||||
}
|
||||
public CustomOrder(int number, String sender) {
|
||||
this.number = number;
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,104 +42,103 @@ import org.springframework.core.serializer.Serializer;
|
||||
* content is. After that message content is parsed from the stream, the stream is assumed
|
||||
* to not have anything after it. In your code you could have delimiters to mark the end
|
||||
* of the stream, or could agree with the client that a valid stream is only n characters,
|
||||
* etc. Eitherway, since its custom, the client and server must have some predefined
|
||||
* etc. Either way, since its custom, the client and server must have some predefined
|
||||
* assumptions in place for the communication to take place.
|
||||
*
|
||||
*
|
||||
* @author: ceposta
|
||||
* @author Christian Posta
|
||||
* @author Gunnar Hillert
|
||||
*/
|
||||
public class CustomSerializerDeserializer implements Serializer<CustomOrder>, Deserializer<CustomOrder>{
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private static final int ORDER_NUMBER_LENGTH = 3;
|
||||
private static final int SENDER_NAME_LENGTH = 10;
|
||||
private static final int MESSAGE_LENGTH_LENGTH = 6;
|
||||
private static final int ORDER_NUMBER_LENGTH = 3;
|
||||
private static final int SENDER_NAME_LENGTH = 10;
|
||||
private static final int MESSAGE_LENGTH_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* Convert a CustomOrder object into a byte-stream
|
||||
*
|
||||
* @param object
|
||||
* @param outputStream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void serialize(CustomOrder object, OutputStream outputStream) throws IOException {
|
||||
byte[] number = Integer.toString(object.getNumber()).getBytes();
|
||||
outputStream.write(number);
|
||||
|
||||
/**
|
||||
* Convert a CustomOrder object into a byte-stream
|
||||
*
|
||||
* @param object
|
||||
* @param outputStream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void serialize(CustomOrder object, OutputStream outputStream) throws IOException {
|
||||
byte[] number = Integer.toString(object.getNumber()).getBytes();
|
||||
outputStream.write(number);
|
||||
byte[] senderName = object.getSender().getBytes();
|
||||
outputStream.write(senderName);
|
||||
|
||||
byte[] senderName = object.getSender().getBytes();
|
||||
outputStream.write(senderName);
|
||||
String lenghtPadded = pad(6, object.getMessage().length());
|
||||
byte[] length = lenghtPadded.getBytes();
|
||||
outputStream.write(length);
|
||||
|
||||
String lenghtPadded = pad(6, object.getMessage().length());
|
||||
byte[] length = lenghtPadded.getBytes();
|
||||
outputStream.write(length);
|
||||
outputStream.write(object.getMessage().getBytes());
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
outputStream.write(object.getMessage().getBytes());
|
||||
outputStream.flush();
|
||||
}
|
||||
private String pad(int desiredLength, int length) {
|
||||
return StringUtils.leftPad(Integer.toString(length), desiredLength, '0');
|
||||
}
|
||||
|
||||
private String pad(int desiredLength, int length) {
|
||||
return StringUtils.leftPad(Integer.toString(length), desiredLength, '0');
|
||||
}
|
||||
/**
|
||||
* Convert a raw byte stream into a CustomOrder
|
||||
*
|
||||
* @param inputStream
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public CustomOrder deserialize(InputStream inputStream) throws IOException {
|
||||
int orderNumber = parseOrderNumber(inputStream);
|
||||
String senderName = parseSenderName(inputStream);
|
||||
|
||||
/**
|
||||
* Convert a raw byte stream into a CustomOrder
|
||||
*
|
||||
* @param inputStream
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public CustomOrder deserialize(InputStream inputStream) throws IOException {
|
||||
int orderNumber = parseOrderNumber(inputStream);
|
||||
String senderName = parseSenderName(inputStream);
|
||||
CustomOrder order = new CustomOrder(orderNumber, senderName);
|
||||
String message = parseMessage(inputStream);
|
||||
order.setMessage(message);
|
||||
return order;
|
||||
}
|
||||
|
||||
CustomOrder order = new CustomOrder(orderNumber, senderName);
|
||||
String message = parseMessage(inputStream);
|
||||
order.setMessage(message);
|
||||
return order;
|
||||
}
|
||||
private String parseMessage(InputStream inputStream) throws IOException {
|
||||
String lengthString = parseString(inputStream, MESSAGE_LENGTH_LENGTH);
|
||||
int lengthOfMessage = Integer.valueOf(lengthString);
|
||||
|
||||
private String parseMessage(InputStream inputStream) throws IOException {
|
||||
String lengthString = parseString(inputStream, MESSAGE_LENGTH_LENGTH);
|
||||
int lengthOfMessage = Integer.valueOf(lengthString);
|
||||
String message = parseString(inputStream, lengthOfMessage);
|
||||
return message;
|
||||
}
|
||||
|
||||
String message = parseString(inputStream, lengthOfMessage);
|
||||
return message;
|
||||
}
|
||||
private String parseString(InputStream inputStream, int length) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
private String parseString(InputStream inputStream, int length) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int c;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
c = inputStream.read();
|
||||
checkClosure(c);
|
||||
builder.append((char)c);
|
||||
}
|
||||
|
||||
int c;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
c = inputStream.read();
|
||||
checkClosure(c);
|
||||
builder.append((char)c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
private String parseSenderName(InputStream inputStream) throws IOException {
|
||||
return parseString(inputStream, SENDER_NAME_LENGTH);
|
||||
}
|
||||
|
||||
private String parseSenderName(InputStream inputStream) throws IOException {
|
||||
return parseString(inputStream, SENDER_NAME_LENGTH);
|
||||
}
|
||||
private int parseOrderNumber(InputStream inputStream) throws IOException {
|
||||
String value = parseString(inputStream, ORDER_NUMBER_LENGTH);
|
||||
return Integer.valueOf(value.toString());
|
||||
}
|
||||
|
||||
private int parseOrderNumber(InputStream inputStream) throws IOException {
|
||||
String value = parseString(inputStream, ORDER_NUMBER_LENGTH);
|
||||
return Integer.valueOf(value.toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the byte passed in is the "closed socket" byte
|
||||
* Note, I put this in here just as an example, but you could just extend the
|
||||
* {@link org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer} class
|
||||
* which has this method
|
||||
*
|
||||
* @param bite
|
||||
* @throws IOException
|
||||
*/
|
||||
/**
|
||||
* Check whether the byte passed in is the "closed socket" byte
|
||||
* Note, I put this in here just as an example, but you could just extend the
|
||||
* {@link org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer} class
|
||||
* which has this method
|
||||
*
|
||||
* @param bite
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void checkClosure(int bite) throws IOException {
|
||||
if (bite < 0) {
|
||||
logger.debug("Socket closed during message assembly");
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.integration.samples.tcpclientserver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.springframework.context.support.GenericXmlApplicationContext;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
|
||||
import org.springframework.integration.ip.util.TestingUtilities;
|
||||
import org.springframework.integration.test.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* Demonstrates the use of a gateway as an entry point into the integration flow.
|
||||
* The message generated by the gateway is sent over tcp by the outbound gateway
|
||||
* to the inbound gateway. In turn the inbound gateway sends the message to an
|
||||
* echo service and the echoed response comes back over tcp and is returned to
|
||||
* the test case for verification.
|
||||
*
|
||||
* The test uses explicit transformers to convert the byte array payloads to
|
||||
* Strings.
|
||||
*
|
||||
* Several other samples are provided as JUnit test-cases:
|
||||
*
|
||||
* <ul>
|
||||
* <li>TcpClientServerDemoWithConversionServiceTest</li>
|
||||
* <li>TcpServerConnectionDeserializeTest</li>
|
||||
* <li>TcpServerCustomSerializerTest</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
public final class Main {
|
||||
|
||||
/**
|
||||
* Prevent instantiation.
|
||||
*/
|
||||
private Main() {}
|
||||
|
||||
/**
|
||||
* Load the Spring Integration Application Context
|
||||
*
|
||||
* @param args - command line arguments
|
||||
*/
|
||||
public static void main(final String... args) {
|
||||
|
||||
final Scanner scanner = new Scanner(System.in);
|
||||
|
||||
System.out.println("\n========================================================="
|
||||
+ "\n "
|
||||
+ "\n Welcome to the Spring Integration "
|
||||
+ "\n TCP-Client-Server Sample! "
|
||||
+ "\n "
|
||||
+ "\n For more information please visit: "
|
||||
+ "\n http://www.springintegration.org/ "
|
||||
+ "\n "
|
||||
+ "\n=========================================================" );
|
||||
|
||||
final GenericXmlApplicationContext context = Main.setupContext();
|
||||
final SimpleGateway gateway = context.getBean(SimpleGateway.class);
|
||||
final AbstractServerConnectionFactory crLfServer = context.getBean(AbstractServerConnectionFactory.class);
|
||||
|
||||
System.out.print("Waiting for server to accept connections...");
|
||||
TestingUtilities.waitListening(crLfServer, 10000L);
|
||||
System.out.println("running.\n\n");
|
||||
|
||||
System.out.println("Please enter some text and press <enter>: ");
|
||||
System.out.println("\tNote:");
|
||||
System.out.println("\t- Entering FAIL will create an exception");
|
||||
System.out.println("\t- Entering q will quit the application");
|
||||
System.out.print("\n");
|
||||
System.out.println("\t--> Please also check out the other samples, " +
|
||||
"that are provided as JUnit tests.");
|
||||
System.out.println("\t--> You can also connect to the server on port '" + crLfServer.getPort() + "' using Telnet.\n\n");
|
||||
|
||||
while (true) {
|
||||
|
||||
final String input = scanner.nextLine();
|
||||
|
||||
if("q".equals(input.trim())) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
final String result = gateway.send(input);
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Exiting application...bye.");
|
||||
System.exit(0);
|
||||
|
||||
}
|
||||
|
||||
public static GenericXmlApplicationContext setupContext() {
|
||||
final GenericXmlApplicationContext context = new GenericXmlApplicationContext();
|
||||
|
||||
System.out.print("Detect open server socket...");
|
||||
int availableServerSocket = SocketUtils.findAvailableServerSocket(5678);
|
||||
|
||||
final Map<String, Object> sockets = new HashMap<String, Object>();
|
||||
sockets.put("availableServerSocket", availableServerSocket);
|
||||
|
||||
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);
|
||||
|
||||
context.getEnvironment().getPropertySources().addLast(propertySource);
|
||||
|
||||
System.out.println("using port " + context.getEnvironment().getProperty("availableServerSocket"));
|
||||
|
||||
context.load("classpath:META-INF/spring/integration/tcpClientServerDemo-context.xml");
|
||||
context.registerShutdownHook();
|
||||
context.refresh();
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.integration.samples.tcpclientserver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
|
||||
/**
|
||||
* The configured inbound gateway uses CRLF delimited messages which means
|
||||
* it works fine as a very simple Telnet server - connect using
|
||||
* telnet localhost 11111 - each time you hit enter you should see your input
|
||||
* echoed back, preceded by 'echo:'.
|
||||
*
|
||||
* Alternatively, you can also customize the port by providing an additional system
|
||||
* property at startup e.g. <i>-DavailableServerSocket=7777</i>
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
public class TelnetServer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"/META-INF/spring/integration/tcpClientServerDemo-context.xml"}, false);
|
||||
|
||||
final Map<String, Object> sockets = new HashMap<String, Object>();
|
||||
sockets.put("availableServerSocket", 11111);
|
||||
|
||||
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);
|
||||
|
||||
context.getEnvironment().getPropertySources().addLast(propertySource);
|
||||
context.refresh();
|
||||
|
||||
System.out.println("Use telnet and connect to port: " + context.getEnvironment().getProperty("availableServerSocket"));
|
||||
System.out.println("Press Enter/Return in the console to exit");
|
||||
System.in.read();
|
||||
System.out.println("exiting application...bye.\n\n");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,7 +44,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* assert that the payload, once it reaches a component (in this case, the message listener
|
||||
* we create and attach to the incomingServerChannel), does not have any of the Stx/Etx bytes.
|
||||
*
|
||||
* @author: ceposta
|
||||
* @author Christian Posta
|
||||
* @author Gunnar Hillert
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* the Java socket API on the front end (client) and the Spring Integration TCP inbound
|
||||
* gateway with the custom serializer/deserializers.
|
||||
*
|
||||
* @author ceposta
|
||||
* @author Christian Posta
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -26,9 +26,7 @@ import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.support.GenericXmlContextLoader;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
public class CustomTestContextLoader extends GenericXmlContextLoader {
|
||||
|
||||
@@ -44,7 +42,7 @@ public class CustomTestContextLoader extends GenericXmlContextLoader {
|
||||
sockets.put("availableServerSocket", availableServerSocket);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Available Server Socket 1: " + availableServerSocket);
|
||||
LOGGER.info("Available server socket: " + availableServerSocket);
|
||||
}
|
||||
|
||||
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);
|
||||
|
||||
Reference in New Issue
Block a user