提交 2f426c0a 编写于 作者: A Allen Wang

Ribbon client extension skeletons. Rename ribbon-rxnetty to ribbon-transport.

上级 32191a32
......@@ -67,7 +67,7 @@ project(':ribbon-httpclient') {
}
}
project(':ribbon-rxnetty') {
project(':ribbon-transport') {
dependencies {
compile project(':ribbon-core')
compile project(':ribbon-loadbalancer')
......@@ -91,7 +91,7 @@ project(':ribbon-eureka') {
project(':ribbon-examples') {
dependencies {
compile project(':ribbon-httpclient')
compile project(':ribbon-rxnetty')
compile project(':ribbon-transport')
compile 'com.google.code.gson:gson:2.2.4'
compile 'com.thoughtworks.xstream:xstream:1.4.5'
compile 'com.sun.jersey:jersey-server:1.11'
......@@ -112,8 +112,7 @@ project(':ribbon-client-extensions') {
dependencies {
compile 'com.netflix.hystrix:hystrix-core:1.4.0-RC4'
compile 'com.netflix.evcache:evcache-client:1.0.5'
compile project(':ribbon-httpclient')
compile project(':ribbon-rxnetty')
compile project(':ribbon-transport')
}
}
......@@ -7,19 +7,19 @@ import rx.functions.Action1;
import com.netflix.client.config.ClientConfigBuilder;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.netty.RibbonTransport;
import com.netflix.ribbonclientextensions.HystrixResponse;
import com.netflix.ribbonclientextensions.Ribbon;
import com.netflix.ribbonclientextensions.hystrix.HystrixResponse;
public class RibbonExamples {
public static void main(String[] args) {
IClientConfig config = ClientConfigBuilder.newBuilderWithArchaiusProperties("myclient").build();
HttpClient<ByteBuf, ByteBuf> transportClient = RibbonTransport.newHttpClient(config);
Ribbon.newHttpClient(transportClient).newRequestTemplate()
Ribbon.from(transportClient).newRequestTemplate()
.withUri("/{id}").requestBuilder().withValue("id", 1).build().execute();
// example showing the use case of getting the entity with Hystrix meta data
Ribbon.newHttpClient(transportClient).newRequestTemplate()
Ribbon.from(transportClient).newRequestTemplate()
.withUri("/{id}").requestBuilder().withValue("id", 1).build().withHystrixInfo().toObservable()
.subscribe(new Action1<HystrixResponse<ByteBuf>>(){
@Override
......
......@@ -4,16 +4,12 @@ import java.util.concurrent.Future;
import rx.Observable;
import com.netflix.hystrix.HystrixExecutableInfo;
public interface HystrixResponse<T> {
HystrixExecutableInfo<T> getHystrixInfo();
T execute();
public interface AsyncRequest<T> {
public T execute();
Observable<T> toObservable();
public Future<T> queue();
Observable<T> observe();
public Observable<T> observe();
Future<T> queue();
public Observable<T> toObservable();
}
package com.netflix.ribbonclientextensions;
/**
* Created by mcohen on 4/23/14.
*/
abstract public class CacheConfig {
protected final String cacheKeyTemplate;
public CacheConfig(String cacheKeyTemplate){
this.cacheKeyTemplate = cacheKeyTemplate;
}
}
package com.netflix.ribbonclientextensions;
/**
* Created by mcohen on 4/25/14.
*/
public class CacheHandler {
import rx.Observable;
public interface CacheProvider<T> {
Observable<T> get(String key);
}
package com.netflix.ribbonclientextensions;
public interface ClientResponse<T, R> {
public T getData();
public R getMetaData();
}
package com.netflix.ribbonclientextensions;
/**
* Created by mcohen on 4/23/14.
*/
public class CustomerContext {
public long customerId;
//todo other common params
}
package com.netflix.ribbonclientextensions;
import java.util.List;
import io.reactivex.netty.protocol.http.client.ContentSource;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.http.client.RawContentSource;
import com.netflix.hystrix.HystrixCommandProperties.Setter;
import com.netflix.ribbonclientextensions.hystrix.FallbackProvider;
import com.netflix.ribbonclientextensions.interfaces.CacheProvider;
public class HttpRequestTemplate<I, O> implements RequestTemplate<I, O, HttpClientResponse<O>> {
......@@ -38,14 +36,32 @@ public class HttpRequestTemplate<I, O> implements RequestTemplate<I, O, HttpClie
}
@Override
public RequestTemplate<I, O, HttpClientResponse<O>> withCacheProvider(
List<CacheProvider<O>> cacheProviders) {
public RequestTemplate<I, O, HttpClientResponse<O>> withFallbackDeterminator(
FallbackDeterminator<HttpClientResponse<O>> fallbackDeterminator) {
return this;
}
@Override
public RequestTemplate<I, O, HttpClientResponse<O>> withFallbackDeterminator(
FallbackDeterminator<HttpClientResponse<O>> fallbackDeterminator) {
public RequestTemplate<I, O, HttpClientResponse<O>> addCacheProvider(
CacheProvider<O> cacheProvider, String cacheKeyTemplate) {
return this;
}
@Override
public RequestTemplate<I, O, HttpClientResponse<O>> withHystrixCommandPropertiesDefaults(
Setter setter) {
return this;
}
@Override
public RequestTemplate<I, O, HttpClientResponse<O>> withHystrixThreadPoolPropertiesDefaults(
com.netflix.hystrix.HystrixThreadPoolProperties.Setter setter) {
return this;
}
@Override
public RequestTemplate<I, O, HttpClientResponse<O>> withHystrixCollapserPropertiesDefaults(
com.netflix.hystrix.HystrixCollapserProperties.Setter setter) {
return this;
}
}
package com.netflix.ribbonclientextensions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.HashSet;
import java.util.Set;
import org.apache.http.entity.ContentType;
@SuppressWarnings("UnusedDeclaration")
public class HttpService {
public static final String X_NETFLIX_CLIENT_IMPLEMENTATION_VERSION = "X-Netflix.client.Implementation-Version";
/*
Either defaults == null and the rest are != null or defaults != null and the rest start out not null;
*/
protected Set<Integer> successfulHttpCodes = null;
protected Set<Integer> errorHttpCodes = null;
protected Multimap<String, String> headers = null;
protected Class errorClass = null;
protected String restClientName = null;
protected Boolean extraTargetVariablesOK = null;
protected final HttpService defaults;
public HttpService() {
successfulHttpCodes = new HashSet<Integer>();
errorHttpCodes = new HashSet<Integer>();
headers = ArrayListMultimap.create();
errorClass = null;
restClientName = null;
extraTargetVariablesOK = Boolean.FALSE;
defaults = null;
}
public HttpService(HttpService defaults) {
this.defaults = defaults;
}
/**
* Adds a header to the list of headers to send.
*
* Headers are additive and will be added to any default headers.
*
* @param headerName Name of header
* @param headerValue value for header.
* @return this
*/
public HttpService withHeader(String headerName, String headerValue) {
ensureLocalHeaders();
headers.put(headerName, headerValue);
return this;
}
/**
* Adds a header to the list of headers to send, removing any existing header with the same name first.
* <p/>
* Headers are additive and will be added to any default headers.
*
* @param headerName Name of header
* @param headerValue value for header.
* @return this
*/
public HttpService withUniqueHeader(String headerName, String headerValue) {
ensureLocalHeaders();
headers.removeAll(headerName);
headers.put(headerName, headerValue);
return this;
}
private void ensureLocalHeaders() {
if (defaults != null && headers == null) {
headers = ArrayListMultimap.create();
headers.putAll(defaults.getHeaders());
}
}
public Multimap<String, String> getHeaders() {
return (defaults != null && headers == null) ? defaults.getHeaders() : headers;
}
public HttpService withErrorClass(Class errorClass) {
this.errorClass = errorClass;
return this;
}
public Class getErrorClass() {
return (defaults != null && errorClass == null) ? defaults.getErrorClass() : errorClass;
}
/**
* @param httpResults results that indicate a successful and expected completion of the request.
* @return ResourceTemplate
*/
public HttpService withSuccessHttpStatus(int... httpResults) {
if (defaults != null && successfulHttpCodes == null) {
successfulHttpCodes = new HashSet<Integer>();
successfulHttpCodes.addAll(defaults.getSuccessfulHttpCodes());
}
for (int i : httpResults) {
successfulHttpCodes.add(Integer.valueOf(i));
}
return this;
}
public Set<Integer> getSuccessfulHttpCodes() {
return (defaults != null && successfulHttpCodes == null) ? defaults.getSuccessfulHttpCodes() : successfulHttpCodes;
}
/**
* @param httpResults results tht are not success but the server may return under normal operations. A client
* should NEVER fall back on these results.
* @return ResourceTemplate
*/
public HttpService withErrorHttpCodes(int... httpResults) {
if (defaults != null && errorHttpCodes == null) {
errorHttpCodes = new HashSet<Integer>();
errorHttpCodes.addAll(defaults.getErrorHttpCodes());
}
for (int i : httpResults) {
errorHttpCodes.add(Integer.valueOf(i));
}
return this;
}
public Set<Integer> getErrorHttpCodes() {
return (defaults != null && errorHttpCodes == null) ? defaults.getErrorHttpCodes() : errorHttpCodes;
}
/**
* override the default contentType
*
* @param contentType content type
* @return ResourceTemplate
*/
public HttpService withContentType(ContentType contentType) {
withUniqueHeader("Content-Type", contentType.getMimeType());
return this;
}
/**
* override the default accepted contentTypes
*
* @param contentTypes content typ
* @return ResourceTemplate
*/
public HttpService withAcceptContentTypes(ContentType... contentTypes) {
StringBuilder value = new StringBuilder();
for (ContentType contentType : contentTypes) {
if (value.length() != 0) {
value.append(", ");
}
value.append(contentType);
}
withHeader("Accept", value.toString());
return this;
}
public HttpService withClientVersion(Class clazz) {
return withClientVersion(clazz.getPackage().getImplementationVersion());
}
public HttpService withClientVersion(String version) {
withUniqueHeader(X_NETFLIX_CLIENT_IMPLEMENTATION_VERSION, version);
return this;
}
public HttpService withRESTClientName(String restClientName) {
this.restClientName = restClientName;
return this;
}
public String getRESTClientName() {
return (defaults != null && restClientName == null) ? defaults.getRESTClientName() : restClientName;
}
public HttpService allowExtraTargetVariables() {
this.extraTargetVariablesOK = Boolean.TRUE;
return this;
}
public HttpService noExtraTargetVariables() {
this.extraTargetVariablesOK = Boolean.FALSE;
return this;
}
public boolean isExtraTargetVariablesOK() {
return (defaults != null && extraTargetVariablesOK == null) ? defaults.isExtraTargetVariablesOK() : extraTargetVariablesOK.booleanValue();
}
}
package com.netflix.ribbonclientextensions;
import java.util.List;
import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.ribbonclientextensions.hystrix.FallbackProvider;
import com.netflix.ribbonclientextensions.interfaces.CacheProvider;
/**
* @author awang
......@@ -20,12 +20,17 @@ public interface RequestTemplate<I, O, R> {
RequestTemplate<I, O, R> withFallbackDeterminator(FallbackDeterminator<R> fallbackDeterminator);
RequestTemplate<I, O, R> withCacheProvider(List<CacheProvider<O>> cacheProviders);
RequestTemplate<I, O, R> addCacheProvider(CacheProvider<O> cacheProvider, String cacheKeyTemplate);
RequestTemplate<I, O, R> withHystrixCommandPropertiesDefaults(HystrixCommandProperties.Setter setter);
RequestTemplate<I, O, R> withHystrixThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter setter);
RequestTemplate<I, O, R> withHystrixCollapserPropertiesDefaults(HystrixCollapserProperties.Setter setter);
public abstract class RequestBuilder<O> {
public abstract RequestBuilder<O> withValue(String key, Object value);
public abstract RibbonRequest<O> build();
}
}
package com.netflix.ribbonclientextensions;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Multimap;
import com.netflix.ribbonclientextensions.template.TemplateParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
@SuppressWarnings("UnusedDeclaration")
public class Resource<Result> {
private static final Logger logger = LoggerFactory.getLogger(Resource.class);
final ResourceTemplate<Result> resourceTemplate;
Map<String, String> targetMap = null;
StringBuilder queryParams;
Object entity;
String primaryCacheKey;
String secondaryCacheKey;
CustomerContext customerContext;
/**
* @param resourceTemplate the resourceTemplate for this target.
*/
protected Resource(ResourceTemplate<Result> resourceTemplate) {
this.resourceTemplate = resourceTemplate;
targetMap = new HashMap<String, String>();
}
/**
* specified the one of the path targets of the URI : /ribbonclientextensions/{targetName1}/{targetName2}/{;targetName3}...
*
* @param targetName name of the target i.e. targetName of {targetName} in a resourceTemplate's URI.
* @param targetValue value to replace {targetName} with.
* @return Builder
*/
public Resource<Result> withTarget(String targetName, Object targetValue) {
targetMap.put(targetName, URLEncode(targetValue.toString()));
return this;
}
/**
* There are times when you need to double encode your path target. Specifically if the target includes "/" or "\"
* specified the one of the path targets of the URI : /ribbonclientextensions/{target1}/{target2}/...
*
* @param targetName name of the target i.e. target1 of {target1} above.
* @param targetValue value placed here.
* @return Builder
*/
public Resource<Result> withDoubleEncodedTarget(String targetName, Object targetValue) {
return withTarget(targetName, URLEncode(targetValue.toString()));
}
/**
* A true REST api does not have query parameters. It is recommended that either the target ribbonclientextensions be defined
* in a way you don't need a parameter or make use of matrix parameters
*
* @param name parameter name
* @param value parameter value
* @return Builder
*/
public Resource<Result> withQueryParameter(String name, String value) {
if (value != null) {
if (queryParams == null) {
queryParams = new StringBuilder();
} else {
queryParams.append('&');
}
queryParams.append(name)
.append('=')
.append(URLEncode(value));
}
return this;
}
/**
* There are times when you need to double encode your path target.
* A true REST api does not have query parameters. It is recommended that either the target ribbonclientextensions be defined
* in a way you don't need a parameter or make use of matrix parameters
*
* @param name parameter name
* @param value parameter value
* @return Builder
*/
public Resource<Result> withDoubleEncodedQueryParameter(String name, String value) {
withQueryParameter(name, URLEncode(value));
return this;
}
/**
* Send an entity along. normally used for PUT and POST requests.
*
* @param entity entity to send.
* @return Builder
*/
public Resource<Result> withEntity(Object entity) {
this.entity = entity;
return this;
}
/*
public Resource<Result> withSniperMonkey(SniperMonkey sniperMonkey) {
this.sniperMonkey = sniperMonkey;
return this;
}
public SniperMonkey getSniperMonkey() {
return sniperMonkey;
}
*/
/**
* build the target.
*
* @return Builder
*/
public URI toURI() throws URISyntaxException {
StringBuilder uri = new StringBuilder(resourceTemplate.toURIFragment(targetMap));
// Bad form to have query params in a REST call.
if (queryParams != null && queryParams.length() > 0) {
uri.append('?').append(queryParams);
}
try {
return new URI(uri.toString());
} catch (URISyntaxException e) {
logger.error(String.format("%s -- uri: '%s'", e.getMessage(), uri.toString()), e);
throw e;
}
}
private String URLEncode(Object value) {
if (value == null) {
return null;
}
try {
return URLEncoder.encode(value.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("Could not encode: %s", value, e);
return value.toString();
}
}
public ResourceTemplate<Result> getResourceTemplate() {
return resourceTemplate;
}
public Object getEntity() {
return entity;
}
public void addHeader(String name, String value) {
//todo
}
public Resource<Result> withCustomerContext(CustomerContext customerContext) {
this.customerContext = customerContext;
return this;
}
public String getCacheKey() throws URISyntaxException {
if(resourceTemplate.primaryCacheConfig.cacheKeyTemplate == null) return "";
return TemplateParser.toData(targetMap,
resourceTemplate.primaryCacheConfig.cacheKeyTemplate,
resourceTemplate.parsedList);
}
public Observable<Response<Result>> execute() {
return null;
}
public Multimap<String,String> getHeaders() {
return resourceTemplate.httpService.getHeaders(); // todo ( include all headers from template and resource)
}
}
package com.netflix.ribbonclientextensions;
import com.netflix.evcache.EVCacheException;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.ribbonclientextensions.evcache.EvCacheConfig;
import com.netflix.ribbonclientextensions.evcache.EvCacheHandler;
import com.netflix.ribbonclientextensions.hystrix.ClientCommand;
import java.net.URISyntaxException;
/**
* Created by mcohen on 4/25/14.
*/
public class ResourceCommand<T> extends ClientCommand<T> {
Resource<T> resource;
protected ResourceCommand(HystrixCommandGroupKey group, HystrixCommandKey command, Resource<T> resource) {
super(group, command);
}
@Override
protected T run() throws Exception {
if(resource.getResourceTemplate().primaryCacheConfig!= null){
return getFromCache(resource.getResourceTemplate().primaryCacheConfig);
}
return null;//todo
}
private T getFromCache(CacheConfig cacheConfig) {
if(cacheConfig instanceof EvCacheConfig){
if(resource.getResourceTemplate().primaryCacheHandler == null){
resource.getResourceTemplate().primaryCacheHandler = new EvCacheHandler<T>((EvCacheConfig) cacheConfig);
}
try {
return ((EvCacheHandler<T>)(resource.getResourceTemplate().primaryCacheHandler)).get(resource.getCacheKey());
} catch (EVCacheException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return null;
}
protected T getFallback(){
return null; //todo
}
}
package com.netflix.ribbonclientextensions;
import com.netflix.client.http.HttpRequest;
import com.netflix.ribbonclientextensions.hystrix.FallbackHandler;
import com.netflix.ribbonclientextensions.interfaces.CacheProvider;
import com.netflix.ribbonclientextensions.template.MatrixVar;
import com.netflix.ribbonclientextensions.template.PathVar;
import com.netflix.ribbonclientextensions.template.TemplateParser;
import com.netflix.ribbonclientextensions.template.TemplateVar;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @param <Result> The Class returned by a successful service call
*/
@SuppressWarnings("UnusedDeclaration")
public class ResourceTemplate<Result> {
protected HttpService httpService;
protected final List<CacheProvider<Result>> cacheProviders = new LinkedList<CacheProvider<Result>>();
public CacheHandler primaryCacheHandler;
public CacheConfig primaryCacheConfig;
public CacheConfig secondaryCacheConfig;
protected final Class<Result> resultClass;
protected final HttpRequest.Verb verb;
protected final String uriTemplate;
protected FallbackHandler<Result> fallbackHandler;
protected final List<Object> parsedList;
protected String commandName;
/**
* Defines a REST Resource or endpoint. Is is expected that a HystrixRESTClient will define the expected successful
* HTTP status codes as well as non successful HTTP status codes that occur under normal operation.
* e.g. Authoritative errors such as 404 Not Found or 401 Unauthorized. Fall back if used should never fall back for
* successful or authoritative error status codes.
*
* @param verb Verb for this call (GET, POST, PUT, etc)
* @param resultClass the class to be returned by the call. (Need the class so we can create one)
* @param uriTemplate the REST resourceTemplate to build URI fragments from.
*
*/
public ResourceTemplate(HttpRequest.Verb verb, Class<Result> resultClass, String uriTemplate) {
this(verb, resultClass, uriTemplate, null);
}
/**
* Defines a REST Resource or endpoint. Is is expected that a HystrixRESTClient will define the expected successful
* HTTP status codes as well as non successful HTTP status codes that occur under normal operation.
* e.g. Authoritative errors such as 404 Not Found or 401 Unauthorized. Fall back if used should never fall back for
* successful or authoritative error status codes.
*
* @param verb Verb for this call (GET, POST, PUT, etc)
* @param resultClass the class to be returned by the call. (Need the class so we can create one)
* @param uriTemplate the REST resourceTemplate will be appended to the application context to product the uri.
* template possible URI
* Path parameter:
* /app/v1/Customer/{custId} -> /app/v1/Customer/123456
* Path parameter and a matrix parameter:
* /app/v1/Customer/{custId}{;member} => /app/v1/Customer/123456;member=true
* Matrix parameters are optional:
* /app/v1/Customer/{custId}{;member} => /app/v1/Customer/123456
*
*
*
* @param def a HttpService containing default values for the template.
*/
public ResourceTemplate(HttpRequest.Verb verb, Class<Result> resultClass, String uriTemplate, HttpService def) {
httpService = def;
this.resultClass = resultClass;
this.verb = verb;
this.uriTemplate = cleanResource(uriTemplate);
this.parsedList = TemplateParser.parseTemplate(uriTemplate);
commandName = defaultCommandName();
}
/**
* constructor for use only by @see com.netflix.ribbonclientextensions.Resource
*
* @param template ResourceTemplate to copy.
*/
protected ResourceTemplate(ResourceTemplate<Result> template) {
httpService = template.httpService;
this.parsedList = Collections.unmodifiableList(template.parsedList);
this.resultClass = template.resultClass;
this.verb = template.verb;
this.uriTemplate = template.uriTemplate;
this.commandName = template.commandName;
// might be modified make a copy.
this.cacheProviders.addAll(template.cacheProviders);
}
protected String defaultCommandName() {
String name;
name = this.verb + this.uriTemplate;
// normalize for property file
name = name.replaceAll("/", "_");
name = name.replaceAll("[{}]", "-");
return name;
}
protected String cleanResource(String resourceTemplate) {
if (resourceTemplate != null) {
resourceTemplate = resourceTemplate.trim();
if (!resourceTemplate.isEmpty() && !resourceTemplate.equals("/")) {
if (resourceTemplate.endsWith("/")) {
resourceTemplate = resourceTemplate.substring(0, resourceTemplate.length() - 1);
}
if (!resourceTemplate.startsWith("/")) {
resourceTemplate = "/" + resourceTemplate;
}
}
}
return resourceTemplate;
}
public ResourceTemplate<Result> withCommandName(String commandName) {
this.commandName = commandName;
return this;
}
public ResourceTemplate<Result> withCacheProvider(CacheProvider<Result> cacheProvider) {
this.cacheProviders.add(cacheProvider);
return this;
}
public Class<Result> getResultClass() {
return resultClass;
}
public HttpRequest.Verb getVerb() {
return verb;
}
public String getTemplate() {
return uriTemplate;
}
/**
* Build the URI from a List. The URI template has been previously parsed e.g.
* /dms/v1/device/{esn}/customer/{custId} parses into:
* (String)/dms/v1/device/
* (ResourceTemplate.Var)esn
* (String)/customer/
* (ResourceTemplate.Var)custId
* <p/>
* Resource.Builder's withTarget(name, value) will put the value in a map for later retrieval.
* .withTarget("esn", "test-esn")
* .withTarget("custId", 22)
* <p/>
* results in the map:
* esn -> test-esn
* custId -> 22
* <p/>
* when a value in the list of type String is found it is appended to the URI, if a value of type ResourceTemplate.Var is found
* it is looked up in the variables map and appended to the list if it exists. if not the original {...} text is preserved
* and a resulting uri of /dms/v1/device/test-esn/customer/22
* <p/>
* Avg time per call: 0.00218028 ms
*
* @param variables variables to inject into the URI
* @return URI
*/
public String toURIFragment(Map<String, String> variables) throws URISyntaxException {
int params = variables.size();
String uri = TemplateParser.toData(variables, uriTemplate, parsedList);
if (params != 0 && !httpService.isExtraTargetVariablesOK()) {
//TODO one could determine the extra template variables at this point to provide a better message.
throw new URISyntaxException(uriTemplate, String.format("extra template variable(s) supplied, variables: %s",
Arrays.toString(variables.keySet().toArray())));
}
return uri;
}
public ResourceTemplate<Result> withFallbackHandler(FallbackHandler<Result> fallbackHandler) {
this.fallbackHandler = fallbackHandler;
return this;
}
public FallbackHandler<Result> getFallbackHandler() {
return fallbackHandler;
}
public ResourceTemplate<Result> withSecondaryCache(CacheConfig cacheConfig) {
secondaryCacheConfig = cacheConfig;
return this;
}
public ResourceTemplate<Result> withPrimaryCache(CacheConfig cacheConfig) {
primaryCacheConfig = cacheConfig;
return this;
}
public ResourceTemplate<Result> withResponseClass(Class classClass) {
//todo
return this;
}
public String getCommandName() {
return commandName;
}
public boolean isSuccessfulHttpStatus(int statusCode) {
return httpService.getSuccessfulHttpCodes().contains(Integer.valueOf(statusCode));
}
public boolean isErrorHttpStatus(int statusCode) {
return httpService.getErrorHttpCodes().contains(Integer.valueOf(statusCode));
}
public boolean isAuthoritativeHttpStatus(int statusCode) {
return isSuccessfulHttpStatus(statusCode) || isErrorHttpStatus(statusCode);
}
public Resource<Result> resource() {
return new Resource<Result>(this);
}
public ResourceTemplate<Result> withResponseDeserializer() {
//todo
return this;
}
}
package com.netflix.ribbonclientextensions;
import java.util.Map;
/**
* Created by mcohen on 4/24/14.
*/
public class Response<Result> {
Result data;
Map<String, String> headers;
public Result getData() {
return data;
}
}
package com.netflix.ribbonclientextensions;
import rx.functions.Action1;
import com.netflix.client.config.ClientConfigBuilder;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.netty.RibbonTransport;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.HttpClient;
public final class Ribbon {
......@@ -14,11 +7,11 @@ public final class Ribbon {
private Ribbon() {
}
public static <I, O> RibbonHttpClient<I, O> newHttpClient(HttpClient<I, O> transportClient) {
public static <I, O> RibbonHttpClient<I, O> from(HttpClient<I, O> transportClient) {
return null;
}
public static <I, O, T> T newHttpClient(Class<T> contract, HttpClient<I, O> transportClient) {
public static <I, O, T> T create(Class<T> contract, HttpClient<I, O> transportClient) {
return null;
}
......
package com.netflix.ribbonclientextensions;
import rx.Observable;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixObservableCommand;
class RibbonHystrixCommand<T> extends HystrixObservableCommand<T> {
public RibbonHystrixCommand(HystrixCommandGroupKey group) {
super(group);
// TODO Auto-generated constructor stub
}
public RibbonHystrixCommand(
com.netflix.hystrix.HystrixObservableCommand.Setter setter) {
super(setter);
// TODO Auto-generated constructor stub
}
@Override
protected Observable<T> run() {
// TODO Auto-generated method stub
return null;
}
@Override
protected Observable<T> getFallback() {
// TODO Auto-generated method stub
return super.getFallback();
}
}
package com.netflix.ribbonclientextensions;
import java.util.concurrent.Future;
import com.netflix.ribbonclientextensions.hystrix.HystrixResponse;
import rx.Observable;
public interface RibbonRequest<T> extends AsyncRequest<T> {
public interface RibbonRequest<T> {
// public RibbonRequest<Map<String, Object>> asDictionary();
public T execute();
public Future<T> queue();
public Observable<T> observe();
public Observable<T> toObservable();
public RibbonRequest<HystrixResponse<T>> withHystrixInfo();
}
package com.netflix.ribbonclientextensions.evcache;
import com.netflix.evcache.EVCacheException;
/**
* Created by mcohen on 4/22/14.
*/
public class CacheFaultException extends Exception {
public CacheFaultException(Throwable throwable) {
super(throwable);
}
}
package com.netflix.ribbonclientextensions.evcache;
import static com.google.common.base.Preconditions.checkNotNull;
import com.netflix.evcache.EVCache;
import com.netflix.evcache.EVCacheException;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
import com.netflix.ribbonclientextensions.hystrix.ClientCommand;
import com.netflix.ribbonclientextensions.hystrix.FallbackHandler;
public abstract class EvCacheCommand <T> extends ClientCommand<T> {
private final EvCacheHandler<T> cacheHandler;
protected final String key;
static HystrixCommandGroupKey group = new HystrixCommandGroupKey() {
@Override
public String name() {
return "evcache";
}
};
protected EvCacheCommand(HystrixCommandKey command, EvCacheHandler cacheHandler, String key) {
super(group, command, ExecutionIsolationStrategy.SEMAPHORE);
this.cacheHandler = checkNotNull(cacheHandler, "'cache' cannot be null");
this.key = key;
}
public EvCacheCommand(EvCacheHandler cacheHandler, String key) {
this(HystrixCommandKey.Factory.asKey("EVCacheGetCommand"), cacheHandler, key);
}
@Override
protected T run() throws CacheFaultException {
try {
return cacheHandler.get(key);
} catch (EVCacheException e) {
throw new CacheFaultException(e);
}
}
@Override
protected String getCacheKey() {
return cacheHandler.toString() + ":" + key;
}
@Override
protected T getFallback() {
return null;
}
}
\ No newline at end of file
package com.netflix.ribbonclientextensions.evcache;
import com.netflix.evcache.EVCache;
import com.netflix.evcache.EVCacheTranscoder;
import com.netflix.ribbonclientextensions.CacheConfig;
/**
* Created by mcohen on 4/22/14.
*/
public class EvCacheConfig extends CacheConfig{
EVCacheTranscoder transcoder;
boolean touchOnGet = false;
boolean enableZoneFallback = true;
int timeToLive = EVCache.Builder.DEFAULT_TTL;
String appName;
String cacheName;
public EvCacheConfig(String appName,
String cacheName,
boolean enableZoneFallback,
int timeToLive,
boolean touchOnGet,
EVCacheTranscoder transcoder,
String cacheKeyTemplate) {
super(cacheKeyTemplate);
this.appName = appName;
this.cacheName = cacheName;
this.enableZoneFallback = enableZoneFallback;
this.timeToLive = timeToLive;
this.touchOnGet = touchOnGet;
this.transcoder = transcoder;
}
public EvCacheConfig(String appName,
String cacheName,
boolean enableZoneFallback,
int timeToLive,
boolean touchOnGet,
String cacheKeyTemplate) {
super(cacheKeyTemplate);
this.appName = appName;
this.cacheName = cacheName;
this.enableZoneFallback = enableZoneFallback;
this.timeToLive = timeToLive;
this.touchOnGet = touchOnGet;
}
public EvCacheConfig(String appName, String cacheName,String cacheKeyTemplate) {
super(cacheKeyTemplate);
this.appName = appName;
this.cacheName = cacheName;
}
public EvCacheConfig(String appName, String cacheName, boolean enableZoneFallback,String cacheKeyTemplate) {
super(cacheKeyTemplate);
this.appName = appName;
this.cacheName = cacheName;
this.enableZoneFallback = enableZoneFallback;
}
public EvCacheConfig(String appName, String cacheName, boolean enableZoneFallback, int timeToLive, String cacheKeyTemplate) {
super(cacheKeyTemplate);
this.appName = appName;
this.cacheName = cacheName;
this.enableZoneFallback = enableZoneFallback;
this.timeToLive = timeToLive;
}
}
package com.netflix.ribbonclientextensions.evcache;
import com.netflix.evcache.EVCache;
import com.netflix.evcache.EVCacheException;
import com.netflix.evcache.EVCacheTranscoder;
import com.netflix.ribbonclientextensions.CacheHandler;
/**
* Created by mcohen on 4/22/14.
*/
public class EvCacheHandler<T> extends CacheHandler {
EvCacheConfig config;
EVCache evCache;
public EvCacheHandler(EvCacheConfig config) {
this.config = config;
EVCache.Builder builder = new EVCache.Builder();
if(config.enableZoneFallback) builder.enableZoneFallback();
builder.setDefaultTTL(config.timeToLive);
builder.setAppName(config.appName);
builder.setCacheName(config.cacheName);
evCache = builder.build();
}
public T get(String key) throws EVCacheException {
if(config.touchOnGet){
}
return evCache.get(key);
}
@Override
public String toString(){
return evCache.toString();
}
}
package com.netflix.ribbonclientextensions.hystrix;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
/**
* @author dray
*
*/
public abstract class ClientCommand<R> extends HystrixCommand<R> {
protected ClientCommand(HystrixCommandGroupKey group, HystrixCommandKey command) {
super(Setter.withGroupKey(group).andCommandKey(command));
}
protected ClientCommand(HystrixCommandGroupKey group, HystrixCommandKey command, ExecutionIsolationStrategy executionIsolationStrategy) {
super(Setter.withGroupKey(group)
.andCommandKey(command)
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(executionIsolationStrategy)));
}
}
\ No newline at end of file
package com.netflix.ribbonclientextensions.hystrix;
import com.netflix.hystrix.exception.HystrixRuntimeException;
/**
* Created by mcohen on 4/21/14.
*/
public abstract class FallbackHandler<T> {
String commandKey;
private FallbackHandler(){
}
public FallbackHandler(String commandKey){
super();
this.commandKey = commandKey;
}
abstract protected FallbackResponse handleCommandException(HystrixRuntimeException exception);
abstract protected FallbackResponse handleShortCircuit(HystrixRuntimeException exception);
abstract protected FallbackResponse handleRejectedSemaphore(HystrixRuntimeException exception);
abstract protected FallbackResponse handleRejectedSemaphoreFallback(HystrixRuntimeException exception);
abstract protected FallbackResponse handleTimeout(HystrixRuntimeException exception);
}
package com.netflix.ribbonclientextensions.hystrix;
import rx.Observable;
import java.util.Map;
/**
* Created by mcohen on 4/22/14.
*/
public class FallbackResponse<T> {
Observable<T> content;
int status_code = 200;
Map<String, String> responseHeaders;
public FallbackResponse(Observable<T> content){
this.content = content;
}
public FallbackResponse(Observable<T> content, int status_code){
this.content = content;
this.status_code = status_code;
}
public FallbackResponse(Observable<T> content, int status_code, Map<String, String> responseHeaders) {
this.content = content;
this.status_code = status_code;
this.responseHeaders = responseHeaders;
}
public void setContent(Observable<T> content) {
this.content = content;
}
public void setStatus_code(int status_code) {
this.status_code = status_code;
}
public void setResponseHeaders(Map<String, String> responseHeaders) {
this.responseHeaders = responseHeaders;
}
public Observable<T> getContent() {
return content;
}
public int getStatus_code() {
return status_code;
}
public Map<String, String> getResponseHeaders() {
return responseHeaders;
}
}
package com.netflix.ribbonclientextensions.hystrix;
import rx.Observable;
import rx.functions.Func1;
public interface FallbackResponseProvider<T, R> {
}
package com.netflix.ribbonclientextensions.hystrix;
import com.netflix.hystrix.HystrixExecutableInfo;
import com.netflix.ribbonclientextensions.AsyncRequest;
public interface HystrixResponse<T> extends AsyncRequest<T> {
HystrixExecutableInfo<T> getHystrixInfo();
}
package com.netflix.ribbonclientextensions.interfaces;
public interface CacheProvider<T> {
T get();
}
package com.netlfix.resource;
import com.netflix.client.http.HttpRequest;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.netflix.ribbonclientextensions.*;
import com.netflix.ribbonclientextensions.evcache.EvCacheConfig;
import com.netflix.ribbonclientextensions.hystrix.FallbackHandler;
import com.netflix.ribbonclientextensions.hystrix.FallbackResponse;
import com.sun.jersey.client.impl.ClientRequestImpl;
import org.apache.http.entity.ContentType;
import rx.Observable;
/**
* Created by mcohen on 5/1/14.
*/
public class SampleClient {
HttpService service = new HttpService();
class SampleFallbackHandler<T> extends FallbackHandler<T> {
public SampleFallbackHandler(String commandKey) {
super(commandKey);
}
@Override
protected FallbackResponse handleCommandException(HystrixRuntimeException exception) {
return null;
}
@Override
protected FallbackResponse handleShortCircuit(HystrixRuntimeException exception) {
return null;
}
@Override
protected FallbackResponse handleRejectedSemaphore(HystrixRuntimeException exception) {
return null;
}
@Override
protected FallbackResponse handleRejectedSemaphoreFallback(HystrixRuntimeException exception) {
return null;
}
@Override
protected FallbackResponse handleTimeout(HystrixRuntimeException exception) {
return null;
}
}
public String testV1Blocking(String id1, String id2, CustomerContext context) {
ResourceTemplate<String> template = getTestV1Template();
Resource<String> resource = template.resource();
resource.addHeader("myCoolHeader", "moo!!");
//data
resource.withTarget("id1", id1);
resource.withTarget("id2", id2);
resource.withTarget("custId", context.customerId); //for CacheKey
resource = resource.withCustomerContext(context);
Observable<Response<String>> response = resource.execute();
Response<String> r = response.toBlockingObservable().first();
return r.getData();
}
private ResourceTemplate<String> getTestV1Template() {
HttpService service = getHttpService();
ResourceTemplate<String> template = new ResourceTemplate<String>(
HttpRequest.Verb.GET, String.class,
"/test/v1/{id1}/{id2}",
service);
EvCacheConfig cacheConfig = new EvCacheConfig(
"test", //app name
"myCache", //cache name
true, //zone fallback
36000, // ttl
true, // touch on get
"{custId}:{id1}"); // cacheKeyTemplate
template.withPrimaryCache(cacheConfig);
template = template.withPrimaryCache(cacheConfig);
template = template.withFallbackHandler(new SampleFallbackHandler<String>("MyCommand"));
//to handle concrete responses
template = template.withResponseClass(String.class);
return template;
}
private HttpService getHttpService() {
if(service != null) return service;
service = new HttpService();
service.withRESTClientName("api").
withSuccessHttpStatus(200,202,302,301).
withHeader("test", "header").
withContentType(ContentType.APPLICATION_JSON);
return service;
}
}
package com.netlfix.resource;
import com.google.common.collect.Multimap;
import com.netflix.client.http.HttpRequest;
import com.netflix.ribbonclientextensions.HttpService;
import com.netflix.ribbonclientextensions.Resource;
import com.netflix.ribbonclientextensions.ResourceTemplate;
import com.sun.jersey.client.impl.ClientRequestImpl;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
public class TestTemplates {
@Test
public void testSimple() {
HttpService defaults = new HttpService()
.withClientVersion("clientName");
ResourceTemplate<String> template = new ResourceTemplate<String>(
HttpRequest.Verb.GET, String.class,
"/test/v1/{id1}/{id2}",
defaults);
Resource<String> resource = template.resource();
try {
URI uri = resource.toURI();
Assert.fail("should have thrown an exception because there are no values for the variables in the URI");
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("was not supplied"));
}
resource.withTarget("id1", "val1");
try {
URI uri = resource.toURI();
Assert.fail("should have thrown an exception because one of the variables in the URI is missing");
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("was not supplied"));
}
resource.withTarget("id2", "val2");
try {
URI uri = resource.toURI();
Assert.assertEquals( "URI does not have expected value", "/test/v1/val1/val2", uri.toString());
} catch (URISyntaxException e) {
Assert.fail(e.getMessage() + "\n" + Arrays.toString(e.getStackTrace()));
}
//test extra parameter
resource.withTarget("id3", "val3");
try {
URI uri = resource.toURI();
Assert.assertEquals("URI does not have expected value", "/test/v1/val1/val2", uri.toString());
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("extra template variable"));
}
}
@Test
public void testClientVersion() {
HttpService defaults = new HttpService()
.withClientVersion(ClientRequestImpl.class);
ResourceTemplate <String> template = new ResourceTemplate<String>(
HttpRequest.Verb.GET, String.class,
"/test/v1/{id1}/{id2}",
defaults);
Resource<String> resource = template.resource();
Multimap<String, String> headers = resource.getHeaders();
String value = headers.get(HttpService.X_NETFLIX_CLIENT_IMPLEMENTATION_VERSION).iterator().next();
Assert.assertEquals("client version mismatch expected the version of the jersey-bundle*.jar", "1.11", value);
}
@Test
public void testMatrix() {
HttpService defaults = new HttpService()
.withClientVersion("clientName");
ResourceTemplate<String> template = new ResourceTemplate<String>(
HttpRequest.Verb.GET, String.class,
"/test/v1{;id1}/{id2}",
defaults);
Resource<String> resource = template.resource();
// test no parameters
try {
URI uri = resource.toURI();
Assert.fail("should have thrown an exception because there are no values for the variables in the URI");
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("was not supplied"));
}
// test that a required parameter is missing
resource.withTarget("id1", "val1");
try {
URI uri = resource.toURI();
Assert.fail("should have thrown an exception because there is no value for val2");
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("was not supplied"));
}
// test matrix parameter is missing but required parameters are supplied.
resource = template.resource();
resource.withTarget("id2", "val2");
try {
URI uri = resource.toURI();
Assert.assertEquals("URI does not have expected value", "/test/v1/val2", uri.toString());
} catch (URISyntaxException e) {
Assert.fail("should Not have thrown an exception because one id1 is a matrix parameter and therefor optional");
}
// test required and matrix parameters are supplied.
resource.withTarget("id1", "val1");
try {
URI uri = resource.toURI();
Assert.assertEquals("URI does not have expected value", "/test/v1;id1=val1/val2", uri.toString());
} catch (URISyntaxException e) {
Assert.fail(e.getMessage() + "\n" + Arrays.toString(e.getStackTrace()));
}
//test extra parameter
resource.withTarget("id3", "val3");
try {
URI uri = resource.toURI();
Assert.assertEquals("URI does not have expected value", "/test/v1/val1/val2", uri.toString());
} catch (URISyntaxException e) {
Assert.assertTrue("Wrong error message", e.getReason().contains("extra template variable"));
}
}
}
rootProject.name='ribbon'
include 'ribbon-core', 'ribbon-loadbalancer', 'ribbon-httpclient', 'ribbon-eureka', 'ribbon-rxnetty', 'ribbon-examples', 'ribbon-test', 'ribbon-client-extensions'
\ No newline at end of file
include 'ribbon-core', 'ribbon-loadbalancer', 'ribbon-httpclient', 'ribbon-eureka', 'ribbon-transport', 'ribbon-examples', 'ribbon-test', 'ribbon-client-extensions'
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册