I'm a newbie on WCF. I'm able to build a self-hosted WCF service and 4 .NET applications connected to it using NetTcpBindings. Now I need one more client written in Python2.7 script to connect to this WCF service providing duplex communication.
As far as I googled, WsDualHttpBinding with SOAP 1.2 should be used.
Here is my service interface
namespace GPH_QuickMessageServiceLib
{
/// <summary>
/// GPH Quick Message Service Operations
/// </summary>
[ServiceContract(
Name = "GPH_QuickMessageService",
Namespace = "GPH_QuickMessageServiceLib",
SessionMode = SessionMode.Required,
CallbackContract = typeof(IMessageServiceCallback))]
public interface IMessageServiceInbound
{
[OperationContract]
[WebInvoke]
int JoinTheConversation(string userName);
[OperationContract]
[WebInvoke]
int LeaveTheConversation(string userName);
}
public interface IMessageServiceCallback
{
[OperationContract(IsOneWay = true)]
[WebInvoke]
void NotifyUserJoinedTheConversation(string userName, List<string> SubscriberList);
[OperationContract(IsOneWay = true)]
[WebInvoke]
void NotifyUserLeftTheConversation(string userName, List<string> SubscriberList);
}
}
Here is my service behavior
namespace GPH_QuickMessageServiceLib
{
/// <summary>
/// GPH Quick Message Service behaviour
/// </summary>
[ServiceBehavior(
ConcurrencyMode = ConcurrencyMode.Single,
InstanceContextMode = InstanceContextMode.PerCall)]
public class GPH_QuickMessageService : IMessageServiceInbound
{
private static List<IMessageServiceCallback> _callbackList = new List<IMessageServiceCallback>();
// number of current users - 0 to begin with
private static int _registeredUsers = 0;
private static List<string> SubscriberList = new List<string>();
private static Dictionary<string, IMessageServiceCallback> NotifyList = new Dictionary<string, IMessageServiceCallback>(); // Default Constructor
// Default Constructor
public GPH_QuickMessageService() { }
public int JoinTheConversation(string userName)
{
// Subscribe the user to the conversation
IMessageServiceCallback registeredUser = OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();
if (!_callbackList.Contains(registeredUser))
{
_callbackList.Add(registeredUser);
SubscriberList.Add(userName);//Note the callback list is just a list of channels.
NotifyList.Add(userName, registeredUser);//Bind the username to the callback channel ID
}
_callbackList.ForEach(
delegate (IMessageServiceCallback callback)
{
callback.NotifyUserJoinedTheConversation(userName, SubscriberList);
_registeredUsers++;
});
return _registeredUsers;
}
public int LeaveTheConversation(string userName)
{
// Unsubscribe the user from the conversation.
IMessageServiceCallback registeredUser = OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();
if (_callbackList.Contains(registeredUser))
{
_callbackList.Remove(registeredUser);
NotifyList.Remove(userName);
SubscriberList.Remove(userName);
_registeredUsers--;
}
// Notify everyone that user has arrived.
// Use an anonymous delegate and generics to do our dirty work.
_callbackList.ForEach(
delegate (IMessageServiceCallback callback)
{
callback.NotifyUserLeftTheConversation(userName, SubscriberList);
});
return _registeredUsers;
}
}
}
Here is my App.config in service-host application
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<system.serviceModel>
<services>
<service name="GPH_QuickMessageServiceLib.GPH_QuickMessageService"
behaviorConfiguration = "QuickMessageServiceMEXBehavior">
<endpoint address ="soap"
binding="wsDualHttpBinding"
contract="GPH_QuickMessageServiceLib.IMessageServiceInbound" />
<endpoint address ="service"
binding="netTcpBinding"
contract="GPH_QuickMessageServiceLib.IMessageServiceInbound" />
<!-- Enable the MEX endpoint -->
<endpoint address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange" />
<!-- Need to add this so MEX knows the address of our service -->
<host>
<baseAddresses>
<add baseAddress="http://localhost:2709/GPH_QuickMessageService/soap"/>
<add baseAddress="net.tcp://localhost:8868/GPH_QuickMessageService"/>
</baseAddresses>
</host>
</service>
</services>
<!-- A behavior definition for MEX -->
<behaviors>
<serviceBehaviors>
<behavior name="QuickMessageServiceMEXBehavior" >
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
And here is what I have in Python2.7 script (I'm using suds-jurko 0.6 with a hack to use SOAP 1.2 as .NET wsDualHttpBinding only supports SOAP 1.2)
from suds.client import Client
from suds.bindings import binding
import logging
# Just for debugging purposes.
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)
# Telnic's SOAP server expects a SOAP 1.2 envelope, not a SOAP 1.1 envelope
# and will complain if this hack isn't done.
binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope')
client = Client('http://localhost:2709/GPH_QuickMessageService/soap?wsdl',
headers={'Content-Type': 'application/soap+xml'})
# This will now work just fine.
result = client.service.JoinTheConversation('RIDE')
print client
print 'result = %s' % result
As i guess, my python client already bound to server and get the list of available operations but it could not get the result from those operations. It always returns None
C:\Python27\python.exe C:/Users/sev_user/PycharmProjects/WcfInteration/venv/Scripts/suds_client.py
DEBUG:suds.client:sending to (http://localhost:2709/GPH_QuickMessageService/soap/soap)
message:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="GPH_QuickMessageServiceLib" xmlns:ns1="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Header/>
<ns1:Body>
<ns0:JoinTheConversation>
<ns0:userName>RIDE</ns0:userName>
</ns0:JoinTheConversation>
</ns1:Body>
</SOAP-ENV:Envelope>
DEBUG:suds.client:headers = {'SOAPAction': '"GPH_QuickMessageServiceLib/GPH_QuickMessageService/JoinTheConversation"', 'Content-Type': 'application/soap+xml'}
Suds ( https://fedorahosted.org/suds/ ) version: 0.6
Service ( GPH_QuickMessageService ) tns="http://tempuri.org/"
Prefixes (3)
ns0 = "GPH_QuickMessageServiceLib"
ns1 = "http://schemas.microsoft.com/2003/10/Serialization/"
ns2 = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"
Ports (2):
(WSDualHttpBinding_GPH_QuickMessageService)
Methods (7):
JoinTheConversation(xs:string userName)
LeaveTheConversation(xs:string userName)
NotifyUserJoinedTheConversation()
NotifyUserLeftTheConversation()
NotifyUserOfMessage()
ReceiveMessage(xs:string userName, ns2:ArrayOfstring addressList, xs:string userMessage)
sum(xs:int a, xs:int b)
Types (4):
ns2:ArrayOfstring
ns1:char
ns1:duration
ns1:guid
(NetTcpBinding_GPH_QuickMessageService)
Methods (7):
JoinTheConversation(xs:string userName)
LeaveTheConversation(xs:string userName)
NotifyUserJoinedTheConversation()
NotifyUserLeftTheConversation()
NotifyUserOfMessage()
ReceiveMessage(xs:string userName, ns2:ArrayOfstring addressList, xs:string userMessage)
sum(xs:int a, xs:int b)
Types (4):
ns2:ArrayOfstring
ns1:char
ns1:duration
ns1:guid
result = None
DEBUG:suds.client:HTTP succeeded:
Process finished with exit code 0
I have tried several ways other than suds, such as ZSI, zeep, but always get result as 'None' or '0'. I have attached logger on these SOAP client process and always get either 'HTTP succeed' or '202 Accepted'. Couldn't figure out myself what should be wrong here.
Did any body face the same problem? Please give me some clue to resolve this.
Or any other idea to get duplex communication between Python2.7 and WCF is always appreciated.
Related
I'm invoking a web service that requires WS-Addressing SOAP headers. I'm using Apache Camel with CXF to invoke the web service. When I configure the CXF endpoint with the web service's WSDL, it's smart enough to automatically add WS-Adressing SOAP headers, but I need to set a custom MessageId.
Here is the message that is currently being sent:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<ws:international xmlns:ws="http://www.w3.org/2005/09/ws-i18n">
<ws:locale xmlns:ws="http://www.w3.org/2005/09/ws-i18n">en_CA</ws:locale>
</ws:international>
<fram:user wsa:IsReferenceParameter="true" xmlns:fram="http://wsbo.webservice.ephs.pdc.ibm.com/Framework/" xmlns:wsa="http://www.w3.org/2005/08/addressing">BESTSystem</fram:user>
<Action soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">http://webservice.ephs.pdc.ibm.com/Client/QueryHumanSubjects</Action>
<MessageID soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6</MessageID>
<To soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">https://panweb5.panorama.gov.bc.ca:8081/ClientWebServicesWeb/ClientProvider</To>
<ReplyTo soap:mustUnderstand="true" xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
</soap:Header>
<soap:Body>
<ns2:queryHumanSubjectsRequest xmlns:ns2="http://wsbo.webservice.ephs.pdc.ibm.com/Client/" xmlns:ns3="http://wsbo.webservice.ephs.pdc.ibm.com/FamilyHealth/">
<!-- stuff -->
</ns2:queryHumanSubjectsRequest>
</soap:Body>
</soap:Envelope>
As you can see, the MessageId value is "urn:uuid:945cfd10-9fd2-48f9-80b4-ac1b9f3293c6". I need to set a custom value.
I tried adding the MessageId header they way I add the other headers like "international" and "user", but some part of the framework overrides the value.
// Note this doesn't work! Something overrides the value. It works for other headers.
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
List<SoapHeader> headers = CastUtils.cast((List<?>) in.getHeader(Header.HEADER_LIST));
SOAPFactory sf = SOAPFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
QName MESSAGE_ID_HEADER = new QName("http://www.w3.org/2005/08/addressing", "MessageID", "wsa");
SOAPElement messageId = sf.createElement(MESSAGE_ID_HEADER);
messageId.setTextContent("customValue");
SoapHeader soapHeader = new SoapHeader(MESSAGE_ID_HEADER, messageId);
headers.add(soapHeader);
}
The CXF website has some documentation on how to set WS-Addressing headers, but I don't see how to apply it to Apache Camel. The Apache Camel CXF documentation doesn't specifically mention WS-Addressing either.
The documentation links you posted actually do have the information you need, although it's not immediately obvious how to apply it to Camel.
The CXF documentation says that:
The CXF org.apache.cxf.ws.addressing.impl.AddressingPropertiesImpl object can be used to control many aspects of WS-Addressing including the Reply-To:
AddressingProperties maps = new AddressingPropertiesImpl();
EndpointReferenceType ref = new EndpointReferenceType();
AttributedURIType add = new AttributedURIType();
add.setValue("http://localhost:9090/decoupled_endpoint");
ref.setAddress(add);
maps.setReplyTo(ref);
maps.setFaultTo(ref);
((BindingProvider)port).getRequestContext()
.put("javax.xml.ws.addressing.context", maps);
Note that it sets the addressing properties on the "RequestContext".
The Apache Camel documentation says that:
How to propagate a camel-cxf endpoint’s request and response context
CXF client API provides a way to invoke the operation with request and response context. If you are using a camel-cxf endpoint producer to invoke the outside web service, you can set the request context and get response context with the following code:
CxfExchange exchange = (CxfExchange)template.send(getJaxwsEndpointUri(), new Processor() {
public void process(final Exchange exchange) {
final List<String> params = new ArrayList<String>();
params.add(TEST_MESSAGE);
// Set the request context to the inMessage
Map<String, Object> requestContext = new HashMap<String, Object>();
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, JAXWS_SERVER_ADDRESS);
exchange.getIn().setBody(params);
exchange.getIn().setHeader(Client.REQUEST_CONTEXT , requestContext);
exchange.getIn().setHeader(CxfConstants.OPERATION_NAME, GREET_ME_OPERATION);
}
});
The above example has some stuff we don't need, but the important thing is that it shows us how to set the CXF Request Context.
Put them together and you get:
#Override
public void process(Exchange exchange) throws Exception {
AttributedURIType messageIDAttr = new AttributedURIType();
messageIDAttr.setValue("customValue");
AddressingProperties maps = new AddressingProperties();
maps.setMessageID(messageIDAttr);
Map<String, Object> requestContext = new HashMap<>();
requestContext.put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, maps);
exchange.getIn().setHeader(Client.REQUEST_CONTEXT, requestContext);
}
// org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES = "javax.xml.ws.addressing.context"
// org.apache.cxf.endpoint.Client.REQUEST_CONTEXT = "RequestContext"
Warning: In my route, I invoke multiple different web services sequentially. I discovered that after setting the RequestContext as shown above, Camel started using the same RequestContext for all web services, which resulted in an error: "A header representing a Message Addressing Property is not valid and the message cannot be processed". This is because the incorrect "Action" header was used for all web service invocations after the first.
I traced this back to Apache Camel using a "RequestContext" Exchange property, separate from the header we set, which apparently takes priority over the header. If I remove this property prior to calling subsequent web services, CXF automatically fills in the correct Action header.
if your problem not solved, I suggest you to combine your cxf service with custom interceptor. it easy to work with your soap message. like this:
<bean id="TAXWSS4JOutInterceptorBean" name="TAXWSS4JOutInterceptorBean" class="com.javainuse.beans.SetDetailAnswerInterceptor " />
<cxf:cxfEndpoint id="CXFTest" address="/javainuse/learn"
endpointName="a:SOATestEndpoint" serviceName="a:SOATestEndpointService"
serviceClass="com.javainuse.SOATestEndpoint"
xmlns:a ="http://javainuse.com">
<cxf:binding>
<soap:soapBinding mtomEnabled="false" version="1.2" />
</cxf:binding>
<cxf:features>
<wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing"/>
</cxf:features>
<cxf:inInterceptors>
<ref bean="TAXWSS4JInInterceptorBean" />
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="TAXWSS4JOutInterceptorBean" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
</cxf:outFaultInterceptors>
</cxf:cxfEndpoint>
and in the interceptor you can set soap headers like this:
public class SetDetailAnswerInterceptor extends WSS4JOutInterceptor {
public SetDetailAnswerInterceptor() {
}
#Override
public void handleMessage(SoapMessage mc) {
AttributedURIType value = new AttributedURIType();
value.setValue("test");
((AddressingProperties) mc.get("javax.xml.ws.addressing.context.outbound")).setMessageID(value);
}
}
When I add the following NuGet package to my WebJob: Microsoft.Azure.WebJobs.ServiceBus 2.0.0,
two new items are added to the app.config file.
It seems they are both used to define the Service Bus connection string.
Can I get rid of one of them?
<connectionStrings>
<add name="AzureWebJobsServiceBus" connectionString="..." />
</connectionStrings>
<appSettings>
<!-- Service Bus specific app setings for messaging connections -->
<add key="Microsoft.ServiceBus.ConnectionString" value="..." />
</appSettings>
Thanks for your help!
It seems they are both used to define the Service Bus connection string. Can I get rid of one of them?
According the source code of ServiceBusConfiguration, WebJob Service Bus SDK(ServiceBusTrigger) will use the connection string which stored under the connectionStrings section.
public string ConnectionString
{
get
{
if (!_connectionStringSet)
{
_connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.ServiceBus);
_connectionStringSet = true;
}
return _connectionString;
}
set
{
_connectionString = value;
_connectionStringSet = true;
}
}
You can also set the connection string at runtime.
JobHostConfiguration config = new JobHostConfiguration();
config.UseServiceBus(new ServiceBusConfiguration() { ConnectionString = "" });
JobHost host = new JobHost(config);
If you want to create a instance of Service Bus Client(for example QueueClient) to do some specific operations, you could use the Service Bus connection string configured in appSettings.
//Use CloudConfigurationManager to read the connection string stored in appSettings
string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
var client = QueueClient.CreateFromConnectionString(connectionString, "queueName");
I'm trying to create an inbound gateway for a SOAP service, that accepts SOAP requests like the following:
<?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<RequestHeader xmlns="http://test.com/">
<SecurityToken>mytoken</SecurityToken>
<RequestID>1234</RequestID>
</RequestHeader>
</S:Header>
<S:Body>
<BaseRequest xmlns="http://test.com/">
<RequestData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="BalanceRequest">
<CustomerID>1234</CustomerID>
</RequestData>
</BaseRequest>
</S:Body>
</S:Envelope>
I want to use JAXB for marshalling/unmarshalling the request/response. I have managed to configure Spring/Spring-integration with the following:
<oxm:jaxb2-marshaller id="soapMarshaller" context-path="com.test" />
<int:channel id="soap-channel"/>
<int-ws:inbound-gateway id="ws-inbound-gateway"
request-channel="soap-channel"
marshaller="soapMarshaller"/>
<int:service-activator input-channel="soap-channel">
<bean class="com.test.SoapServiceActivator"/>
</int:service-activator>
And I have tried to extract the SOAP header and body in the service activator.
#ServiceActivator
public BaseResponse issueResponseFor(BaseRequest body,
#Headers Map<String, Object> headerMap) {
return null;
}
BaseRequest is a JAXB annotated class.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"requestData"
})
#XmlRootElement(name = "BaseRequest")
public class BaseRequest {
#XmlElement(name = "RequestData")
protected BaseRequestBody requestData;
}
My problem is that in the variable body, I get the body of the SOAP request, but I didn't find anyway to extract the headers of the SOAP request. The headerMap variable holds only the standard Spring-Integration headers (replyChannel, errorChannel, id, timestamp). By headers, I mean the SecurityToken+RequestID, and also the HTTP header with the name of the requested action.
Any idea how to do that ?
Try to use mapped-request-headers="*".
By default the the DefaultSoapHeaderMapper maps only standard headers. And in this case it is only WebServiceHeaders.SOAP_ACTION
UPDATE
Quoting Omar :-)
Thanks ! The following code is working great :
#Autowired
#Qualifier("soapMarshaller")
Jaxb2Marshaller marshaller;
#ServiceActivator
public BaseResponse issueResponseFor(BaseRequest request, #Header("RequestHeader") SoapHeaderElement soapHeader) {
BaseRequestHeader requestHeader = (BaseRequestHeader) JAXBIntrospector.getValue(marshaller.unmarshal(soapHeader.getSource()));`
After I got my single-page web app working (web pages served with ServiceStack's RazorFormat() MVC, not .ASP MVC), I ran a (previously passing) test for the service. The test failed. Tested the web app again (debug run, navigate to //localhost:1337/ResourceList in the browser): still working. Is something wrong with my test?
Here's the error:
Test Name: TestResourceList
Test FullName: [0-1015]ServiceWrapper.Test.TestSWrapperServices.TestResourceList
Test Source: c:\Users\uname\Documents\Visual Studio 2012\Projects\ServiceWrapper\UnitTestProject1\ServiceTests.cs : line 96
Test Outcome: Failed
Test Duration: 0:00:02.188
Result Message:
System.Net.WebException : Unable to connect to the remote server
----> System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it 127.0.0.1:1337
Result StackTrace:
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](String httpMethod, String relativeOrAbsoluteUrl, Object request)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Get[TResponse](IReturn`1 request)
at ServiceWrapper.Test.TestSWrapperServices.TestResourceList() in c:\Users\uname\Documents\Visual Studio 2012\Projects\ServiceWrapper\UnitTestProject1\ServiceTests.cs:line 98
--SocketException
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)
Here's the test:
namespace ServiceWrapper.Test
{
[TestFixture]
public class TestSWrapperServices
{
AppHost appHost;
private const string ListeningOn = "http://*:1337/";
public const string Host = "http://localhost:1337";
private const string BaseUri = Host + "/";
[TestFixtureSetUp]
public void OnTestFixtureSetUp()
{
var appSettings = new AppSettings();
var username = Environment.GetEnvironmentVariable("USERNAME");
var userdomain = Environment.GetEnvironmentVariable("USERDOMAIN");
AppHost.AppConfig = new AppConfig(new AppSettings());
appHost = new AppHost();
// initialize Service Server
ServiceServer.SetUser(AppHost.AppConfig.UserName, AppHost.AppConfig.Password);
ServiceServer.SetLog(String.Empty);
try
{
appHost.Init();
appHost.Start(ListeningOn);
}
catch (HttpListenerException ex)
{
if (ex.ErrorCode == 5)
{
System.Diagnostics.Debug.WriteLine("You need to run the following command (as admin):");
System.Diagnostics.Debug.WriteLine(" netsh http add urlacl url={0} user={1}\\{2} listen=yes",
ListeningOn, userdomain, username);
}
else
{
System.Diagnostics.Debug.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
}
}
[TestFixtureTearDown]
public void OnTestFixtureTearDown()
{
appHost.Dispose();
}
[Test]
public void TestResourceList()
{
JsonServiceClient client = new JsonServiceClient(BaseUri);
ResourceList response = client.Get(new ResourceList());
Assert.Contains("Some Value", response.property);
}
[Test]
}
}
I upgraded to the latest ServiceStack - 3.9.55, and it still didn't work. So, I started over again, sanity checking from the beginning. It turns out that the program.cs ListeningOn has http://*:1337/ while the nunit TestFixture ListeningOn was http://localhost:1337/
Checking urlacl (as admin) for http://localhost:1337/:
C:\Windows\system32>netsh http show urlacl url=http://localhost:1337/
URL Reservations:
-----------------
Checking urlacl (as admin) for http://*:1337/:
C:\Windows\system32>netsh http show urlacl url=http://*:1337/
URL Reservations:
-----------------
Reserved URL : http://*:1337/
User: DOMAIN\user
Listen: Yes
Delegate: No
SDDL: D:(A;;GX;;;S-1-5-21-2595267603-2801715271-1705165942-1002)
My earlier troubleshooting left the two projects with inconsistent ListeningOn values. Interestingly, using http://*:1337/ doesn't work as a wildcard url, as perhaps I had expected.
Here's a handy code snippet to help you build the add urlacl command. It also provides a useful (!) sanity check on the exact url you're listening on.
Console.WriteLine("You need to run the following command:");
Console.WriteLine(" netsh http add urlacl url={0} user={1}\\{2} listen=yes",
ListeningOn, userdomain, username);
--- Update ---
Upgrading ServiceStack eliminated the 'connection actively refused' error message. Once ListeningOn values were unified, the real
error message was exposed:
Result Message: ServiceStack.ServiceClient.Web.WebServiceException : Service Unavailable
Result StackTrace:
at ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowWebServiceException[TResponse](Exception ex, String requestUri)
at ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowResponseTypeException[TResponse](Object request, Exception ex, String requestUri)
at ServiceStack.ServiceClient.Web.ServiceClientBase.HandleResponseException[TResponse](Exception ex, Object request, String requestUri, Func`1 createWebRequest, Func`2 getResponse, TResponse& response)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](String httpMethod, String relativeOrAbsoluteUrl, Object request)
at ServiceStack.ServiceClient.Web.ServiceClientBase.Get[TResponse](IReturn`1 request)
at RemoteServerWrapper.Test.TestRSWrapperServices.TestDataList() in c:\Users\user\Documents\Visual Studio 2012\Projects\RemoteServerWrapper\UnitTestProject1\ServiceTests.cs:line 183
It's still obscure -- but at least it's not reporting something that's completely different from the real issue. So then I implemented trace in my app.config, like this:
<configuration>
<!-- ... other config settings ... -->
<system.diagnostics>
<sources>
<source name="System.Net" tracemode="includehex" maxdatasize="1024">
<listeners>
<add name="System.Net"/>
<add name="console"/>
</listeners>
</source>
<source name="System.Net.HttpListener">
<listeners>
<add name="System.Net"/>
<add name="console"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
<add name="System.Net.HttpListener" value="Verbose"/>
</switches>
<sharedListeners>
<add name="console"
type="System.Diagnostics.ConsoleTraceListener"
initializeData="false"/>
<add name="System.Net"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="network.log"
/>
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
</configuration>
Which exposed a better error message:
ERROR: [::1]:1337 Request not found: /datarequest?DataKey=some_key&startDate=20130701&endDate=20130708
OK - now I have to pull in the servicestack sources so I can step through the code and figure out why I'm getting 'Not Found' in the test, when it works when I 'debug/run' and test via the browser. Turns out that RestHandler.FindMatchingRestPath(httpMethod, pathInfo, contentType) wasn't returning a match. Humm. Why is that? The AppHost is declared identically. So, what's different?
The rest services live in my project's main assembly. When run from 'debug/run' the default assembly has the services, and everything works. But when run from the test project, with the services assembly added as a reference, servicestack can't find them. They're not in the default location, relative to the test project. So I added an AppHost class at the top of my test file, rather than relying on the one from my program.cs, and declared it as follows:
public class RSWrapperServicesAppHostHttpListener
: AppHostHttpListenerBase
{
public RSWrapperServicesAppHostHttpListener()
: base("RSWrapper Services Tests", typeof(DataRequestService).Assembly) { }
// 'DataRequestService' is a random rest service class,
// defined in the referenced services assembly
}
Now ServiceStack is happy, and my tests work again.
How did they ever work? Originally everything was jumbled together all in one project. Once I separated things into separate assemblies, i.e. DTO, Services, Business Logic and Tests, I broke it. But since I was temporarily holding off on unit tests while getting the UI working, I didn't notice right away.
I'm attempting to create 2 separate web services, both within one spring deployment, both with the wsdl's being generated from the same xsd schemas, yet have them be routed to two separate end points so i can handle the requests differently in the separate contexts.
Ex:
Webservice 1: subset of access, lower privileges and security constraints
Webservice 2: higher privileges
<sws:dynamic-wsdl id="spml-readonly"
portTypeName="SpmlReadOnlyService"
locationUri="SpmlReadOnly">
<sws:xsd location="/WEB-INF/xsd/spml/pstc_spmlv2_core.xsd"/>
</sws:dynamic-wsdl>
<sws:dynamic-wsdl id="spml-crud"
portTypeName="SpmlCrudService"
locationUri="SpmlCrud">
<sws:xsd location="/WEB-INF/xsd/spml/pstc_spmlv2_core.xsd"/>
<sws:xsd location="/WEB-INF/xsd/spml/pstc_spmlv2_search.xsd"/>
<sws:xsd location="/WEB-INF/xsd/spml/pstc_spmlv2_batch.xsd"/>
</sws:dynamic-wsdl>
Now since both wsdls are based off of the same xsds, the 'namespace' and 'localPart" of the requests come across the wire identical, regardless of which web service i'm hitting (/SpmlReadOnly or /SpmlCrud).
Therefore, that's ruling out the deprecated PayloadRootQNameEndpointMapping since the localPart and namespace are still identical, etc,... and my current config simply routes the requests to the same endpoint method handler, and i have no way of distinguishing which web service was called:
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "lookupRequest")
#ResponsePayload
public Source handleLookupRequest(SoapMessage message) throws Exception {
...
}
Is what I'm able to do even possible? If the xsd's are shared and have identical namespaces at the root of the schema, and the same localPart method requests, will there ever be a way to distinguish between them and map to two different end points? Any information on this would be useful! I'm hoping i don't have to set up two separate .wars and deploy them separately with their own code bases on a server!
Thanks,
Damian
You need something that combines URI and PayloadRoot mapping. Unfortunately Spring-Ws doesn't have something like this. But because it's very extensible it's really easy to achieve this.
TL;DR
See This branch at GitHub for working example
Details
You need to create mapping of combined URI+QName to org.springframework.ws.server.endpoint.MethodEndpoint instances. Also you should minimize the code which would duplicate existing Spring-Ws functions.
So 1) You need to explicitly configure Spring-Ws annotations without using <sws:annotation-driven />:
This is your requirement (with my schemas):
<ws:dynamic-wsdl id="spml-readonly" portTypeName="SpmlReadOnlyService" locationUri="SpmlReadOnly">
<ws:xsd location="classpath:springws/model/schema.xsd" />
</ws:dynamic-wsdl>
<ws:dynamic-wsdl id="spml-crud" portTypeName="SpmlCrudService" locationUri="SpmlCrud">
<ws:xsd location="classpath:springws/model/schema.xsd" />
<ws:xsd location="classpath:springws/model/schema2.xsd" />
</ws:dynamic-wsdl>
This is all you need to do by hand which normally is configured by <sws:annotation-driven /> (one adapter with one JAXB marshaller):
<bean class="org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter">
<property name="methodArgumentResolvers">
<list>
<ref local="marshallingPayloadMethodProcessor"/>
</list>
</property>
<property name="methodReturnValueHandlers">
<list>
<ref local="marshallingPayloadMethodProcessor"/>
</list>
</property>
</bean>
<bean id="marshallingPayloadMethodProcessor" class="org.springframework.ws.server.endpoint.adapter.method.MarshallingPayloadMethodProcessor">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
</bean>
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPaths">
<list>
<value>springws.model</value>
</list>
</property>
</bean>
This is custom mapping:
<bean class="springws.PathAndPayloadRootAnnotationEndpointMapping" />
And 2) You should create your own mapping
public class PathAndPayloadRootAnnotationEndpointMapping extends PayloadRootAnnotationMethodEndpointMapping
{
#Override
protected QName getLookupKeyForMessage(MessageContext messageContext) throws Exception
{
String urlPart = "";
QName payloadRootPart = super.getLookupKeyForMessage(messageContext);
TransportContext transportContext = TransportContextHolder.getTransportContext();
if (transportContext != null) {
WebServiceConnection connection = transportContext.getConnection();
if (connection != null && connection instanceof HttpServletConnection) {
String requestURI = ((HttpServletConnection)connection).getHttpServletRequest().getRequestURI();
String contextPath = ((HttpServletConnection)connection).getHttpServletRequest().getContextPath();
urlPart = requestURI.substring(contextPath.length());
}
}
return new QName(payloadRootPart.getNamespaceURI(), urlPart + "/" + payloadRootPart.getLocalPart());
}
#Override
protected List<QName> getLookupKeysForMethod(Method method)
{
List<QName> result = new ArrayList<QName>();
RequestMapping rm = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequestMapping.class);
String urlPart = rm == null || rm.value().length != 1 ? "" : rm.value()[0];
List<QName> methodPart = super.getLookupKeysForMethod(method);
for (QName qName : methodPart) {
result.add(new QName(qName.getNamespaceURI(), urlPart + "/" + qName.getLocalPart()));
}
return result;
}
}
which extends org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping. And all it does is extending the keys (QNames of payload root elements) of messages with the information extracted from the endpoint URI. I've used Spring's #org.springframework.web.bind.annotation.RequestMapping annotation for that, but someone thinking it's a hack may create his/her own annotation.
So for endpoint like this:
#org.springframework.ws.server.endpoint.annotation.Endpoint
#RequestMapping("/ws/SpmlReadOnly")
public class Endpoint1
{
#ResponsePayload
#PayloadRoot(namespace = "urn:test", localPart = "method1Request")
public Response2 method(#RequestPayload Request1 request) throws Exception
{
return new Response2("e1 m1");
}
}
the key is not:
namespace = urn:test
localName = method1Request
but this:
namespace = urn:test
localName = /ws/SpmlReadOnly/method1Request
The protected QName getLookupKeyForMessage(MessageContext messageContext) method ensures that the mapping URI is independent of the WAR context, the application is deployed at.