Update test cases for Sentinel web filter and gRPC adapter
Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
parent
3de817be10
commit
695fa9d84c
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.adapter.grpc;
|
||||
|
||||
import io.grpc.Server;
|
||||
|
|
@ -6,18 +21,18 @@ import io.grpc.ServerBuilder;
|
|||
import java.io.IOException;
|
||||
|
||||
class GrpcTestServer {
|
||||
|
||||
private Server server;
|
||||
|
||||
GrpcTestServer() {
|
||||
}
|
||||
GrpcTestServer() {}
|
||||
|
||||
void start(int port, boolean shouldintercept) throws IOException {
|
||||
void start(int port, boolean shouldIntercept) throws IOException {
|
||||
if (server != null) {
|
||||
throw new IllegalStateException("Server already running!");
|
||||
}
|
||||
ServerBuilder<?> serverBuild = ServerBuilder.forPort(port)
|
||||
.addService(new FooServiceImpl());
|
||||
if (shouldintercept) {
|
||||
.addService(new FooServiceImpl());
|
||||
if (shouldIntercept) {
|
||||
serverBuild.intercept(new SentinelGrpcServerInterceptor());
|
||||
}
|
||||
server = serverBuild.build();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
|||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -41,9 +43,9 @@ public class SentinelGrpcClientInterceptorTest {
|
|||
private final int threshold = 2;
|
||||
private final GrpcTestServer server = new GrpcTestServer();
|
||||
|
||||
private void configureFlowRule() {
|
||||
private void configureFlowRule(int count) {
|
||||
FlowRule rule = new FlowRule()
|
||||
.setCount(threshold)
|
||||
.setCount(count)
|
||||
.setGrade(RuleConstant.FLOW_GRADE_QPS)
|
||||
.setResource(resourceName)
|
||||
.setLimitApp("default")
|
||||
|
|
@ -51,42 +53,44 @@ public class SentinelGrpcClientInterceptorTest {
|
|||
FlowRuleManager.loadRules(Collections.singletonList(rule));
|
||||
}
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
public void testGrpcClientInterceptor() throws Exception {
|
||||
final int port = 19328;
|
||||
|
||||
configureFlowRule();
|
||||
configureFlowRule(threshold);
|
||||
server.start(port, false);
|
||||
|
||||
FooServiceClient client = new FooServiceClient("localhost", port, new SentinelGrpcClientInterceptor());
|
||||
final int total = 8;
|
||||
for (int i = 0; i < total; i++) {
|
||||
sendRequest(client);
|
||||
}
|
||||
|
||||
assertTrue(sendRequest(client));
|
||||
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT);
|
||||
assertNotNull(clusterNode);
|
||||
assertEquals(1, clusterNode.passQps());
|
||||
|
||||
assertEquals((total - threshold) / 2, clusterNode.blockRequest());
|
||||
assertEquals(total / 2, clusterNode.totalRequest());
|
||||
// Not allowed to pass.
|
||||
configureFlowRule(0);
|
||||
|
||||
long totalQps = clusterNode.totalQps();
|
||||
long passQps = clusterNode.passQps();
|
||||
long blockQps = clusterNode.blockQps();
|
||||
assertEquals(total, totalQps);
|
||||
assertEquals(total - threshold, blockQps);
|
||||
assertEquals(threshold, passQps);
|
||||
// The second request will be blocked.
|
||||
assertFalse(sendRequest(client));
|
||||
assertEquals(1, clusterNode.blockQps());
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private void sendRequest(FooServiceClient client) {
|
||||
private boolean sendRequest(FooServiceClient client) {
|
||||
try {
|
||||
FooResponse response = client.sayHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build());
|
||||
System.out.println(ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT).avgRt());
|
||||
System.out.println("Response: " + response);
|
||||
return true;
|
||||
} catch (StatusRuntimeException ex) {
|
||||
System.out.println("Blocked, cause: " + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
FlowRuleManager.loadRules(null);
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
|||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -43,9 +45,9 @@ public class SentinelGrpcServerInterceptorTest {
|
|||
|
||||
private FooServiceClient client;
|
||||
|
||||
private void configureFlowRule() {
|
||||
private void configureFlowRule(int count) {
|
||||
FlowRule rule = new FlowRule()
|
||||
.setCount(threshold)
|
||||
.setCount(count)
|
||||
.setGrade(RuleConstant.FLOW_GRADE_QPS)
|
||||
.setResource(resourceName)
|
||||
.setLimitApp("default")
|
||||
|
|
@ -53,41 +55,44 @@ public class SentinelGrpcServerInterceptorTest {
|
|||
FlowRuleManager.loadRules(Collections.singletonList(rule));
|
||||
}
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
public void testGrpcServerInterceptor() throws Exception {
|
||||
final int port = 19329;
|
||||
client = new FooServiceClient("localhost", port);
|
||||
|
||||
configureFlowRule();
|
||||
configureFlowRule(threshold);
|
||||
server.start(port, true);
|
||||
|
||||
final int total = 8;
|
||||
for (int i = 0; i < total; i++) {
|
||||
sendRequest();
|
||||
}
|
||||
assertTrue(sendRequest());
|
||||
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.IN);
|
||||
assertNotNull(clusterNode);
|
||||
assertEquals(1, clusterNode.passQps());
|
||||
|
||||
assertEquals((total - threshold) / 2, clusterNode.blockRequest());
|
||||
assertEquals(total / 2, clusterNode.totalRequest());
|
||||
// Not allowed to pass.
|
||||
configureFlowRule(0);
|
||||
|
||||
long totalQps = clusterNode.totalQps();
|
||||
long passQps = clusterNode.passQps();
|
||||
long blockQps = clusterNode.blockQps();
|
||||
assertEquals(total, totalQps);
|
||||
assertEquals(total - threshold, blockQps);
|
||||
assertEquals(threshold, passQps);
|
||||
// The second request will be blocked.
|
||||
assertFalse(sendRequest());
|
||||
assertEquals(1, clusterNode.blockQps());
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private void sendRequest() {
|
||||
private boolean sendRequest() {
|
||||
try {
|
||||
FooResponse response = client.anotherHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build());
|
||||
System.out.println("Response: " + response);
|
||||
return true;
|
||||
} catch (StatusRuntimeException ex) {
|
||||
System.out.println("Blocked, cause: " + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
FlowRuleManager.loadRules(null);
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -28,5 +28,23 @@
|
|||
<version>${servlet.api.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>1.5.17.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>1.5.17.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -57,8 +57,7 @@ public class WebCallbackManager {
|
|||
return requestOriginParser;
|
||||
}
|
||||
|
||||
public static void setRequestOriginParser(
|
||||
RequestOriginParser requestOriginParser) {
|
||||
public static void setRequestOriginParser(RequestOriginParser requestOriginParser) {
|
||||
WebCallbackManager.requestOriginParser = requestOriginParser;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public final class FilterUtil {
|
|||
url.append("?").append(request.getQueryString());
|
||||
}
|
||||
|
||||
if (StringUtil.isEmpty(WebServletConfig.getBlockPage())) {
|
||||
if (StringUtil.isBlank(WebServletConfig.getBlockPage())) {
|
||||
writeDefaultBlockedPage(response);
|
||||
} else {
|
||||
String redirectUrl = WebServletConfig.getBlockPage() + "?http_referer=" + url.toString();
|
||||
|
|
@ -75,7 +75,7 @@ public final class FilterUtil {
|
|||
|
||||
private static void writeDefaultBlockedPage(HttpServletResponse response) throws IOException {
|
||||
PrintWriter out = response.getWriter();
|
||||
out.println("Blocked by Sentinel (flow limiting)");
|
||||
out.print(DEFAULT_BLOCK_MSG);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
|
@ -183,5 +183,7 @@ public final class FilterUtil {
|
|||
return i;
|
||||
}
|
||||
|
||||
public static final String DEFAULT_BLOCK_MSG = "Blocked by Sentinel (flow limiting)";
|
||||
|
||||
private FilterUtil() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.adapter.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.callback.DefaultUrlCleaner;
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
|
||||
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
|
||||
import com.alibaba.csp.sentinel.node.ClusterNode;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = TestApplication.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class CommonFilterTest {
|
||||
|
||||
private static final String HELLO_STR = "Hello!";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
private void configureRulesFor(String resource, int count) {
|
||||
configureRulesFor(resource, count, "default");
|
||||
}
|
||||
|
||||
private void configureRulesFor(String resource, int count, String limitApp) {
|
||||
FlowRule rule = new FlowRule()
|
||||
.setCount(count)
|
||||
.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
rule.setResource(resource);
|
||||
if (StringUtil.isNotBlank(limitApp)) {
|
||||
rule.setLimitApp(limitApp);
|
||||
}
|
||||
FlowRuleManager.loadRules(Collections.singletonList(rule));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommonFilterMiscellaneous() throws Exception {
|
||||
String url = "/hello";
|
||||
this.mvc.perform(get(url))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(HELLO_STR));
|
||||
|
||||
ClusterNode cn = ClusterBuilderSlot.getClusterNode(url);
|
||||
assertNotNull(cn);
|
||||
assertEquals(1, cn.passQps());
|
||||
|
||||
testCommonBlockAndRedirectBlockPage(url, cn);
|
||||
|
||||
// Test for url cleaner.
|
||||
testUrlCleaner();
|
||||
|
||||
testCustomOriginParser();
|
||||
}
|
||||
|
||||
private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cn) throws Exception {
|
||||
configureRulesFor(url, 0);
|
||||
// The request will be blocked and response is default block message.
|
||||
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
|
||||
assertEquals(1, cn.blockQps());
|
||||
|
||||
// Test for redirect.
|
||||
String redirectUrl = "http://some-location.com";
|
||||
WebServletConfig.setBlockPage(redirectUrl);
|
||||
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(header().string("Location", redirectUrl + "?http_referer=http://localhost/hello"));
|
||||
|
||||
FlowRuleManager.loadRules(null);
|
||||
WebServletConfig.setBlockPage("");
|
||||
}
|
||||
|
||||
private void testUrlCleaner() throws Exception {
|
||||
final String fooPrefix = "/foo/";
|
||||
String url1 = fooPrefix + 1;
|
||||
String url2 = fooPrefix + 2;
|
||||
WebCallbackManager.setUrlCleaner(new UrlCleaner() {
|
||||
@Override
|
||||
public String clean(String originUrl) {
|
||||
if (originUrl.startsWith(fooPrefix)) {
|
||||
return "/foo/*";
|
||||
}
|
||||
return originUrl;
|
||||
}
|
||||
});
|
||||
this.mvc.perform(get(url1).accept(MediaType.TEXT_PLAIN))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("Hello 1"));
|
||||
this.mvc.perform(get(url2).accept(MediaType.TEXT_PLAIN))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("Hello 2"));
|
||||
ClusterNode cn = ClusterBuilderSlot.getClusterNode(fooPrefix + "*");
|
||||
assertEquals(2, cn.passQps());
|
||||
assertNull(ClusterBuilderSlot.getClusterNode(url1));
|
||||
assertNull(ClusterBuilderSlot.getClusterNode(url2));
|
||||
|
||||
WebCallbackManager.setUrlCleaner(new DefaultUrlCleaner());
|
||||
}
|
||||
|
||||
private void testCustomOriginParser() throws Exception {
|
||||
String url = "/hello";
|
||||
String limitOrigin = "userA";
|
||||
final String headerName = "S-User";
|
||||
configureRulesFor(url, 0, limitOrigin);
|
||||
|
||||
WebCallbackManager.setRequestOriginParser(new RequestOriginParser() {
|
||||
@Override
|
||||
public String parseOrigin(HttpServletRequest request) {
|
||||
String origin = request.getHeader(headerName);
|
||||
return origin != null ? origin : "";
|
||||
}
|
||||
});
|
||||
|
||||
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, "userB"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(HELLO_STR));
|
||||
// This will be blocked.
|
||||
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN).header(headerName, limitOrigin))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
|
||||
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(HELLO_STR));
|
||||
|
||||
WebCallbackManager.setRequestOriginParser(null);
|
||||
FlowRuleManager.loadRules(null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
FlowRuleManager.loadRules(null);
|
||||
ClusterBuilderSlot.getClusterNodeMap().clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.adapter.servlet;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@Configuration
|
||||
public class FilterConfig {
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean sentinelFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(new CommonFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setName("sentinelFilter");
|
||||
registration.setOrder(1);
|
||||
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.adapter.servlet;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.alibaba.csp.sentinel.adapter.servlet;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
@RestController
|
||||
public class TestController {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public String apiHello() {
|
||||
return "Hello!";
|
||||
}
|
||||
|
||||
@GetMapping("/err")
|
||||
public String apiError() {
|
||||
return "Oops...";
|
||||
}
|
||||
|
||||
@GetMapping("/foo/{id}")
|
||||
public String apiFoo(@PathVariable("id") Long id) {
|
||||
return "Hello " + id;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue