Skip to content
This repository has been archived by the owner on May 30, 2022. It is now read-only.

Add swagger support for SOFARPC restful #131

Merged
merged 9 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions sofa-boot-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@
<optional>true</optional>
</dependency>

<!-- Swagger Dependency -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class SofaBootRpcProperties {
private String restMaxRequestSize;
private String restTelnet;
private String restDaemon;
private boolean restSwagger;
/* rest end */

/* dubbo start*/
Expand Down Expand Up @@ -571,4 +572,12 @@ public Environment getEnvironment() {
public void setEnvironment(Environment environment) {
this.environment = environment;
}

public boolean isRestSwagger() {
return restSwagger;
}

public void setRestSwagger(boolean restSwagger) {
this.restSwagger = restSwagger;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 com.alipay.sofa.rpc.boot.swagger;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

/**
* A swagger service to provide the swagger open API.
*
* @author khotyn
*/
@Path("swagger")
public interface SwaggerService {
@GET
@Path("openapi")
@Produces("application/json")
String openapi();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 com.alipay.sofa.rpc.boot.swagger;

import com.alipay.sofa.rpc.boot.runtime.param.RestBindingParam;
import com.alipay.sofa.runtime.api.aware.ClientFactoryAware;
import com.alipay.sofa.runtime.api.client.ClientFactory;
import com.alipay.sofa.runtime.api.client.ServiceClient;
import com.alipay.sofa.runtime.api.client.param.BindingParam;
import com.alipay.sofa.runtime.api.client.param.ServiceParam;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;

import java.util.ArrayList;
import java.util.List;

public class SwaggerServiceApplicationListener implements ApplicationListener<ApplicationStartedEvent>,
ClientFactoryAware {
private ClientFactory clientFactory;

@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
List<BindingParam> bindingParams = new ArrayList<>();
bindingParams.add(new RestBindingParam());

ServiceParam serviceParam = new ServiceParam();
serviceParam.setInterfaceType(SwaggerService.class);
serviceParam.setInstance(new SwaggerServiceImpl());
serviceParam.setBindingParams(bindingParams);

ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);
serviceClient.service(serviceParam);
}

@Override
public void setClientFactory(ClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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 com.alipay.sofa.rpc.boot.swagger;

import com.alipay.sofa.rpc.boot.runtime.binding.RpcBindingType;
import com.alipay.sofa.runtime.SofaFramework;
import com.alipay.sofa.runtime.service.component.ServiceComponent;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder;
import io.swagger.v3.oas.integration.GenericOpenApiContext;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.OpenApiContextLocator;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.integration.api.OpenApiContext;
import io.swagger.v3.oas.models.OpenAPI;

import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

/**
* @author khotyn
*/
public class SwaggerServiceImpl implements SwaggerService {
private OpenAPI openapi;
private Set<String> restfulServices;

@Override
public String openapi() {
if (openapi == null) {
synchronized (this) {
if (openapi == null) {
openapi = buildOpenApi();
}
}
} else {
if (!getAllRestfulService().equals(restfulServices)) {
synchronized (this) {
if (!getAllRestfulService().equals(restfulServices)) {
openapi = updateOpenApi();
}
}
}
}

return Json.pretty(openapi);

}

private OpenAPI updateOpenApi() {
OpenApiContext openApiContext = OpenApiContextLocator.getInstance().getOpenApiContext(
OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT);
if (openApiContext instanceof GenericOpenApiContext) {
restfulServices = getAllRestfulService();
SwaggerConfiguration oasConfig = new SwaggerConfiguration().resourceClasses(restfulServices);
((GenericOpenApiContext) openApiContext).getOpenApiScanner().setConfiguration(oasConfig);
try {
((GenericOpenApiContext) openApiContext).setCacheTTL(0);
return openApiContext.read();
} finally {
((GenericOpenApiContext) openApiContext).setCacheTTL(-1);
}
} else {
return null;
}
}

private OpenAPI buildOpenApi() {
try {
restfulServices = getAllRestfulService();
SwaggerConfiguration oasConfig = new SwaggerConfiguration().resourceClasses(restfulServices);

OpenApiContext oac = new JaxrsOpenApiContextBuilder()
.openApiConfiguration(oasConfig)
.buildContext(true);
return oac.read();
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
}

private Set<String> getAllRestfulService() {
return SofaFramework.getRuntimeSet().stream()
.map(srm -> srm.getComponentManager().getComponentInfosByType(ServiceComponent.SERVICE_COMPONENT_TYPE))
.flatMap(Collection::stream)
.collect(Collectors.toSet())
.stream()
.filter(ci -> {
ServiceComponent sc = (ServiceComponent) ci;
return sc.getService().getBinding(RpcBindingType.REST_BINDING_TYPE) != null;
})
.map(sc -> ((ServiceComponent) sc).getService().getInterfaceType())
.map(Class::getName).collect(Collectors.toSet());
}
}
13 changes: 13 additions & 0 deletions sofa-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
<artifactId>tomcat-embed-el</artifactId>
</dependency>

<!-- Swagger Dependency -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand All @@ -78,6 +86,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@
import com.alipay.sofa.rpc.boot.health.RpcAfterHealthCheckCallback;
import com.alipay.sofa.rpc.boot.runtime.adapter.helper.ConsumerConfigHelper;
import com.alipay.sofa.rpc.boot.runtime.adapter.helper.ProviderConfigHelper;
import com.alipay.sofa.rpc.boot.swagger.SwaggerServiceApplicationListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
Expand Down Expand Up @@ -141,6 +144,12 @@ public SofaBootRpcStartListener sofaBootRpcStartListener(
registryConfigContainer);
}

@Bean
@ConditionalOnProperty(name = "com.alipay.sofa.rpc.rest-swagger", havingValue = "true")
public ApplicationListener swaggerServiceApplicationListener() {
return new SwaggerServiceApplicationListener();
}

@Configuration
@ConditionalOnClass({ ReadinessCheckCallback.class })
public static class SofaModuleHealthCheckConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,28 @@
import com.alipay.sofa.rpc.boot.invoke.HelloFutureService;
import com.alipay.sofa.rpc.boot.invoke.HelloSyncService;
import com.alipay.sofa.rpc.boot.lazy.LazyService;
import com.alipay.sofa.rpc.boot.rest.AddService;
import com.alipay.sofa.rpc.boot.rest.RestService;
import com.alipay.sofa.rpc.boot.retry.RetriesService;
import com.alipay.sofa.rpc.boot.retry.RetriesServiceImpl;
import com.alipay.sofa.rpc.boot.runtime.param.RestBindingParam;
import com.alipay.sofa.rpc.boot.threadpool.ThreadPoolService;
import com.alipay.sofa.rpc.config.ConsumerConfig;
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
import com.alipay.sofa.runtime.api.annotation.SofaClientFactory;
import com.alipay.sofa.runtime.api.annotation.SofaReference;
import com.alipay.sofa.runtime.api.annotation.SofaReferenceBinding;
import com.alipay.sofa.runtime.api.client.ClientFactory;
import com.alipay.sofa.runtime.api.client.ServiceClient;
import com.alipay.sofa.runtime.api.client.param.BindingParam;
import com.alipay.sofa.runtime.api.client.param.ServiceParam;
import com.alipay.sofa.runtime.spi.binding.Binding;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -51,11 +64,14 @@
import org.springframework.context.annotation.ImportResource;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

@SpringBootApplication
@SpringBootTest
@SpringBootTest(properties = "com.alipay.sofa.rpc.rest-swagger=true")
@RunWith(SpringRunner.class)
@ImportResource("classpath*:spring/test_all.xml")
public class SofaBootRpcAllTest {
Expand Down Expand Up @@ -109,12 +125,15 @@ public class SofaBootRpcAllTest {
private AnnotationService annotationService;

@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", serializeType = "protobuf"),
jvmFirst = false)
jvmFirst = false, uniqueId = "pb")
private AnnotationService annotationServicePb;

@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", loadBalancer = "roundRobin"), uniqueId = "loadbalancer")
private AnnotationService annotationLoadBalancerService;

@SofaClientFactory
private ClientFactory clientFactory;

@Test
public void testInvoke() throws InterruptedException {
Assert.assertEquals("sync", helloSyncService.saySync("sync"));
Expand Down Expand Up @@ -224,4 +243,33 @@ public void testLoadBalancerAnnotation() throws NoSuchFieldException, IllegalAcc

Assert.assertTrue("Found roundrobin reference", found);
}

@Test
public void testRestSwagger() throws IOException {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpUriRequest request = new HttpGet("http://localhost:8341/swagger/openapi");
HttpResponse response = httpClient.execute(request);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertTrue(EntityUtils.toString(response.getEntity()).contains("/webapi/restService"));
}

@Test
public void testRestSwaggerAddService() throws IOException {
List<BindingParam> bindingParams = new ArrayList<>();
bindingParams.add(new RestBindingParam());

ServiceParam serviceParam = new ServiceParam();
serviceParam.setInterfaceType(AddService.class);
serviceParam.setInstance((AddService) () -> "Hello");
serviceParam.setBindingParams(bindingParams);

ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);
serviceClient.service(serviceParam);

HttpClient httpClient = HttpClientBuilder.create().build();
HttpUriRequest request = new HttpGet("http://localhost:8341/swagger/openapi");
HttpResponse response = httpClient.execute(request);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertTrue(EntityUtils.toString(response.getEntity()).contains("/webapi/add_service"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.springframework.stereotype.Component;

@Component
@SofaService(bindings = { @SofaServiceBinding(bindingType = "bolt", serializeType = "protobuf") })
@SofaService(bindings = { @SofaServiceBinding(bindingType = "bolt", serializeType = "protobuf") }, uniqueId = "pb")
public class AnnotationServicePbImpl implements AnnotationService {
@Override
public String hello() {
Expand Down
Loading