Java tutorial
/* * 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.dcp.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 DCP 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). * <p/> * All such services are exposed by DCP 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. * <p/> * This service provides an easy way to support pushing updates to a web client, such as observing needed services * or service factories for changes. * <p/> * Remote services may subscribe for updates using special web socket based API and these subscriptions are * automatically removed whenever web socket connection is closed. * <p/> * See {@link com.vmware.dcp.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<Long, Operation> pendingOperations = new ConcurrentHashMap<>(); private static class OperationResponse { long id; Map<String, String> responseHeaders; int statusCode; String responseJsonBody; } public WebSocketService(ChannelHandlerContext ctx, URI uri) { this.ctx = ctx; this.uri = uri; } 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<String, String> 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 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()); } } }); } }