Refactor config mechanism for OkHttp adapter and polish related code

- One config per interceptor instead of the global config
- Polish document and demo

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
This commit is contained in:
Eric Zhao 2020-07-22 11:04:48 +08:00
parent c7949f5f4e
commit 86f8bb7b38
12 changed files with 168 additions and 136 deletions

View File

@ -18,22 +18,24 @@ We can add the `SentinelOkHttpInterceptor` interceptor when `OkHttpClient` at in
```java ```java
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor()) .addInterceptor(new SentinelOkHttpInterceptor(new SentinelOkHttpConfig()))
.build(); .build();
``` ```
## Configuration ## Configuration
- `SentinelOkHttpConfig` configuration: `SentinelOkHttpConfig` configuration:
| name | description | type | default value | | name | description | type | default value |
|------|------------|------|-------| |------|------------|------|-------|
| extractor | custom resource extractor | `OkHttpResourceExtractor` | `DefaultOkHttpResourceExtractor` | | resourcePrefix | customized resource name prefix | `String` | `okhttp:` |
| resourceExtractor | customized resource extractor | `OkHttpResourceExtractor` | `DefaultOkHttpResourceExtractor` |
| fallback | handle request when it is blocked | `OkHttpFallback` | `DefaultOkHttpFallback` | | fallback | handle request when it is blocked | `OkHttpFallback` | `DefaultOkHttpFallback` |
### extractor (resource extractor) ### Resource Extractor
We can define `OkHttpResourceExtractor` to custom resource extractor replace `DefaultOkHttpResourceExtractor`, for example: okhttp:GET:ip:port/okhttp/back/1 ==> /okhttp/back/{id} We can define `OkHttpResourceExtractor` to customize the logic of extracting resource name from the HTTP request.
For example: `okhttp:GET:ip:port/okhttp/back/1 ==> /okhttp/back/{id}`
```java ```java
OkHttpResourceExtractor extractor = (request, connection) -> { OkHttpResourceExtractor extractor = (request, connection) -> {
@ -44,20 +46,20 @@ OkHttpResourceExtractor extractor = (request, connection) -> {
} }
return resource; return resource;
}; };
SentinelOkHttpConfig.setExtractor(extractor);
``` ```
### fallback (Block handling) The pattern of default resource name extractor is `${HTTP_METHOD}:${URL}` (e.g. `GET:/foo`).
We can define `OkHttpFallback` to handle request is blocked according to the actual scenario, for example: ### Fallback (Block handling)
We can define `OkHttpFallback` to handle blocked request. For example:
```java ```java
public class DefaultOkHttpFallback implements OkHttpFallback { public class DefaultOkHttpFallback implements OkHttpFallback {
@Override @Override
public Response handle(Request request, Connection connection, BlockException e) { public Response handle(Request request, Connection connection, BlockException e) {
// Just wrap and throw the exception. return new Response(myErrorBuilder);
throw new SentinelRpcException(e);
} }
} }
``` ```

View File

@ -0,0 +1,78 @@
/*
* Copyright 1999-2020 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.okhttp;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.OkHttpFallback;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author zhaoyuguang
* @author Eric Zhao
*/
public class SentinelOkHttpConfig {
public static final String DEFAULT_RESOURCE_PREFIX = "okhttp:";
private final String resourcePrefix;
private final OkHttpResourceExtractor resourceExtractor;
private final OkHttpFallback fallback;
public SentinelOkHttpConfig() {
this(DEFAULT_RESOURCE_PREFIX);
}
public SentinelOkHttpConfig(String resourcePrefix) {
this(resourcePrefix, new DefaultOkHttpResourceExtractor(), new DefaultOkHttpFallback());
}
public SentinelOkHttpConfig(OkHttpResourceExtractor resourceExtractor, OkHttpFallback fallback) {
this(DEFAULT_RESOURCE_PREFIX, resourceExtractor, fallback);
}
public SentinelOkHttpConfig(String resourcePrefix,
OkHttpResourceExtractor resourceExtractor,
OkHttpFallback fallback) {
AssertUtil.notNull(resourceExtractor, "resourceExtractor cannot be null");
AssertUtil.notNull(fallback, "fallback cannot be null");
this.resourcePrefix = resourcePrefix;
this.resourceExtractor = resourceExtractor;
this.fallback = fallback;
}
public String getResourcePrefix() {
return resourcePrefix;
}
public OkHttpResourceExtractor getResourceExtractor() {
return resourceExtractor;
}
public OkHttpFallback getFallback() {
return fallback;
}
@Override
public String toString() {
return "SentinelOkHttpConfig{" +
"resourcePrefix='" + resourcePrefix + '\'' +
", resourceExtractor=" + resourceExtractor +
", fallback=" + fallback +
'}';
}
}

View File

@ -16,9 +16,10 @@
package com.alibaba.csp.sentinel.adapter.okhttp; package com.alibaba.csp.sentinel.adapter.okhttp;
import com.alibaba.csp.sentinel.*; import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.okhttp.config.SentinelOkHttpConfig;
import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.util.StringUtil;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -30,22 +31,33 @@ import java.io.IOException;
*/ */
public class SentinelOkHttpInterceptor implements Interceptor { public class SentinelOkHttpInterceptor implements Interceptor {
private final SentinelOkHttpConfig config;
public SentinelOkHttpInterceptor() {
this.config = new SentinelOkHttpConfig();
}
public SentinelOkHttpInterceptor(SentinelOkHttpConfig config) {
AssertUtil.notNull(config, "config cannot be null");
this.config = config;
}
@Override @Override
public Response intercept(Chain chain) throws IOException { public Response intercept(Chain chain) throws IOException {
Entry entry = null; Entry entry = null;
try { try {
Request request = chain.request(); Request request = chain.request();
String name = SentinelOkHttpConfig.getExtractor().extract(request.url().toString(), request, chain.connection()); String name = config.getResourceExtractor().extract(request, chain.connection());
if (!StringUtil.isEmpty(SentinelOkHttpConfig.getPrefix())) { if (StringUtil.isNotBlank(config.getResourcePrefix())) {
name = SentinelOkHttpConfig.getPrefix() + name; name = config.getResourcePrefix() + name;
} }
entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
return chain.proceed(request); return chain.proceed(request);
} catch (BlockException e) { } catch (BlockException e) {
return SentinelOkHttpConfig.getFallback().handle(chain.request(), chain.connection(), e); return config.getFallback().handle(chain.request(), chain.connection(), e);
} catch (Throwable t) { } catch (IOException ex) {
Tracer.traceEntry(t, entry); Tracer.traceEntry(ex, entry);
throw t; throw ex;
} finally { } finally {
if (entry != null) { if (entry != null) {
entry.exit(); entry.exit();

View File

@ -1,59 +0,0 @@
/*
* Copyright 1999-2020 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.okhttp.config;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.OkHttpFallback;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* @author zhaoyuguang
*/
public final class SentinelOkHttpConfig {
private static volatile String prefix = "okhttp:";
private static volatile OkHttpResourceExtractor extractor = new DefaultOkHttpResourceExtractor();
private static volatile OkHttpFallback fallback = new DefaultOkHttpFallback();
public static String getPrefix() {
return prefix;
}
public static void setPrefix(String prefix) {
AssertUtil.notNull(prefix, "prefix cannot be null");
SentinelOkHttpConfig.prefix = prefix;
}
public static OkHttpResourceExtractor getExtractor() {
return extractor;
}
public static void setExtractor(OkHttpResourceExtractor extractor) {
AssertUtil.notNull(extractor, "extractor cannot be null");
SentinelOkHttpConfig.extractor = extractor;
}
public static OkHttpFallback getFallback() {
return fallback;
}
public static void setFallback(OkHttpFallback fallback) {
AssertUtil.notNull(fallback, "fallback cannot be null");
SentinelOkHttpConfig.fallback = fallback;
}
}

View File

@ -24,7 +24,7 @@ import okhttp3.Request;
public class DefaultOkHttpResourceExtractor implements OkHttpResourceExtractor { public class DefaultOkHttpResourceExtractor implements OkHttpResourceExtractor {
@Override @Override
public String extract(String url, Request request, Connection connection) { public String extract(Request request, Connection connection) {
return request.method() + ":" + request.url().toString(); return request.method() + ":" + request.url().toString();
} }
} }

View File

@ -23,5 +23,12 @@ import okhttp3.Request;
*/ */
public interface OkHttpResourceExtractor { public interface OkHttpResourceExtractor {
String extract(String url, Request request, Connection connection); /**
* Extracts the resource name from the HTTP request.
*
* @param request HTTP request entity
* @param connection HTTP connection
* @return the resource name of current request
*/
String extract(Request request, Connection connection);
} }

View File

@ -17,8 +17,8 @@ package com.alibaba.csp.sentinel.adapter.okhttp;
import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.adapter.okhttp.app.TestApplication; import com.alibaba.csp.sentinel.adapter.okhttp.app.TestApplication;
import com.alibaba.csp.sentinel.adapter.okhttp.config.SentinelOkHttpConfig;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor; import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback;
import com.alibaba.csp.sentinel.node.ClusterNode; import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
import okhttp3.Connection; import okhttp3.Connection;
@ -48,17 +48,17 @@ public class SentinelOkHttpInterceptorTest {
@Test @Test
public void testSentinelOkHttpInterceptor0() throws Exception { public void testSentinelOkHttpInterceptor0() throws Exception {
// With prefix
SentinelOkHttpConfig config = new SentinelOkHttpConfig("okhttp:");
String url0 = "http://localhost:" + port + "/okhttp/back"; String url0 = "http://localhost:" + port + "/okhttp/back";
SentinelOkHttpConfig.setPrefix("okhttp:");
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor()) .addInterceptor(new SentinelOkHttpInterceptor(config))
.build(); .build();
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url0) .url(url0)
.build(); .build();
System.out.println(client.newCall(request).execute().body().string()); System.out.println(client.newCall(request).execute().body().string());
ClusterNode cn = ClusterBuilderSlot.getClusterNode(SentinelOkHttpConfig.getPrefix() + "GET:" + url0); ClusterNode cn = ClusterBuilderSlot.getClusterNode(config.getResourcePrefix() + "GET:" + url0);
assertNotNull(cn); assertNotNull(cn);
Constants.ROOT.removeChildList(); Constants.ROOT.removeChildList();
@ -69,25 +69,26 @@ public class SentinelOkHttpInterceptorTest {
public void testSentinelOkHttpInterceptor1() throws Exception { public void testSentinelOkHttpInterceptor1() throws Exception {
String url0 = "http://localhost:" + port + "/okhttp/back/1"; String url0 = "http://localhost:" + port + "/okhttp/back/1";
SentinelOkHttpConfig.setExtractor(new OkHttpResourceExtractor() { SentinelOkHttpConfig config = new SentinelOkHttpConfig(new OkHttpResourceExtractor() {
@Override @Override
public String extract(String url, Request request, Connection connection) { public String extract(Request request, Connection connection) {
String regex = "/okhttp/back/"; String regex = "/okhttp/back/";
String url = request.url().toString();
if (url.contains(regex)) { if (url.contains(regex)) {
url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}";
} }
return request.method() + ":" + url; return request.method() + ":" + url;
} }
}); }, new DefaultOkHttpFallback());
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor()) .addInterceptor(new SentinelOkHttpInterceptor(config))
.build(); .build();
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url0) .url(url0)
.build(); .build();
System.out.println(client.newCall(request).execute().body().string()); System.out.println(client.newCall(request).execute().body().string());
String url1 = SentinelOkHttpConfig.getPrefix() + "GET:http://localhost:" + port + "/okhttp/back/{id}"; String url1 = config.getResourcePrefix() + "GET:http://localhost:" + port + "/okhttp/back/{id}";
ClusterNode cn = ClusterBuilderSlot.getClusterNode(url1); ClusterNode cn = ClusterBuilderSlot.getClusterNode(url1);
assertNotNull(cn); assertNotNull(cn);

View File

@ -15,6 +15,10 @@
*/ */
package com.alibaba.csp.sentinel.adapter.okhttp.config; package com.alibaba.csp.sentinel.adapter.okhttp.config;
import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpConfig;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback;
import org.junit.Test; import org.junit.Test;
/** /**
@ -22,18 +26,13 @@ import org.junit.Test;
*/ */
public class SentinelOkHttpConfigTest { public class SentinelOkHttpConfigTest {
@Test(expected = IllegalArgumentException.class)
public void testConfigSetPrefix() {
SentinelOkHttpConfig.setPrefix(null);
}
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testConfigSetCleaner() { public void testConfigSetCleaner() {
SentinelOkHttpConfig.setExtractor(null); SentinelOkHttpConfig config = new SentinelOkHttpConfig(null, new DefaultOkHttpFallback());
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testConfigSetFallback() { public void testConfigSetFallback() {
SentinelOkHttpConfig.setFallback(null); SentinelOkHttpConfig config = new SentinelOkHttpConfig(new DefaultOkHttpResourceExtractor(), null);
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package com.alibaba.csp.sentinel.adapter.okhttp.extractor; package com.alibaba.csp.sentinel.adapter.okhttp.extractor;
import com.alibaba.csp.sentinel.adapter.okhttp.config.SentinelOkHttpConfig;
import okhttp3.Connection; import okhttp3.Connection;
import okhttp3.Request; import okhttp3.Request;
import org.junit.Test; import org.junit.Test;
@ -32,19 +31,19 @@ public class OkHttpResourceExtractorTest {
OkHttpResourceExtractor extractor = new DefaultOkHttpResourceExtractor(); OkHttpResourceExtractor extractor = new DefaultOkHttpResourceExtractor();
String url = "http://localhost:8083/okhttp/back"; String url = "http://localhost:8083/okhttp/back";
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.build(); .build();
extractor.extract(url, request, null); String resource = extractor.extract(request, null);
System.out.println(extractor.extract(url, request, null)); assertEquals("GET:" + url, resource);
assertEquals("GET:"+url, extractor.extract(url, request, null));
} }
@Test @Test
public void testCustomizeOkHttpUrlCleaner() { public void testCustomizeOkHttpUrlCleaner() {
OkHttpResourceExtractor extractor = new OkHttpResourceExtractor() { OkHttpResourceExtractor extractor = new OkHttpResourceExtractor() {
@Override @Override
public String extract(String url, Request request, Connection connection) { public String extract(Request request, Connection connection) {
String regex = "/okhttp/back/"; String regex = "/okhttp/back/";
String url = request.url().toString();
if (url.contains(regex)) { if (url.contains(regex)) {
url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}";
} }
@ -53,9 +52,8 @@ public class OkHttpResourceExtractorTest {
}; };
String url = "http://localhost:8083/okhttp/back/abc"; String url = "http://localhost:8083/okhttp/back/abc";
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.build(); .build();
extractor.extract(url, request, null); assertEquals("GET:http://localhost:8083/okhttp/back/{id}", extractor.extract(request, null));
assertEquals("GET:http://localhost:8083/okhttp/back/{id}", extractor.extract(url, request, null));
} }
} }

View File

@ -17,10 +17,6 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba.csp</groupId> <groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-okhttp-adapter</artifactId> <artifactId>sentinel-okhttp-adapter</artifactId>
@ -41,5 +37,11 @@
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
<version>${okhttp.version}</version> <version>${okhttp.version}</version>
</dependency> </dependency>
<!-- Add this so that we could display the resources via /tree command -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -15,11 +15,6 @@
*/ */
package com.alibaba.csp.sentinel.demo.okhttp; package com.alibaba.csp.sentinel.demo.okhttp;
import com.alibaba.csp.sentinel.adapter.okhttp.config.SentinelOkHttpConfig;
import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor;
import okhttp3.Connection;
import okhttp3.Request;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -27,23 +22,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author zhaoyuguang * @author zhaoyuguang
*/ */
@SpringBootApplication @SpringBootApplication
public class OkHttpDemoApplication implements CommandLineRunner { public class OkHttpDemoApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(OkHttpDemoApplication.class); SpringApplication.run(OkHttpDemoApplication.class);
} }
@Override
public void run(String... args) {
SentinelOkHttpConfig.setExtractor(new OkHttpResourceExtractor() {
@Override
public String extract(String url, Request request, Connection connection) {
String regex = "/okhttp/back/";
if (url.contains(regex)) {
url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}";
}
return request.method() + ":" + url;
}
});
}
} }

View File

@ -15,7 +15,10 @@
*/ */
package com.alibaba.csp.sentinel.demo.okhttp.controller; package com.alibaba.csp.sentinel.demo.okhttp.controller;
import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpConfig;
import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpInterceptor; import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpInterceptor;
import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -35,6 +38,17 @@ public class OkHttpTestController {
@Value("${server.port}") @Value("${server.port}")
private Integer port; private Integer port;
private final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor(new SentinelOkHttpConfig((request, connection) -> {
String regex = "/okhttp/back/";
String url = request.url().toString();
if (url.contains(regex)) {
url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}";
}
return request.method() + ":" + url;
}, new DefaultOkHttpFallback())))
.build();
@RequestMapping("/okhttp/back") @RequestMapping("/okhttp/back")
public String back() { public String back() {
return "Welcome Back!"; return "Welcome Back!";
@ -56,12 +70,9 @@ public class OkHttpTestController {
} }
private String getRemoteString(String id) throws IOException { private String getRemoteString(String id) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SentinelOkHttpInterceptor())
.build();
Request request = new Request.Builder() Request request = new Request.Builder()
.url("http://localhost:" + port + "/okhttp/back" + (id == null ? "" : "/" + id)) .url("http://localhost:" + port + "/okhttp/back" + (id == null ? "" : "/" + id))
.build(); .build();
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
return response.body().string(); return response.body().string();
} }