From 2f6e20c6ce218da3e31c73277d4c4519fbe69b86 Mon Sep 17 00:00:00 2001 From: Volodymyr Khrushchak Date: Mon, 12 Mar 2018 20:54:57 +0200 Subject: [PATCH] method parameters as http headers --- .../examples/rx/AbstractRxMovieClient.java | 1 + .../examples/rx/proxy/MovieService.java | 25 +++++------ .../rx/proxy/RxMovieProxyExample.java | 14 +++---- .../rx/proxy/RxMovieProxyExample.java | 15 ++++--- .../netflix/ribbon/proxy/MethodTemplate.java | 42 +++++++++++++++++++ .../ribbon/proxy/MethodTemplateExecutor.java | 12 ++++++ .../ribbon/proxy/annotation/VarHeader.java | 30 +++++++++++++ .../proxy/MethodTemplateExecutorTest.java | 19 +++++++++ .../ribbon/proxy/MethodTemplateTest.java | 11 +++++ .../proxy/sample/MovieServiceInterfaces.java | 3 +- 10 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/VarHeader.java diff --git a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/AbstractRxMovieClient.java b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/AbstractRxMovieClient.java index 3ea2abea7..49e465359 100644 --- a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/AbstractRxMovieClient.java +++ b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/AbstractRxMovieClient.java @@ -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[] triggerMoviesRegistration(); diff --git a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java index 347aad7a0..bae67ea5a 100644 --- a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java +++ b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/MovieService.java @@ -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; /** @@ -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 recommendationsByUserId(@Var("userId") String userId); + RibbonRequest 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 recommendationsBy(@Var("category") String category, @Var("ageGroup") String ageGroup); + RibbonRequest 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 registerMovie(@Content Movie movie); + RibbonRequest 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 updateRecommendations(@Var("userId") String userId, @Content String movieId); + RibbonRequest updateRecommendations(@Var("userId") String userId, @Content String movieId, + @VarHeader("X-Auth-Token") String token); } diff --git a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java index d7d7820eb..12bd67e3a 100644 --- a/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java +++ b/ribbon-examples/src/main/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java @@ -45,9 +45,9 @@ public RxMovieProxyExample(int port) { @Override protected Observable[] 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() }; } @@ -55,8 +55,8 @@ protected Observable[] triggerMoviesRegistration() { @Override protected Observable[] 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() }; } @@ -64,8 +64,8 @@ protected Observable[] triggerRecommendationsUpdate() { @Override protected Observable[] 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() }; } diff --git a/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java b/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java index 2baeb4ba0..848188e3e 100644 --- a/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java +++ b/ribbon-guice/src/test/java/com/netflix/ribbon/examples/rx/proxy/RxMovieProxyExample.java @@ -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 @@ -26,9 +25,9 @@ public RxMovieProxyExample(MovieService movieService) { @Override protected Observable[] 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() }; } @@ -36,8 +35,8 @@ protected Observable[] triggerMoviesRegistration() { @Override protected Observable[] 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() }; } @@ -45,8 +44,8 @@ protected Observable[] triggerRecommendationsUpdate() { @Override protected Observable[] 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() }; } diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java index eb5bcd815..913c28273 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplate.java @@ -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; @@ -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> contentTansformerClass; private final Class resultType; @@ -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; @@ -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; } @@ -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> contentTansformerClass; private Class resultType; @@ -144,6 +163,7 @@ private MethodAnnotationValues(Method method) { extractParamNamesWithIndexes(); extractContentArgPosition(); extractContentTransformerClass(); + extractHeaderNamesWithIndexes(); extractResultType(); } @@ -169,6 +189,28 @@ private void extractParamNamesWithIndexes() { } } + private void extractHeaderNamesWithIndexes() { + List nameList = new ArrayList(); + List idxList = new ArrayList(); + 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; diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java index 33ec1a001..02969918a 100644 --- a/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/MethodTemplateExecutor.java @@ -71,6 +71,7 @@ public ByteBuf call(byte[] toTransform, ByteBufAllocator byteBufAllocator) { public RibbonRequest executeFromTemplate(Object[] args) { HttpRequestBuilder requestBuilder = httpRequestTemplateBuilder.build().requestBuilder(); withParameters(requestBuilder, args); + withHeaders(requestBuilder, args); withContent(requestBuilder, args); return (RibbonRequest) requestBuilder.build(); @@ -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) { diff --git a/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/VarHeader.java b/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/VarHeader.java new file mode 100644 index 000000000..0d9d700f2 --- /dev/null +++ b/ribbon/src/main/java/com/netflix/ribbon/proxy/annotation/VarHeader.java @@ -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(); +} diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java index 6ab111169..902406199 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateExecutorTest.java @@ -107,6 +107,7 @@ public void testGetQueryWithDomainObjectResult() throws Exception { assertEquals(ribbonRequestMock, ribbonRequest); } + @Test public void testGetQueryWithByteBufResult() throws Exception { expectUrlBase("GET", "/rawMovies/{id}"); @@ -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); diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java index eaa74b095..bfd90321c 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/MethodTemplateTest.java @@ -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")); diff --git a/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java b/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java index c82b37960..5f69eaa57 100644 --- a/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java +++ b/ribbon/src/test/java/com/netflix/ribbon/proxy/sample/MovieServiceInterfaces.java @@ -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; @@ -89,7 +90,7 @@ public static interface SampleMovieService { @TemplateName("deleteMovie") @Http(method = HttpMethod.DELETE, uri = "/movies/{id}") - RibbonRequest deleteMovie(@Var("id") String id); + RibbonRequest deleteMovie(@Var("id") String id, @VarHeader("Token") String token); } public static interface ShortMovieService {