use of io.micronaut.websocket.context.WebSocketBean in project micronaut-core by micronaut-projects.
the class AbstractNettyWebSocketHandler method callOpenMethod.
/**
* Calls the open method of the websocket bean.
*
* @param ctx THe handler context
*/
protected void callOpenMethod(ChannelHandlerContext ctx) {
if (session == null) {
return;
}
Optional<? extends MethodExecutionHandle<?, ?>> executionHandle = webSocketBean.openMethod();
if (executionHandle.isPresent()) {
MethodExecutionHandle<?, ?> openMethod = executionHandle.get();
BoundExecutable boundExecutable = null;
try {
boundExecutable = bindMethod(originatingRequest, webSocketBinder, openMethod, Collections.emptyList());
} catch (Throwable e) {
if (LOG.isErrorEnabled()) {
LOG.error("Error Binding method @OnOpen for WebSocket [" + webSocketBean + "]: " + e.getMessage(), e);
}
if (session.isOpen()) {
session.close(CloseReason.INTERNAL_ERROR);
}
}
if (boundExecutable != null) {
try {
BoundExecutable finalBoundExecutable = boundExecutable;
Object result = invokeExecutable(finalBoundExecutable, openMethod);
if (Publishers.isConvertibleToPublisher(result)) {
Flux<?> flowable = Flux.from(instrumentPublisher(ctx, result));
flowable.subscribe(o -> {
}, error -> {
if (LOG.isErrorEnabled()) {
LOG.error("Error Opening WebSocket [" + webSocketBean + "]: " + error.getMessage(), error);
}
if (session.isOpen()) {
session.close(CloseReason.INTERNAL_ERROR);
}
}, () -> {
});
}
} catch (Throwable e) {
forwardErrorToUser(ctx, t -> {
if (LOG.isErrorEnabled()) {
LOG.error("Error Opening WebSocket [" + webSocketBean + "]: " + t.getMessage(), t);
}
}, e);
// since we failed to call onOpen, we should always close here
if (session.isOpen()) {
session.close(CloseReason.INTERNAL_ERROR);
}
}
}
}
}
use of io.micronaut.websocket.context.WebSocketBean in project micronaut-core by micronaut-projects.
the class NettyServerWebSocketUpgradeHandler method handleHandshake.
/**
* Do the handshaking for WebSocket request.
*
* @param ctx The channel handler context
* @param req The request
* @param webSocketBean The web socket bean
* @param response The response
* @return The channel future
*/
protected ChannelFuture handleHandshake(ChannelHandlerContext ctx, NettyHttpRequest req, WebSocketBean<?> webSocketBean, MutableHttpResponse<?> response) {
int maxFramePayloadLength = webSocketBean.messageMethod().map(m -> m.intValue(OnMessage.class, "maxPayloadLength").orElse(65536)).orElse(65536);
String subprotocols = webSocketBean.getBeanDefinition().stringValue(ServerWebSocket.class, "subprotocols").filter(s -> !StringUtils.isEmpty(s)).orElse(null);
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketURL(ctx, req), subprotocols, true, maxFramePayloadLength);
handshaker = wsFactory.newHandshaker(req.getNativeRequest());
MutableHttpHeaders headers = response.getHeaders();
io.netty.handler.codec.http.HttpHeaders nettyHeaders;
if (headers instanceof NettyHttpHeaders) {
nettyHeaders = ((NettyHttpHeaders) headers).getNettyHeaders();
} else {
nettyHeaders = new DefaultHttpHeaders();
for (Map.Entry<String, List<String>> entry : headers) {
nettyHeaders.add(entry.getKey(), entry.getValue());
}
}
Channel channel = ctx.channel();
if (handshaker == null) {
return WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel);
} else {
return handshaker.handshake(channel, req.getNativeRequest(), nettyHeaders, channel.newPromise());
}
}
use of io.micronaut.websocket.context.WebSocketBean in project micronaut-core by micronaut-projects.
the class NettyServerWebSocketUpgradeHandler method channelRead0.
@Override
protected final void channelRead0(ChannelHandlerContext ctx, NettyHttpRequest<?> msg) {
ServerRequestContext.set(msg);
Optional<UriRouteMatch<Object, Object>> optionalRoute = router.find(HttpMethod.GET, msg.getUri().toString(), msg).filter(rm -> rm.isAnnotationPresent(OnMessage.class) || rm.isAnnotationPresent(OnOpen.class)).findFirst();
MutableHttpResponse<?> proceed = HttpResponse.ok();
AtomicReference<HttpRequest<?>> requestReference = new AtomicReference<>(msg);
Flux<MutableHttpResponse<?>> responsePublisher;
if (optionalRoute.isPresent()) {
UriRouteMatch<Object, Object> rm = optionalRoute.get();
msg.setAttribute(HttpAttributes.ROUTE_MATCH, rm);
msg.setAttribute(HttpAttributes.ROUTE_INFO, rm);
proceed.setAttribute(HttpAttributes.ROUTE_MATCH, rm);
proceed.setAttribute(HttpAttributes.ROUTE_INFO, rm);
responsePublisher = Flux.just(proceed);
} else {
responsePublisher = routeExecutor.onError(new HttpStatusException(HttpStatus.NOT_FOUND, "WebSocket Not Found"), msg);
}
Publisher<? extends MutableHttpResponse<?>> finalPublisher = routeExecutor.filterPublisher(requestReference, responsePublisher);
final Scheduler scheduler = Schedulers.fromExecutorService(ctx.channel().eventLoop());
Mono.from(finalPublisher).publishOn(scheduler).subscribeOn(scheduler).contextWrite(reactorContext -> reactorContext.put(ServerRequestContext.KEY, requestReference.get())).subscribe((Consumer<MutableHttpResponse<?>>) actualResponse -> {
if (actualResponse == proceed) {
UriRouteMatch routeMatch = actualResponse.getAttribute(HttpAttributes.ROUTE_MATCH, UriRouteMatch.class).get();
WebSocketBean<?> webSocketBean = webSocketBeanRegistry.getWebSocket(routeMatch.getTarget().getClass());
handleHandshake(ctx, msg, webSocketBean, actualResponse);
ChannelPipeline pipeline = ctx.pipeline();
try {
NettyServerWebSocketHandler webSocketHandler = new NettyServerWebSocketHandler(nettyEmbeddedServices, webSocketSessionRepository, handshaker, webSocketBean, msg, routeMatch, ctx, routeExecutor.getCoroutineHelper().orElse(null));
pipeline.addBefore(ctx.name(), NettyServerWebSocketHandler.ID, webSocketHandler);
pipeline.remove(ChannelPipelineCustomizer.HANDLER_HTTP_STREAM);
pipeline.remove(NettyServerWebSocketUpgradeHandler.this);
ChannelHandler accessLoggerHandler = pipeline.get(ChannelPipelineCustomizer.HANDLER_ACCESS_LOGGER);
if (accessLoggerHandler != null) {
pipeline.remove(accessLoggerHandler);
}
} catch (Throwable e) {
if (LOG.isErrorEnabled()) {
LOG.error("Error opening WebSocket: " + e.getMessage(), e);
}
ctx.writeAndFlush(new CloseWebSocketFrame(CloseReason.INTERNAL_ERROR.getCode(), CloseReason.INTERNAL_ERROR.getReason()));
}
} else {
ctx.writeAndFlush(actualResponse);
}
});
}
use of io.micronaut.websocket.context.WebSocketBean in project micronaut-core by micronaut-projects.
the class AbstractNettyWebSocketHandler method handleWebSocketFrame.
/**
* Handles WebSocket frame request.
*
* @param ctx The context
* @param msg The frame
*/
protected void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame msg) {
if (msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame || msg instanceof ContinuationWebSocketFrame) {
if (messageHandler == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("WebSocket bean [" + webSocketBean.getTarget() + "] received message, but defined no @OnMessage handler. Dropping frame...");
}
writeCloseFrameAndTerminate(ctx, CloseReason.UNSUPPORTED_DATA);
} else {
ByteBuf msgContent = msg.content().retain();
if (!msg.isFinalFragment()) {
frameBuffer.updateAndGet((buffer) -> {
if (buffer == null) {
buffer = ctx.alloc().compositeBuffer();
}
buffer.addComponent(true, msgContent);
return buffer;
});
return;
}
ByteBuf content;
CompositeByteBuf buffer = frameBuffer.getAndSet(null);
if (buffer == null) {
content = msgContent;
} else {
buffer.addComponent(true, msgContent);
content = buffer;
}
Argument<?> bodyArgument = this.getBodyArgument();
Optional<?> converted = ConversionService.SHARED.convert(content, bodyArgument);
content.release();
if (!converted.isPresent()) {
MediaType mediaType;
try {
mediaType = messageHandler.stringValue(Consumes.class).map(MediaType::of).orElse(MediaType.APPLICATION_JSON_TYPE);
} catch (IllegalArgumentException e) {
exceptionCaught(ctx, e);
return;
}
try {
converted = mediaTypeCodecRegistry.findCodec(mediaType).map(codec -> codec.decode(bodyArgument, new NettyByteBufferFactory(ctx.alloc()).wrap(msg.content())));
} catch (CodecException e) {
messageProcessingException(ctx, e);
return;
}
}
if (converted.isPresent()) {
Object v = converted.get();
NettyWebSocketSession currentSession = getSession();
ExecutableBinder<WebSocketState> executableBinder = new DefaultExecutableBinder<>(Collections.singletonMap(bodyArgument, v));
try {
BoundExecutable boundExecutable = executableBinder.bind(messageHandler.getExecutableMethod(), webSocketBinder, new WebSocketState(currentSession, originatingRequest));
Object result = invokeExecutable(boundExecutable, messageHandler);
if (Publishers.isConvertibleToPublisher(result)) {
Flux<?> flowable = Flux.from(instrumentPublisher(ctx, result));
flowable.subscribe(o -> {
}, error -> messageProcessingException(ctx, error), () -> messageHandled(ctx, session, v));
} else {
messageHandled(ctx, session, v);
}
} catch (Throwable e) {
messageProcessingException(ctx, e);
}
} else {
writeCloseFrameAndTerminate(ctx, CloseReason.UNSUPPORTED_DATA.getCode(), CloseReason.UNSUPPORTED_DATA.getReason() + ": " + "Received data cannot be converted to target type: " + bodyArgument);
}
}
} else if (msg instanceof PingWebSocketFrame) {
// respond with pong
PingWebSocketFrame frame = (PingWebSocketFrame) msg.retain();
ctx.writeAndFlush(new PongWebSocketFrame(frame.content()));
} else if (msg instanceof PongWebSocketFrame) {
if (pongHandler != null) {
ByteBuf content = msg.content();
WebSocketPongMessage message = new WebSocketPongMessage(NettyByteBufferFactory.DEFAULT.wrap(content));
NettyWebSocketSession currentSession = getSession();
ExecutableBinder<WebSocketState> executableBinder = new DefaultExecutableBinder<>(Collections.singletonMap(getPongArgument(), message));
try {
BoundExecutable boundExecutable = executableBinder.bind(pongHandler.getExecutableMethod(), webSocketBinder, new WebSocketState(currentSession, originatingRequest));
Object result = invokeExecutable(boundExecutable, pongHandler);
if (Publishers.isConvertibleToPublisher(result)) {
// delay the buffer release until the publisher has completed
content.retain();
Flux<?> flowable = Flux.from(instrumentPublisher(ctx, result));
flowable.subscribe(o -> {
}, error -> {
if (LOG.isErrorEnabled()) {
LOG.error("Error Processing WebSocket Pong Message [" + webSocketBean + "]: " + error.getMessage(), error);
}
exceptionCaught(ctx, error);
}, content::release);
}
} catch (Throwable e) {
if (LOG.isErrorEnabled()) {
LOG.error("Error Processing WebSocket Message [" + webSocketBean + "]: " + e.getMessage(), e);
}
exceptionCaught(ctx, e);
}
}
} else if (msg instanceof CloseWebSocketFrame) {
CloseWebSocketFrame cwsf = (CloseWebSocketFrame) msg;
handleCloseFrame(ctx, cwsf);
} else {
writeCloseFrameAndTerminate(ctx, CloseReason.UNSUPPORTED_DATA);
}
}
use of io.micronaut.websocket.context.WebSocketBean in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method connectWebSocket.
private <T> Flux<T> connectWebSocket(URI uri, MutableHttpRequest<?> request, Class<T> clientEndpointType, WebSocketBean<T> webSocketBean) {
Bootstrap bootstrap = this.bootstrap.clone();
if (webSocketBean == null) {
webSocketBean = webSocketRegistry.getWebSocket(clientEndpointType);
}
WebSocketBean<T> finalWebSocketBean = webSocketBean;
return Flux.create(emitter -> {
SslContext sslContext = buildSslContext(uri);
WebSocketVersion protocolVersion = finalWebSocketBean.getBeanDefinition().enumValue(ClientWebSocket.class, "version", WebSocketVersion.class).orElse(WebSocketVersion.V13);
int maxFramePayloadLength = finalWebSocketBean.messageMethod().map(m -> m.intValue(OnMessage.class, "maxPayloadLength").orElse(65536)).orElse(65536);
String subprotocol = finalWebSocketBean.getBeanDefinition().stringValue(ClientWebSocket.class, "subprotocol").orElse(StringUtils.EMPTY_STRING);
RequestKey requestKey;
try {
requestKey = new RequestKey(uri);
} catch (HttpClientException e) {
emitter.error(e);
return;
}
bootstrap.remoteAddress(requestKey.getHost(), requestKey.getPort());
initBootstrapForProxy(bootstrap, sslContext != null, requestKey.getHost(), requestKey.getPort());
bootstrap.handler(new HttpClientInitializer(sslContext, requestKey.getHost(), requestKey.getPort(), false, false, false, null) {
@Override
protected void addFinalHandler(ChannelPipeline pipeline) {
pipeline.remove(ChannelPipelineCustomizer.HANDLER_HTTP_DECODER);
ReadTimeoutHandler readTimeoutHandler = pipeline.get(ReadTimeoutHandler.class);
if (readTimeoutHandler != null) {
pipeline.remove(readTimeoutHandler);
}
Optional<Duration> readIdleTime = configuration.getReadIdleTimeout();
if (readIdleTime.isPresent()) {
Duration duration = readIdleTime.get();
if (!duration.isNegative()) {
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_IDLE_STATE, new IdleStateHandler(duration.toMillis(), duration.toMillis(), duration.toMillis(), TimeUnit.MILLISECONDS));
}
}
final NettyWebSocketClientHandler webSocketHandler;
try {
String scheme = (sslContext == null) ? "ws" : "wss";
URI webSocketURL = UriBuilder.of(uri).scheme(scheme).host(host).port(port).build();
MutableHttpHeaders headers = request.getHeaders();
HttpHeaders customHeaders = EmptyHttpHeaders.INSTANCE;
if (headers instanceof NettyHttpHeaders) {
customHeaders = ((NettyHttpHeaders) headers).getNettyHeaders();
}
if (StringUtils.isNotEmpty(subprotocol)) {
customHeaders.add("Sec-WebSocket-Protocol", subprotocol);
}
webSocketHandler = new NettyWebSocketClientHandler<>(request, finalWebSocketBean, WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, protocolVersion, subprotocol, true, customHeaders, maxFramePayloadLength), requestBinderRegistry, mediaTypeCodecRegistry, emitter);
pipeline.addLast(WebSocketClientCompressionHandler.INSTANCE);
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_MICRONAUT_WEBSOCKET_CLIENT, webSocketHandler);
} catch (Throwable e) {
emitter.error(new WebSocketSessionException("Error opening WebSocket client session: " + e.getMessage(), e));
}
}
});
addInstrumentedListener(bootstrap.connect(), future -> {
if (!future.isSuccess()) {
emitter.error(future.cause());
}
});
}, FluxSink.OverflowStrategy.ERROR);
}
Aggregations