diff --git a/modules/iso-http-client/build.gradle b/modules/iso-http-client/build.gradle
new file mode 100644
index 0000000000..7b759431c9
--- /dev/null
+++ b/modules/iso-http-client/build.gradle
@@ -0,0 +1,10 @@
+description = 'jPOS-EE :: ISO over HTTP Client Code'
+
+dependencies {
+ compile libraries.jpos
+
+ compile "org.eclipse.jetty:jetty-client:${jettyVersion}"
+ compile "org.eclipse.jetty.http2:http2-client:${jettyVersion}"
+ compile "org.eclipse.jetty.http2:http2-http-client-transport:${jettyVersion}"
+}
+
diff --git a/modules/iso-http-client/src/main/java/org/jpos/http/HttpClientSocketFactory.java b/modules/iso-http-client/src/main/java/org/jpos/http/HttpClientSocketFactory.java
new file mode 100644
index 0000000000..c7ee6149b4
--- /dev/null
+++ b/modules/iso-http-client/src/main/java/org/jpos/http/HttpClientSocketFactory.java
@@ -0,0 +1,321 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.jpos.iso.ISOClientSocketFactory;
+import org.jpos.iso.ISOException;
+import org.jpos.core.Configurable;
+import org.jpos.core.Configuration;
+import org.jpos.core.ConfigurationException;
+import org.jpos.core.SimpleConfiguration;
+import org.jpos.util.LogEvent;
+import org.jpos.util.Logger;
+import org.jpos.util.SimpleLogSource;
+
+
+/**
+ * HttpClientSocketFactory
is used by BaseChannel
+ * in order to provide hooks for ISO traffic over HTTP.
+ * Used in conjunction with module iso-http-qrest or iso-http-servlet
+ * on the server side.
+ *
+ * @version $Revision$ $Date$
+ * @author Ozzy Espaillat
+ *
+ */
+public class HttpClientSocketFactory extends SimpleLogSource
+implements ISOClientSocketFactory, Configurable {
+ enum HttpType {HTTP, HTTP2};
+ static final int DEFAULT_SENDER_THREADS=5;
+
+ private static HttpClientSocketFactory DEFAULT;
+
+ public static synchronized HttpClientSocketFactory getDefaultInstance() {
+ if (DEFAULT == null) {
+ DEFAULT = new HttpClientSocketFactory(true);
+ }
+ return DEFAULT;
+ }
+
+ private Map httpClients = new HashMap();
+
+ private Configuration cfg;
+
+ boolean failOnError = false;
+ int senderThreads = DEFAULT_SENDER_THREADS;
+ private int maxSendPerRequest = 1;
+
+ String sslProviderClass = null;
+ private String trustStore=null;
+ private String trustStorePassword=null;
+ private String keyStore=null;
+ private String password=null;
+ private String keyPassword=null;
+ private boolean serverAuthNeeded=false;
+ private String[] enabledCipherSuites;
+ private String[] enabledProtocols;
+
+ public void setKeyStore(String keyStore){
+ this.keyStore=keyStore;
+ }
+
+ public void setPassword(String password){
+ this.password=password;
+ }
+
+ public void setKeyPassword(String keyPassword){
+ this.keyPassword=keyPassword;
+ }
+
+ public void setServerAuthNeeded(boolean serverAuthNeeded){
+ this.serverAuthNeeded=serverAuthNeeded;
+ }
+
+ public int getMaxSendPerRequest() {
+ return maxSendPerRequest;
+ }
+
+ public void setMaxSendPerRequest(int maxSendPerRequest) {
+ this.maxSendPerRequest = maxSendPerRequest;
+ }
+
+ private synchronized HttpClient getHttpClient(HttpType type) {
+ HttpClient httpClient = httpClients.get(type);
+ if (httpClient == null) {
+ LogEvent evt = new LogEvent (this, "info");
+ // Instantiate and configure the SslContextFactory
+ SslContextFactory sslContextFactory = new SslContextFactory(!serverAuthNeeded);
+ sslContextFactory.setExcludeCipherSuites("^.*_(MD5|SHA1)$");
+
+ if (keyStore != null && keyStore.length() > 0) {
+ sslContextFactory.setKeyStorePath(keyStore);
+ evt.addMessage("keystore", keyStore);
+ sslContextFactory.setKeyStorePassword(password);
+ if (keyPassword != null && keyPassword.length() > 0) {
+ sslContextFactory.setKeyManagerPassword(keyPassword);
+ }
+ }
+ if (trustStore != null && trustStore.length() > 0) {
+ sslContextFactory.setTrustStorePath(trustStore);
+ sslContextFactory.setTrustStorePassword(trustStorePassword);
+ }
+ if (enabledCipherSuites != null && enabledCipherSuites.length > 0) {
+ sslContextFactory.setIncludeCipherSuites(enabledCipherSuites);
+ evt.addMessage("cipher", Arrays.toString(enabledCipherSuites));
+ }
+ if (enabledProtocols != null && enabledProtocols.length > 0) {
+ sslContextFactory.setIncludeProtocols(enabledProtocols);
+ evt.addMessage("protocol", Arrays.toString(enabledProtocols));
+ }
+
+ try {
+ if (sslProviderClass != null) {
+ Provider sslProviderName = (Provider) Class.forName(sslProviderClass).newInstance();
+ if (Security.getProvider(sslProviderName.getName()) != null) {
+ Security.addProvider(sslProviderName);
+ }
+ evt.addMessage("Using " + sslProviderName.getName() + " Provider.");
+ sslContextFactory.setProvider(sslProviderName.getName());
+ }
+ } catch (Throwable e) {
+ evt.addMessage("error", "Failed to load OpenSSLProvider, ignoring: " + e.toString());
+ }
+
+ switch (type) {
+ case HTTP2 :
+ HTTP2Client h2Client = new HTTP2Client();
+ h2Client.setSelectors(1);
+ h2Client.addBean(sslContextFactory);
+
+ HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
+
+
+
+ // Instantiate HttpClient with the SslContextFactory
+ httpClient = new HttpClient(transport, sslContextFactory);
+ evt.addMessage("Using HTTP2 transport!");
+
+ break;
+ case HTTP :
+ // Instantiate HttpClient with the SslContextFactory
+ httpClient = new HttpClient(sslContextFactory);
+ evt.addMessage("Using HTTP1.1 transport!");
+ break;
+ }
+
+ // Configure HttpClient, for example:
+ httpClient.setFollowRedirects(false);
+
+ //httpClient.setMaxConnectionsPerDestination(senderThreads*2);
+
+ // Start HttpClient
+ try {
+ httpClient.start();
+ } catch (Exception e) {
+ evt.addMessage("error", "Could not start http client");
+ evt.addMessage(e);
+ }
+ Logger.log(evt);
+
+ httpClients.put(type, httpClient);
+ } else if (!httpClient.isRunning()) {
+ // Start HttpClient
+ try {
+ httpClient.start();
+ } catch (Exception e) {
+ LogEvent evt = new LogEvent (this, "warn");
+ evt.addMessage("error", "Could not start http client");
+ evt.addMessage(e);
+ Logger.log(evt);
+ }
+ }
+ try {
+ while(!httpClient.isStarted() && !httpClient.isFailed()) {
+ Thread.sleep(100);
+ }
+ } catch (InterruptedException e) {}
+
+ return httpClient;
+ }
+
+ @Override
+ public void finalize() {
+ for(HttpClient httpClient: httpClients.values()) {
+ if (httpClient != null) {
+ try {
+ httpClient.stop();
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+
+ public HttpClientSocketFactory() {
+ this(false);
+ }
+
+ public HttpClientSocketFactory(boolean failOnError) {
+ this(failOnError, DEFAULT_SENDER_THREADS);
+ }
+
+ public HttpClientSocketFactory(int senderThreads) {
+ this(false, senderThreads);
+ }
+
+ public HttpClientSocketFactory(boolean failOnError, int senderThreads) {
+ super();
+ try {
+ setConfiguration(new SimpleConfiguration());
+ } catch (ConfigurationException e) {
+ }
+
+ this.senderThreads = senderThreads;
+ this.failOnError = failOnError;
+ }
+
+
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, ISOException {
+ LogEvent evt = new LogEvent (this, "info");
+ Socket s = null;
+
+ evt.addMessage("connect");
+ if (host.toLowerCase().startsWith("http")) {
+ HttpType type;
+ if (host.startsWith("http2")) {
+ type = HttpType.HTTP2;
+ host = "http" + host.substring("http2".length());
+ } else {
+ type = HttpType.HTTP;
+ }
+ if (host.endsWith("/iso")) {
+ host = host + "/" + port;
+ } else if (host.endsWith("/iso/")) {
+ host = host + port;
+ }
+ evt.addMessage("type", type.name());
+ evt.addMessage("host", host);
+ evt.addMessage("senders", Integer.toString(senderThreads));
+ evt.addMessage("failOnError", Boolean.toString(failOnError));
+
+ HttpSocketWrapper httpSocket = new HttpSocketWrapper(this, getHttpClient(type), host, senderThreads, failOnError);
+ httpSocket.setMaxSendPerRequest(maxSendPerRequest);
+ s = httpSocket;
+ } else {
+ evt.addMessage("type", "socket");
+ evt.addMessage("host", host);
+ evt.addMessage("port", Integer.toString(port));
+
+ s = new Socket(host, port);
+ }
+ Logger.log(evt);
+
+ return s;
+ }
+
+ @Override
+ public void setConfiguration(Configuration cfg) throws ConfigurationException {
+ this.cfg = cfg;
+ failOnError = cfg.getBoolean("failOnError", false);
+ maxSendPerRequest = cfg.getInt("maxSendPerRequest", 1);
+ senderThreads = cfg.getInt("senderThreads", DEFAULT_SENDER_THREADS);
+ sslProviderClass = cfg.get("sslProvider", null);
+ serverAuthNeeded = cfg.getBoolean("serverauth");
+
+ trustStore = cfg.get("truststore", System.getProperty("javax.net.ssl.trustStore"));
+ trustStorePassword = cfg.get("truststorepassword", System.getProperty("javax.net.ssl.trustStorePassword", "changeit"));
+
+ keyStore = cfg.get("keystore", System.getProperty("javax.net.ssl.keyStore"));
+ String passwordProp = cfg.get("storepasswordprop", "javax.net.ssl.keyStorePassword");
+ password = cfg.get("storepassword", System.getProperty(passwordProp));
+
+ String keyPaswwordProp = cfg.get("keypassword", "jpos.ssl.keypass");
+ keyPassword = cfg.get("keypassword", System.getProperty(keyPaswwordProp));
+
+ enabledCipherSuites = cfg.getAll("addEnabledCipherSuite");
+ enabledProtocols = cfg.getAll("addEnabledProtocol");
+ }
+
+ public Configuration getConfiguration() {
+ return cfg;
+ }
+
+ public static void main(String args[]) throws Exception {
+ HttpClientSocketFactory f = HttpClientSocketFactory.getDefaultInstance();
+ f.sslProviderClass = null;
+ HttpClient c = f.getHttpClient(HttpType.HTTP);
+ System.out.println(c.GET(args[0]).getContentAsString());
+ c.stop();
+ }
+
+}
diff --git a/modules/iso-http-client/src/main/java/org/jpos/http/HttpSocketWrapper.java b/modules/iso-http-client/src/main/java/org/jpos/http/HttpSocketWrapper.java
new file mode 100644
index 0000000000..627097ef67
--- /dev/null
+++ b/modules/iso-http-client/src/main/java/org/jpos/http/HttpSocketWrapper.java
@@ -0,0 +1,246 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ *
+ * Wrapper around the Socket interface to connect via HTTP.
+ *
+ */
+public class HttpSocketWrapper extends Socket {
+ private static final Logger logger = LoggerFactory.getLogger(HttpSocketWrapper.class);
+
+ HttpClientSocketFactory httpClientSocketFactory;
+ URI host;
+ private OutputStream out;
+ private PipedInputStream in;
+ private PipedOutputStream pOut;
+ private boolean isConnected = false;
+ private boolean closeOnError;
+
+ private LinkedBlockingQueue queue;
+ private Thread t[];
+
+ private int timeout = 0;
+ private int maxSendPerRequest = 1;
+ private int clients = 5;
+
+
+ public HttpSocketWrapper(HttpClientSocketFactory httpClientSocketFactory, HttpClient http, String hostStr, int clients, boolean failOnError) throws IOException {
+ this.httpClientSocketFactory = httpClientSocketFactory;
+ this.host = URI.create(hostStr);
+
+ this.clients = clients;
+ this.closeOnError = failOnError;
+ isConnected = true;
+
+ in = new PipedInputStream(512*clients);
+ pOut = new PipedOutputStream(in);
+
+ queue = new LinkedBlockingQueue(clients);
+ Runnable job = new Runnable() {
+ @Override
+ public void run() {
+ List list = new ArrayList();
+ while (isConnected()) {
+ byte data[];
+ try {
+ data = queue.poll(10, TimeUnit.SECONDS);
+ if (data == null || data.length == 0) {
+ continue;
+ }
+ list.add(data);
+ if (getMaxSendPerRequest() > 1) {
+ queue.drainTo(list, getMaxSendPerRequest()-1);
+ }
+ } catch (InterruptedException e1) {
+ logger.warn("Thread interrupted while waiting on queue.", e1);
+ }
+ logger.debug("Connecting to {} with {} messages.", host, list.size());
+ try {
+ Request request = http.POST(host).content(new BytesContentProvider(list.toArray(new byte[0][0])));
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.send(listener); // Asynchronous send
+ ContentResponse r = listener.get(getWaitTime(), TimeUnit.SECONDS); // Timed block
+ logger.debug("Received Response: {}", r);
+ if(r.getStatus() == 200) {
+ byte responseData[] = r.getContent();
+ if (responseData != null) {
+ pOut.write(responseData);
+ synchronized(in) {
+ in.notifyAll();
+ }
+ }
+ } else {
+ try {
+ if (HttpSocketWrapper.this.closeOnError) {
+ logger.warn("Failed to get response, clossing socket");
+ HttpSocketWrapper.this.close();
+ }
+ } catch (IOException e) {
+ logger.warn("Problem while trying to close", e);
+ }
+
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to execute request.", e);
+ try {
+ if (HttpSocketWrapper.this.closeOnError) {
+ logger.warn("Failed to get response, clossing socket");
+ HttpSocketWrapper.this.close();
+ }
+ } catch (IOException c) {
+ logger.warn("Problem while trying to close", c);
+ }
+ }
+ list.clear();
+ }
+ }
+ };
+ t = new Thread[clients];
+ for (int i = 0; i < clients; i++) {
+ t[i] = new Thread(job, "HttpClient-" + i);
+ t[i].start();
+ }
+ out = new ByteArrayOutputStream(256) {
+
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ if (this.size() > 0) {
+ synchronized (this) {
+ byte data[] = this.toByteArray();
+ this.reset();
+ try {
+ queue.put(data);
+ } catch (InterruptedException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ int getWaitTime() {
+ return (timeout > 0) ? timeout * 2 : 30000;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return isConnected;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return in;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return out;
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ try {
+ return InetAddress.getByName(host.getHost());
+ } catch (Exception ignore) {}
+ return InetAddress.getLoopbackAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return host.getPort();
+ }
+
+ @Override
+ public void setSoLinger(boolean on, int linger) throws SocketException {
+ }
+
+ @Override
+ public synchronized void setSoTimeout(int timeout) throws SocketException {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public synchronized int getSoTimeout() {
+ return timeout;
+ }
+
+ @Override
+ public void setKeepAlive(boolean on) throws SocketException {
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ this.isConnected = false;
+ logger.debug("Closing socket.");
+ for(int i = 0; i < clients; i++) {
+ try {
+ queue.offer(new byte[0], 10, TimeUnit.SECONDS);
+ } catch (InterruptedException ignore) {}
+ }
+ if (pOut != null) {
+ pOut.close();
+ }
+ pOut = null;
+ if (in != null) {
+ in.close();
+ }
+ in = null;
+ if (out != null)
+ out.close();
+ out = null;
+ super.close();
+ }
+
+ public int getMaxSendPerRequest() {
+ return maxSendPerRequest;
+ }
+
+ public void setMaxSendPerRequest(int maxSendPerRequest) {
+ this.maxSendPerRequest = maxSendPerRequest;
+ }
+
+}
diff --git a/modules/iso-http-server/build.gradle b/modules/iso-http-server/build.gradle
new file mode 100644
index 0000000000..8eded71ff2
--- /dev/null
+++ b/modules/iso-http-server/build.gradle
@@ -0,0 +1,7 @@
+description = 'jPOS-EE :: ISO over HTTP Client Code'
+
+dependencies {
+ compile libraries.jpos
+ compile "org.jdom:jdom2:2.0.6"
+}
+
diff --git a/modules/iso-http-server/src/main/java/org/jpos/http/ClientRequestDetails.java b/modules/iso-http-server/src/main/java/org/jpos/http/ClientRequestDetails.java
new file mode 100644
index 0000000000..4cf0c40d09
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/http/ClientRequestDetails.java
@@ -0,0 +1,70 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+public class ClientRequestDetails {
+ String host;
+ int port;
+ int length;
+ int timeout = 25 * 1000;
+
+ public ClientRequestDetails(String host, int port, int length, int timeout) {
+ this(host, port, length);
+ this.timeout = timeout;
+ }
+
+ public ClientRequestDetails(String host, int port, int length) {
+ this.host = host;
+ this.port = port;
+ this.length = length;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsg.java b/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsg.java
new file mode 100644
index 0000000000..876c3daecd
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsg.java
@@ -0,0 +1,226 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom2.Element;
+import org.jpos.core.ConfigurationException;
+import org.jpos.iso.BaseChannel;
+import org.jpos.iso.HttpWrapperUtil;
+import org.jpos.iso.ISOChannelWrapper;
+import org.jpos.iso.ISOFilter;
+import org.jpos.iso.ISOMsg;
+import org.jpos.iso.ISOPackager;
+import org.jpos.iso.ISORequestListener;
+import org.jpos.iso.ISOServer;
+import org.jpos.q2.QFactory;
+import org.jpos.q2.iso.ChannelAdaptor;
+import org.jpos.q2.iso.QServer;
+import org.jpos.util.NameRegistrar;
+import org.jpos.util.NameRegistrar.NotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FilterISOMsg {
+ private static final Logger logger = LoggerFactory.getLogger(FilterISOMsg.class);
+
+ private static final int MIN_TIMEOUT = 5*000; // 5s min timeout
+
+ Collection requestListeners = new ArrayList();
+ ISOChannelWrapper channel = new ISOChannelWrapper();
+ String name;
+ int port;
+
+ public FilterISOMsg(String serverName) {
+ this.name = serverName;
+ }
+
+ public FilterISOMsg(BaseChannel isoChannel) {
+ this(isoChannel, null);
+ }
+
+ public FilterISOMsg(BaseChannel isoChannel, Collection requestListeners) {
+ configure(isoChannel, requestListeners);
+ }
+
+ public void configure(BaseChannel isoChannel, Collection requestListeners) {
+ channel.setChannel(isoChannel);
+ if (requestListeners != null) {
+ this.requestListeners = requestListeners;
+ }
+ if (name == null) {
+ name = isoChannel.getName();
+ }
+ if (isoChannel.getPort() > 0) {
+ port = isoChannel.getPort();
+ }
+ }
+
+ public ISOResponse process(byte[] inMsg, ClientRequestDetails requestDetails) {
+ requestDetails.setLength(inMsg.length);
+ ByteArrayInputStream in = new ByteArrayInputStream(inMsg);
+ return process(in, requestDetails);
+ }
+
+ public ISOResponse process(InputStream in, ClientRequestDetails requestDetails) {
+ ISOResponse res = null;
+ BaseChannel c = null;
+ try {
+ initialize();
+ ByteArrayOutputStream out = new ByteArrayOutputStream(512);
+ c = channel.getChannel(in, out, requestDetails);
+ List mList = new ArrayList();
+ do {
+ ISOMsg m = c.receive();
+ mList.add(m);
+ } while (HttpWrapperUtil.hasMoreData(c));
+ logger.debug("Received: expected {} bytes with {} messages.", requestDetails.length, mList.size());
+ res = new ISOResponse(c, mList, out);
+ for (ISOMsg m : mList) {
+ m.setSource(res);
+ processListeners(m);
+ }
+ } catch (Exception e) {
+ logger.error("Failed to process txn", e);
+ }
+
+ res.getResponse(requestDetails.getTimeout());
+
+ if (c != null) {
+ try {
+ c.disconnect();
+ } catch (IOException ignore) {
+ }
+ }
+
+ return res;
+ }
+
+ public void processListeners(ISOMsg request) {
+ for (ISORequestListener listener : requestListeners) {
+ if (listener.process(request.getSource(), request)) {
+ break;
+ }
+ }
+ }
+
+ private Collection getListeners(QServer server) throws ConfigurationException {
+ QFactory factory = server.getFactory();
+ @SuppressWarnings("rawtypes")
+ Iterator iter = server.getPersist().getChildren("request-listener").iterator();
+ Collection listeners = new ArrayList();
+ while (iter.hasNext()) {
+ Element l = (Element) iter.next();
+ ISORequestListener listener = (ISORequestListener) factory.newInstance(l.getAttributeValue("class"));
+ factory.setLogger(listener, l);
+ factory.setConfiguration(listener, l);
+ listeners.add(listener);
+ }
+ return listeners;
+ }
+
+ private BaseChannel getChannel(QServer server) throws ConfigurationException {
+ Element persist = server.getPersist();
+ Element e = persist.getChild("channel");
+ if (e == null) {
+ throw new ConfigurationException("channel element missing");
+ }
+
+ ChannelAdaptor adaptor = new ChannelAdaptor();
+ BaseChannel channel = (BaseChannel) adaptor.newChannel(e, server.getFactory());
+ if (channel == null) {
+ throw new ConfigurationException("Could not instanciate ISOChannel");
+ }
+ return channel;
+ }
+
+ public synchronized void initialize() throws ConfigurationException, NotFoundException {
+ if (getPackager() == null && name != null) {
+ Object entry = NameRegistrar.getIfExists(name);
+ if (entry instanceof QServer) {
+ QServer server = (QServer) entry;
+ configure(getChannel(server), getListeners(server));
+ setPort(server.getPort());
+ }
+ if (entry instanceof ISOServer) {
+ ISOServer server = (ISOServer) entry;
+ HttpWrapperUtil.configureFilter(this, server);
+ if (getPackager() == null) {
+ BaseChannel c = (BaseChannel) server.getISOChannel(":last");
+ if (c != null) {
+ channel.setChannel((BaseChannel) c.clone());
+ }
+ }
+ setPort(server.getPort());
+ }
+ }
+ if (getPort() <= 0 && name != null) {
+ Object entry = NameRegistrar.getIfExists(name);
+ if (entry instanceof QServer) {
+ QServer server = (QServer) entry;
+ setPort(server.getPort());
+ }
+ if (entry instanceof ISOServer) {
+ ISOServer server = (ISOServer) entry;
+ setPort(server.getPort());
+ }
+ }
+ }
+
+ public int getTimeout() {
+ return Math.max(channel.getChannel().getTimeout(), MIN_TIMEOUT);
+ }
+
+ public ISOPackager getPackager() {
+ return channel.getPackager();
+ }
+
+ public void setPackager(ISOPackager clientPackager) {
+ channel.setPackager(clientPackager);
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Collection getIncomingFilters() {
+ return channel.getChannel().getIncomingFilters();
+ }
+
+ public Collection getOutgoingFilters() {
+ return channel.getChannel().getOutgoingFilters();
+ }
+
+}
diff --git a/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsgRegistry.java b/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsgRegistry.java
new file mode 100644
index 0000000000..9cbf107779
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/http/FilterISOMsgRegistry.java
@@ -0,0 +1,157 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jpos.core.ConfigurationException;
+import org.jpos.iso.ISOServer;
+import org.jpos.q2.iso.QServer;
+import org.jpos.util.NameRegistrar;
+import org.jpos.util.NameRegistrar.NotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FilterISOMsgRegistry {
+ private static final Logger logger = LoggerFactory.getLogger(FilterISOMsgRegistry.class);
+
+ protected Map registry = new HashMap();
+ protected Map portRegistry = new HashMap();
+
+ protected boolean autoRegister = false;
+
+ public static final FilterISOMsgRegistry instance = new FilterISOMsgRegistry();
+
+ public boolean add(String name, boolean initialize) throws ConfigurationException, NotFoundException {
+ if (registry.get(name) != null) return false;
+
+ if ("*".equals(name)) {
+ autoRegister = true;
+ addAll(initialize);
+ } else {
+ FilterISOMsg filter = new FilterISOMsg(name);
+ init(filter, initialize);
+ registry.put(name, filter);
+ }
+
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getNameRegistryMap() throws ConfigurationException {
+ // In order to support old jpos getMap and the newer getAsMap
+ // we have to use reflection.
+ Map map = null;
+ Method getMap = null;
+ try {
+ getMap = NameRegistrar.class.getMethod("getAsMap", new Class[0]);
+ } catch (Exception e) {
+ try {
+ getMap = NameRegistrar.class.getMethod("getMap", new Class[0]);
+ } catch (NoSuchMethodException | SecurityException e1) {
+ }
+ }
+ if (getMap != null) {
+ try {
+ map = (Map) getMap.invoke(NameRegistrar.class, new Object[0]);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ logger.warn("Error getting NameRegistrar getMap or getAsMap, posibly wrong JPOS version.", e);
+ throw new ConfigurationException(e);
+ }
+ }
+ return map;
+ }
+
+ private void addAll(boolean initialize) throws ConfigurationException {
+ logger.debug("Adding All servers to http.");
+ Map r = getNameRegistryMap();
+ // Look for QServers to add. Note Also look for ISOServer to
+ // keep compatibility with really old jpos.
+ for(Map.Entry entry: r.entrySet()) {
+ if (entry.getValue() instanceof QServer || entry.getValue() instanceof ISOServer) {
+ try {
+ add(entry.getKey(), initialize);
+ } catch (ConfigurationException | NotFoundException e) {
+ logger.warn("Problem registering server, ignoring", e);
+ }
+ }
+ }
+
+ }
+
+ public List getRegisteredNames() {
+ return new ArrayList(registry.keySet());
+ }
+
+ public List getRegisteredPorts() {
+ return new ArrayList(portRegistry.keySet());
+ }
+
+ protected void init(FilterISOMsg filter, boolean initialize) throws ConfigurationException, NotFoundException {
+
+ if (initialize) {
+ filter.initialize();
+ }
+
+ if (filter.getPort() > 0) {
+ portRegistry.put(filter.getPort(), filter.getName());
+ }
+ }
+
+ public FilterISOMsg get(String name) {
+ FilterISOMsg filter = registry.get(name);
+ if (filter == null && name.matches("^\\d+$")) {
+ Integer port = new Integer(name);
+ name = portRegistry.get(port);
+ if (name != null) {
+ filter = registry.get(name);
+ } else {
+ if (autoRegister) {
+ try {
+ addAll(true);
+ } catch (ConfigurationException ignore) {
+ logger.warn("Problem with NameRegistrar posibly wrong JPOS version, ignoring.", ignore);
+ }
+ }
+ Map unregistered = new HashMap();
+ unregistered.putAll(registry);
+ unregistered.keySet().removeAll(portRegistry.values());
+ for (FilterISOMsg f: unregistered.values()) {
+ try {
+ init(f, true);
+ } catch (ConfigurationException | NotFoundException e) {
+ logger.warn("Problem initializing server for http, ignoring", e);
+ }
+ if (f.getPort() == port) {
+ filter = f;
+ break;
+ }
+ }
+ }
+ }
+
+ return filter;
+ }
+
+}
diff --git a/modules/iso-http-server/src/main/java/org/jpos/http/ISOResponse.java b/modules/iso-http-server/src/main/java/org/jpos/http/ISOResponse.java
new file mode 100644
index 0000000000..6edd53ba05
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/http/ISOResponse.java
@@ -0,0 +1,88 @@
+/*
+ *
+ /*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jpos.iso.ISOChannel;
+import org.jpos.iso.ISOException;
+import org.jpos.iso.ISOMsg;
+import org.jpos.iso.ISOSource;
+
+public class ISOResponse implements ISOSource {
+ ISOChannel channel;
+ ByteArrayOutputStream out;
+ boolean timedout = false;
+
+ public List request;
+ public List response;
+
+ public ISOResponse(ISOChannel channel, List request, ByteArrayOutputStream out) {
+ super();
+ this.channel = channel;
+ this.request = request;
+ this.out = out;
+ response = new ArrayList(request.size());
+ }
+
+ @Override
+ public synchronized void send(ISOMsg m) throws IOException, ISOException {
+ response.add(m);
+ channel.send(m);
+ notifyAll();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return !timedout && !hasResponse();
+ }
+
+ public boolean hasResponse() {
+ return response.size() >= request.size();
+ }
+
+ public synchronized byte[] getResponse(long timeoutMs) {
+ long timeout = System.currentTimeMillis() + timeoutMs;
+ while (!hasResponse() && timeout > System.currentTimeMillis()) {
+ try {
+ wait(timeout - System.currentTimeMillis());
+ } catch (Exception ignore) {
+ }
+ }
+ if (!hasResponse()) {
+ timedout = true;
+ }
+
+ if (response.isEmpty()) {
+ return null;
+ } else {
+ return out.toByteArray();
+ }
+ }
+
+ public byte[] getResponse() {
+ return (!response.isEmpty() ? out.toByteArray() : null);
+ }
+
+}
diff --git a/modules/iso-http-server/src/main/java/org/jpos/iso/HttpWrapperUtil.java b/modules/iso-http-server/src/main/java/org/jpos/iso/HttpWrapperUtil.java
new file mode 100644
index 0000000000..03750b1b90
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/iso/HttpWrapperUtil.java
@@ -0,0 +1,41 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.iso;
+
+import java.io.IOException;
+
+import org.jpos.http.FilterISOMsg;
+
+public class HttpWrapperUtil {
+
+ public static boolean hasMoreData(BaseChannel c) throws IOException {
+ return c.serverIn.available() > 0;
+ }
+
+ public static void configureFilter(FilterISOMsg filter, ISOServer server) {
+ ISOChannel c = server.clientSideChannel != null ? server.clientSideChannel : server.getISOChannel(":last");
+ c = (c != null ? (ISOChannel) c.clone() : null);
+ filter.configure((BaseChannel) c, server.listeners);
+ if (filter.getPackager() == null) {
+ filter.setPackager(server.clientPackager);
+ }
+ filter.setPort(server.getPort());
+ }
+
+}
diff --git a/modules/iso-http-server/src/main/java/org/jpos/iso/ISOChannelWrapper.java b/modules/iso-http-server/src/main/java/org/jpos/iso/ISOChannelWrapper.java
new file mode 100644
index 0000000000..f9c19e44b3
--- /dev/null
+++ b/modules/iso-http-server/src/main/java/org/jpos/iso/ISOChannelWrapper.java
@@ -0,0 +1,124 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2018 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.iso;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+import org.jpos.http.ClientRequestDetails;
+
+public class ISOChannelWrapper {
+ private BaseChannel channel;
+
+ public BaseChannel getChannel(InputStream in, OutputStream out, ClientRequestDetails requestDetails)
+ throws IOException {
+ BaseChannel c = (BaseChannel) channel.clone();
+ FakeSocket s = new FakeSocket(in, out);
+ s.setRequest(requestDetails);
+ c.connect(s);
+
+ return c;
+ }
+
+ public BaseChannel getChannel() {
+ return channel;
+ }
+
+ public void setChannel(BaseChannel channel) {
+ this.channel = channel;
+ }
+
+ public void setPackager(ISOPackager p) {
+ channel.setPackager(p);
+ }
+
+ public ISOPackager getPackager() {
+ return channel != null ? channel.getPackager() : null;
+ }
+
+ static class FakeSocket extends Socket {
+ InputStream in;
+ OutputStream out;
+ boolean isConnected = false;
+ ClientRequestDetails requestDetails;
+
+ public FakeSocket(InputStream in, OutputStream out) {
+ this.in = in;
+ this.out = out;
+ isConnected = true;
+ }
+
+ public void setRequest(ClientRequestDetails requestDetails) {
+ this.requestDetails = requestDetails;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return isConnected;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return in;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return out;
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ try {
+ return (requestDetails != null && requestDetails.getHost() != null)
+ ? InetAddress.getByName(requestDetails.getHost()) : InetAddress.getLoopbackAddress();
+ } catch (Exception ignore) {
+
+ }
+ return InetAddress.getLoopbackAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return (requestDetails != null && requestDetails.getHost() != null) ? requestDetails.getPort() : 0;
+ }
+
+ @Override
+ public void setSoLinger(boolean on, int linger) throws SocketException {
+ }
+
+ @Override
+ public synchronized void setSoTimeout(int timeout) throws SocketException {
+ }
+
+ @Override
+ public void setKeepAlive(boolean on) throws SocketException {
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ this.isConnected = false;
+ super.close();
+ }
+ }
+}
diff --git a/modules/iso-http-servlet/build.gradle b/modules/iso-http-servlet/build.gradle
new file mode 100644
index 0000000000..56781610a4
--- /dev/null
+++ b/modules/iso-http-servlet/build.gradle
@@ -0,0 +1,20 @@
+description = 'jPOS-EE :: ISO over HTTP Client Code'
+
+apply plugin: 'war'
+
+dependencies {
+ compile libraries.jpos
+ compile libraries.servlet_api
+ compile "javax.ws.rs:javax.ws.rs-api:${jaxrsVersion}"
+ compile project(':modules:iso-http-server')
+ providedCompile project(':modules:jetty')
+}
+
+jar {
+ from 'src/main/webapp'
+}
+
+apply from: "${rootProject.projectDir}/jpos-app.gradle"
+
+installApp.dependsOn('war')
+dist.dependsOn('war')
diff --git a/modules/iso-http-servlet/src/main/java/org/jpos/http/service/HttpTransactionServlet.java b/modules/iso-http-servlet/src/main/java/org/jpos/http/service/HttpTransactionServlet.java
new file mode 100644
index 0000000000..d448e319fe
--- /dev/null
+++ b/modules/iso-http-servlet/src/main/java/org/jpos/http/service/HttpTransactionServlet.java
@@ -0,0 +1,139 @@
+package org.jpos.http.service;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.jpos.core.ConfigurationException;
+import org.jpos.util.NameRegistrar.NotFoundException;
+
+import org.jpos.http.ClientRequestDetails;
+import org.jpos.http.FilterISOMsg;
+import org.jpos.http.FilterISOMsgRegistry;
+import org.jpos.http.ISOResponse;
+
+
+/**
+ * HttpTransactionServlet
is used as a servlet
+ * in order to provide hooks for ISO traffic over HTTP using
+ * a servlet container like Jetty.
+ * Used in conjunction with module iso-http-servlet
+ * on the server side.
+ *
+ * @version $Revision$ $Date$
+ * @author Ozzy Espaillat
+ *
+ */
+@SuppressWarnings("serial")
+public class HttpTransactionServlet extends HttpServlet {
+
+ private static final Logger logger = LoggerFactory.getLogger(HttpTransactionServlet.class);
+ private static final int MIN_TIMEOUT = 20*1000;
+ static final FilterISOMsgRegistry registry = FilterISOMsgRegistry.instance;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ try {
+ String initChannels = config.getInitParameter("channels");
+ if (initChannels == null || initChannels.length() == 0) {
+ registry.add("*", false);
+ } else {
+ for (String channel: initChannels.split("[,; ]")) {
+ registry.add(channel, false);
+ }
+ }
+ } catch (ConfigurationException | NotFoundException e) {
+ logger.warn("Failed to configure channels:", e);
+ throw new ServletException(e);
+ }
+ }
+
+ protected String getChannel(HttpServletRequest req) {
+ String path = req.getPathInfo();
+ if (path.endsWith("/")) {
+ path = path.substring(0, path.length()-1);
+ }
+ return path.substring(path.lastIndexOf('/')+1);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse res)
+ throws ServletException, IOException {
+ if (!MediaType.APPLICATION_OCTET_STREAM.equals(request.getContentType())) {
+ res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Content type must be " + MediaType.APPLICATION_OCTET_STREAM);
+ return;
+ }
+
+ String src = getChannel(request);
+ FilterISOMsg filter = registry.get(src);
+
+ if (filter == null) {
+ res.sendError(HttpServletResponse.SC_NOT_FOUND, "Port was not found on this server. Valid sources are: " + registry.getRegisteredPorts());
+ } else {
+ long startTime = System.currentTimeMillis();
+ logger.debug("Received request on {} with filter {}", src, filter.getName());
+ ClientRequestDetails clientDetails = new ClientRequestDetails(request.getRemoteAddr(),
+ request.getServerPort(),
+ request.getContentLength(),
+ Math.max(filter.getTimeout(), MIN_TIMEOUT));
+
+ ISOResponse isoRes = filter.process(request.getInputStream(), clientDetails);
+ byte data[] = isoRes.getResponse();
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Received: {} messages and responded with {} messges with request {} and response {} bytes in {}ms, timeout is {}",
+ isoRes.request.size(), isoRes.response.size(), request.getContentLength(),
+ (System.currentTimeMillis()-startTime), (data == null?0:data.length),
+ clientDetails.getTimeout());
+ }
+ if (data != null) {
+ res.setStatus(HttpServletResponse.SC_OK);
+
+ res.setContentLength(data.length);
+ res.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+
+ OutputStream out = res.getOutputStream();
+ out.write(data);
+ out.close();
+ } else {
+ res.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, "Response took longer than " + filter.getTimeout() + "ms timeout.");
+ }
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse res)
+ throws ServletException, IOException {
+ String src = getChannel(request);
+ FilterISOMsg filter = registry.get(src);
+
+ if (filter == null) {
+ res.sendError(HttpServletResponse.SC_NOT_FOUND, "Port was not found on this server. Valid sources are: " + registry.getRegisteredNames() + " and ports: " + registry.getRegisteredPorts());
+ } else if (filter.getPackager() == null) {
+ logger.debug("Packager is not yet available. " + filter.getPackager());
+ try {
+ filter.initialize();
+ } catch (Exception e) {
+ logger.debug("Failed configuring channel " + filter.getName(), e);
+ }
+ logger.debug("After initialized: " + filter.getPackager());
+ res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, filter.getName() + " is not available because packager is: " + filter.getPackager());
+ } else {
+ res.setStatus(HttpServletResponse.SC_OK);
+ byte msg[] = (filter.getName() + " is ready to accept transactions.").getBytes();
+ res.setContentLength(msg.length);
+ OutputStream out = res.getOutputStream();
+ out.write(msg);
+ out.close();
+ }
+ }
+}
diff --git a/modules/iso-http-servlet/src/main/webapp/WEB-INF/jetty-web.xml b/modules/iso-http-servlet/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000000..8698f586ed
--- /dev/null
+++ b/modules/iso-http-servlet/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ /transaction
+ /webapps/@warname@
+ false
+
+
diff --git a/modules/iso-http-servlet/src/main/webapp/WEB-INF/web.xml b/modules/iso-http-servlet/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..c7b8f4ca7e
--- /dev/null
+++ b/modules/iso-http-servlet/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,18 @@
+
+
+
+ ISO HTTP Interface
+
+
+
+ HttpTransactionServlet
+ org.jpos.http.service.HttpTransactionServlet
+ 1
+
+
+
+ HttpTransactionServlet
+ /iso/*
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 86856c2931..7f2ac6536d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -37,7 +37,11 @@ include ':modules:core',
':modules:binlog-quartz',
':modules:cryptoservice',
':modules:qrest',
- ':modules:cryptoserver'
+ ':modules:cryptoserver',
+ ':modules:iso-http-client',
+ ':modules:iso-http-server',
+ ':modules:iso-http-servlet'
+
rootProject.name = 'jposee'