/** * Internal method to transmit a Sendable message, called by send() and by the retry mechanism. * This handles: * - de-queueing reliable messages that have been acknowledged synchronously (or for which an explicit error has been received), * - collecting any synchronous response which is stored in the SynchronousResponse property of the Sendable instance. * - calling the response handler for synchronous requests * - error logging * * Note that due to a design error in SDS not all target endpoint URLs are resolvable from SDS: * "forwarded" messages for which Spine is an intermediary must resolve the endpoint URL some * other way. See the SDSconnection API documentation for how this is configured. * * @param Message to transmit */ private void doTransmission(Sendable s) { // Create a socket and then do the TLS over it to // make an SSL stream. Then write the Sendable down the stream and get any // "stuff" back synchronously. If we do get a synchronous ack, remove the // reference to the transmitted message from "requests". // Socket clearSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // Note this uses the "ResolvedUrl" from the Sendable, not the "Url" from SDS because the // SDS data will be broken by design for "forwarded" services. // // TODO NEXT: Persist and send from persisted message string host = null; string syncresponse = null; if (s.ResolvedUrl == null) { // Sending persisted, reloaded message host = ((EbXmlMessage)s).getHost(); clearSocket.Connect(host, HTTPS); } else { // "Primary" transmission s.persist(); Uri uri = new Uri(s.ResolvedUrl); host = uri.Host; if (uri.Port == -1) { clearSocket.Connect(uri.Host, HTTPS); } else { clearSocket.Connect(host, uri.Port); } } if (!s.recordTry()) { if (s.getMessageId() != null) { removeRequest((EbXmlMessage)s); s.Expire(); } return; } string httpResponseLine = null; using (NetworkStream n = new NetworkStream(clearSocket)) { SslStream ssl = new SslStream(n, false, validateRemoteCertificate, new LocalCertificateSelectionCallback(getLocalCertificate)); //ssl.AuthenticateAsClient(host); ssl.AuthenticateAsClient(host, endpointCertificateCollection, SslProtocols.Tls, true); if (!ssl.IsAuthenticated || !ssl.IsSigned || !ssl.IsEncrypted) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("Failed to authenticate SSL connection", EventLogEntryType.Error); throw new Exception("Failed to authenticate SSL connection"); } s.write(ssl); ssl.Flush(); // Read any response ... // ... first line first... httpResponseLine = readline(ssl); // ... then the rest of it int contentlength = getHeader(ssl); if (contentlength == -1) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("Failed to read response content length", EventLogEntryType.Error); return; } // Process response and add it to the Sendable - Spine synchronous responses are all fairly // small, even for with-history retrievals if (contentlength > 0) { int read = 0; byte[] buffer = new byte[contentlength]; do { int r = ssl.Read(buffer, read, contentlength - read); if (r == -1) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("Premature EOF sending " + s.getMessageId(), EventLogEntryType.Error); break; } read += r; } while (read < contentlength); syncresponse = Encoding.UTF8.GetString(buffer); } } s.SyncronousResponse = syncresponse; if (s.Type == Sendable.SOAP) { if (synchronousHandlers.ContainsKey(s.SoapAction)) { ISynchronousResponseHandler h = synchronousHandlers[s.SoapAction]; h.handle((SpineSOAPRequest)s); } else { defaultSynchronousResponseHandler.handle((SpineSOAPRequest)s); } return; } // "Don't listen for any response" conditions where we remove this message from the // current set of things-we're-waiting-on-responses-for are: // Explicit error (HTTP 500) // A synchronous ebXML response (Acknowledgement or MessageError) with our message id in it. // // Note: The contract properties in SDS are a mess, so don't bother trying to infer behaviour // from them. // if (s.getMessageId() != null) // Don't try for acks. { if (s.SyncronousResponse != null) { if (httpResponseLine.Contains("HTTP 5") || (s.SyncronousResponse.Contains(s.getMessageId()))) { removeRequest((EbXmlMessage)s); } } } }
/** * Registers an instance of a synchronous response handler, against the SOAPaction of the * request message. */ public void addSynchronousResponseHandler(string sa, ISynchronousResponseHandler h) { synchronousHandlers.Add(sa, h); }
/** * Singleton constructor. */ private ConnectionManager() { getPasswordProvider(); requests = new Dictionary <string, Sendable>(); expiryHandlers = new Dictionary <string, IExpiredMessageHandler>(); handlers = new Dictionary <string, ISpineHandler>(); synchronousHandlers = new Dictionary <string, ISynchronousResponseHandler>(); itkTrunkHandler = new ITKTrunkHandler(); handlers.Add("urn:nhs:names:services:itk/COPC_IN000001GB01", itkTrunkHandler); handlers.Add("\"urn:nhs:names:services:itk/COPC_IN000001GB01\"", itkTrunkHandler); loadCertificate(); messageDirectory = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, MESSAGE_DIRECTORY_REGVAL, ""); if (messageDirectory.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No message directory provided - only in-memory persistence available", EventLogEntryType.Warning); } // Make sure messageDirectory is terminated with a path delimiter, because we're going // to need to add message ids to it when we call depersist() to delete persisted messages // on expiry or explicit transmission result. // if (!messageDirectory.EndsWith("\\")) { messageDirectory = messageDirectory + "\\"; } myIp = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, MY_IP_REGVAL, ""); if (myIp.Length == 0) { myIp = null; EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No local IP address provided - will use first non-localhost interface", EventLogEntryType.Warning); } expiredDirectory = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, EXPIRED_DIRECTORY_REGVAL, ""); if (expiredDirectory.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No expired message directory provided - administrative handling of unsent messages NOT available", EventLogEntryType.Warning); } sdsConnection = new SDSconnection(); try { retryCheckPeriod = Int64.Parse((string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, RETRY_TIMER_PERIOD_REGVAL, "")); } catch { } string nulldefault = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, USE_NULL_DEFAULT_SYNCHRONOUS_HANDLER_REGVAL, ""); if (nulldefault.ToLower().StartsWith("y")) { defaultSynchronousResponseHandler = new NullSynchronousResponseHandler(); } else { defaultSynchronousResponseHandler = new DefaultFileSaveSynchronousResponseHandler(); } defaultSpineHandler = new DefaultFileSaveSpineHandler(); persistDurations = loadReceivedPersistDurations(); if (retryCheckPeriod != 0) { retryProcessorTimer = new Timer(processRetries, null, retryCheckPeriod, retryCheckPeriod); } }
/** * Singleton constructor. */ private ConnectionManager() { getPasswordProvider(); requests = new Dictionary<string, Sendable>(); expiryHandlers = new Dictionary<string, IExpiredMessageHandler>(); handlers = new Dictionary<string, ISpineHandler>(); synchronousHandlers = new Dictionary<string, ISynchronousResponseHandler>(); itkTrunkHandler = new ITKTrunkHandler(); handlers.Add("urn:nhs:names:services:itk/COPC_IN000001GB01", itkTrunkHandler); handlers.Add("\"urn:nhs:names:services:itk/COPC_IN000001GB01\"", itkTrunkHandler); loadCertificate(); messageDirectory = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, MESSAGE_DIRECTORY_REGVAL, ""); if (messageDirectory.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No message directory provided - only in-memory persistence available", EventLogEntryType.Warning); } // Make sure messageDirectory is terminated with a path delimiter, because we're going // to need to add message ids to it when we call depersist() to delete persisted messages // on expiry or explicit transmission result. // if (!messageDirectory.EndsWith("\\")) messageDirectory = messageDirectory + "\\"; myIp = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, MY_IP_REGVAL, ""); if (myIp.Length == 0) { myIp = null; EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No local IP address provided - will use first non-localhost interface", EventLogEntryType.Warning); } expiredDirectory = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, EXPIRED_DIRECTORY_REGVAL, ""); if (expiredDirectory.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No expired message directory provided - administrative handling of unsent messages NOT available", EventLogEntryType.Warning); } sdsConnection = new SDSconnection(); try { retryCheckPeriod = Int64.Parse((string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, RETRY_TIMER_PERIOD_REGVAL, "")); } catch { } string nulldefault = (string)Registry.GetValue(CONNECTION_MANAGER_REGSITRY_KEY, USE_NULL_DEFAULT_SYNCHRONOUS_HANDLER_REGVAL, ""); if (nulldefault.ToLower().StartsWith("y")) defaultSynchronousResponseHandler = new NullSynchronousResponseHandler(); else defaultSynchronousResponseHandler = new DefaultFileSaveSynchronousResponseHandler(); defaultSpineHandler = new DefaultFileSaveSpineHandler(); persistDurations = loadReceivedPersistDurations(); if (retryCheckPeriod != 0) retryProcessorTimer = new Timer(processRetries, null, retryCheckPeriod, retryCheckPeriod); }