com.vmware.xenon.common.WebSocketService Maven / Gradle / Ivy
/*
* Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, without warranties or
* conditions of any kind, EITHER EXPRESS OR IMPLIED. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.vmware.xenon.common;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
/**
* A service which allow to attach remote services to a node via WebSocket connection. For every attached service
* actual service implementation lives on a remote client attached to a not via WebSocket (for example, in a web
* browser).
*
* All such services are exposed by a Xenon node as /core/ws-service/{some-uuid}. Service links are always generated by
* the node. When a request to a such remote service comes in, it is forwarded to client over a WebSocket and its
* response is forwarded to original caller. Hence these services have a real link and remote implementation body.
*
* This service provides an easy way to support pushing updates to a web client, such as observing needed services
* or service factories for changes.
*
* Remote services may subscribe for updates using special web socket based API and these subscriptions are
* automatically removed whenever web socket connection is closed.
*
* See {@link com.vmware.xenon.common.http.netty.NettyHttpClientRequestHandler} for more details on WebSocket interaction
* protocol.
*/
public final class WebSocketService extends StatelessService {
private final ChannelHandlerContext ctx;
private final URI uri;
private Map pendingOperations = new ConcurrentHashMap<>();
private static class OperationResponse {
long id;
Map responseHeaders;
int statusCode;
String responseJsonBody;
}
public WebSocketService(ChannelHandlerContext ctx, URI uri) {
this.ctx = ctx;
this.uri = uri;
super.toggleOption(ServiceOption.HTML_USER_INTERFACE, true);
}
public void handleWebSocketMessage(String body) {
OperationResponse or = Utils.fromJson(body, OperationResponse.class);
Operation op = this.pendingOperations.remove(or.id);
if (op == null) {
logFine("Unknown operation id: %d", or.id);
return;
}
for (Map.Entry entry : or.responseHeaders.entrySet()) {
op.addResponseHeader(entry.getKey(), entry.getValue());
}
op.setStatusCode(or.statusCode);
if (or.responseJsonBody != null) {
op.setContentType(Operation.MEDIA_TYPE_APPLICATION_JSON);
op.setBodyNoCloning(or.responseJsonBody);
}
op.complete();
}
@Override
public void authorizeRequest(Operation op) {
op.complete();
}
@Override
public void handleRequest(Operation op) {
prepareRequest(op);
Operation.SerializedOperation serializedOperation = Operation.SerializedOperation
.create(op);
this.pendingOperations.put(op.getId(), op);
ChannelFuture promise = this.ctx.writeAndFlush(new TextWebSocketFrame("POST "
+ this.uri.toString() + Operation.CR_LF
+ Utils.toJson(serializedOperation)));
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
op.fail(future.cause());
WebSocketService.this.pendingOperations.remove(op.getId());
}
}
});
}
}