I am trying to implement a basic chat application with Spring boot & Stomp Protocol. I am not able to send message to specific user via SimpMessagingTemplate.convertAndSendToUser
All of my messages are pushed to all connected sockets.
my controller:
#Controller
public class MessageController {
private final SimpMessagingTemplate simpMessagingTemplate;
/**
* Constructor for object
*
* #param simpMessagingTemplate
*/
public MessageController(final SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
/**
* Responsible for sharing message through web socket.s
*
* #param message
* to share with audience.
* #return
*/
#MessageMapping("/message")
#SendTo("/topic/message")
public Message send(Message message) {
String time = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
message.setTime(time);
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/topic/message", message);
return message;
}
}
web socket configuration:
#EnableWebSocketMessageBroker
#Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private static final int MESSAGE_BUFFER_SIZE = 8192;
private static final long SECOND_IN_MILLIS = 1000L;
private static final long HOUR_IN_MILLIS = SECOND_IN_MILLIS * 60 * 60;
/*
* (non-Javadoc)
*
* #see org.springframework.web.socket.config.annotation.
* AbstractWebSocketMessageBrokerConfigurer#configureMessageBroker(org.
* springframework.messaging.simp.config.MessageBrokerRegistry)
*/
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// simple broker is applicable for first setup.
// To scale application enableStompBrokerRelay has to be configured.
// documentation :
// https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-handle-broker-relay
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
/*
* (non-Javadoc)
*
* #see org.springframework.web.socket.config.annotation.
* WebSocketMessageBrokerConfigurer#registerStompEndpoints(org.
* springframework.web.socket.config.annotation.StompEndpointRegistry)
*/
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
/**
* Bean for servlet container configuration. Sets message buffer size and
* idle timeout.
*
* #return
*/
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(MESSAGE_BUFFER_SIZE);
container.setMaxBinaryMessageBufferSize(MESSAGE_BUFFER_SIZE);
container.setMaxSessionIdleTimeout(HOUR_IN_MILLIS);
container.setAsyncSendTimeout(SECOND_IN_MILLIS);
return container;
}
}
basic security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("1").password("1").roles("USER");
auth.inMemoryAuthentication().withUser("2").password("2").roles("USER");
auth.inMemoryAuthentication().withUser("3").password("3").roles("USER");
}
}
and javascript code snippet:
dataStream = $websocket('ws://localhost:8080/chat');
stomp = Stomp.over(dataStream.socket);
var startListener = function() {
connected = true;
stomp.subscribe('/topic/message', function(data) {
messages.push(JSON.parse(data.body));
listener.notify();
});
};
stomp.connect({
'Login' : name,
passcode : name,
'client-id' : name
}, startListener);
send = function(request) {
stomp.send('/app/message', {}, JSON.stringify(request));
}
You should subscribe the special destination.
stomp.subscribe('/topic/message' + client_id, function(data) {
messages.push(JSON.parse(data.body));
listener.notify();
});
#SendTo("/topic/message") with return will send message to all client subscribe to "/topic/message", meanwhile follow code send message to all client subsribe to "/topic/message/{message.getTo()}":
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/topic/message", message);
Related
I need to browse messages from an active mq using Camel route without consuming the messages.
The messages in the JMS queue are to be read(only browsed and not consumed) and moved to a database while ensuring that the original queue remains intact.
public class CamelStarter {
private static CamelContext camelContext;
public static void main(String[] args) throws Exception {
camelContext = new DefaultCamelContext();
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnectionFactory.DEFAULT_BROKER_URL);
camelContext.addComponent("jms", JmsComponent.jmsComponent(connectionFactory));
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("jms:queue:testQueue").to("browse:orderReceived") .to("jms:queue:testQueue1");
}
}
);
camelContext.start();
Thread.sleep(1000);
inspectReceivedOrders();
camelContext.stop();
}
public static void inspectReceivedOrders() {
BrowsableEndpoint browse = camelContext.getEndpoint("browse:orderReceived", BrowsableEndpoint.class);
List<Exchange> exchanges = browse.getExchanges();
System.out.println("Browsing queue: "+ browse.getEndpointUri() + " size: " + exchanges.size());
for (Exchange exchange : exchanges) {
String payload = exchange.getIn().getBody(String.class);
String msgId = exchange.getIn().getHeader("JMSMessageID", String.class);
System.out.println(msgId + "=" +payload);
}
As far as I know, not possible in Camel to read (without consuming !) JMS messages...
The only workaround I found (in a JEE app) was to define a startup EJB with a timer, holding a QueueBrowser, and delegating the msg processing to a Camel route:
#Singleton
#Startup
public class MyQueueBrowser {
private TimerService timerService;
#Resource(mappedName="java:/jms/queue/com.company.myqueue")
private Queue sourceQueue;
#Inject
#JMSConnectionFactory("java:/ConnectionFactory")
private JMSContext jmsContext;
#Inject
#Uri("direct:readMessage")
private ProducerTemplate camelEndpoint;
#PostConstruct
private void init() {
TimerConfig timerConfig = new TimerConfig(null, false);
ScheduleExpression se = new ScheduleExpression().hour("*").minute("*/"+frequencyInMin);
timerService.createCalendarTimer(se, timerConfig);
}
#Timeout
public void scheduledExecution(Timer timer) throws Exception {
QueueBrowser browser = null;
try {
browser = jmsContext.createBrowser(sourceQueue);
Enumeration<Message> msgs = browser.getEnumeration();
while ( msgs.hasMoreElements() ) {
Message jmsMsg = msgs.nextElement();
// + here: read body and/or properties of jmsMsg
camelEndpoint.sendBodyAndHeader(body, myHeaderName, myHeaderValue);
}
} catch (JMSRuntimeException jmsException) {
...
} finally {
browser.close();
}
}
}
Apache camel browse component is exactly designed for that. Check here for the documentation.
Can't say more since you have not provided any other information.
Let's asssume you have a route like this
from("activemq:somequeue).to("bean:someBean")
or
from("activemq:somequeue).process(exchange -> {})
All you got to do it put a browse endpoint in between like this
from("activemq:somequeue).to("browse:someHandler").to("bean:someBean")
Then write a class like this
#Component
public class BrowseQueue {
#Autowired
CamelContext camelContext;
public void inspect() {
BrowsableEndpoint browse = camelContext.getEndpoint("browse:someHandler", BrowsableEndpoint.class);
List<Exchange> exchanges = browse.getExchanges();
for (Exchange exchange : exchanges) {
......
}
}
}
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
Are there any way to configure log4j2 to read Appender attributes from for example a spring bean? I am curious especially in JmsAppender to dynamically set it's target destination based on a parameter read from database and not from JNDI context.
BR
Zoltán
Your best chance is to extend the JMSAppender and override the append methods in the logger. A good example is here
This case , the class extends and uses AMQ to post these messages into. You should be able to extend this from the DB and use API's to get a handle to the Queue or Topic and start appending messages into it. This assumes that you have the right client libraries and permissions to connect to the messaging provider (e.g. in WMQ you may need the QM Name , Queue , Host, port) from the DB (in your case). The extended JMS appender can then be used in your LOG4J2 configuration for sending log messages.
It seems that i found a hybrid soution which is very useful, custom JmsAppender combined with spring context:
#Plugin(name = "OwnJmsAppender", category = "Core", elementType = "appender", printObject = true)
public class OwnJmsAppender extends AbstractAppender {
private final Lock lock = new ReentrantLock();
private Session session;
private Connection connection;
private Destination destination;
private MessageProducer producer;
protected OwnJmsAppender(String name, Filter filter, Layout<? extends Serializable> layout, final boolean ignoreExceptions) {
super(name, filter, layout, ignoreExceptions);
init();
}
#Override
public void append(LogEvent le) {
this.lock.lock();
try {
if (connection == null) {
init();
}
byte[] bytes = getLayout().toByteArray(le);
TextMessage message = session.createTextMessage(new String(bytes, Charset.forName("UTF-8")));
producer.send(message);
} catch (JMSException e) {
LOGGER.error(e);
} finally {
this.lock.unlock();
}
}
#Override
public void stop() {
super.stop();
try {
session.close();
connection.close();
} catch (JMSException e) {
LOGGER.error(e);
}
}
/**
* Reading attributes from log4j2.xml configuration by {#link PluginElement}
* annotation. Also initiates the logger.
*
* #param name
* #param layout
* #param filter
* #return
*/
#PluginFactory
public static OwnJmsAppender createAppender(#PluginAttribute("name") String name,
#PluginElement("PatternLayout") Layout<? extends Serializable> layout, #PluginElement("Filter") final Filter filter) {
if (name == null) {
LOGGER.error("No name provided for OwnJmsAppender");
return null;
}
return new OwnJmsAppender(name, filter, getLayout(layout), true);
}
private static Layout<? extends Serializable> getLayout(Layout<? extends Serializable> layout) {
Layout<? extends Serializable> finalLayout = layout;
if (finalLayout == null) {
finalLayout = PatternLayout.createDefaultLayout();
}
return finalLayout;
}
private void init() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonDbConfig.class);
ParameterStorage parameterStorage = (DatabaseParameterStorage) context.getBean("databaseParameterStorage");
// the parameterStorage springbean reads params from database
String brokerUri = parameterStorage.getStringValue("broker.url");
String queueName = "logQueue";
context.close();
try {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUri);
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
destination = session.createQueue(queueName);
producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
} catch (JMSException e) {
LOGGER.error(e);
}
}
}
And call it from log4j2.xml:
<Configuration>
<Appenders>
<OwnJmsAppender name="jmsQueue">
<PatternLayout pattern="%maxLen{%d{DEFAULT} [%p] - %m %xEx%n}{500}" />
</OwnJmsAppender>
</Appenders>
<Loggers>
<Logger name="com.your.package" level="info" additivity="false">
<AppenderRef ref="jmsQueue" />
</Logger>
</Loggers>
</Configuration>
I wish to make my own ' user ' entity to log in the application.
what I want to know is that if it's possible and if it is possible then where should I take precaution, which points should I consider and which files I would need to modify?
You need create a userclass by implementing UserDetailsService like
#Transactional
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
MyUser details = userDao.getUser(username);
Collection<simplegrantedauthority> authorities = new ArrayList<simplegrantedauthority>();
SimpleGrantedAuthority userAuthority = new SimpleGrantedAuthority(
"ROLE_USER");
SimpleGrantedAuthority adminAuthority = new SimpleGrantedAuthority(
"ROLE_ADMIN");
if (details.getRole().equals("user"))
authorities.add(userAuthority);
else if (details.getRole().equals("admin")) {
authorities.add(userAuthority);
authorities.add(adminAuthority);
}
UserDetails user = new User(details.getUsername(),
details.getPassword(), true, true, true, true, authorities);
return user;
}
}
an then configure spring to use your customuser object... like
<authentication-manager>
<authentication-provider user-service-ref="authService">
</authentication-provider>
</authentication-manager>
Full example can be found at Spring Custom User with DAO and Entity
You could create a user service extending UserDetailsService, and then create your UserDetail object.
It would be similar to this.
Create you User class:
public class MyUserDetails implements UserDetails {
private UserEntity user;
private List<GrantedAuthority> authorities;
/**
* Constructor
*/
public MyUserDetails(UserEntity user) {
this.user = user;
this.authorities = Arrays.asList(new SimpleGrantedAuthority(user.getRole().name()));
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#getAuthorities()
*/
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#getPassword()
*/
#Override
public String getPassword() {
return user.getPassword();
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#getUsername()
*/
#Override
public String getUsername() {
return user.getEmail();
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#isAccountNonExpired()
*/
#Override
public boolean isAccountNonExpired() {
return true;
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#isAccountNonLocked()
*/
#Override
public boolean isAccountNonLocked() {
return !user.isLocked();
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#isCredentialsNonExpired()
*/
#Override
public boolean isCredentialsNonExpired() {
return !user.isExpired();
}
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetails#isEnabled()
*/
#Override
public boolean isEnabled() {
return user.isEnabled();
}
/**
* #return the user
*/
public UserEntity getUser() {
return user;
}
/**
* #param user the user to set
*/
public void setUser(UserEntity user) {
this.user = user;
}
}
Then you should create your UserDetailsService:
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepo;
/*
* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userDao.findByEmail(username);
if (user == null) {
LOGGER.warn("User {} does not exist in our database", username);
throw new UsernameNotFoundException("User not found.");
}
return new MyUserDetails(user);
}
}
And finally you should add the configuration for Spring security to use your service:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
}
}
I'm using GWT RPC to connect to server side. On server side I've to connect to MySQL database, from which I get data I need. My question is which is best practice when I want to test my application, because I haven't deployed it yet on Tomcat app server, and it seems I cant test it, if it is not deployed. When I test it in development mode, I can't connect to database. Do I have to deploy it on Tomcat to test my application, or is there any other way.
P.S. I included JDBC driver, so that is not a problem.
You don't need to deploy to Tomcat or any other webserver to be able to test your application. Separate you test in several layers (JDBC or JPA layer, Service layer), use JUnit and Spring test and you'll be fine.
Take a look at this documentation:
http://static.springsource.org/spring/docs/2.5.x/reference/testing.html
Don't really know what problems you're encountering at this time since you don't have any exceptions posted, but the best practice I follow when I test my GWT code in Development Mode is to proxy the request from GWT to a backend implementation running on a different app server. Since you're planning on moving your code to tomcat, I'm assuming you will be moving your GWT compiled code under the war directory of your J2EE project running on tomcat and the actual db calls will be done from this J2EE project. Follow the below steps.
Basically, in your GWT application, on the 'server' side, you need to create a proxy servlet that will proxy all of the requests to a different port (the port on which your backend application is running -- for example tomcat's port).
You will need to have both applications running at the same time (obviously on different ports). That way, when you're in GWT Development Mode, all of the requests are sent to the jetty app server first, and then forwarded to tomcat, and that's how you can keep on testing without copying the files back and forth and testing your real backend implementation.
The proxy Servlet (from http://edwardstx.net/wiki/attach/HttpProxyServlet/ProxyServlet.java)
package com.xxxxxxx.xxxxx.gwt.xxxxx.server;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
/**
* ProxyServlet from http://edwardstx.net/wiki/attach/HttpProxyServlet/ProxyServlet.java
* (This seems to be a derivative of Noodle -- http://noodle.tigris.org/)
*
* Patched to skip "Transfer-Encoding: chunked" headers, avoid double slashes
* in proxied URLs, handle GZip and allow GWT RPC.
*/
public class ProxyServlet extends HttpServlet {
private static final int FOUR_KB = 4196;
/**
* Serialization UID.
*/
private static final long serialVersionUID = 1L;
/**
* Key for redirect location header.
*/
private static final String STRING_LOCATION_HEADER = "Location";
/**
* Key for content type header.
*/
private static final String STRING_CONTENT_TYPE_HEADER_NAME = "Content-Type";
/**
* Key for content length header.
*/
private static final String STRING_CONTENT_LENGTH_HEADER_NAME = "Content-Length";
/**
* Key for host header
*/
private static final String STRING_HOST_HEADER_NAME = "Host";
/**
* The directory to use to temporarily store uploaded files
*/
private static final File FILE_UPLOAD_TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));
// Proxy host params
/**
* The host to which we are proxying requests. Default value is "localhost".
*/
private String stringProxyHost = "localhost";
/**
* The port on the proxy host to wihch we are proxying requests. Default value is 80.
*/
private int intProxyPort = 80;
/**
* The (optional) path on the proxy host to wihch we are proxying requests. Default value is "".
*/
private String stringProxyPath = "";
/**
* Setting that allows removing the initial path from client. Allows specifying /twitter/* as synonym for twitter.com.
*/
private boolean removePrefix;
/**
* The maximum size for uploaded files in bytes. Default value is 5MB.
*/
private int intMaxFileUploadSize = 5 * 1024 * 1024;
private boolean isSecure;
private boolean followRedirects;
/**
* Initialize the ProxyServlet
* #param servletConfig The Servlet configuration passed in by the servlet container
*/
public void init(ServletConfig servletConfig) {
// Get the proxy host
String stringProxyHostNew = servletConfig.getInitParameter("proxyHost");
if (stringProxyHostNew == null || stringProxyHostNew.length() == 0) {
throw new IllegalArgumentException("Proxy host not set, please set init-param 'proxyHost' in web.xml");
}
this.setProxyHost(stringProxyHostNew);
// Get the proxy port if specified
String stringProxyPortNew = servletConfig.getInitParameter("proxyPort");
if (stringProxyPortNew != null && stringProxyPortNew.length() > 0) {
this.setProxyPort(Integer.parseInt(stringProxyPortNew));
}
// Get the proxy path if specified
String stringProxyPathNew = servletConfig.getInitParameter("proxyPath");
if (stringProxyPathNew != null && stringProxyPathNew.length() > 0) {
this.setProxyPath(stringProxyPathNew);
}
// Get the maximum file upload size if specified
String stringMaxFileUploadSize = servletConfig.getInitParameter("maxFileUploadSize");
if (stringMaxFileUploadSize != null && stringMaxFileUploadSize.length() > 0) {
this.setMaxFileUploadSize(Integer.parseInt(stringMaxFileUploadSize));
}
}
/**
* Performs an HTTP GET request
* #param httpServletRequest The {#link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* #param httpServletResponse The {#link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a GET request
String destinationUrl = this.getProxyURL(httpServletRequest);
debug("GET Request URL: " + httpServletRequest.getRequestURL(),
"Destination URL: " + destinationUrl);
GetMethod getMethodProxyRequest = new GetMethod(destinationUrl);
// Forward the request headers
setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, getMethodProxyRequest);
// Execute the proxy request
this.executeProxyRequest(getMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Performs an HTTP POST request
* #param httpServletRequest The {#link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* #param httpServletResponse The {#link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a standard POST request
String contentType = httpServletRequest.getContentType();
String destinationUrl = this.getProxyURL(httpServletRequest);
debug("POST Request URL: " + httpServletRequest.getRequestURL(),
" Content Type: " + contentType,
" Destination URL: " + destinationUrl);
PostMethod postMethodProxyRequest = new PostMethod(destinationUrl);
// Forward the request headers
setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, postMethodProxyRequest);
// Check if this is a mulitpart (file upload) POST
if (contentType == null || PostMethod.FORM_URL_ENCODED_CONTENT_TYPE.equals(contentType)) {
this.handleStandardPost(postMethodProxyRequest, httpServletRequest);
} else {
this.handleContentPost(postMethodProxyRequest, httpServletRequest);
}
// Execute the proxy request
this.executeProxyRequest(postMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Sets up the given {#link PostMethod} to send the same standard POST
* data as was sent in the given {#link HttpServletRequest}
* #param postMethodProxyRequest The {#link PostMethod} that we are
* configuring to send a standard POST request
* #param httpServletRequest The {#link HttpServletRequest} that contains
* the POST data to be sent via the {#link PostMethod}
*/
#SuppressWarnings("unchecked")
private void handleStandardPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) {
// Get the client POST data as a Map
Map mapPostParameters = (Map) httpServletRequest.getParameterMap();
// Create a List to hold the NameValuePairs to be passed to the PostMethod
List listNameValuePairs = new ArrayList();
// Iterate the parameter names
for (String stringParameterName : mapPostParameters.keySet()) {
// Iterate the values for each parameter name
String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName);
for (String stringParamterValue : stringArrayParameterValues) {
// Create a NameValuePair and store in list
NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue);
listNameValuePairs.add(nameValuePair);
}
}
// Set the proxy request POST data
postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(new NameValuePair[]{}));
}
/**
* Sets up the given {#link PostMethod} to send the same content POST
* data (JSON, XML, etc.) as was sent in the given {#link HttpServletRequest}
* #param postMethodProxyRequest The {#link PostMethod} that we are
* configuring to send a standard POST request
* #param httpServletRequest The {#link HttpServletRequest} that contains
* the POST data to be sent via the {#link PostMethod}
*/
private void handleContentPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) throws IOException, ServletException {
StringBuilder content = new StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
for (;;) {
String line = reader.readLine();
if (line == null) break;
content.append(line);
}
String contentType = httpServletRequest.getContentType();
String postContent = content.toString();
if (contentType.startsWith("text/x-gwt-rpc")) {
String clientHost = httpServletRequest.getLocalName();
if (clientHost.equals("127.0.0.1")) {
clientHost = "localhost";
}
int clientPort = httpServletRequest.getLocalPort();
String clientUrl = clientHost + ((clientPort != 80) ? ":" + clientPort : "");
String serverUrl = stringProxyHost + ((intProxyPort != 80) ? ":" + intProxyPort : "") + httpServletRequest.getServletPath();
//debug("Replacing client (" + clientUrl + ") with server (" + serverUrl + ")");
postContent = postContent.replace(clientUrl , serverUrl);
}
String encoding = httpServletRequest.getCharacterEncoding();
debug("POST Content Type: " + contentType + " Encoding: " + encoding,
"Content: " + postContent);
StringRequestEntity entity;
try {
entity = new StringRequestEntity(postContent, contentType, encoding);
} catch (UnsupportedEncodingException e) {
throw new ServletException(e);
}
// Set the proxy request POST data
postMethodProxyRequest.setRequestEntity(entity);
}
/**
* Executes the {#link HttpMethod} passed in and sends the proxy response
* back to the client via the given {#link HttpServletResponse}
* #param httpMethodProxyRequest An object representing the proxy request to be made
* #param httpServletResponse An object by which we can send the proxied
* response back to the client
* #throws IOException Can be thrown by the {#link HttpClient}.executeMethod
* #throws ServletException Can be thrown to indicate that another error has occurred
*/
private void executeProxyRequest(
HttpMethod httpMethodProxyRequest,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a default HttpClient
HttpClient httpClient = new HttpClient();
httpMethodProxyRequest.setFollowRedirects(false);
// Execute the request
int intProxyResponseCode = httpClient.executeMethod(httpMethodProxyRequest);
String response = httpMethodProxyRequest.getResponseBodyAsString();
// Check if the proxy response is a redirect
// The following code is adapted from org.tigris.noodle.filters.CheckForRedirect
// Hooray for open source software
if (intProxyResponseCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ && intProxyResponseCode responseHeaders = Arrays.asList(headerArrayResponse);
if (isBodyParameterGzipped(responseHeaders)) {
debug("GZipped: true");
if (!followRedirects && intProxyResponseCode == HttpServletResponse.SC_MOVED_TEMPORARILY) {
response = httpMethodProxyRequest.getResponseHeader(STRING_LOCATION_HEADER).getValue();
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
intProxyResponseCode = HttpServletResponse.SC_OK;
httpServletResponse.setHeader(STRING_LOCATION_HEADER, response);
} else {
response = new String(ungzip(httpMethodProxyRequest.getResponseBody()));
}
httpServletResponse.setContentLength(response.length());
}
// Send the content to the client
debug("Received status code: " + intProxyResponseCode,
"Response: " + response);
httpServletResponse.getWriter().write(response);
}
/**
* The response body will be assumed to be gzipped if the GZIP header has been set.
*
* #param responseHeaders of response headers
* #return true if the body is gzipped
*/
private boolean isBodyParameterGzipped(List responseHeaders) {
for (Header header : responseHeaders) {
if (header.getValue().equals("gzip")) {
return true;
}
}
return false;
}
/**
* A highly performant ungzip implementation. Do not refactor this without taking new timings.
* See ElementTest in ehcache for timings
*
* #param gzipped the gzipped content
* #return an ungzipped byte[]
* #throws java.io.IOException when something bad happens
*/
private byte[] ungzip(final byte[] gzipped) throws IOException {
final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzipped));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(gzipped.length);
final byte[] buffer = new byte[FOUR_KB];
int bytesRead = 0;
while (bytesRead != -1) {
bytesRead = inputStream.read(buffer, 0, FOUR_KB);
if (bytesRead != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
}
byte[] ungzipped = byteArrayOutputStream.toByteArray();
inputStream.close();
byteArrayOutputStream.close();
return ungzipped;
}
public String getServletInfo() {
return "GWT Proxy Servlet";
}
/**
* Retrieves all of the headers from the servlet request and sets them on
* the proxy request
*
* #param httpServletRequest The request object representing the client's
* request to the servlet engine
* #param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
#SuppressWarnings("unchecked")
private void setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
// Get an Enumeration of all of the header names sent by the client
Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames();
while (enumerationOfHeaderNames.hasMoreElements()) {
String stringHeaderName = (String) enumerationOfHeaderNames.nextElement();
if (stringHeaderName.equalsIgnoreCase(STRING_CONTENT_LENGTH_HEADER_NAME)) {
continue;
}
// As per the Java Servlet API 2.5 documentation:
// Some headers, such as Accept-Language can be sent by clients
// as several headers each with a different value rather than
// sending the header as a comma separated list.
// Thus, we get an Enumeration of the header values sent by the client
Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName);
while (enumerationOfHeaderValues.hasMoreElements()) {
String stringHeaderValue = (String) enumerationOfHeaderValues.nextElement();
// In case the proxy host is running multiple virtual servers,
// rewrite the Host header to ensure that we get content from
// the correct virtual server
if (stringHeaderName.equalsIgnoreCase(STRING_HOST_HEADER_NAME)) {
stringHeaderValue = getProxyHostAndPort();
}
Header header = new Header(stringHeaderName, stringHeaderValue);
// Set the same header on the proxy request
httpMethodProxyRequest.setRequestHeader(header);
}
}
}
/**
* Retrieves all of the cookies from the servlet request and sets them on
* the proxy request
*
* #param httpServletRequest The request object representing the client's
* request to the servlet engine
* #param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
#SuppressWarnings("unchecked")
private void setProxyRequestCookies(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
// Get an array of all of all the cookies sent by the client
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null) {
return;
}
for (Cookie cookie : cookies) {
cookie.setDomain(stringProxyHost);
cookie.setPath(httpServletRequest.getServletPath());
httpMethodProxyRequest.setRequestHeader("Cookie", cookie.getName() + "=" + cookie.getValue() + "; Path=" + cookie.getPath());
}
}
// Accessors
private String getProxyURL(HttpServletRequest httpServletRequest) {
// Set the protocol to HTTP
String protocol = (isSecure) ? "https://" : "http://";
String stringProxyURL = protocol + this.getProxyHostAndPort() + "/gui";
// simply use whatever servlet path that was part of the request as opposed to getting a preset/configurable proxy path
if (!removePrefix) {
if (httpServletRequest.getServletPath() != null) {
stringProxyURL += httpServletRequest.getServletPath();
}
}
stringProxyURL += "/";
// Handle the path given to the servlet
String pathInfo = httpServletRequest.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/")) {
if (stringProxyURL != null && stringProxyURL.endsWith("/")) {
// avoid double '/'
stringProxyURL += pathInfo.substring(1);
}
} else {
stringProxyURL += httpServletRequest.getPathInfo();
}
// Handle the query string
if (httpServletRequest.getQueryString() != null) {
stringProxyURL += "?" + httpServletRequest.getQueryString();
}
stringProxyURL = stringProxyURL.replaceAll("/null", "");
//System.out.println("----stringProxyURL: " + stringProxyURL);
return stringProxyURL;
}
private String getProxyHostAndPort() {
if (this.getProxyPort() == 80) {
return this.getProxyHost();
} else {
return this.getProxyHost() + ":" + this.getProxyPort();
}
}
protected String getProxyHost() {
return this.stringProxyHost;
}
protected void setProxyHost(String stringProxyHostNew) {
this.stringProxyHost = stringProxyHostNew;
}
protected int getProxyPort() {
return this.intProxyPort;
}
protected void setSecure(boolean secure) {
this.isSecure = secure;
}
protected void setFollowRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
}
protected void setProxyPort(int intProxyPortNew) {
this.intProxyPort = intProxyPortNew;
}
protected String getProxyPath() {
return this.stringProxyPath;
}
protected void setProxyPath(String stringProxyPathNew) {
this.stringProxyPath = stringProxyPathNew;
}
protected void setRemovePrefix(boolean removePrefix) {
this.removePrefix = removePrefix;
}
protected int getMaxFileUploadSize() {
return this.intMaxFileUploadSize;
}
protected void setMaxFileUploadSize(int intMaxFileUploadSizeNew) {
this.intMaxFileUploadSize = intMaxFileUploadSizeNew;
}
private void debug(String ... msg) {
for (String m : msg) {
//System.out.println("[DEBUG] " + m);
}
}
}
You then need to subclass it and provide the port:
package xxx.xxxx.xxxxx;
import javax.servlet.ServletConfig;
public class MyProxyServlet extends ProxyServlet {
public void init(ServletConfig servletConfig) {
//System.out.println("in the init");
setFollowRedirects(true);
setRemovePrefix(false);
//setProxyPath("gui/" + getProxyPath());
setProxyPort(8080);
}
}