Skip to content

Commit

Permalink
[SCB-1959] add rest invocation creator
Browse files Browse the repository at this point in the history
  • Loading branch information
wujimin committed May 30, 2020
1 parent 42816bc commit c0f6414
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.common.rest;

import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static org.apache.servicecomb.core.exception.ExceptionCodes.GENERIC_CLIENT;
import static org.apache.servicecomb.core.exception.ExceptionCodes.NOT_DEFINED_ANY_SCHEMA;

import java.util.Map;

import javax.annotation.Nonnull;
import javax.ws.rs.core.HttpHeaders;

import org.apache.commons.lang3.StringUtils;
import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessor;
import org.apache.servicecomb.common.rest.definition.RestOperationMeta;
import org.apache.servicecomb.common.rest.locator.OperationLocator;
import org.apache.servicecomb.common.rest.locator.ServicePathManager;
import org.apache.servicecomb.core.Const;
import org.apache.servicecomb.core.Endpoint;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.definition.MicroserviceMeta;
import org.apache.servicecomb.core.exception.Exceptions;
import org.apache.servicecomb.core.invocation.InvocationCreator;
import org.apache.servicecomb.core.invocation.InvocationFactory;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.vertx.core.json.Json;

public abstract class RestProducerInvocationCreator implements InvocationCreator {
private static final Logger LOGGER = LoggerFactory.getLogger(RestVertxProducerInvocationCreator.class);

protected final MicroserviceMeta microserviceMeta;

protected final Endpoint endpoint;

protected final HttpServletRequestEx requestEx;

protected final HttpServletResponseEx responseEx;

private RestOperationMeta restOperationMeta;

protected ProduceProcessor produceProcessor;

public RestProducerInvocationCreator(@Nonnull MicroserviceMeta microserviceMeta, @Nonnull Endpoint endpoint,
@Nonnull HttpServletRequestEx requestEx, @Nonnull HttpServletResponseEx responseEx) {
this.microserviceMeta = microserviceMeta;
this.endpoint = endpoint;
this.requestEx = requestEx;
this.responseEx = responseEx;
}

@Override
public Invocation create() {
initRestOperation();

Invocation invocation = InvocationFactory.forProvider(endpoint,
restOperationMeta.getOperationMeta(),
null);
initInvocationContext(invocation);

initProduceProcessor();
initTransportContext(invocation);

invocation.addLocalContext(RestConst.REST_REQUEST, requestEx);

return invocation;
}

protected void initInvocationContext(Invocation invocation) {
String strCseContext = requestEx.getHeader(Const.CSE_CONTEXT);
if (StringUtils.isEmpty(strCseContext)) {
return;
}

@SuppressWarnings("unchecked")
Map<String, String> invocationContext = Json.decodeValue(strCseContext, Map.class);
invocation.mergeContext(invocationContext);
}

protected abstract void initTransportContext(Invocation invocation);

private void initRestOperation() {
OperationLocator locator = locateOperation();
requestEx.setAttribute(RestConst.PATH_PARAMETERS, locator.getPathVarMap());
restOperationMeta = locator.getOperation();
}

private OperationLocator locateOperation() {
ServicePathManager servicePathManager = ServicePathManager.getServicePathManager(microserviceMeta);
if (servicePathManager == null) {
LOGGER.error("No schema defined for {}:{}.", microserviceMeta.getAppId(), microserviceMeta.getMicroserviceName());
throw Exceptions.create(NOT_FOUND, NOT_DEFINED_ANY_SCHEMA, NOT_FOUND.getReasonPhrase());
}

return servicePathManager.producerLocateOperation(requestEx.getRequestURI(), requestEx.getMethod());
}

private void initProduceProcessor() {
produceProcessor = restOperationMeta.ensureFindProduceProcessor(requestEx);
if (produceProcessor == null) {
LOGGER.error("Accept {} is not supported, operation={}.", requestEx.getHeader(HttpHeaders.ACCEPT),
restOperationMeta.getOperationMeta().getMicroserviceQualifiedName());

String msg = String.format("Accept %s is not supported", requestEx.getHeader(HttpHeaders.ACCEPT));
throw Exceptions.create(NOT_ACCEPTABLE, GENERIC_CLIENT, msg);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.common.rest;

import javax.annotation.Nonnull;

import org.apache.servicecomb.core.Endpoint;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.definition.MicroserviceMeta;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;

import io.vertx.ext.web.RoutingContext;

public class RestVertxProducerInvocationCreator extends RestProducerInvocationCreator {
private final RoutingContext routingContext;

public RestVertxProducerInvocationCreator(@Nonnull RoutingContext routingContext,
@Nonnull MicroserviceMeta microserviceMeta, @Nonnull Endpoint endpoint,
@Nonnull HttpServletRequestEx requestEx, @Nonnull HttpServletResponseEx responseEx) {
super(microserviceMeta, endpoint, requestEx, responseEx);
this.routingContext = routingContext;
}

@Override
protected void initTransportContext(Invocation invocation) {
VertxHttpTransportContext transportContext = new VertxHttpTransportContext(routingContext, requestEx, responseEx,
produceProcessor);
invocation.setTransportContext(transportContext);
routingContext.put(RestConst.REST_INVOCATION_CONTEXT, invocation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.servicecomb.common.rest;

import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;

import javax.ws.rs.core.HttpHeaders;

import org.apache.servicecomb.common.rest.definition.RestOperationMeta;
import org.apache.servicecomb.common.rest.locator.ServicePathManager;
import org.apache.servicecomb.config.ConfigUtil;
import org.apache.servicecomb.core.Const;
import org.apache.servicecomb.core.Endpoint;
import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.SCBEngine;
import org.apache.servicecomb.core.SCBStatus;
import org.apache.servicecomb.core.bootstrap.SCBBootstrap;
import org.apache.servicecomb.core.definition.MicroserviceMeta;
import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import io.vertx.core.json.Json;
import io.vertx.ext.web.RoutingContext;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Mocked;
import mockit.Verifications;

public class RestProducerInvocationCreatorTest {
@Injectable
RoutingContext routingContext;

@Injectable
MicroserviceMeta microserviceMeta;

@Injectable
ServicePathManager servicePathManager;

@Mocked
RestOperationMeta restOperationMeta;

@Injectable
Endpoint endpoint;

@Injectable
HttpServletRequestEx requestEx;

@Injectable
HttpServletResponseEx responseEx;

RestProducerInvocationCreator creator;

static SCBEngine engine;

@BeforeClass
public static void beforeClass() throws Exception {
ArchaiusUtils.resetConfig();
ConfigUtil.installDynamicConfig();

engine = SCBBootstrap.createSCBEngineForTest();
engine.setStatus(SCBStatus.UP);
}

@AfterClass
public static void afterClass() throws Exception {
engine.destroy();
ArchaiusUtils.resetConfig();
}

@Before
public void setUp() {
creator = new RestVertxProducerInvocationCreator(routingContext, microserviceMeta, endpoint,
requestEx, responseEx);
}

private void mockGetServicePathManager() {
mockGetServicePathManager(servicePathManager);
}

private void mockGetServicePathManager(final ServicePathManager servicePathManager) {
new Expectations(ServicePathManager.class) {
{
ServicePathManager.getServicePathManager(microserviceMeta);
result = servicePathManager;
}
};
}

@Test
public void should_failed_when_not_defined_any_schema() {
mockGetServicePathManager(null);

InvocationException throwable = (InvocationException) catchThrowable(() -> creator.create());
CommonExceptionData data = (CommonExceptionData) throwable.getErrorData();

assertThat(throwable.getStatusCode()).isEqualTo(NOT_FOUND.getStatusCode());
assertThat(Json.encode(data)).isEqualTo("{\"code\":\"SCB.0002\",\"message\":\"Not Found\"}");
}

@Test
public void should_failed_when_accept_is_not_support() {
mockGetServicePathManager();
new Expectations() {
{
requestEx.getHeader(HttpHeaders.ACCEPT);
result = "test-type";

restOperationMeta.ensureFindProduceProcessor(requestEx);
result = null;
}
};

InvocationException throwable = (InvocationException) catchThrowable(() -> creator.create());
CommonExceptionData data = (CommonExceptionData) throwable.getErrorData();

assertThat(throwable.getStatusCode()).isEqualTo(NOT_ACCEPTABLE.getStatusCode());
assertThat(Json.encode(data))
.isEqualTo("{\"code\":\"SCB.0000\",\"message\":\"Accept test-type is not supported\"}");
}

@Test
public void should_save_requestEx_in_invocation_context() {
mockGetServicePathManager();

Invocation invocation = creator.create();

Object request = invocation.getLocalContext(RestConst.REST_REQUEST);
assertThat(request).isSameAs(requestEx);
}

@Test
public void should_save_path_var_map_in_requestEx() {
mockGetServicePathManager();

creator.create();

new Verifications() {
{
requestEx.setAttribute(RestConst.PATH_PARAMETERS, any);
times = 1;
}
};
}

@Test
public void should_merge_invocation_context_from_request() {
mockGetServicePathManager();
new Expectations() {
{
requestEx.getHeader(Const.CSE_CONTEXT);
result = "{\"k\":\"v\"}";
}
};

Invocation invocation = creator.create();

assertThat(invocation.getContext("k")).isEqualTo("v");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

public interface ExceptionCodes {
String GENERIC_CLIENT = "SCB.0000";
String NOT_DEFINED_ANY_SCHEMA = "SCB.0002";

String GENERIC_SERVER = "SCB.5000";
}

0 comments on commit c0f6414

Please sign in to comment.