Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

method parameters as http headers #369

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*/
public abstract class AbstractRxMovieClient {
protected static final String TEST_USER = "user1";
protected static final String TEST_TOKEN = "abc";
protected static final Pattern NEW_LINE_SPLIT_RE = Pattern.compile("\n");

protected abstract Observable<ByteBuf>[] triggerMoviesRegistration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.netflix.ribbon.proxy.annotation.Hystrix;
import com.netflix.ribbon.proxy.annotation.TemplateName;
import com.netflix.ribbon.proxy.annotation.Var;
import com.netflix.ribbon.proxy.annotation.VarHeader;
import io.netty.buffer.ByteBuf;

/**
Expand All @@ -50,49 +51,49 @@ public interface MovieService {
method = HttpMethod.GET,
uri = "/users/{userId}/recommendations",
headers = {
@Header(name = "X-Platform-Version", value = "xyz"),
@Header(name = "X-Auth-Token", value = "abc")
@Header(name = "X-Platform-Version", value = "xyz")
})
@Hystrix(
validator = RecommendationServiceResponseValidator.class,
fallbackHandler = RecommendationServiceFallbackHandler.class)
@CacheProvider(key = "{userId}", provider = InMemoryCacheProviderFactory.class)
RibbonRequest<ByteBuf> recommendationsByUserId(@Var("userId") String userId);
RibbonRequest<ByteBuf> recommendationsByUserId(@Var("userId") String userId,
@VarHeader("X-Auth-Token") String token);

@TemplateName("recommendationsBy")
@Http(
method = HttpMethod.GET,
uri = "/recommendations?category={category}&ageGroup={ageGroup}",
headers = {
@Header(name = "X-Platform-Version", value = "xyz"),
@Header(name = "X-Auth-Token", value = "abc")
@Header(name = "X-Platform-Version", value = "xyz")
})
@Hystrix(
validator = RecommendationServiceResponseValidator.class,
fallbackHandler = RecommendationServiceFallbackHandler.class)
@CacheProvider(key = "{category},{ageGroup}", provider = InMemoryCacheProviderFactory.class)
RibbonRequest<ByteBuf> recommendationsBy(@Var("category") String category, @Var("ageGroup") String ageGroup);
RibbonRequest<ByteBuf> recommendationsBy(@Var("category") String category, @Var("ageGroup") String ageGroup,
@VarHeader("X-Auth-Token") String token);

@TemplateName("registerMovie")
@Http(
method = HttpMethod.POST,
uri = "/movies",
headers = {
@Header(name = "X-Platform-Version", value = "xyz"),
@Header(name = "X-Auth-Token", value = "abc")
@Header(name = "X-Platform-Version", value = "xyz")
})
@Hystrix(validator = RecommendationServiceResponseValidator.class)
@ContentTransformerClass(RxMovieTransformer.class)
RibbonRequest<ByteBuf> registerMovie(@Content Movie movie);
RibbonRequest<ByteBuf> registerMovie(@Content Movie movie,
@VarHeader("X-Auth-Token") String token);

@TemplateName("updateRecommendations")
@Http(
method = HttpMethod.POST,
uri = "/users/{userId}/recommendations",
headers = {
@Header(name = "X-Platform-Version", value = "xyz"),
@Header(name = "X-Auth-Token", value = "abc")
@Header(name = "X-Platform-Version", value = "xyz")
})
@Hystrix(validator = RecommendationServiceResponseValidator.class)
RibbonRequest<ByteBuf> updateRecommendations(@Var("userId") String userId, @Content String movieId);
RibbonRequest<ByteBuf> updateRecommendations(@Var("userId") String userId, @Content String movieId,
@VarHeader("X-Auth-Token") String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ public RxMovieProxyExample(int port) {
@Override
protected Observable<ByteBuf>[] triggerMoviesRegistration() {
return new Observable[]{
movieService.registerMovie(Movie.ORANGE_IS_THE_NEW_BLACK).toObservable(),
movieService.registerMovie(Movie.BREAKING_BAD).toObservable(),
movieService.registerMovie(Movie.HOUSE_OF_CARDS).toObservable()
movieService.registerMovie(Movie.ORANGE_IS_THE_NEW_BLACK, TEST_TOKEN).toObservable(),
movieService.registerMovie(Movie.BREAKING_BAD, TEST_TOKEN).toObservable(),
movieService.registerMovie(Movie.HOUSE_OF_CARDS, TEST_TOKEN).toObservable()
};
}

@SuppressWarnings("unchecked")
@Override
protected Observable<ByteBuf>[] triggerRecommendationsUpdate() {
return new Observable[]{
movieService.updateRecommendations(TEST_USER, Movie.ORANGE_IS_THE_NEW_BLACK.getId()).toObservable(),
movieService.updateRecommendations(TEST_USER, Movie.BREAKING_BAD.getId()).toObservable()
movieService.updateRecommendations(TEST_USER, Movie.ORANGE_IS_THE_NEW_BLACK.getId(), TEST_TOKEN).toObservable(),
movieService.updateRecommendations(TEST_USER, Movie.BREAKING_BAD.getId(), TEST_TOKEN).toObservable()
};
}

@SuppressWarnings("unchecked")
@Override
protected Observable<ByteBuf>[] triggerRecommendationsSearch() {
return new Observable[]{
movieService.recommendationsByUserId(TEST_USER).toObservable(),
movieService.recommendationsBy("Drama", "Adults").toObservable()
movieService.recommendationsByUserId(TEST_USER, TEST_TOKEN).toObservable(),
movieService.recommendationsBy("Drama", "Adults", TEST_TOKEN).toObservable()
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import com.google.inject.Singleton;
import com.netflix.ribbon.examples.rx.AbstractRxMovieClient;
import com.netflix.ribbon.examples.rx.common.Movie;
import com.netflix.ribbon.examples.rx.proxy.MovieService;
import com.netflix.ribbon.proxy.ProxyLifeCycle;

@Singleton
Expand All @@ -26,27 +25,27 @@ public RxMovieProxyExample(MovieService movieService) {
@Override
protected Observable<ByteBuf>[] triggerMoviesRegistration() {
return new Observable[]{
movieService.registerMovie(Movie.ORANGE_IS_THE_NEW_BLACK).toObservable(),
movieService.registerMovie(Movie.BREAKING_BAD).toObservable(),
movieService.registerMovie(Movie.HOUSE_OF_CARDS).toObservable()
movieService.registerMovie(Movie.ORANGE_IS_THE_NEW_BLACK, TEST_TOKEN).toObservable(),
movieService.registerMovie(Movie.BREAKING_BAD, TEST_TOKEN).toObservable(),
movieService.registerMovie(Movie.HOUSE_OF_CARDS, TEST_TOKEN).toObservable()
};
}

@SuppressWarnings("unchecked")
@Override
protected Observable<ByteBuf>[] triggerRecommendationsUpdate() {
return new Observable[]{
movieService.updateRecommendations(TEST_USER, Movie.ORANGE_IS_THE_NEW_BLACK.getId()).toObservable(),
movieService.updateRecommendations(TEST_USER, Movie.BREAKING_BAD.getId()).toObservable()
movieService.updateRecommendations(TEST_USER, Movie.ORANGE_IS_THE_NEW_BLACK.getId(), TEST_TOKEN).toObservable(),
movieService.updateRecommendations(TEST_USER, Movie.BREAKING_BAD.getId(), TEST_TOKEN).toObservable()
};
}

@SuppressWarnings("unchecked")
@Override
protected Observable<ByteBuf>[] triggerRecommendationsSearch() {
return new Observable[]{
movieService.recommendationsByUserId(TEST_USER).toObservable(),
movieService.recommendationsBy("Drama", "Adults").toObservable()
movieService.recommendationsByUserId(TEST_USER, TEST_TOKEN).toObservable(),
movieService.recommendationsBy("Drama", "Adults", TEST_TOKEN).toObservable()
};
}

Expand Down
42 changes: 42 additions & 0 deletions ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.netflix.ribbon.proxy.annotation.ContentTransformerClass;
import com.netflix.ribbon.proxy.annotation.TemplateName;
import com.netflix.ribbon.proxy.annotation.Var;
import com.netflix.ribbon.proxy.annotation.VarHeader;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.channel.ContentTransformer;
import rx.Observable;
Expand Down Expand Up @@ -48,6 +49,8 @@ class MethodTemplate {
private final String templateName;
private final String[] paramNames;
private final int[] valueIdxs;
private final String[] headersNames;
private final int[] headersIdxs;
private final int contentArgPosition;
private final Class<? extends ContentTransformer<?>> contentTansformerClass;
private final Class<?> resultType;
Expand All @@ -59,6 +62,8 @@ class MethodTemplate {
templateName = values.templateName;
paramNames = values.paramNames;
valueIdxs = values.valueIdxs;
headersNames = values.headerNames;
headersIdxs = values.headerIdxs;
contentArgPosition = values.contentArgPosition;
contentTansformerClass = values.contentTansformerClass;
resultType = values.resultType;
Expand All @@ -85,6 +90,18 @@ public int getParamSize() {
return paramNames.length;
}

public String getHeaderName(int idx) {
return headersNames[idx];
}

public int getHeaderPosition(int idx) {
return headersIdxs[idx];
}

public int getHeaderSize() {
return headersNames.length;
}

public int getContentArgPosition() {
return contentArgPosition;
}
Expand Down Expand Up @@ -133,6 +150,8 @@ private static class MethodAnnotationValues {
private String templateName;
private String[] paramNames;
private int[] valueIdxs;
private String[] headerNames;
private int[] headerIdxs;
private int contentArgPosition;
private Class<? extends ContentTransformer<?>> contentTansformerClass;
private Class<?> resultType;
Expand All @@ -144,6 +163,7 @@ private MethodAnnotationValues(Method method) {
extractParamNamesWithIndexes();
extractContentArgPosition();
extractContentTransformerClass();
extractHeaderNamesWithIndexes();
extractResultType();
}

Expand All @@ -169,6 +189,28 @@ private void extractParamNamesWithIndexes() {
}
}

private void extractHeaderNamesWithIndexes() {
List<String> nameList = new ArrayList<String>();
List<Integer> idxList = new ArrayList<Integer>();
Annotation[][] params = method.getParameterAnnotations();
for (int i = 0; i < params.length; i++) {
for (Annotation a : params[i]) {
if (a.annotationType().equals(VarHeader.class)) {
String name = ((VarHeader) a).value();
nameList.add(name);
idxList.add(i);
}
}
}
int size = nameList.size();
headerNames = new String[size];
headerIdxs = new int[size];
for (int i = 0; i < size; i++) {
headerNames[i] = nameList.get(i);
headerIdxs[i] = idxList.get(i);
}
}

private void extractContentArgPosition() {
Annotation[][] params = method.getParameterAnnotations();
int pos = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public ByteBuf call(byte[] toTransform, ByteBufAllocator byteBufAllocator) {
public <O> RibbonRequest<O> executeFromTemplate(Object[] args) {
HttpRequestBuilder<?> requestBuilder = httpRequestTemplateBuilder.build().requestBuilder();
withParameters(requestBuilder, args);
withHeaders(requestBuilder, args);
withContent(requestBuilder, args);

return (RibbonRequest<O>) requestBuilder.build();
Expand Down Expand Up @@ -101,6 +102,17 @@ private void withParameters(HttpRequestBuilder<?> requestBuilder, Object[] args)
}
}

private void withHeaders(HttpRequestBuilder<?> requestBuilder, Object[] args) {
int length = methodTemplate.getHeaderSize();
for (int i = 0; i < length; i++) {
String name = methodTemplate.getHeaderName(i);
Object value = args[methodTemplate.getHeaderPosition(i)];
if (value != null) {
requestBuilder.withHeader(name, value.toString());
}
}
}

@SuppressWarnings({"unchecked", "rawtypes"})
private void withContent(HttpRequestBuilder<?> requestBuilder, Object[] args) {
if (methodTemplate.getContentArgPosition() < 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2014 Netflix, Inc.
*
* 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.netflix.ribbon.proxy.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Volodymyr Khrushchak
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface VarHeader {
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public void testGetQueryWithDomainObjectResult() throws Exception {

assertEquals(ribbonRequestMock, ribbonRequest);
}

@Test
public void testGetQueryWithByteBufResult() throws Exception {
expectUrlBase("GET", "/rawMovies/{id}");
Expand Down Expand Up @@ -144,6 +145,24 @@ public void testPostWithByteArray() throws Exception {
doTestPostWith("/binaries/byteArray", "registerByteArrayBinary", new byte[]{1});
}

@Test
public void testDeleteWithVariableHeader() throws Exception {
expectUrlBase("DELETE", "/movies/{id}");

expect(requestBuilderMock.withRequestProperty("id", "id123")).andReturn(requestBuilderMock);
expect(requestBuilderMock.withHeader("Token", "42")).andReturn(requestBuilderMock);
expect(httpResourceGroupMock.newTemplateBuilder("deleteMovie")).andReturn(httpRequestTemplateBuilderMock);

replayAll();

MethodTemplateExecutor executor = createExecutor(SampleMovieService.class, "deleteMovie");
RibbonRequest ribbonRequest = executor.executeFromTemplate(new Object[]{"id123", "42"});

verifyAll();

assertEquals(ribbonRequestMock, ribbonRequest);
}

private void doTestPostWith(String uriTemplate, String methodName, Object contentObject) {
expectUrlBase("POST", uriTemplate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public void testGetWithTwoParameters() throws Exception {
assertEquals(1, template.getParamPosition(1));
}

@Test
public void testDeleteWithVariableHeader() throws Exception {
MethodTemplate template = new MethodTemplate(methodByName(SampleMovieService.class, "deleteMovie"));

assertEquals("deleteMovie", template.getTemplateName());
assertEquals("id", template.getParamName(0));
assertEquals(0, template.getParamPosition(0));
assertEquals("Token", template.getHeaderName(0));
assertEquals(1, template.getHeaderPosition(0));
}

@Test
public void testTemplateNameCanBeDerivedFromMethodName() throws Exception {
MethodTemplate template = new MethodTemplate(methodByName(TemplateNameDerivedFromMethodName.class, "myTemplateName"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.netflix.ribbon.proxy.annotation.ResourceGroup;
import com.netflix.ribbon.proxy.annotation.TemplateName;
import com.netflix.ribbon.proxy.annotation.Var;
import com.netflix.ribbon.proxy.annotation.VarHeader;
import com.netflix.ribbon.proxy.sample.HystrixHandlers.MovieFallbackHandler;
import com.netflix.ribbon.proxy.sample.HystrixHandlers.SampleHttpResponseValidator;
import io.netty.buffer.ByteBuf;
Expand Down Expand Up @@ -89,7 +90,7 @@ public static interface SampleMovieService {

@TemplateName("deleteMovie")
@Http(method = HttpMethod.DELETE, uri = "/movies/{id}")
RibbonRequest<ByteBuf> deleteMovie(@Var("id") String id);
RibbonRequest<ByteBuf> deleteMovie(@Var("id") String id, @VarHeader("Token") String token);
}

public static interface ShortMovieService {
Expand Down