I would like to log some information in case of fault.
In particular I'd like to log the ip address and port of the client contacting the server, the username if the security in active, and, if possible, also the incoming message.
I added an interceptor in the getOutFaultInterceptors chain of the endpoint, but in the handleMessage I don't know which properties I can use.
Some ideas?
Thank you
In your endpoint xml definition, you could add the following to log incoming messages:
<bean id="logInInterceptor"
class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<jaxws:inInterceptors>
<ref bean="logInInterceptor"/>
</jaxws:inInterceptors>
and then use the bus to limit how many characters you want logged:
<cxf:bus>
<cxf:features>
<cxf:logging limit="102400"/>
</cxf:features>
<cxf:bus>
You haven't mentioned what your method of authentication is, so ff you are using an implementation of UsernameTokenValidator, you could log the incoming username there.
To log details like the client's ip address and port, extend LoggingInInterceptor, then use the following code in handleMessage():
handleMessage() {
HttpServletRequest request =
(HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
if (null != request) {
String clientAddress = request.getRemoteAddr();
int remotePort = request.getRemotePort();
// log them
}
}
Also have a look at this thread.
I solved in this way
public FaultInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage message) throws Fault {
Fault fault = (Fault) message.getContent(Exception.class);
Message inMessage = message.getExchange().getInMessage();
if (inMessage == null) return;
String xmlMessage = null;
InputStream is = inMessage.getContent(InputStream.class);
String rawXml = null;
if (is != null) {
rawXml = is.toString();
}
String username = null;
if (rawXml != null && rawXml.length() > 0) {
try {
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression xpathExpression;
xpathExpression = xpath.compile("//*[local-name()=\'Envelope\']/*[local-name()=\'Header\']/*[local-name()=\'Security\']" +
"/*[local-name()=\'UsernameToken\']/*[local-name()=\'Username\']");
InputSource source = new InputSource(new StringReader(rawXml));
username = xpathExpression.evaluate(source);
} catch (XPathExpressionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
xmlMessage = XMLUtils.prittyPrinter(is.toString());
}
String clientAddress = "<unknown>";
int clientPort = -1;
HttpServletRequest request = (HttpServletRequest)inMessage.get(AbstractHTTPDestination.HTTP_REQUEST);
if (null != request) {
clientAddress = request.getRemoteAddr();
clientPort = request.getRemotePort();
}
logger.warn("User: " + username + " [" + clientAddress + ":" + clientPort + "] caused fault: " + fault +
"\nMessage received: \n" + xmlMessage);
}
I found the "inMessage" property and on it I found the original message (and I can retrieve the username) and the "request" from which I retrieved the host and port.
Thank you.
I think you should view the request input stream as consumed when handling the fault.
I suggest you always log the incoming message, and extract some kind of message correlation id - for example username. Keep it as a Message header.
For fault logging, use a fault interceptor which is limited to looking at the input request.
Tie regular + fault logging together with the message correlation id.
Log the full soap request, not just the payload. Soap requests might have headers in addition to the body.
See this question for regular logging, add in addition an output fault interceptor like so:
public class SoapFaultLoggingOutInterceptor extends AbstractPhaseInterceptor<Message> {
private static final String LOCAL_NAME = "MessageID";
private static final int PROPERTIES_SIZE = 128;
private String name = "<interceptor name not set>";
protected Logger logger = null;
protected Level level;
public SoapFaultLoggingOutInterceptor() {
this(LogUtils.getLogger(SoapFaultLoggingOutInterceptor.class), Level.WARNING);
}
public SoapFaultLoggingOutInterceptor(Logger logger, Level reformatSuccessLevel) {
super(Phase.MARSHAL);
this.logger = logger;
this.level = reformatSuccessLevel;
}
public void setName(String name) {
this.name = name;
}
public void handleMessage(Message message) throws Fault {
if (!logger.isLoggable(level)) {
return;
}
StringBuilder buffer = new StringBuilder(PROPERTIES_SIZE);
// perform local logging - to the buffer
buffer.append(name);
logProperties(buffer, message);
logger.log(level, buffer.toString());
}
/**
* Gets theMessageID header in the list of headers.
*
*/
protected String getIdHeader(Message message) {
return getHeader(message, LOCAL_NAME);
}
protected String getHeader(Message message, String name) {
List<Header> headers = (List<Header>) message.get(Header.HEADER_LIST);
if(headers != null) {
for(Header header:headers) {
if(header.getName().getLocalPart().equalsIgnoreCase(name)) {
return header.getObject().toString();
}
}
}
return null;
}
protected void logProperties(StringBuilder buffer, Message message) {
final String messageId = getIdHeader(message);
if(messageId != null) {
buffer.append(" MessageId=");
buffer.append(messageId);
}
Message inMessage = message.getExchange().getInMessage();
HttpServletRequest request = (HttpServletRequest)inMessage.get(AbstractHTTPDestination.HTTP_REQUEST);
buffer.append(" RemoteAddr=");
buffer.append(request.getRemoteAddr());
}
public Logger getLogger() {
return logger;
}
public String getName() {
return name;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
where MessageID is the correlation / breadcrumb id.
Related
I have a route as follows:
from(fromEndpoint).routeId("ticketRoute")
.log("Received Tickets : ${body}")
.doTry()
.process(exchange -> {
List<TradeTicketDto> ticketDtos = (List<TradeTicketDto>) exchange.getIn().getBody();
ticketDtos.stream()
.forEach(t -> solaceMessagePublisher.sendAsText("BOOKINGSERVICE.TICKET.UPDATED", t));
ticketToTradeConverter.convert(ticketDtos)
.forEach(t -> solaceMessagePublisher.sendAsText("BOOKINGSERVICE.TRADE.UPDATED", t));
}).doCatch(java.lang.RuntimeException.class)
.log(exceptionMessage().toString() + " --> ${body}");
solaceMessagePublisher is a utility class in application which performs some action on passed object (second argument) and finally converts it to json string and sends to a jms topic (first argument).
SolaceMessagePublisher.java
public void sendAsText(final String destinationKey, Object payload) {
LOGGER.debug("Sending object as text to %s",destinationKey);
String destinationValue = null;
if (StringUtils.isNotEmpty(destinationKey)) {
destinationValue = properties.getProperty(destinationKey);
}
LOGGER.debug("Identified Destination Value = %s from key %s", destinationValue,destinationKey);
if (StringUtils.isEmpty(destinationValue)) {
throw new BaseServiceException("Invalid destination for message");
}
sendAsTextToDestination(destinationValue, payload);
}
public void sendAsTextToDestination(final String destinationValue, Object payload) {
if (payload == null) {
LOGGER.debug(" %s %s",EMPTY_PAYLOAD_ERROR_MESSAGE, destinationValue);
return;
}
final String message = messageCreator.createMessageEnvelopAsJSON(payload, ContextProvider.getUserInContext());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created message = " + message);
}
jmsTemplate.send(destinationValue, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
LOGGER.debug("Creating JMS Text Message");
return session.createTextMessage(message);
}
});
}
I am having a problem in creating a mock endpoint to listen to messages sent to this topic. Question is how to listen to the messages sent to a topic which is out of camel context?
I have tried in my Test using mock:jms:endpoint. It doesn't work.
My Test is as below
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { SiteMain.class })
public class TicketRouteCamelTest extends CamelSpringTestSupport{
#Autowired
protected BaseMessageEnvelopCreator messageCreator;
private static final String MOCK_TICKET_UPDATED_QUEUE = "direct:mockTicketUpdated";
#Before
public void configureMockEndpoints() throws Exception {
//mock input
final AdviceWithRouteBuilder mockRouteAdvice = new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith(MOCK_TICKET_UPDATED_QUEUE);
}
};
context().getRouteDefinition("ticketRoute").adviceWith(context(), mockRouteAdvice);
}
#Test
public void testTicketRouteWithListOfTickets() throws Exception {
//create test data
TradeTicketDto tradeTicketDto = TradeTestDataHelper.getTradeTicketDto();
//create an exchange and set its body with test data
List<TradeTicketDto> list = new ArrayList<>();
list.add(tradeTicketDto);
list.add(tradeTicketDto);
Exchange requestExchange = ExchangeBuilder.anExchange(context()).build();
requestExchange.getIn().setBody(list);
//create assert on the mock endpoints
MockEndpoint mockTicketUpdatedEndpoint = getMockEndpoint("mock:DEV/bookingservice/ticket/updated");
mockTicketUpdatedEndpoint.expectedBodiesReceived(
messageCreator.createMessageEnvelopAsJSON(list.get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1), ContextProvider.getUserInContext()) );
MockEndpoint mockTradeUpdatedEndpoint = getMockEndpoint("mock:DEV/bookingservice/trade/updated");
mockTradeUpdatedEndpoint.expectedBodiesReceived(
messageCreator.createMessageEnvelopAsJSON(list.get(0).getTicketInstruments().get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(0).getTicketInstruments().get(1), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1).getTicketInstruments().get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1).getTicketInstruments().get(1), ContextProvider.getUserInContext()));
//send test exchange to request mock endpoint
template.send(MOCK_TICKET_UPDATED_QUEUE, requestExchange);
//test the asserts
assertMockEndpointsSatisfied();
}
}
On running test actual bodies received on mockendpont is 0
Mock is NOT a queue for consumers/producers to exchange data. Its a sink for testing purpose where you can setup expectations on the mock.
If you want to simulate a JMS via some kind of other means, then take a look at the stub component: http://camel.apache.org/stub
Its also listed in the bottom of the testing docs at: http://camel.apache.org/testing
I am Using GCM (Google Cloud Messaging).In that what i want i want to send J Son from the server side .On Client side I want to receive that for simple message i have done but i am stucked how could i pass J Son from the server side to the client side.
Please help me to resolve this.
This is my Server side code
public class GCMBroadcast extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String SENDER_ID = "";
private static final String ANDROID_DEVICE = "";
private List<String> androidTargets = new ArrayList<String>();
public GCMBroadcast() {
super();
androidTargets.add(ANDROID_DEVICE);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String collapseKey = "";
String userMessage = "";
try {
userMessage = request.getParameter("Message");
collapseKey = request.getParameter("CollapseKey");
} catch (Exception e) {
e.printStackTrace();
return;
}
Sender sender = new Sender(SENDER_ID);
Message message = new Message.Builder()
.collapseKey(collapseKey)
.addData("message", userMessage)
.build();
try {
MulticastResult result = sender.send(message, androidTargets, 1);
System.out.println("Response: " + result.getResults().toString());
if (result.getResults() != null) {
int canonicalRegId = result.getCanonicalIds();
if (canonicalRegId != 0) {
System.out.println("response " +canonicalRegId );
}
} else {
int error = result.getFailure();
System.out.println("Broadcast failure: " + error);
}
} catch (Exception e) {
e.printStackTrace();
}
request.setAttribute("CollapseKey", collapseKey);
request.setAttribute("Message", userMessage);
request.getRequestDispatcher("XX.jsp").forward(request, response);
}
}
Your payload (added to the Message by calls to addData) can only be name/value pairs. If you want to send a JSON, you can put a JSON string in the value of such name/value pair. Then you'll have to parse that JSON yourself in the client side.
For example :
.addData("message","{\"some_json_key\":\"some_json_value\"}")
I am brand new to both REST and RESTlet- I got everything up and communicating last night but what I found this morning is that everything I pass into the server is always becoming null.
just as a sample app i have the following - a User Objectify entity (id, emailAddress, and version), and a RESTUserProxy object (id, emailAddress) - I wasn't originally sure if i could pass Objectify Entities back and after not being able to see anything switched it to the Proxy object - if i can get it to work this way I will try switching it back
the front end is as follows:
public interface RESTUserResourceProxy extends ClientProxy {
#Get
public void find(String emailAddress, Result<RESTUserProxy> callback);
#Put
public void persist(RESTUserProxy user, Result<Void> callback);
#Delete
public void delete(RESTUserProxy user, Result<Void> callback);
}
the backend code is as follows (this is currently extremely ugly - i got a little frustrated just trying to see something and put in a ton of sysouts)
public class RESTUserServerResource extends ServerResource implements RESTUserResource {
private final UserDao userDao;
public RESTUserServerResource() {
System.out.println("CREATED USER RESOURCE IMPL");
userDao = new UserDao();
}
#Override
#Get
public RESTUserProxy find() {
System.out.println("reference = " + getReference());
Form queryParams = getReference().getQueryAsForm();
System.out.println("query params = " + queryParams);
System.out.println("query = " + getQuery());
System.out.println("query string = " + getQuery().getQueryString());
String searchQuery = (String) getRequest().getAttributes().get("searchQuery");
System.out.println("search query = " + searchQuery) ;
return null;
// if (emailAddress == null) {
// return null;
// }
// System.out.println("user resource impl find [" + emailAddress + "]");
// final User user = userDao.find(emailAddress.getText());
// if (user != null) {
// System.out.println("found user ");
// return new RESTUserProxy(user.getId(), user.getEmailAddress());
// } else {
// System.out.println("found absolutely nothing");
// return null;
// }
}
#Override
#Put
public void persist(RESTUserProxy userProxy) {
System.out.println("user proxy = " + userProxy);
if (userProxy == null) {
return;
}
final User user = userDao.find(userProxy.getId());
user.setEmailAddress(userProxy.getEmailAddress());
user.setId(userProxy.getId());
userDao.persist(user);
}
#Override
#Delete
public void delete(RESTUserProxy userProxy) {
final User user = userDao.find(userProxy.getId());
userDao.delete(user);
}
}
what im having problems with is that eerythings coming through as null - a lot of other answers on here said to get the query to get the params - but here the query is null
below is the output of calling find and persist
reference = http://127.0.0.1:8888/users/123
query params = []
query = []
query string =
search query = null
i'm sure i'm doing something stupid here i just have no idea how to proceed right now. Any help as to what i'm doing wrong would be greatly appreciated.
This is due to GAE not supporting chunked encoding. See workaround here:
http://wiki.restlet.org/docs_2.1/13-restlet/21-restlet/318-restlet/303-restlet.html#dsy303-restlet_gwt
I'm scratching my head over this:
Using an Interceptor to check a few SOAP headers, how can I abort the interceptor chain but still respond with an error to the user?
Throwing a Fault works regarding the output, but the request is still being processed and I'd rather not have all services check for some flag in the message context.
Aborting with "message.getInterceptorChain().abort();" really aborts all processing, but then there's also nothing returned to the client.
What's the right way to go?
public class HeadersInterceptor extends AbstractSoapInterceptor {
public HeadersInterceptor() {
super(Phase.PRE_LOGICAL);
}
#Override
public void handleMessage(SoapMessage message) throws Fault {
Exchange exchange = message.getExchange();
BindingOperationInfo bop = exchange.getBindingOperationInfo();
Method action = ((MethodDispatcher) exchange.get(Service.class)
.get(MethodDispatcher.class.getName())).getMethod(bop);
if (action.isAnnotationPresent(NeedsHeaders.class)
&& !headersPresent(message)) {
Fault fault = new Fault(new Exception("No headers Exception"));
fault.setFaultCode(new QName("Client"));
try {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element detail = doc.createElementNS(Soap12.SOAP_NAMESPACE, "mynamespace");
detail.setTextContent("Missing some headers...blah");
fault.setDetail(detail);
} catch (ParserConfigurationException e) {
}
// bad: message.getInterceptorChain().abort();
throw fault;
}
}
}
Following the suggestion by Donal Fellows I'm adding an answer to my question.
CXF heavily relies on Spring's AOP which can cause problems of all sorts, at least here it did. I'm providing the complete code for you. Using open source projects I think it's just fair to provide my own few lines of code for anyone who might decide not to use WS-Security (I'm expecting my services to run on SSL only). I wrote most of it by browsing the CXF sources.
Please, comment if you think there's a better approach.
/**
* Checks the requested action for AuthenticationRequired annotation and tries
* to login using SOAP headers username/password.
*
* #author Alexander Hofbauer
*/
public class AuthInterceptor extends AbstractSoapInterceptor {
public static final String KEY_USER = "UserAuth";
#Resource
UserService userService;
public AuthInterceptor() {
// process after unmarshalling, so that method and header info are there
super(Phase.PRE_LOGICAL);
}
#Override
public void handleMessage(SoapMessage message) throws Fault {
Logger.getLogger(AuthInterceptor.class).trace("Intercepting service call");
Exchange exchange = message.getExchange();
BindingOperationInfo bop = exchange.getBindingOperationInfo();
Method action = ((MethodDispatcher) exchange.get(Service.class)
.get(MethodDispatcher.class.getName())).getMethod(bop);
if (action.isAnnotationPresent(AuthenticationRequired.class)
&& !authenticate(message)) {
Fault fault = new Fault(new Exception("Authentication failed"));
fault.setFaultCode(new QName("Client"));
try {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element detail = doc.createElementNS(Soap12.SOAP_NAMESPACE, "test");
detail.setTextContent("Failed to authenticate.\n" +
"Please make sure to send correct SOAP headers username and password");
fault.setDetail(detail);
} catch (ParserConfigurationException e) {
}
throw fault;
}
}
private boolean authenticate(SoapMessage msg) {
Element usernameNode = null;
Element passwordNode = null;
for (Header header : msg.getHeaders()) {
if (header.getName().getLocalPart().equals("username")) {
usernameNode = (Element) header.getObject();
} else if (header.getName().getLocalPart().equals("password")) {
passwordNode = (Element) header.getObject();
}
}
if (usernameNode == null || passwordNode == null) {
return false;
}
String username = usernameNode.getChildNodes().item(0).getNodeValue();
String password = passwordNode.getChildNodes().item(0).getNodeValue();
User user = null;
try {
user = userService.loginUser(username, password);
} catch (BusinessException e) {
return false;
}
if (user == null) {
return false;
}
msg.put(KEY_USER, user);
return true;
}
}
As mentioned above, here's the ExceptionHandler/-Logger. At first I wasn't able to use it in combination with JAX-RS (also via CXF, JAX-WS works fine now). I don't need JAX-RS anyway, so that problem is gone now.
#Aspect
public class ExceptionHandler {
#Resource
private Map<String, Boolean> registeredExceptions;
/**
* Everything in my project.
*/
#Pointcut("within(org.myproject..*)")
void inScope() {
}
/**
* Every single method.
*/
#Pointcut("execution(* *(..))")
void anyOperation() {
}
/**
* Log every Throwable.
*
* #param t
*/
#AfterThrowing(pointcut = "inScope() && anyOperation()", throwing = "t")
public void afterThrowing(Throwable t) {
StackTraceElement[] trace = t.getStackTrace();
Logger logger = Logger.getLogger(ExceptionHandler.class);
String info;
if (trace.length > 0) {
info = trace[0].getClassName() + ":" + trace[0].getLineNumber()
+ " threw " + t.getClass().getName();
} else {
info = "Caught throwable with empty stack trace";
}
logger.warn(info + "\n" + t.getMessage());
logger.debug("Stacktrace", t);
}
/**
* Handles all exceptions according to config file.
* Unknown exceptions are always thrown, registered exceptions only if they
* are set to true in config file.
*
* #param pjp
* #throws Throwable
*/
#Around("inScope() && anyOperation()")
public Object handleThrowing(ProceedingJoinPoint pjp) throws Throwable {
try {
Object ret = pjp.proceed();
return ret;
} catch (Throwable t) {
// We don't care about unchecked Exceptions
if (!(t instanceof Exception)) {
return null;
}
Boolean throwIt = registeredExceptions.get(t.getClass().getName());
if (throwIt == null || throwIt) {
throw t;
}
}
return null;
}
}
Short answer, the right way to abort in a client-side interceptor before the sending the request is to create the Fault with a wrapped exception :
throw new Fault(
new ClientException( // or any non-Fault exception, else blocks in
// abstractClient.checkClientException() (waits for missing response code)
"Error before sending the request"), Fault.FAULT_CODE_CLIENT);
Thanks to post contributors for helping figuring it out.
CXF allows you to specify that your interceptor goes before or after certain interceptors. If your interceptor is processing on the inbound side (which based on your description is the case) there is an interceptor called CheckFaultInterceptor. You can configure your interceptor to go before it:
public HeadersInterceptor(){
super(Phase.PRE_LOGICAL);
getBefore().add(CheckFaultInterceptor.class.getName());
}
The check fault interceptor in theory checks if a fault has occurred. If one has, it aborts the interceptor chain and invokes the fault handler chain.
I have not yet been able to test this (it is fully based on the available documentation I've come across trying to solve a related problem)
public final class MyScreen extends MainScreen {
/**
* Creates a new MyScreen object
*/
public MyScreen() {
MyScreen myScreen = new MyScreen();
String a = myScreen.getPage("http://www.google.com");
System.out.println("+++ " + a);
}
public void parse(String xml) {
}
public String getPage(String url) {
String response = "";
try {
StreamConnection s = (StreamConnection) Connector.open(url);
InputStream input = s.openInputStream();
byte[] data = new byte[256];
int len = 0;
StringBuffer raw = new StringBuffer();
while (-1 != (len = input.read(data))) {
raw.append(new String(data, 0, len));
}
response = raw.toString();
input.close();
s.close();
} catch (Exception e) {}
return response;
}
}
When I execute this program in my Blackberry simulator, I get a StackOverflow error.
How might I resolve this?
checkout this :
1). Http connection error on the blackberry real device
2). http://docs.blackberry.com/en/developers/deliverables/11938/CS_create_first_available_HTTP_connection_857706_11.jsp
this may help you.