I need to design a SOAP api (my first one!). What are the best practices regarding errors returned to the caller.
Assuming an api as follow
[WebMethod]
public List<someClass> GetList(String param1)
{
}
Should I
Throw an exception. Let the SOAP infrastructure generate a SOAP fault -- and the caller would have to try/catch. This is not very explanatory to the caller
Have the return parameter be a XMLDOcument of some sort, with the first element being a return value and then the List.
Looking at the return SOAP packet I see that the response generated looks like the following
<GetListResponse>
<GetListResult>
...
...
</GetListResult>
</GetListResponse>
Can we somehow change the return packet so that the "GetListResult" element is changed to "GetListError" in case of error
Any other way?
Thanks!
Probably the most appropriate SOA pattern to follow would be a Fault Contract, which is essentially a Data Contract that is wrapped in the SOAPException.
I am posting examples in .NET, since it looks like that is what you are using (and that is what I know :) )
In WCF, you can define a DataContract, then decorate your OperationContract interface with a a "FaultContract" attribute that specifies it as the return value:
public partial interface MyServiceContract
{
[System.ServiceModel.FaultContract(typeof(MyService.FaultContracts.ErrorMessageFaultContract))]
[System.ServiceModel.OperationContract(...)]
ResponseMessage SOAMethod(RequestMessage request) {...}
}
For ASMX web services, (as it appears you are using from your code snippet), you can't use this attribute or setup. So to implement the pattern, you would need to:
Define a serializable class to hold your exception information (i.e. ErrorData)
When an exception is thrown in your service, catch it and in your error handling code, add the info to the ErrorData class
Append the serialized ErrorData class to a SoapException class:
SoapException mySoapException = new SoapException(message, SoapException.ServerFaultCode, "", serialzedErrorDataClass);
Throw the SoapException in your code
On your client side, you will need to deserialize the message to interpret it.
Kind of seems like a lot of work, but this way you have total control of what data gets returned. Incidentally, this is the pattern that is used by the ServiceFactory from Microsoft patterns & practices for ASMX web services.
There is some good information on Coding Horror.
You can cause the correct fault to be returned from old ASMX services, but it's not easy. First of all, you'll have to hand-code the WSDL, because the ASMX infrastructure will never create Fault elements in a WSDL. Then, you have to serialize the desired fault data into an XmlElement that you will then provide as the Detail property of the SoapException that you will throw.
It's much easier with WCF. You can declare a number of FaultContracts for each operation, and WCF will generate the correct WSDL to refer to them. You then simply throw a FaultException, where type T is the type of the FaultContract. Pass the T instance to the constructor, and you're all set.
I can't give you specifies for .net (which seems to be what you're asking), but SOAP provides a mechanism for expressing strongly-typed exceptions. The SOAP fault element can have an optional FaultDetail sub-element, and this can contain arbitrary XML documents, such as your GetListError. These document types should be defined in the WSDL as a wsdl:fault inside the wsdl:operation
The trick is persuading the web service stack to turn an exception (which is the "correct" way of writing your business logic) into a properly marshalled fault detail. And I can't help you with that bit.
Related
I am using ws:outbound-gateway to invoke soap service. I have added interceptor as well.
<int-ws:outbound-gateway id="wsOutboundGateway"
request-channel="requestChannel"
uri="{soapURI}"
message-sender="httpMessageSender"
message-factory="messageFactory"
interceptor="myInterceptor"
marshaller="jaxbMarshaller" unmarshaller="jaxbMarshaller">
<int-ws:uri-variable name="soapURI" expression="headers.soapURI"/>
</int-ws:outbound-gateway>
myInterceptor is a class which implements ClientInterceptor.
Query: I have the information in message header which needs to be intercepted. Is there any way to receive the message header in the interceptor.
Note : I am setting the header value in thread local and getting back in the interceptor now.
Any better solution, please suggest.
It depends of the premise and context.
Sorry, but you have to share more info. What is the header? What do you do with that? Maybe there is no need to intercept it in the ClientInterceptor, but would be better even before <int-ws:outbound-gateway>?
UPDATE
I have the info in message header where I have to pass it in the soap header.
Actually ClientInterceptor is fully for different purpose and its intention do not modify the message.
There is WebServiceMessageCallback abstraction for message modification before sending.
But having your requirements like pass SOAP header I can suggest you to take a look into out-of-the-box component like DefaultSoapHeaderMapper. Its populateStandardHeaders deal only with SoapAction and populateUserDefinedHeader populates only as soapHeader.addAttribute(). So, consider some extension of that class to insert custom tags into soapHeader. And already without any ThreadLocal hacks.
I have a Client that communicate with the Server using web-services.
The Model layer is implemented in the server side,
and I wonder what is the right way to pass parameters in the web-service methods.
Should it pass primitive types (and DataSets) or Objects?
For example, If I have the object Pesron, and I want my web-service to add new person
Should I pass the Object to the web-service method (i.e. AddNewMethod(Person newPerson)) or,
should I pass Primitives to the web-service method (i.e. AddNewMethod(int pId, String pName))
Thanks.
you should use AddNewMethod(Person newPerson)).
Reasons:
1. Cleaner code.
2. Less prone to errors.
3. You can use data model client side.
4. You are not going for overloading of functions? AddNewMethod(int pId, String pName) gives you options for overloading.
I need to set a global parameter in the Webmodule of my Delphi CGI webservice so that I can use it throughout the life of the request as a unique identifier (traceId) for logging purposes. The unique identifier needs to be generated in the Webmodule because the first thing I log is the raw XML that is received. I then log all sorts of debug information in my actual web service methods and I needs the Id to tie these all together.
I can't figure out a way to do this. The actual Webmodule is not accessible in my webservice methods and any attempts just throw an access violation. I'm probably doing this in completely the wrong way but I can think of another solution.
Any ideas?
Assuming you're talking about a SOAP webservice, you can use GetSOAPWebModule to get a reference to your web module from within your service method.
Example:
uses
WebBrokerSOAP, MyWebModuleUnit;
procedure TMyService.MyMethod;
var
MyWebModule: TMyWebModule;
TraceID: Integer;
begin
MyWebModule := GetSOAPWebModule as TMyWebModule;
TraceID := MyWebModule.TraceID;
end;
Hey guys. I've developed some services in REST that are running on Glassfish 3.1.
I have a Java SE application and I am using Jersey as the client api.
public <T> T findAll_JSON(Class<T> responseType) throws UniformInterfaceException {
WebResource resource = webResource;
return resource.accept(MediaType.APPLICATION_JSON).get(responseType);
}
This is the client code that is generated by Netbeans.
My main problem is passing a list of objects as the response type
Here is the client code.
List<PkgLine> pkgLine = service.findAll_JSON(List.class);
System.out.println(pkgLine.get(5).getLineStatus());
service.close();
Obviously this is not working because the response needs to be a List of PkgLine. How do I pass that as a generic? My service is set to return the List of PkgLine. Thanks.
The problem is "erasure". You can declare a List<PkgLine> in your program, but at runtime the information that the objects in the List are PkgLines is erased. List<String>, List<PkgLine> and List<Object> are all the same type at runtime. (There's a reason why this is so; I won't explain it here but you can look up "erasure" if you are interested.)
The objects in the List are still PkgLines of course, but to the List they are just Objects, and you'll have to cast each one to a PkgLine. It's not pretty.
List<?> pkgLine = service.findAll_JSON(List.class);
System.out.println(((PkgLine)(pkgLine.get(5))).getLineStatus());
service.close();
What about parsing an array? It has the same json represantation.
You could write something like this:
resource.accept(MediaType.APPLICATION_JSON).get(PkgLine[].class);
Lately, we've been seeing exceptions like this in our .NET (.asmx) webservices:
System.Web.Services.Protocols.SoapException: Server was unable to read request. ---> System.InvalidOperationException: There is an error in XML document (868, -3932). ---> System.Xml.XmlException: '.', hexadecimal value 0x00, is an invalid character. Line 868, position -3932.
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
at System.Xml.XmlTextReaderImpl.Throw(Int32 pos, String res, String[] args)
at System.Xml.XmlTextReaderImpl.ThrowInvalidChar(Int32 pos, Char invChar)
at System.Xml.XmlTextReaderImpl.ParseNumericCharRefInline(Int32 startPos, Boolean expand, BufferBuilder internalSubsetBuilder, Int32& charCount, EntityType& entityType)
at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& outOrChars)
at System.Xml.XmlTextReaderImpl.ParseText()
at System.Xml.XmlTextReaderImpl.ParseElementContent()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlTextReader.Read()
at System.Web.Services.Protocols.SoapServerProtocol.SoapEnvelopeReader.Read()
at System.Xml.XmlReader.ReadElementString()
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read14_SendErrlog()
at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer12.Deserialize(XmlSerializationReader reader)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
--- End of inner exception stack trace ---
at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()
How can I debug this exception? This exception is getting reported to us from a SOAP filter which looks for exceptions in message.Stage = SoapMessageStage.AfterSerialize.
Is there any way to get at the original soap request? How do I get an invalid character at line 868, column -3932? How can there a negative column 3932?
This is one of the irritating things about the Microsoft web services approach -- if the request cannot be deserialized into the objects in your web method signature then the service consumer gets a cryptic message. And to top it off, the request never makes it into your web service because it cannot be deserialzied so you can't handle the error gracefully.
What I would do to help with these types of issues is to create a new SoapExtension that simply lets you output the raw XML to a destination that is convenient for you (file or Trace to be read by DebugView or whatever else you like). The code would go in the BeforeDeserialize stage. You could enable the SoapExtension via web.config in the event you wanted to investigate one of these issues. The downside of using the web.config to add the SoapExtension is that it will be active for the entire web application. You could add some additional custom configuration that would allow your service to only log information for a specific endpoint or a specific web method if you wanted.
Usually, just by seeing the incoming XML you can see what the problem is. If not, then you could try to manually run the captured XML through small program that invokes the XML serializer and see if you can find out what is going on. Another useful tool is Web Service Studio 2 which is a test harness which lets you enter data and invoke your service (and also submit any XML you want).
In terms of your specific issue, here is my take/guess. It looks like ASCII character null is getting encoded and sent to your service which is invalid according to the XML Spec. The simple answer is not to send that character. But who is sending that character? Is it a .NET client? Do you have control over the client? If you need to work around someone else's bug, then you may have to replace the offending character(s) with another character (perhaps empty string).
You should be able to get the original message by using another SoapExtension. In fact, the same extension could probably be modified to make a copy of the input, and to discard it if there is no exception. The original input would then be available to you if an exception occurred.
You could also use an external tool like Fiddler to watch what's being sent to you.
FYI: SoapException.Message is intentionally left vague to prevent exposing too much information which could potentially be used to exploit the system.
For your particular case I'd take John's advice and install Fiddler to monitor the actual HTTP traffic and view the message on the wire.
The portion of your exception that jumps out at me is the "hexadecimal value 0x00, is an invalid character" but as you mentioned the line number it points to is bunk--so it's nothing concrete.
What kind of parameters do you pass to the service? Are you doing any kind of custom encoding with a SOAP extension? Any additional SOAP headers being added?