Improve handling of ws agent started event

Add checking on ready state for websocket connection.
Make it possible rout org.eclipse.che.api.core.rest.ApiInfoService on wsAgent form MachineExtensionProxyServlet.
Renamee package in assembly-machine-war to more accurate for it

Signed-off-by: Vitaly Parfonov <vparfonov@codenvy.com>
6.19.x
Vitaly Parfonov 2016-02-26 16:27:32 +02:00
parent d98b7b94e1
commit f8c94e6b27
12 changed files with 228 additions and 57 deletions

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import com.google.common.base.Strings;

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import com.google.common.collect.ImmutableMap;
import com.google.inject.servlet.ServletModule;

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import javax.inject.Inject;
import javax.inject.Named;

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@ -101,7 +101,7 @@ public class MachineModule extends AbstractModule {
bind(WSocketEventBusClient.class).asEagerSingleton();
bind(String.class).annotatedWith(Names.named("event.bus.url")).toProvider(EventBusURLProvider.class);
bind(org.eclipse.che.ide.ext.java.server.ApiEndpointAccessibilityChecker.class);
bind(ApiEndpointAccessibilityChecker.class);
}
//it's need for WSocketEventBusClient and in the future will be replaced with the property

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import javax.inject.Provider;
import java.net.URI;

View File

@ -8,7 +8,7 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.server;
package org.eclipse.che.wsagent.server;
import com.google.inject.Provider;

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.rest;
/**
* Describe information available service like fqn of class and path.
*
* @author Vitalii Parfonov
*/
public class RestServiceInfo {
private String fqn;
private String regex;
private String path;
public RestServiceInfo(String fqn, String regex, String path) {
this.fqn = fqn;
this.regex = regex;
this.path = path;
}
/**
* FQN of REST service class
* @return fqn
*/
public String getFqn() {
return fqn;
}
/**
* Regular expressions for URI pattern.
* @return
*/
public String getRegex() {
return regex;
}
/**
* Describe the Path annotation, see {@link javax.ws.rs.Path}
* @return
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "RestServiceInfo{" +
"fqn='" + fqn + '\'' +
", regex='" + regex + '\'' +
", path='" + path + '\'' +
'}';
}
}

View File

@ -10,28 +10,39 @@
*******************************************************************************/
package org.eclipse.che.api.machine.gwt.client;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.api.machine.gwt.client.events.WsAgentStateEvent;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper;
import org.eclipse.che.ide.rest.AsyncRequestCallback;
import org.eclipse.che.ide.rest.AsyncRequestFactory;
import org.eclipse.che.ide.rest.RestServiceInfo;
import org.eclipse.che.ide.rest.StringUnmarshaller;
import org.eclipse.che.ide.ui.loaders.initialization.InitialLoadingInfo;
import org.eclipse.che.ide.ui.loaders.initialization.LoaderPresenter;
import org.eclipse.che.ide.util.loging.Log;
import org.eclipse.che.ide.websocket.MessageBus;
import org.eclipse.che.ide.websocket.MessageBusProvider;
import org.eclipse.che.ide.websocket.WebSocket;
import org.eclipse.che.ide.websocket.events.ConnectionClosedHandler;
import org.eclipse.che.ide.websocket.events.ConnectionErrorHandler;
import org.eclipse.che.ide.websocket.events.ConnectionOpenedHandler;
import org.eclipse.che.ide.websocket.events.WebSocketClosedEvent;
import java.util.ArrayList;
import java.util.List;
import static org.eclipse.che.api.machine.gwt.client.WsAgentState.STARTED;
import static org.eclipse.che.api.machine.gwt.client.WsAgentState.STOPPED;
import static org.eclipse.che.ide.ui.loaders.initialization.InitialLoadingInfo.Operations.WS_AGENT_BOOTING;
import static org.eclipse.che.ide.ui.loaders.initialization.OperationInfo.Status.ERROR;
import static org.eclipse.che.ide.ui.loaders.initialization.OperationInfo.Status.IN_PROGRESS;
import static org.eclipse.che.ide.ui.loaders.initialization.OperationInfo.Status.SUCCESS;
@ -42,72 +53,88 @@ import static org.eclipse.che.ide.ui.loaders.initialization.OperationInfo.Status
@Singleton
public class WsAgentStateController implements ConnectionOpenedHandler, ConnectionClosedHandler, ConnectionErrorHandler {
private final Timer retryConnectionTimer;
private final EventBus eventBus;
private final MessageBusProvider messageBusProvider;
private final InitialLoadingInfo initialLoadingInfo;
private final LoaderPresenter loader;
private final EventBus eventBus;
private final MessageBusProvider messageBusProvider;
private final InitialLoadingInfo initialLoadingInfo;
private final LoaderPresenter loader;
private final AsyncRequestFactory asyncRequestFactory;
private final String extPath;
private MessageBus messageBus;
private WsAgentState state;
private String wsUrl;
private int countRetry;
private AsyncCallback<MessageBus> messageBusCallback;
private WebSocket testConnection; //use it only for testing state of ext-service
//not used now added it for future if it we will have possibility check that service available for client call
private final List<RestServiceInfo> availableServices;
private MessageBus messageBus;
private WsAgentState state;
private String wsUrl;
private String wsId;
private AsyncCallback<MessageBus> messageBusCallback;
@Inject
public WsAgentStateController(EventBus eventBus,
LoaderPresenter loader,
MessageBusProvider messageBusProvider,
AsyncRequestFactory asyncRequestFactory,
@Named("cheExtensionPath") String extPath,
InitialLoadingInfo initialLoadingInfo) {
this.loader = loader;
this.eventBus = eventBus;
this.messageBusProvider = messageBusProvider;
this.asyncRequestFactory = asyncRequestFactory;
this.extPath = extPath;
this.initialLoadingInfo = initialLoadingInfo;
retryConnectionTimer = new Timer() {
@Override
public void run() {
connect();
countRetry--;
}
};
availableServices = new ArrayList<>();
}
public void initialize(String wsUrl) {
this.wsUrl = wsUrl;
this.countRetry = 50;
this.state = WsAgentState.STOPPED;
public void initialize(String wsUrl, String wsId) {
this.wsUrl = wsUrl + "/" + wsId;
this.wsId = wsId;
this.state = STOPPED;
initialLoadingInfo.setOperationStatus(WS_AGENT_BOOTING.getValue(), IN_PROGRESS);
connect();
checkHttpConnection();
}
@Override
public void onClose(WebSocketClosedEvent event) {
Log.info(getClass(), "Test WS connection closed with code " + event.getCode() + " reason: " + event.getReason() +
" workspace agent started well ");
Log.info(getClass(), "Test WS connection closed with code " + event.getCode() + " reason: " + event.getReason());
if (state.equals(STARTED)) {
state = STOPPED;
eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent());
}
}
@Override
public void onError() {
if (countRetry > 0) {
retryConnectionTimer.schedule(1000);
} else {
state = WsAgentState.STOPPED;
initialLoadingInfo.setOperationStatus(WS_AGENT_BOOTING.getValue(), ERROR);
loader.hide();
Log.info(getClass(), "Test WS connection error");
if (state.equals(STARTED)) {
state = STOPPED;
eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent());
}
}
@Override
public void onOpen() {
testConnection.close(); //close testing connection now we know ws-agent already start
messageBus = messageBusProvider.createMachineMessageBus(wsUrl);
state = WsAgentState.STARTED;
messageBus.removeOnOpenHandler(this);
MessageBus.ReadyState readyState = messageBus.getReadyState();
Log.info(getClass(), readyState.toString());
//need to make sure ready state equals 1 (OPEN) in same situations after opening it still equals 0 (CONNECTING)
if (!readyState.equals(MessageBus.ReadyState.OPEN)) {
new Timer() {
@Override
public void run() {
Log.info(getClass(), messageBus.getReadyState());
if (messageBus.getReadyState().equals(MessageBus.ReadyState.OPEN)) {
cancel();
started();
}
}
}.scheduleRepeating(100);
} else {
started();
}
}
private void started() {
state = STARTED;
initialLoadingInfo.setOperationStatus(WS_AGENT_BOOTING.getValue(), SUCCESS);
loader.hide();
@ -117,6 +144,8 @@ public class WsAgentStateController implements ConnectionOpenedHandler, Connecti
eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent());
}
public WsAgentState getState() {
return state;
}
@ -134,10 +163,48 @@ public class WsAgentStateController implements ConnectionOpenedHandler, Connecti
});
}
private void connect() {
testConnection = WebSocket.create(wsUrl);
testConnection.setOnOpenHandler(this);
testConnection.setOnCloseHandler(this);
testConnection.setOnErrorHandler(this);
/**
* Goto checking HTTP connection via getting all registered REST Services
*/
private void checkHttpConnection() {
asyncRequestFactory.createGetRequest(extPath + "/" + wsId + "/").send(new AsyncRequestCallback<String>(new StringUnmarshaller()) {
@Override
protected void onSuccess(String result) {
JSONObject object = JSONParser.parseStrict(result).isObject();
if (object.containsKey("rootResources")) {
JSONArray rootResources = object.get("rootResources").isArray();
for (int i = 0; i < rootResources.size(); i++) {
JSONObject rootResource = rootResources.get(i).isObject();
String regex = rootResource.get("regex").isString().stringValue();
String fqn = rootResource.get("fqn").isString().stringValue();
String path = rootResource.get("path").isString().stringValue();
availableServices.add(new RestServiceInfo(fqn,regex,path));
}
checkWsConnection();
}
}
@Override
protected void onFailure(Throwable exception) {
Log.error(getClass(), exception.getMessage());
new Timer() {
@Override
public void run() {
checkHttpConnection();
}
}.schedule(1000);
}
});
}
/**
* Try to connect via WebSocket connection
*/
private void checkWsConnection() {
messageBus = messageBusProvider.createMachineMessageBus(wsUrl);
messageBus.addOnCloseHandler(this);
messageBus.addOnCloseHandler(this);
messageBus.addOnOpenHandler(this);
}
}

View File

@ -47,7 +47,8 @@ import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
@Singleton
public class MachineExtensionProxyServlet extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(MachineExtensionProxyServlet.class);
private static final Pattern EXTENSION_API_URI = Pattern.compile(".*?/ext/[^/]+/(?<workspaceId>[^/]+)/?.*");
private static final String WORKSPACE_ID_PATTERN = "([^/]+)";
private static final Pattern EXTENSION_API_URI = Pattern.compile(".*?/ext/([^/]+/" + WORKSPACE_ID_PATTERN + "/?.*|" + WORKSPACE_ID_PATTERN + "/)");
private final int extServicesPort;
private final MachineManager machineManager;
@ -119,7 +120,7 @@ public class MachineExtensionProxyServlet extends HttpServlet {
String workspaceId;
final Matcher matcher = EXTENSION_API_URI.matcher(req.getRequestURI());
if (matcher.matches()) {
workspaceId = matcher.group("workspaceId");
workspaceId = matcher.group(2) != null ? matcher.group(2) : matcher.group(3);
} else {
throw new NotFoundException("No workspace id is found in request.");
}
@ -131,9 +132,21 @@ public class MachineExtensionProxyServlet extends HttpServlet {
}
final UriBuilder uriBuilder = UriBuilder.fromUri(server.getUrl())
.replacePath(req.getRequestURI())
.replaceQuery(req.getQueryString());
//check contain original url path to service before wsId
//if yes we just change host and port and forward to the ws-agent
//if not we need to cut wsId from URL
if (matcher.group(2) == null) {
// here we cut workspaceId from extension API Url for getting access to org.eclipse.che.api.core.rest.ApiInfoService
// e.g localhost:8080/ide/ext/{wsid} -> localhost:{extServicesPort}/ide/ext/
String originUri = req.getRequestURI();
final int indexOf = originUri.lastIndexOf('/', originUri.length() - 2);
uriBuilder.replacePath(originUri.substring(0, indexOf + 1));
} else {
uriBuilder.replacePath(req.getRequestURI());
}
return uriBuilder.build().toString();
}

View File

@ -219,6 +219,32 @@ public class MachineExtensionProxyServletTest {
}
}
@Test
public void shouldBeAbleToProxyWithout() throws Exception {
final String path = "/api/ext/" + WORKSPACE_ID + "/";
final String defaultContextPath = contextHandler.getContextPath();
try {
contextHandler.setContextPath("/api/ext/");
MockHttpServletRequest mockRequest =
new MockHttpServletRequest(PROXY_ENDPOINT + path,
new ByteArrayInputStream(new byte[0]),
0,
"GET",
defaultHeaders);
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
proxyServlet.service(mockRequest, mockResponse);
assertEquals(mockResponse.getStatus(), 200);
assertEquals(extensionApiRequest.uri, "/api/ext/");
} finally {
contextHandler.setContextPath(defaultContextPath);
}
}
@Test
public void shouldProxyWithQueryString() throws Exception {
final String query = "key1=value1&key2=value2&key2=value3";

View File

@ -281,7 +281,7 @@ public class MachineManagerImpl implements MachineManager, WorkspaceStoppedHandl
appContext.setDevMachineId(machineId);
appContext.setProjectsRoot(machineDto.getRuntime().projectsRoot());
devMachine = entityFactory.createMachine(machineDto);
wsAgentStateController.initialize(devMachine.getWsServerExtensionsUrl() + "/" + appContext.getWorkspaceId());
wsAgentStateController.initialize(devMachine.getWsServerExtensionsUrl(), appContext.getWorkspaceId());
}
});
}