I am using org.eclipse.jetty.websocket.client.WebSocketClient to establish a websocket connection.
After the initial handshake(Protocol switch/Upgrade) the websocket session is established.
Here is the code snipped i am using:
WebSocketClient client = new WebSocketClient();
client.start();
URI echoUri = new URI("destinationURI");
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader("myCustomHeader", "CustomHeader");
client.connect(socket, echoUri, request);
Collection<WebSocketSession> sessions = client.getConnectionManager().getSessions();
for (WebSocketSession webSocketSession : sessions) {
webSocketSession.getRemote().sendString("<Custome message>");//I am able to recieve the messages //to the configured message handler
}
My message handler looks like:
#Override
protected void handleTextMessage(WebSocketSession session,
TextMessage message//This is what i sent above) throws Exception {
session.getHandshakeHeaders();//This has "myCustomHeader", "CustomHeader"
BinaryMessage binaryMessage = new BinaryMessage(new String(
"Hello . This is message sent from server").getBytes());
session.sendMessage(binaryMessage);
}
Is it possible to send a custom header, after the web socket session is established?
Here is what i tried:
WebSocketClient client = new WebSocketClient();
client.start();
URI echoUri = new URI("destinationURI");
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader("myCustomHeader", "CustomHeader");
client.connect(socket, echoUri, request);
Collection<WebSocketSession> sessions = client.getConnectionManager().getSessions();
for (WebSocketSession webSocketSession : sessions) {
webSocketSession.getUpgradeRequest().setHeader("mySecondCustomHeader","MySecondCustomHeader");
webSocketSession.getRemote().sendString("<Custome message>");//I am able to recieve the messages //to the configured message handler
}
I am only getting myCustomHeader and not mySecondCustomHeader in session.getHandshakeHeaders()
#Override
protected void handleTextMessage(WebSocketSession session,
TextMessage message//This is what i sent above) throws Exception {
session.getHandshakeHeaders();//This has "myCustomHeader", "CustomHeader"
BinaryMessage binaryMessage = new BinaryMessage(new String(
"Hello . This is message sent from server").getBytes());
session.sendMessage(binaryMessage);
}
s it possible to send a custom header, after the web socket session is
established?
No, it is not possible. Once the HTTP negotiation has concluded, the connection only uses binary frames to communicate and cannot do more HTTP interactions.
Related
When i'm trying to send request via jetty httpClient with new Socks4Proxy(socksHost, socksPort); received: java.util.concurrent.ExecutionException: java.io.IOException: SOCKS4 tunnel failed with code 91
HttpClient httpClient = new HttpClient();
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
Socks4Proxy proxy = new Socks4Proxy(socksHost, socksPort);
proxyConfig.getProxies().add(proxy);
httpClient.start();
String url = config.getProperty(stringUrl);
Request request = httpClient.newRequest(url);
request.method(HttpMethod.GET);
request.onResponseContent(new BufferingResponseListener() {
#Override
public void onComplete(Result result) {
String s = getContentAsString();
logger.debug("Received http response message: '{}', status: '{}'", s, result.getResponse().getReason());
}
});
try {
request.send();
} catch (Exception e) {
throw new RuntimeException(e);
}
Per https://www.openssh.com/txt/socks4.protocol
Your 91 status means that request was rejected or failed. The user or source program is not authorized to access the proxy server.
Perhaps your SOCKS4 proxy has an authentication requirement.
I am building a simple REST api which connects a web server to a back end service, which performs a simple check and sends a response.
So client (over HTTP) -> to Web Server (over ACTIVEMQ/CAMEL)-> to Checking-Service, and back again.
The endpoint for the GET request is "/{id}". I'm trying to make this send a message through queue:ws-out to queue:cs-in and map it all the way back again to the original GET request.
The Checking-Service (cs) code is fine, it simply changes a value in the CheckMessage object to true using jmslistener.
I've searched the web thoroughly for examples, but can't get anything to work. The closest one I found was the following.
This is what I have so far on the Web Server (ws).
RestController
import ...
#RestController
public class RESTController extends Exception{
#Autowired
CamelContext camelContext;
#Autowired
JmsTemplate jmsTemplate;
#GetMapping("/{id}")
public String testCamel(#PathVariable String id) {
//Object used to send out
CheckMessage outMsg = new CheckMessage(id);
//Object used to receive response
CheckMessage inMsg = new CheckMessage(id);
//Sending the message out (working)
jmsTemplate.convertAndSend("ws-out", outMsg);
//Returning the response to the client (need correlation to the out message"
return jmsTemplate.receiveSelectedAndConvert("ws-in", ??);
}
}
Listener on ws
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-in")
public void receiveMessage(CheckMessage response) {
}
}
Thanks!
your receive messages from "ws-in" with 2 consumers jmsTemplate.receiveSelectedAndConvert and WSListener !! message from a queue is consumed by one of the 2.
you send messages to "ws-out" and consume from "ws-in" ?? last queue
is empty and not receive any message, you have to send messages to
it
you need a valid selector to retrieve the message with receiveSelectedAndConvert based on JMSCorrelationID as the example you mntioned or the id received from the rest request but you need to add this id to the message headers like below
this.jmsTemplate.convertAndSend("ws-out", id, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
TextMessage tm = session.createTextMessage(new CheckMessage(id));
tm.setJMSCorrelationID(id);
return tm;
}
});
return jmsTemplate.receiveSelectedAndConvert("ws-in", "JMSCorrelationID='" + id+ "'");
forward messages from "ws-out" to "ws-in"
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-out")
public void receiveMessage(CheckMessage response) {
jmsTemplate.convertAndSend("ws-in", response);
}
}
I am trying to send error messages in Spring websockets with STOMP over SockJS.
I am basically trying to achieve which is being done here.
This is my Exception Handler
#MessageExceptionHandler
#SendToUser(value = "/queue/error",broadcast = false)
public ApplicationError handleException(Exception message) throws ApplicationError {
return new ApplicationError("test");
}
And I am subscribing to
stompClient.subscribe('/user/queue/error', stompErrorCallback, {token: accessToken});
User in my case is not authenticated, but from here
While user destinations generally imply an authenticated user, it
isn’t required strictly. A WebSocket session that is not associated
with an authenticated user can subscribe to a user destination. In
such cases the #SendToUser annotation will behave exactly the same as
with broadcast=false, i.e. targeting only the session that sent the
message being handled.
All this works fine when I am throwing this error from myHandler which is my Websocket Handler defined in websocket config.
I have a ClientInboundChannelInterceptor which extends ChannelInterceptorAdapter which intercepts all the messages in preSend.
In case of any exception in this interceptor, I want to throw it back to the user session which sent this message,
public class ClientInboundChannelInterceptor extends ChannelInterceptorAdapter {
#Autowired
#Lazy(value = true)
#Qualifier("brokerMessagingTemplate")
private SimpMessagingTemplate simpMessagingTemplate;
#Override
public Message<?> preSend(Message message, MessageChannel channel) throws IllegalArgumentException{
if(some thing goes wrong)
throw new RuntimeException();
}
#MessageExceptionHandler
#SendToUser(value = "/queue/error",broadcast = false)
public ApplicationError handleException(RuntimeException message) throws ApplicationError {
return new ApplicationError("test");
}
}
#MessageExceptionHandler does not catch this exception. So I tried sending it to the user directly using simpMessagingTemplate.
I basically want to do :
simpMessagingTemplate.convertAndSendToUser(SOMETHING,"/queue/error",e);
SOMETHING should be the correct username but user is not authenticated in my case, so I can't use headerAccessor.getUser().getName()
I have even tried with
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getHeader("","/queue/error",e, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, headerAccessor.getSessionId()));
but this is not working.
I have even tried headerAccessor.getSessionId() in the place of username, but that does not seem to work.
What is the correct way to do this?
What should I use as username in convertAndSendToUser?
My initial intuition was correct, sessionId is used as the username in case of unauthenticated user situations, but the problem was with headers.
After few hours of debugging through #SendToUser and simpMessagingTemplate.convertAndSendToUser(), I realised that if we use #SendToUser headers will be set automatically and we have to explicitly define the headers if we are using simpMessagingTemplate.convertAndSendToUser().
#SendToUser was setting two headers,
simpMessageType:SimpMessageType.MESSAGE,simpSessionId:sessionId
So I have tried adding the headers,
String sessionId = headerAccessor.getSessionId();
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("simpMessageType", SimpMessageType.MESSAGE);
headerMap.put("simpSessionId",sessionId);
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getSessionId(),"/queue/error",e,headerMap);
It did not work, I have tried giving the headers as MessageHeaders
String sessionId = headerAccessor.getSessionId();
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("simpMessageType", SimpMessageType.MESSAGE);
headerMap.put("simpSessionId",sessionId);
MessageHeaders headers = new MessageHeaders(headerMap);
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getSessionId(),"/queue/error",e,headers);
didn't work either.
After some more debugging I found out the correct way to set the headers, and probably this is the only way to create these headers(from SendToMethodReturnValueHandler.java).
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
So finally,
String sessionId = headerAccessor.getSessionId();
template.convertAndSendToUser(sessionId,"/queue/error","tesssssts",createHeaders(sessionId));
did the trick.
You can use convertAndSendToUser() only if that user is subscribed to the destination:
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
Where user can be just sessionId - headerAccessor.getSessionId()
The #MessageExceptionHandler does its work only withing #MessageMapping or #SubscribeMapping.
See SendToMethodReturnValueHandler source code for more info.
I'm using stomp.js over SockJS in my javascript client.
I'm connecting to websocket using
stompClient.connect({}, function (frame) {
stomp over sockJS connection has 2 http requests:
request to /info
http upgrade request
the client sends all cookies. I would like to also send custom headers (e.g. XSRF header) but didn't find a way to do that. Will appreciate any help.
#Rohitdev
So basically you can't send any HTTP headers using stompClient, because STOMP is layer over websockets, and only when websockets handshake happen we have possibility to send custom headers.
So only SockJS can send this headers, but for some reasons don't do this: https://github.com/sockjs/sockjs-client/issues/196
Custom headers:
stompClient.connect({token: "ABC123"}, function(frame) { ... code ...});
Without Custom headers:
stompClient.connect({}, function(frame) { ... code ...});
In Javascript, you can extract an STOMP header using:
username = frame.headers['user-name'];
In the server side, if you are using Spring Framework you can implementa an Interceptor to copy the HTTP parmeters to WebSockets STOMP headers.
public class HttpSessionHandshakeInterceptor_personalised implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// Set ip attribute to WebSocket session
attributes.put("ip", request.getRemoteAddress());
// ============================================= CODIGO PERSONAL
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
// httpServletRequest.getCookies();
// httpServletRequest.getParameter("inquiryId");
// httpServletRequest.getRemoteUser();
String token = httpServletRequest.getParameter("token");
...
}
}
And for send messages without STOMP parameters:
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chatchannel", {},
JSON.stringify({'from':from, 'text':text}));
}
and here you are passing parameters into the STOMP headers.
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chatchannel", {'token':'AA123'},
JSON.stringify({'from':from, 'text':text}));
}
Use #Header(name = "token") annotation inside the method if you are using Spring boot at the server.
Usage -
#Controller
public class SocketController {
static final String token = "1234";
#MessageMapping("/send")
#SendTo("/receive/changes")
public Object notify(MessageModel message, #Header(name = "token") String header)throws Exception {
if(!header.equals(token)) {
// return when headers do not match
return("Unauthorized");
}
// return the model object with associated sent message
return new MessageModel(message.getMessage());
}
}
You should have a MessageModel class with message variable and required getters, setters and contructor.
In frontend use Stomp
Usage -
function sendMessage() {
var text = document.getElementById('text').value;
stompClient.send("/send/message", {'token':'1234'},
JSON.stringify({'message':text}));
}
To add more security you an use CORS in Spring
You must use query as parameter instead to use Authorization in Header.
(?query=token_2222)
example: var socket = new SockJS('/ws?query=token_2222');
then read it in HandshakeInterceptor as Sergio wrote
SockJS JavaScript client does not support sending authorization header with a SockJS request.
Spring Java’s STOMP client allows to set headers for the handshake:
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);
Additional information: https://www.toptal.com/java/stomp-spring-boot-websocket
And you can find a lot of infоrmation about this point on Spring documentation:
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-authentication
Short conclusion: for applications using cookies, integration is very good (Spring Security and other).
For applications using JWT, possible options are:
1. Add as a request parameter and process in the implementation of DefaultHandshakeHandler
var socket = new SockJS('/our-websocket?query=token_2222');
2. OR add directly to the STOMP header of the message:
//add
var headers = {
login: 'mylogin',
passcode: 'mypasscode',
// additional header
'client-id': 'my-client-id'
};
stompClient.connect(headers, function (frame) {}
//Place of processing
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic")
.setRelayHost("127.0.0.1")
.setRelayPort(61613) //rabbitmq-plugins enable rabbitmq_stomp ; docker exec -it ID bash
.setClientLogin("guest")
.setClientPasscode("guest")
.setUserRegistryBroadcast("/topic/registry") //позволяет отправлять сообщеня всем приватные сообщения всем юзерам
;
registry.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/our-websocket")
.setHandshakeHandler(new UserHandshakeHandler())
.setAllowedOriginPatterns("*")
.withSockJS()
;
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// 1) Perform authentication here using standard authentication providers (managers).
//2) Install the user in case of successful authentication or throw an error
accessor.setUser(new UserPrincipal("TEST USER 2"));
}
return message;
}
});
}
}
Just wonder if anyone has experimented with WebSocket proxying (for transparent proxy) using embedded Jetty?
After about a day and a half playing with Jetty 9.1.2.v20140210, all I can tell is that it can't proxy WebSockets in its current form, and adding such support is non-trivial task (afaict at least).
Basically, Jetty ProxyServlet strips out the "Upgrade" and "Connection" header fields regardless of whether it's from a WebSocket handshake request. Adding these fields back is easy as shown below. But, when the proxied server returned a response with HTTP code 101 (switching protocols), no protocol upgrade is done on the proxy server. So, when the first WebSocket packet arrives, the HttpParser chokes and see that as a bad HTTP request.
If anyone already has a solution for it or is familiar with Jetty to suggest what to try, that would be very much appreciated.
Below is the code in my experiment stripping out the unimportant bits:
public class ProxyServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8888);
server.addConnector(connector);
// Setup proxy handler to handle CONNECT methods
ConnectHandler proxy = new ConnectHandler();
server.setHandler(proxy);
// Setup proxy servlet
ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
ServletHolder proxyServlet = new ServletHolder(MyProxyServlet.class);
context.addServlet(proxyServlet, "/*");
server.start();
}
}
#SuppressWarnings("serial")
public class MyProxyServlet extends ProxyServlet
{
#Override
protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
{
// Pass through the upgrade and connection header fields for websocket handshake request.
String upgradeValue = request.getHeader("Upgrade");
if (upgradeValue != null && upgradeValue.compareToIgnoreCase("websocket") == 0)
{
setHeader(proxyRequest, "Upgrade", upgradeValue);
setHeader(proxyRequest, "Connection", request.getHeader("Connection"));
}
}
#Override
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{
super.onResponseHeaders(request, response, proxyResponse);
// Restore the upgrade and connection header fields for websocket handshake request.
HttpFields fields = proxyResponse.getHeaders();
for (HttpField field : fields)
{
if (field.getName().compareToIgnoreCase("Upgrade") == 0)
{
String upgradeValue = field.getValue();
if (upgradeValue != null && upgradeValue.compareToIgnoreCase("websocket") == 0)
{
response.setHeader(field.getName(), upgradeValue);
for (HttpField searchField : fields)
{
if (searchField.getName().compareToIgnoreCase("Connection") == 0) {
response.setHeader(searchField.getName(), searchField.getValue());
}
}
}
}
}
}
}
Let's imagine the proxy scheme that you are trying to build, we have client A, server B and proxy P. Now let's walk through connect workflow:
A established TCP connection with proxy P (A-P)
A sends CONNECT addr(B) request with WebSocket handshake
Here you have the first problem, by HTTP RFC headers used in WS handshake are not end-to-end headers, because for HTTP they make sense only on a transport layer (between two hops).
P establishes TCP connection to B (P-B)
P sends WS handshake HTTP request to B
B responds with HTTP->WS upgrade (by sending 101)
And here is another problem, after sending HTTP 101 server B and client A now will communicate only over TCP, but jetty servlet doesn't support plain TCP packets propagation. In other words jetty proxy servlet waits till client A will start transmitting HTTP request, which will never happen after A will receive HTTP 101.
You would need to implement this by yourself using WS server and WS client.