├── riposte-spi ├── src │ ├── test │ │ ├── resources │ │ │ └── slf4jtest.properties │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ ├── backstopper │ │ │ ├── handler │ │ │ │ └── riposte │ │ │ │ │ └── config │ │ │ │ │ └── BackstopperRiposteConfigHelperTest.java │ │ │ └── service │ │ │ │ └── riposte │ │ │ │ └── BackstopperRiposteValidatorAdapterTest.java │ │ │ └── riposte │ │ │ ├── server │ │ │ ├── error │ │ │ │ ├── handler │ │ │ │ │ ├── ErrorResponseBodyTest.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── DelegatedErrorResponseBodyTest.java │ │ │ │ └── exception │ │ │ │ │ ├── PathNotFound404ExceptionTest.java │ │ │ │ │ ├── NonblockingEndpointCompletableFutureTimedOutTest.java │ │ │ │ │ ├── UnexpectedMajorErrorHandlingErrorTest.java │ │ │ │ │ ├── Forbidden403ExceptionTest.java │ │ │ │ │ ├── PathParameterMatchingExceptionTest.java │ │ │ │ │ ├── MethodNotAllowed405ExceptionTest.java │ │ │ │ │ ├── InvalidCharsetInContentTypeHeaderExceptionTest.java │ │ │ │ │ ├── DownstreamIdleChannelTimeoutExceptionTest.java │ │ │ │ │ ├── DownstreamChannelClosedUnexpectedlyExceptionTest.java │ │ │ │ │ ├── InvalidRipostePipelineExceptionTest.java │ │ │ │ │ ├── RequestContentDeserializationExceptionTest.java │ │ │ │ │ ├── RequestTooBigExceptionTest.java │ │ │ │ │ └── MultipleMatchingEndpointsExceptionTest.java │ │ │ ├── http │ │ │ │ ├── header │ │ │ │ │ ├── accept │ │ │ │ │ │ ├── MimeMediaRangeTypeTest.java │ │ │ │ │ │ └── MimeMediaRangeSubTypeTest.java │ │ │ │ │ └── AcceptHeaderParserTest.java │ │ │ │ ├── NonblockingEndpointTest.java │ │ │ │ ├── impl │ │ │ │ │ └── BaseResponseInfoBuilderTest.java │ │ │ │ ├── mimetype │ │ │ │ │ └── MimeTypeTest.java │ │ │ │ └── filter │ │ │ │ │ └── RequestAndResponseFilterTest.java │ │ │ └── config │ │ │ │ ├── distributedtracing │ │ │ │ └── ServerSpanNamingAndTaggingStrategyTest.java │ │ │ │ └── impl │ │ │ │ └── DependencyInjectionProvidedServerConfigValuesBaseTest.java │ │ │ ├── util │ │ │ └── MatcherUtilTest.java │ │ │ └── testutils │ │ │ └── Whitebox.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ ├── riposte │ │ ├── server │ │ │ ├── http │ │ │ │ ├── header │ │ │ │ │ ├── accept │ │ │ │ │ │ ├── MediaRangeType.java │ │ │ │ │ │ ├── MediaRangeSubType.java │ │ │ │ │ │ ├── MimeMediaRangeType.java │ │ │ │ │ │ └── MimeMediaRangeSubType.java │ │ │ │ │ └── AcceptHeader.java │ │ │ │ ├── impl │ │ │ │ │ └── RiposteInternalRequestInfo.java │ │ │ │ └── filter │ │ │ │ │ └── ShortCircuitingRequestAndResponseFilter.java │ │ │ ├── error │ │ │ │ ├── exception │ │ │ │ │ ├── InvalidHttpRequestException.java │ │ │ │ │ ├── HostnameResolutionException.java │ │ │ │ │ ├── PathNotFound404Exception.java │ │ │ │ │ ├── UnexpectedMajorErrorHandlingError.java │ │ │ │ │ ├── PathParameterMatchingException.java │ │ │ │ │ ├── MethodNotAllowed405Exception.java │ │ │ │ │ ├── NonblockingEndpointCompletableFutureTimedOut.java │ │ │ │ │ ├── InvalidCharsetInContentTypeHeaderException.java │ │ │ │ │ ├── InvalidRipostePipelineException.java │ │ │ │ │ ├── RequestContentDeserializationException.java │ │ │ │ │ ├── NativeIoExceptionWrapper.java │ │ │ │ │ ├── MissingRequiredContentException.java │ │ │ │ │ ├── TooManyOpenChannelsException.java │ │ │ │ │ ├── MultipleMatchingEndpointsException.java │ │ │ │ │ ├── Unauthorized401Exception.java │ │ │ │ │ ├── IncompleteHttpCallTimeoutException.java │ │ │ │ │ ├── Forbidden403Exception.java │ │ │ │ │ ├── DownstreamChannelClosedUnexpectedlyException.java │ │ │ │ │ ├── RequestTooBigException.java │ │ │ │ │ └── DownstreamIdleChannelTimeoutException.java │ │ │ │ ├── handler │ │ │ │ │ ├── ErrorResponseBodySerializer.java │ │ │ │ │ ├── RiposteUnhandledErrorHandler.java │ │ │ │ │ ├── ErrorResponseInfo.java │ │ │ │ │ ├── impl │ │ │ │ │ │ └── DelegatedErrorResponseBody.java │ │ │ │ │ ├── ErrorResponseBody.java │ │ │ │ │ └── RiposteErrorHandler.java │ │ │ │ └── validation │ │ │ │ │ └── RequestValidator.java │ │ │ ├── metrics │ │ │ │ └── ServerMetricsEvent.java │ │ │ ├── hooks │ │ │ │ ├── PreServerStartupHook.java │ │ │ │ ├── PipelineCreateHook.java │ │ │ │ ├── ServerShutdownHook.java │ │ │ │ └── PostServerStartupHook.java │ │ │ └── config │ │ │ │ └── AppInfo.java │ │ ├── util │ │ │ └── MatcherUtil.java │ │ └── metrics │ │ │ └── MetricsListener.java │ │ └── backstopper │ │ ├── service │ │ └── riposte │ │ │ └── BackstopperRiposteValidatorAdapter.java │ │ └── model │ │ └── riposte │ │ └── ErrorResponseBodyImpl.java └── build.gradle ├── riposte-core ├── src │ ├── test │ │ ├── resources │ │ │ ├── slf4jtest.properties │ │ │ ├── testMultipartFile.txt │ │ │ ├── helloWorld.png │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ ├── server │ │ │ ├── handler │ │ │ │ └── base │ │ │ │ │ └── PipelineContinuationBehaviorTest.java │ │ │ └── channelpipeline │ │ │ │ └── ChannelAttributesTest.java │ │ │ └── testutils │ │ │ └── Whitebox.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ ├── riposte │ │ ├── server │ │ │ ├── http │ │ │ │ └── ProcessingState.java │ │ │ ├── handler │ │ │ │ ├── base │ │ │ │ │ └── PipelineContinuationBehavior.java │ │ │ │ ├── IdleChannelTimeoutHandler.java │ │ │ │ └── SmartHttpContentDecompressor.java │ │ │ └── channelpipeline │ │ │ │ └── message │ │ │ │ ├── ChunkedOutboundMessage.java │ │ │ │ ├── LastOutboundMessage.java │ │ │ │ ├── OutboundMessage.java │ │ │ │ ├── LastOutboundMessageSendLastContentChunk.java │ │ │ │ ├── OutboundMessageSendHeadersChunkFromResponseInfo.java │ │ │ │ ├── OutboundMessageSendContentChunk.java │ │ │ │ └── LastOutboundMessageSendFullResponseInfo.java │ │ └── util │ │ │ └── ErrorContractSerializerHelper.java │ │ └── trace │ │ └── netty │ │ └── RequestWithHeadersNettyAdapter.java └── build.gradle ├── riposte_logo.png ├── gradle.properties ├── riposte-guice-typesafe-config ├── src │ ├── test │ │ ├── resources │ │ │ └── testconfig.conf │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── guice │ │ │ └── typesafeconfig │ │ │ └── TypesafeConfigPropertiesRegistrationGuiceModuleTest.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ └── guice │ │ └── typesafeconfig │ │ └── TypesafeConfigPropertiesRegistrationGuiceModule.java └── build.gradle ├── samples ├── sample-1-helloworld │ ├── buildSample.sh │ ├── runSample.sh │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ ├── Main.java │ │ │ └── helloworld │ │ │ └── HelloWorldEndpoint.java │ │ └── test │ │ └── java │ │ └── com │ │ └── nike │ │ └── helloworld │ │ └── HelloWorldEndpointTest.java └── sample-2-kotlin-todoservice │ ├── buildSample.sh │ ├── runSample.sh │ ├── README.md │ ├── src │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── kotlin │ │ └── com │ │ └── nike │ │ └── todoservice │ │ ├── error │ │ ├── ProjectApiErrorsImpl.kt │ │ └── ProjectApiError.kt │ │ └── Main.kt │ └── build.gradle ├── riposte-service-registration-eureka ├── runLocalEurekaWithDocker.sh ├── src │ ├── vagrant │ │ ├── eureka-server-test.properties │ │ ├── eureka-client-test.properties │ │ └── provision_eureka.sh │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── serviceregistration │ │ │ └── eureka │ │ │ └── EurekaException.java │ └── test │ │ ├── java │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── serviceregistration │ │ │ └── eureka │ │ │ └── EurekaExceptionTest.java │ │ └── resources │ │ └── logback.xml ├── vagrantfile └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── riposte-archaius ├── src │ └── test │ │ └── resources │ │ ├── archaiusserver-compiletimetest.properties │ │ └── archaiusserver.properties └── build.gradle ├── riposte-typesafe-config ├── src │ ├── test │ │ ├── resources │ │ │ ├── typesafeconfigserver-compiletimetest.conf │ │ │ └── typesafeconfigserver.conf │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── typesafeconfig │ │ │ └── util │ │ │ └── TypesafeConfigUtilTest.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ └── riposte │ │ └── typesafeconfig │ │ └── util │ │ └── TypesafeConfigUtil.java └── build.gradle ├── .gitignore ├── .codecov.yml ├── riposte-async-http-client2 ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── client │ │ │ └── asynchttp │ │ │ └── AsyncResponseHandler.java │ └── test │ │ └── java │ │ └── com │ │ └── nike │ │ └── riposte │ │ └── testutils │ │ └── Whitebox.java └── build.gradle ├── riposte-async-http-client ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── client │ │ │ └── asynchttp │ │ │ └── ning │ │ │ └── AsyncResponseHandler.java │ └── test │ │ └── java │ │ └── com │ │ └── nike │ │ └── riposte │ │ └── testutils │ │ └── Whitebox.java └── build.gradle ├── settings.gradle ├── NOTICE.txt ├── riposte-guice ├── build.gradle └── src │ ├── test │ └── java │ │ └── com │ │ └── nike │ │ └── guice │ │ └── PropertiesRegistrationGuiceModuleTest.java │ └── main │ └── java │ └── com │ └── nike │ ├── backstopper │ └── handler │ │ └── riposte │ │ └── config │ │ └── guice │ │ └── BackstopperRiposteConfigGuiceModule.java │ └── guice │ └── PropertiesRegistrationGuiceModule.java ├── riposte-servlet-api-adapter ├── build.gradle └── src │ └── test │ └── java │ └── com │ └── nike │ └── riposte │ └── testutils │ └── Whitebox.java ├── riposte-metrics-codahale ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── metrics │ │ │ └── codahale │ │ │ ├── contrib │ │ │ ├── DefaultJMXReporterFactory.java │ │ │ ├── DefaultConsoleReporterFactory.java │ │ │ ├── RiposteGraphiteReporterFactory.java │ │ │ ├── DefaultSLF4jReporterFactory.java │ │ │ └── DefaultGraphiteReporterFactory.java │ │ │ └── ReporterFactory.java │ └── test │ │ ├── java │ │ └── com │ │ │ └── nike │ │ │ └── riposte │ │ │ └── metrics │ │ │ └── codahale │ │ │ └── contrib │ │ │ └── ReporterFactoryInstanceTest.java │ │ └── resources │ │ └── logback.xml └── build.gradle ├── riposte-auth └── build.gradle ├── .github └── workflows │ └── build.yml ├── riposte-metrics-codahale-signalfx ├── build.gradle └── src │ └── test │ └── java │ └── com │ └── nike │ └── riposte │ └── testutils │ └── Whitebox.java └── CONTRIBUTING.md /riposte-spi/src/test/resources/slf4jtest.properties: -------------------------------------------------------------------------------- 1 | print.level=DEBUG -------------------------------------------------------------------------------- /riposte-core/src/test/resources/slf4jtest.properties: -------------------------------------------------------------------------------- 1 | print.level=DEBUG -------------------------------------------------------------------------------- /riposte-core/src/test/resources/testMultipartFile.txt: -------------------------------------------------------------------------------- 1 | I am a multipart file -------------------------------------------------------------------------------- /riposte_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nike-Inc/riposte/HEAD/riposte_logo.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=0.20.2-SNAPSHOT 2 | groupId=com.nike.riposte 3 | artifactId=riposte 4 | -------------------------------------------------------------------------------- /riposte-guice-typesafe-config/src/test/resources/testconfig.conf: -------------------------------------------------------------------------------- 1 | foo=bar 2 | someList=[foo, bar] -------------------------------------------------------------------------------- /samples/sample-1-helloworld/buildSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew clean build" 2 | ../../gradlew clean build -------------------------------------------------------------------------------- /riposte-service-registration-eureka/runLocalEurekaWithDocker.sh: -------------------------------------------------------------------------------- 1 | docker run -p 7080:8080 netflixoss/eureka:1.3.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nike-Inc/riposte/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/vagrant/eureka-server-test.properties: -------------------------------------------------------------------------------- 1 | eureka.enableSelfPreservation=false 2 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/runSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew run" 2 | echo "NOTE: Type ctrl+c to stop" 3 | ../../gradlew run $* -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/buildSample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "../../gradlew clean build" 4 | ../../gradlew clean build -------------------------------------------------------------------------------- /riposte-archaius/src/test/resources/archaiusserver-compiletimetest.properties: -------------------------------------------------------------------------------- 1 | archaiusServer.foo=overridevalue 2 | netty.leakDetectionLevel=PARANOID -------------------------------------------------------------------------------- /riposte-core/src/test/resources/helloWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nike-Inc/riposte/HEAD/riposte-core/src/test/resources/helloWorld.png -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/runSample.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "./gradlew run" 3 | echo "NOTE: Type ctrl+c to stop" 4 | ../../gradlew run $* -------------------------------------------------------------------------------- /riposte-typesafe-config/src/test/resources/typesafeconfigserver-compiletimetest.conf: -------------------------------------------------------------------------------- 1 | typesafeConfigServer.foo=overridevalue 2 | netty.leakDetectionLevel=PARANOID -------------------------------------------------------------------------------- /riposte-typesafe-config/src/test/resources/typesafeconfigserver.conf: -------------------------------------------------------------------------------- 1 | typesafeConfigServer.foo=basevalue 2 | debugActionsEnabled=true 3 | netty.leakDetectionLevel=ADVANCED -------------------------------------------------------------------------------- /riposte-archaius/src/test/resources/archaiusserver.properties: -------------------------------------------------------------------------------- 1 | archaiusServer.foo=basevalue 2 | debugActionsEnabled=true 3 | someCollection=foo,bar 4 | netty.leakDetectionLevel=ADVANCED -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/vagrant/eureka-client-test.properties: -------------------------------------------------------------------------------- 1 | eureka.us-east-1.availabilityZones=default 2 | 3 | eureka.serviceUrl.defaultZone=http://localhost:8080/eureka/v2/ 4 | eureka.serviceUrl.default.defaultZone=http://localhost:8080/eureka/v2/ -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/header/accept/MediaRangeType.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | /** 4 | * A marker interface for modeling the top-level component of a MediaRange 5 | * 6 | * @author Kirk Peterson 7 | */ 8 | public interface MediaRangeType {} 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle stuff 2 | .gradle 3 | build/ 4 | gradle-app.setting 5 | 6 | ### IntelliJ stuff 7 | .idea/ 8 | *.iml 9 | *.ipr 10 | *.iws 11 | out/ 12 | classes/ 13 | .idea_modules/ 14 | 15 | ### Eclipse stuff 16 | .project 17 | .settings 18 | .classpath 19 | 20 | ### Misc stuff 21 | .vagrant 22 | .DS_Store -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/header/accept/MediaRangeSubType.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | /** 4 | * A marker interface for modeling the subtype component of a MediaRange. 5 | * 6 | * @author Kirk Peterson 7 | */ 8 | public interface MediaRangeSubType {} 9 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Controls the behavior of the codecov.io integration. 2 | # See https://github.com/codecov/support/wiki/Codecov-Yaml for details on the options. 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | enabled: yes 8 | target: 85% 9 | patch: 10 | default: 11 | enabled: yes 12 | target: 90% 13 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/InvalidHttpRequestException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | public class InvalidHttpRequestException extends RuntimeException { 4 | 5 | public InvalidHttpRequestException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/metrics/ServerMetricsEvent.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.metrics; 2 | 3 | /** 4 | * Server metric events. 5 | */ 6 | public enum ServerMetricsEvent { 7 | REQUEST_RECEIVED, RESPONSE_SENT, 8 | // TODO: This should be removed (see todos in ChannelPipelineFinalizerHandler) 9 | RESPONSE_WRITE_FAILED 10 | } 11 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "ubuntu/trusty64" 3 | config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-i386-vagrant-disk1.box" 4 | config.vm.provision "shell", path: "src/vagrant/provision_eureka.sh" 5 | config.vm.network "forwarded_port", guest: 8080, host: 7080 6 | end -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/hooks/PreServerStartupHook.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.hooks; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import io.netty.bootstrap.ServerBootstrap; 6 | 7 | /** 8 | * Hook for pre server startup events. 9 | */ 10 | @FunctionalInterface 11 | public interface PreServerStartupHook { 12 | 13 | void executePreServerStartupHook(@NotNull ServerBootstrap bootstrap); 14 | } 15 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/main/java/com/nike/riposte/serviceregistration/eureka/EurekaException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.serviceregistration.eureka; 2 | 3 | /** 4 | * Encapsulates Eureka related exception. 5 | */ 6 | @SuppressWarnings("WeakerAccess") 7 | public class EurekaException extends RuntimeException { 8 | 9 | public EurekaException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/hooks/PipelineCreateHook.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.hooks; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import io.netty.channel.ChannelPipeline; 6 | 7 | /** 8 | * Hook for pipeline create events - allows to modify the pipeline before the channel is initialized. 9 | */ 10 | @FunctionalInterface 11 | public interface PipelineCreateHook { 12 | 13 | void executePipelineCreateHook(@NotNull ChannelPipeline pipeline); 14 | } 15 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/HostnameResolutionException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * Thrown when a hostname resolution attempt fails. Should usually map to a 503 HTTP status code. 5 | * 6 | * @author Nic Munroe 7 | */ 8 | public class HostnameResolutionException extends RuntimeException { 9 | 10 | public HostnameResolutionException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/PathNotFound404Exception.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * Thrown when a request tries to hit an endpoint that doesn't exist (no matching path). Represents a HTTP 404 response 5 | * code. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | public class PathNotFound404Exception extends RuntimeException { 10 | 11 | public PathNotFound404Exception(String message) { 12 | super(message); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/hooks/ServerShutdownHook.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.hooks; 2 | 3 | import com.nike.riposte.server.config.ServerConfig; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import io.netty.channel.Channel; 8 | 9 | /** 10 | * Hook for server shutdown events. 11 | */ 12 | @FunctionalInterface 13 | public interface ServerShutdownHook { 14 | 15 | void executeServerShutdownHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel); 16 | } 17 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/hooks/PostServerStartupHook.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.hooks; 2 | 3 | import com.nike.riposte.server.config.ServerConfig; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import io.netty.channel.Channel; 8 | 9 | /** 10 | * Hook for post server startup events. 11 | */ 12 | @FunctionalInterface 13 | public interface PostServerStartupHook { 14 | 15 | void executePostServerStartupHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel); 16 | } 17 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/backstopper/handler/riposte/config/BackstopperRiposteConfigHelperTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.riposte.config; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * Tests the functionality of {@link BackstopperRiposteConfigHelper}. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public class BackstopperRiposteConfigHelperTest { 11 | 12 | @Test 13 | public void code_coverage_hoops() { 14 | // jump! 15 | new BackstopperRiposteConfigHelper(); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /riposte-async-http-client2/src/main/java/com/nike/riposte/client/asynchttp/AsyncResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.client.asynchttp; 2 | 3 | import org.asynchttpclient.Response; 4 | 5 | /** 6 | * Interface representing a handler for an async downstream HTTP call's response. Used by {@link AsyncHttpClientHelper}. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public interface AsyncResponseHandler { 11 | 12 | /** 13 | * @return The result of handling the given response. 14 | */ 15 | T handleResponse(Response response) throws Throwable; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /riposte-async-http-client/src/main/java/com/nike/riposte/client/asynchttp/ning/AsyncResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.client.asynchttp.ning; 2 | 3 | import com.ning.http.client.Response; 4 | 5 | /** 6 | * Interface representing a handler for an async downstream HTTP call's response. Used by {@link AsyncHttpClientHelper}. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public interface AsyncResponseHandler { 11 | 12 | /** 13 | * @return The result of handling the given response. 14 | */ 15 | T handleResponse(Response response) throws Throwable; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/handler/ErrorResponseBodyTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Tests the default methods of {@link ErrorResponseBody}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class ErrorResponseBodyTest { 13 | @Test 14 | public void bodyToSerialize_returns_same_instance_by_default() { 15 | ErrorResponseBody instance = () -> "someErrorId"; 16 | assertThat(instance.bodyToSerialize()).isSameAs(instance); 17 | } 18 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'riposte' 2 | include "riposte-spi", 3 | "riposte-core", 4 | "riposte-guice", 5 | "riposte-typesafe-config", 6 | "riposte-guice-typesafe-config", 7 | "riposte-archaius", 8 | "riposte-servlet-api-adapter", 9 | "riposte-auth", 10 | "riposte-metrics-codahale", 11 | "riposte-metrics-codahale-signalfx", 12 | "riposte-async-http-client", 13 | "riposte-async-http-client2", 14 | "riposte-service-registration-eureka", 15 | "samples:sample-1-helloworld", 16 | "samples:sample-2-kotlin-todoservice" 17 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/http/ProcessingState.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http; 2 | 3 | /** 4 | * Interface describing what methods need to be available for a processing state object. 5 | * 6 | * @author Nic Munroe 7 | */ 8 | public interface ProcessingState { 9 | 10 | /** 11 | * Calling this will clean this object's state for a new request. Implementations should do anything necessary to 12 | * properly close/flush/clean/remove/delete/etc any state that might be stale leftover state from a previous 13 | * request. 14 | */ 15 | void cleanStateForNewRequest(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | Riposte 3 | ======= 4 | 5 | Licenses for individual components of this project can be found in a 6 | LICENSE..txt file, located in the 'license' directory at the 7 | root of the project. See those files for the terms of the license for 8 | each component. 9 | 10 | ------------------------------------------------------------------------------- 11 | 12 | This product contains a modified portion of 'Spring Framework', a Java 13 | library, which can be obtained at: 14 | 15 | * LICENSE: 16 | * license/LICENSE.springframework.txt (Apache License 2.0) 17 | * HOMEPAGE: 18 | * https://github.com/spring-projects/spring-framework/ 19 | -------------------------------------------------------------------------------- /riposte-core/src/test/java/com/nike/riposte/server/handler/base/PipelineContinuationBehaviorTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.handler.base; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | 8 | public class PipelineContinuationBehaviorTest { 9 | 10 | @Test 11 | public void exercise_enum_methods_because_code_coverage_tools_are_stupid() { 12 | for (PipelineContinuationBehavior enumValue : PipelineContinuationBehavior.values()) { 13 | assertThat(PipelineContinuationBehavior.valueOf(enumValue.name()), is(enumValue)); 14 | } 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/handler/base/PipelineContinuationBehavior.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.handler.base; 2 | 3 | /** 4 | * Enum that can be used to indicate whether you want the pipeline to continue propagating a given event, or whether you 5 | * want that event to stop. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | public enum PipelineContinuationBehavior { 10 | /** 11 | * Use this when you want an event to continue being propagated to handlers further down the pipeline. 12 | */ 13 | CONTINUE, 14 | /** 15 | * Use this when you want an event to stop being propagated. 16 | */ 17 | DO_NOT_FIRE_CONTINUE_EVENT; 18 | } 19 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/ChunkedOutboundMessage.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | /** 4 | * Marker interface for chunked messages - this does not represent a full response, but rather a single part of a 5 | * multi-chunk response. It might be the first or last chunk, or something in the middle. 6 | *

7 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 8 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public interface ChunkedOutboundMessage extends OutboundMessage { 13 | } 14 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/UnexpectedMajorErrorHandlingError.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.error.handler.RiposteErrorHandler; 4 | 5 | /** 6 | * Typed exception used by {@link RiposteErrorHandler} to indicate that some unexpected (and major) error occurred while 7 | * handling an error. Likely indicates a bug in the error handler that needs to be fixed. 8 | * 9 | * @author Nic Munroe 10 | */ 11 | public class UnexpectedMajorErrorHandlingError extends Exception { 12 | 13 | public UnexpectedMajorErrorHandlingError(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/LastOutboundMessage.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | /** 4 | * Marker interface that indicates this is the last message that will be going through the pipeline related to the 5 | * current request, so any cleanup should be performed (e.g. distributed trace ending, access logging, request metrics, 6 | * etc). 7 | *

8 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 9 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public interface LastOutboundMessage extends OutboundMessage { 14 | } 15 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/OutboundMessage.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | /** 4 | * Marker interface to be used as the message object with the various {@code 5 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods once the correct endpoint handler has processed a 6 | * request and is ready to start sending response info back to the user. 7 | *

8 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 9 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public interface OutboundMessage { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /riposte-guice/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-spi"), 6 | "com.google.inject:guice:$guiceVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "com.nike.backstopper:backstopper-reusable-tests:$backstopperVersion" 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/util/MatcherUtil.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class MatcherUtil { 6 | 7 | // Intentionally protected - use the static methods 8 | protected MatcherUtil() { /* do nothing */ } 9 | 10 | public static String stripEndSlash(@NotNull String path) { 11 | if (path.endsWith("/")) { 12 | if (path.length() == 1) { 13 | // The path is only a slash. i.e. it's the root path. In that case we don't want to strip the slash. 14 | return path; 15 | } 16 | 17 | return path.substring(0, path.length() - 1); 18 | } else { 19 | return path; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/PathParameterMatchingException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * Thrown when the server tries to decode path parameters for a request, but the request path doesn't match the path 5 | * template. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | public class PathParameterMatchingException extends RuntimeException { 10 | 11 | public final String pathTemplate; 12 | public final String nonMatchingUriPath; 13 | 14 | public PathParameterMatchingException(String message, String pathTemplate, String nonMatchingUriPath) { 15 | super(message); 16 | 17 | this.pathTemplate = pathTemplate; 18 | this.nonMatchingUriPath = nonMatchingUriPath; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/MethodNotAllowed405Exception.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * Thrown when a request's path matches an endpoint's path, but that endpoint doesn't want to handle the request's HTTP 5 | * method. Represents a HTTP 405 response code. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | public class MethodNotAllowed405Exception extends RuntimeException { 10 | 11 | public final String requestPath; 12 | public final String requestMethod; 13 | 14 | public MethodNotAllowed405Exception(String message, String requestPath, String requestMethod) { 15 | super(message); 16 | 17 | this.requestPath = requestPath; 18 | this.requestMethod = requestMethod; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /riposte-guice-typesafe-config/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-guice"), 6 | "com.typesafe:config:$typesafeConfigVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "org.assertj:assertj-core:$assertJVersion", 18 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /riposte-servlet-api-adapter/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-spi"), 6 | "javax.servlet:javax.servlet-api:$servletApiVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "org.assertj:assertj-core:$assertJVersion", 18 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/PathNotFound404ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.PathNotFound404Exception} 12 | */ 13 | public class PathNotFound404ExceptionTest { 14 | 15 | @Test 16 | public void should_honor_constructor_params() { 17 | //given 18 | String message = UUID.randomUUID().toString(); 19 | 20 | //when 21 | PathNotFound404Exception ex = new PathNotFound404Exception(message); 22 | 23 | //then 24 | assertThat(ex.getMessage(), is(message)); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/NonblockingEndpointCompletableFutureTimedOut.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * This will be thrown when a nonblocking endpoint's {@link java.util.concurrent.CompletableFuture} fails to complete 5 | * within the necessary amount of time. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | public class NonblockingEndpointCompletableFutureTimedOut extends RuntimeException { 10 | 11 | public final long timeoutValueMillis; 12 | 13 | public NonblockingEndpointCompletableFutureTimedOut(long timeoutValueMillis) { 14 | super("The CompletableFuture was cancelled because it was taking too long. " 15 | + "completable_future_timeout_value_millis=" + timeoutValueMillis); 16 | this.timeoutValueMillis = timeoutValueMillis; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/README.md: -------------------------------------------------------------------------------- 1 | # Riposte Sample Application - Kotlin-based TODO Server 2 | 3 | * Build the sample by running the `./buildSample.sh` script. 4 | * Launch the sample by running the `./runSample.sh` script. It will bind to port 8080. 5 | 6 | ## Things to try 7 | 8 | ### Create A new TODO Item 9 | 10 | `curl -X POST -v http://localhost:8080/todos -d '{ "name":"Amit", "task":"My task" }'` 11 | 12 | ### GET THE new TODO Item 13 | 14 | `curl -v http://localhost:8080/todos/1` 15 | 16 | ### Update the new TODO Item 17 | `curl -X PUT -v http://localhost:8080/todos/1 -d '{"id":1, "name":"Amit", "task":"My task altered" }'` 18 | 19 | ### Delete the new TODO Item 20 | 21 | `curl -v -X DELETE http://localhost:8080/todos/1` 22 | 23 | ## License 24 | 25 | Riposte is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 26 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/test/java/com/nike/riposte/serviceregistration/eureka/EurekaExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.serviceregistration.eureka; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.mockito.Mockito.mock; 9 | 10 | /** 11 | * Tests the functionality of {@link EurekaException}. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class EurekaExceptionTest { 16 | 17 | @Test 18 | public void constructor_works_as_expected() { 19 | // given 20 | String msg = UUID.randomUUID().toString(); 21 | Throwable cause = mock(Throwable.class); 22 | 23 | // when 24 | EurekaException ex = new EurekaException(msg, cause); 25 | 26 | // then 27 | assertThat(ex).hasMessage(msg).hasCause(cause); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/NonblockingEndpointCompletableFutureTimedOutTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | 8 | /** 9 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.NonblockingEndpointCompletableFutureTimedOut} 10 | */ 11 | public class NonblockingEndpointCompletableFutureTimedOutTest { 12 | 13 | @Test 14 | public void should_honor_constructor_params() { 15 | //given 16 | long timeoutValue = 42; 17 | 18 | //when 19 | NonblockingEndpointCompletableFutureTimedOut ex = new NonblockingEndpointCompletableFutureTimedOut(timeoutValue); 20 | 21 | //then 22 | assertThat(ex.timeoutValueMillis, is(timeoutValue)); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/InvalidCharsetInContentTypeHeaderException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * This will be thrown when the server has trouble extracting the charset from the Content-Type header. The server falls 5 | * back gracefully if the charset is not defined, so this exception is only when the specified charset is truly 6 | * invalid. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public class InvalidCharsetInContentTypeHeaderException extends RuntimeException { 11 | 12 | public final String invalidContentTypeHeader; 13 | 14 | public InvalidCharsetInContentTypeHeaderException(String message, Throwable cause, 15 | String invalidContentTypeHeader) { 16 | super(message, cause); 17 | this.invalidContentTypeHeader = invalidContentTypeHeader; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/contrib/DefaultJMXReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.nike.riposte.metrics.codahale.ReporterFactory; 4 | 5 | import com.codahale.metrics.MetricRegistry; 6 | import com.codahale.metrics.Reporter; 7 | import com.codahale.metrics.jmx.JmxReporter; 8 | 9 | /** 10 | * @author pevans 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public class DefaultJMXReporterFactory implements ReporterFactory { 14 | 15 | private Reporter reporter; 16 | 17 | @Override 18 | public synchronized Reporter getReporter(MetricRegistry registry) { 19 | if (null == reporter) { 20 | reporter = JmxReporter.forRegistry(registry).build(); 21 | } 22 | return reporter; 23 | } 24 | 25 | @Override 26 | public boolean isScheduled() { 27 | return false; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/util/MatcherUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Tests the functionality of {@link MatcherUtil} 9 | */ 10 | public class MatcherUtilTest { 11 | 12 | @Test 13 | public void code_coverage_hoops() { 14 | // jump! 15 | new MatcherUtil(); 16 | } 17 | 18 | @Test 19 | public void stripEndSlash_should_strip_an_end_slash() { 20 | String path = "/something/ending/in/slash/"; 21 | assertThat(MatcherUtil.stripEndSlash(path)).isEqualTo("/something/ending/in/slash"); 22 | } 23 | 24 | @Test 25 | public void stripEndSlash_does_nothing_if_path_is_root_path() { 26 | // when 27 | String result = MatcherUtil.stripEndSlash("/"); 28 | 29 | // then 30 | assertThat(result).isEqualTo("/"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /riposte-auth/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-spi"), 6 | ) 7 | compileOnly( 8 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 9 | ) 10 | testImplementation ( 11 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 12 | "org.assertj:assertj-core:$assertJVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "commons-codec:commons-codec:$apacheCommonsCodecVersion", 18 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 19 | "ch.qos.logback:logback-classic:$logbackVersion", 20 | "ch.qos.logback:logback-core:$logbackVersion" 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/InvalidRipostePipelineException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * This will be thrown when the server detects an invalid state that should be prevented by a proper Riposte handler 5 | * pipeline. This represents a major unrecoverable error. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | @SuppressWarnings("WeakerAccess") 10 | public class InvalidRipostePipelineException extends RuntimeException { 11 | 12 | public InvalidRipostePipelineException() { 13 | super(); 14 | } 15 | 16 | public InvalidRipostePipelineException(String message) { 17 | super(message); 18 | } 19 | 20 | public InvalidRipostePipelineException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public InvalidRipostePipelineException(Throwable cause) { 25 | super(cause); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | implementation( 5 | project(":riposte-core"), 6 | "ch.qos.logback:logback-classic:$logbackVersion", 7 | "ch.qos.logback:logback-core:$logbackVersion" 8 | ) 9 | compileOnly( 10 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 11 | ) 12 | testImplementation ( 13 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 14 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 15 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 16 | "junit:junit:$junitVersion", 17 | "org.mockito:mockito-core:$mockitoVersion", 18 | "io.rest-assured:rest-assured:$restAssuredVersion" 19 | ) 20 | } 21 | 22 | apply plugin: "application" 23 | mainClassName = "com.nike.Main" 24 | 25 | run { 26 | systemProperties = System.getProperties() 27 | } 28 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/LastOutboundMessageSendLastContentChunk.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | import io.netty.handler.codec.http.LastHttpContent; 4 | 5 | /** 6 | * Implementation of {@link LastOutboundMessage} intended to be used with multi-chunk/streamed responses - indicates 7 | * this is the final chunk to send to the user and no further response chunks will be coming for the current request. 8 | *

9 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 10 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 11 | * 12 | * @author Nic Munroe 13 | */ 14 | public class LastOutboundMessageSendLastContentChunk extends OutboundMessageSendContentChunk 15 | implements LastOutboundMessage { 16 | 17 | public LastOutboundMessageSendLastContentChunk(LastHttpContent contentChunk) { 18 | super(contentChunk); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/UnexpectedMajorErrorHandlingErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.UnexpectedMajorErrorHandlingError} 12 | */ 13 | public class UnexpectedMajorErrorHandlingErrorTest { 14 | 15 | @Test 16 | public void should_honor_constructor_params() { 17 | //given 18 | String message = UUID.randomUUID().toString(); 19 | Throwable cause = new Exception("kaboom"); 20 | 21 | //when 22 | UnexpectedMajorErrorHandlingError ex = new UnexpectedMajorErrorHandlingError(message, cause); 23 | 24 | //then 25 | assertThat(ex.getMessage(), is(message)); 26 | assertThat(ex.getCause(), is(cause)); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /riposte-typesafe-config/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-core"), 6 | "com.typesafe:config:$typesafeConfigVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "io.rest-assured:rest-assured:$restAssuredVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 20 | "ch.qos.logback:logback-classic:$logbackVersion", 21 | "ch.qos.logback:logback-core:$logbackVersion" 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /riposte-async-http-client/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-core"), 6 | "com.ning:async-http-client:$ningAsyncHttpClientVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 10 | ) 11 | testImplementation ( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "io.rest-assured:rest-assured:$restAssuredVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 20 | "ch.qos.logback:logback-classic:$logbackVersion", 21 | "ch.qos.logback:logback-core:$logbackVersion" 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /riposte-async-http-client2/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-core"), 6 | "org.asynchttpclient:async-http-client:$asyncHttpClientVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 10 | ) 11 | testImplementation( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 14 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "io.rest-assured:rest-assured:$restAssuredVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 20 | "ch.qos.logback:logback-classic:$logbackVersion", 21 | "ch.qos.logback:logback-core:$logbackVersion" 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /riposte-archaius/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-core"), 6 | "com.netflix.archaius:archaius-core:$archaiusVersion", 7 | "commons-configuration:commons-configuration:$apacheCommonsConfigurationVersion" 8 | ) 9 | compileOnly( 10 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 11 | ) 12 | testImplementation ( 13 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 14 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 15 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 16 | "junit:junit:$junitVersion", 17 | "org.mockito:mockito-core:$mockitoVersion", 18 | "io.rest-assured:rest-assured:$restAssuredVersion", 19 | "org.assertj:assertj-core:$assertJVersion", 20 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 21 | "ch.qos.logback:logback-classic:$logbackVersion", 22 | "ch.qos.logback:logback-core:$logbackVersion" 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/Forbidden403ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.UUID; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | 11 | public class Forbidden403ExceptionTest { 12 | 13 | @Before 14 | public void setUp() throws Exception { 15 | } 16 | 17 | @Test 18 | public void should_honor_constructor_params() { 19 | //given 20 | String requestPath = UUID.randomUUID().toString(); 21 | String authorizationHeader = UUID.randomUUID().toString(); 22 | String message = UUID.randomUUID().toString(); 23 | 24 | //when 25 | Forbidden403Exception ex = new Forbidden403Exception(message, requestPath, authorizationHeader); 26 | 27 | //then 28 | assertThat(ex.getMessage(), is(message)); 29 | assertThat(ex.requestPath, is(requestPath)); 30 | assertThat(ex.authorizationHeader, is(authorizationHeader)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/RequestContentDeserializationException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | 7 | /** 8 | * Thrown when the server is unable to deserialize request content, usually because it is malformed (e.g. invalid JSON). 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class RequestContentDeserializationException extends RuntimeException { 13 | 14 | public final String httpMethod; 15 | public final String requestPath; 16 | public final TypeReference desiredObjectType; 17 | 18 | public RequestContentDeserializationException(String exceptionMessage, Throwable cause, RequestInfo requestInfo, 19 | TypeReference desiredObjectType) { 20 | super(exceptionMessage, cause); 21 | this.httpMethod = String.valueOf(requestInfo.getMethod()); 22 | this.requestPath = requestInfo.getPath(); 23 | this.desiredObjectType = desiredObjectType; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | java-version: 11 19 | distribution: 'temurin' 20 | - name: Validate Gradle wrapper 21 | uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b 22 | - name: Build with Gradle 23 | run: ./gradlew clean build 24 | - name: Upload coverage report to CodeCov 25 | uses: codecov/codecov-action@v2 26 | with: 27 | files: build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml 28 | fail_ci_if_error: true 29 | verbose: true 30 | - name: Upload reports and test results to GitHub 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: reports-and-test-results 34 | path: | 35 | build/reports/ 36 | build/test-results/ 37 | -------------------------------------------------------------------------------- /riposte-metrics-codahale-signalfx/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-metrics-codahale"), 6 | "com.signalfx.public:signalfx-codahale:$signalFxCodahaleVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | project(":riposte-guice"), 13 | project(":riposte-core").sourceSets.test.output, 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "org.assertj:assertj-core:$assertJVersion", 16 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 17 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 18 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 19 | "junit:junit:$junitVersion", 20 | "org.mockito:mockito-core:$mockitoVersion", 21 | "io.rest-assured:rest-assured:$restAssuredVersion", 22 | "ch.qos.logback:logback-classic:$logbackVersion", 23 | "ch.qos.logback:logback-core:$logbackVersion" 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/PathParameterMatchingExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.PathParameterMatchingException} 12 | */ 13 | public class PathParameterMatchingExceptionTest { 14 | 15 | @Test 16 | public void should_honor_constructor_params() { 17 | //given 18 | String message = UUID.randomUUID().toString(); 19 | String pathTemplate = UUID.randomUUID().toString(); 20 | String nonMatchingPath = UUID.randomUUID().toString(); 21 | 22 | //when 23 | PathParameterMatchingException ex = new PathParameterMatchingException(message, pathTemplate, nonMatchingPath); 24 | 25 | //then 26 | assertThat(ex.getMessage(), is(message)); 27 | assertThat(ex.pathTemplate, is(pathTemplate)); 28 | assertThat(ex.nonMatchingUriPath, is(nonMatchingPath)); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MethodNotAllowed405ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.MethodNotAllowed405Exception} 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class MethodNotAllowed405ExceptionTest { 16 | 17 | @Test 18 | public void should_honor_constructor_params() { 19 | //given 20 | String requestPath = UUID.randomUUID().toString(); 21 | String requestMethod = UUID.randomUUID().toString(); 22 | String message = UUID.randomUUID().toString(); 23 | 24 | //when 25 | MethodNotAllowed405Exception ex = new MethodNotAllowed405Exception(message, requestPath, requestMethod); 26 | 27 | //then 28 | assertThat(ex.getMessage(), is(message)); 29 | assertThat(ex.requestPath, is(requestPath)); 30 | assertThat(ex.requestMethod, is(requestMethod)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/contrib/ReporterFactoryInstanceTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.Reporter; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertNotNull; 10 | 11 | public class ReporterFactoryInstanceTest { 12 | 13 | @Before 14 | public void setUp() throws Exception { 15 | } 16 | 17 | @Test 18 | public void test() { 19 | MetricRegistry registry = new MetricRegistry(); 20 | Reporter r = new DefaultConsoleReporterFactory().getReporter(registry); 21 | assertNotNull(r); 22 | r = new DefaultJMXReporterFactory().getReporter(registry); 23 | assertNotNull(r); 24 | r = new DefaultSLF4jReporterFactory().getReporter(registry); 25 | assertNotNull(r); 26 | r = new DefaultGraphiteReporterFactory("test", "fakeurl.com", 4242).getReporter(registry); 27 | assertNotNull(r); 28 | r = new RiposteGraphiteReporterFactory("test", "fakeurl.com", 4242).getReporter(registry); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/OutboundMessageSendHeadersChunkFromResponseInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | /** 4 | * Implementation of {@link OutboundMessage} that indicates to the response sender that it should send the first 5 | * response info data back to the user (the first chunk with headers/etc). Since all the necessary info (headers & 6 | * status code) is contained in the current {@link com.nike.riposte.server.http.HttpProcessingState#getResponseInfo()} 7 | * this message does not need to store anything. Users of this class should just refer to {@link #INSTANCE} to avoid 8 | * creating unnecessary objects. 9 | *

10 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 11 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class OutboundMessageSendHeadersChunkFromResponseInfo implements ChunkedOutboundMessage { 16 | 17 | public static final OutboundMessageSendHeadersChunkFromResponseInfo INSTANCE = 18 | new OutboundMessageSendHeadersChunkFromResponseInfo(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /riposte-guice-typesafe-config/src/main/java/com/nike/guice/typesafeconfig/TypesafeConfigPropertiesRegistrationGuiceModule.java: -------------------------------------------------------------------------------- 1 | package com.nike.guice.typesafeconfig; 2 | 3 | import com.nike.guice.PropertiesRegistrationGuiceModule; 4 | import com.nike.internal.util.Pair; 5 | 6 | import com.typesafe.config.Config; 7 | 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * An implementation of {@link PropertiesRegistrationGuiceModule} that gets its properties map from Typesafe Config. 13 | * 14 | * @author Nic Munroe 15 | */ 16 | @SuppressWarnings("WeakerAccess") 17 | public class TypesafeConfigPropertiesRegistrationGuiceModule extends PropertiesRegistrationGuiceModule { 18 | 19 | private final Config config; 20 | 21 | public TypesafeConfigPropertiesRegistrationGuiceModule(Config config) { 22 | this.config = config; 23 | } 24 | 25 | @Override 26 | protected Map getPropertiesMap() { 27 | return config.entrySet().stream() 28 | .map((entry) -> Pair.of(entry.getKey(), entry.getValue().unwrapped().toString())) 29 | .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/trace/netty/RequestWithHeadersNettyAdapter.java: -------------------------------------------------------------------------------- 1 | package com.nike.trace.netty; 2 | 3 | import com.nike.wingtips.http.RequestWithHeaders; 4 | 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.handler.codec.http.HttpRequest; 7 | 8 | /** 9 | * Adapter for {@link HttpRequest} to be used as {@link RequestWithHeaders}. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class RequestWithHeadersNettyAdapter implements RequestWithHeaders { 14 | 15 | @SuppressWarnings("WeakerAccess") 16 | protected final HttpRequest httpRequest; 17 | 18 | public RequestWithHeadersNettyAdapter(HttpRequest httpRequest) { 19 | if (httpRequest == null) 20 | throw new IllegalArgumentException("httpRequest cannot be null"); 21 | 22 | this.httpRequest = httpRequest; 23 | } 24 | 25 | @Override 26 | public String getHeader(String headerName) { 27 | HttpHeaders headers = httpRequest.headers(); 28 | if (headers == null) 29 | return null; 30 | 31 | return headers.get(headerName); 32 | } 33 | 34 | @Override 35 | public Object getAttribute(String name) { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/NativeIoExceptionWrapper.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Thrown when we receive a {@code io.netty.channel.unix.Errors.NativeIoException} so that a usable stack trace shows 7 | * up in the logs ({@code NativeIoException}s are often setup to suppress their stack trace, which makes debugging 8 | * difficult). Should usually map to a 503 HTTP status code. 9 | * 10 | *

NOTE: The {@link NativeIoExceptionWrapper#NativeIoExceptionWrapper(String, IOException)} constructor takes in a 11 | * basic {@link IOException} as the cause, but this wrapper class should only be used if the cause is actually a 12 | * {@code io.netty.channel.unix.Errors.NativeIoException}. We pin it to {@link IOException} to avoid pulling in 13 | * the {@code io.netty:netty-transport-native-unix-common} dependency just to be able to reference 14 | * {@code NativeIoException}. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class NativeIoExceptionWrapper extends RuntimeException { 19 | 20 | public NativeIoExceptionWrapper(String message, IOException cause) { 21 | super(message, cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/MissingRequiredContentException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.http.Endpoint; 4 | import com.nike.riposte.server.http.RequestInfo; 5 | 6 | public class MissingRequiredContentException extends RuntimeException { 7 | 8 | public final String path; 9 | public final String method; 10 | public final String endpointClassName; 11 | 12 | public MissingRequiredContentException() { 13 | this("null", "null", "null"); 14 | } 15 | 16 | public MissingRequiredContentException(String path, String method, String endpointClassName) { 17 | this.path = path; 18 | this.method = method; 19 | this.endpointClassName = endpointClassName; 20 | } 21 | 22 | public MissingRequiredContentException(RequestInfo requestInfo, Endpoint endpoint) { 23 | this( 24 | requestInfo == null? "null" : requestInfo.getPath(), 25 | requestInfo == null? "null" : requestInfo.getMethod() == null ? "null" : requestInfo.getMethod().name(), 26 | endpoint == null ? "null" : endpoint.getClass().getSimpleName() 27 | ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/TooManyOpenChannelsException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.config.ServerConfig; 4 | 5 | /** 6 | * This will be thrown when the server detects too many channels are open (based on {@link 7 | * ServerConfig#maxOpenIncomingServerChannels()}). See the javadocs for {@link 8 | * ServerConfig#maxOpenIncomingServerChannels()} for more information on when this exception should be thrown and how 9 | * the server handles it. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class TooManyOpenChannelsException extends RuntimeException { 14 | 15 | public final int actualOpenChannelsCount; 16 | public final int maxOpenChannelsLimit; 17 | 18 | public TooManyOpenChannelsException(int actualOpenChannelsCount, int maxOpenChannelsLimit) { 19 | super("Too many open channels were detected. This new channel will be immediately closed. Current number of " 20 | + "open channels: " + actualOpenChannelsCount + ", max allowed: " + maxOpenChannelsLimit); 21 | this.actualOpenChannelsCount = actualOpenChannelsCount; 22 | this.maxOpenChannelsLimit = maxOpenChannelsLimit; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/OutboundMessageSendContentChunk.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | import io.netty.handler.codec.http.HttpContent; 4 | 5 | /** 6 | * Implementation of {@link OutboundMessage} that indicates to the response sender that it should write the given {@link 7 | * #contentChunk} to the user. This is intended to be used with multi-chunk/streaming responses only. If you are an 8 | * endpoint handler and you have a full response message to send back you should stuff the response info into the 9 | * current {@link com.nike.riposte.server.http.HttpProcessingState} and use {@link 10 | * LastOutboundMessageSendFullResponseInfo} instead. 11 | *

12 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 13 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 14 | * 15 | * @author Nic Munroe 16 | */ 17 | public class OutboundMessageSendContentChunk implements ChunkedOutboundMessage { 18 | 19 | public final HttpContent contentChunk; 20 | 21 | public OutboundMessageSendContentChunk(HttpContent contentChunk) { 22 | this.contentChunk = contentChunk; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/InvalidCharsetInContentTypeHeaderExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.InvalidCharsetInContentTypeHeaderException} 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class InvalidCharsetInContentTypeHeaderExceptionTest { 16 | 17 | @Test 18 | public void should_honor_constructor_params() { 19 | //given 20 | String invalidHeaderValue = UUID.randomUUID().toString(); 21 | String message = UUID.randomUUID().toString(); 22 | Exception cause = new Exception("kaboom"); 23 | 24 | //when 25 | InvalidCharsetInContentTypeHeaderException ex = new InvalidCharsetInContentTypeHeaderException(message, cause, invalidHeaderValue); 26 | 27 | //then 28 | assertThat(ex.getMessage(), is(message)); 29 | assertThat(ex.getCause(), is(cause)); 30 | assertThat(ex.invalidContentTypeHeader, is(invalidHeaderValue)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/DownstreamIdleChannelTimeoutExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import io.netty.channel.Channel; 8 | 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.mockito.Mockito.doReturn; 12 | import static org.mockito.Mockito.mock; 13 | 14 | /** 15 | * Tests the functionality of {@link DownstreamIdleChannelTimeoutException} 16 | * 17 | * @author Nic Munroe 18 | */ 19 | public class DownstreamIdleChannelTimeoutExceptionTest { 20 | 21 | @Test 22 | public void should_honor_constructor_params() { 23 | //given 24 | long timeoutValue = 42; 25 | Channel channelMock = mock(Channel.class); 26 | String uuid = UUID.randomUUID().toString(); 27 | doReturn(uuid).when(channelMock).toString(); 28 | 29 | //when 30 | DownstreamIdleChannelTimeoutException ex = new DownstreamIdleChannelTimeoutException(timeoutValue, channelMock); 31 | 32 | //then 33 | assertThat(ex.timeoutValueMillis, is(timeoutValue)); 34 | assertThat(ex.channelId, is(uuid)); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.http.Endpoint; 4 | 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * Thrown when multiple endpoints want to match a request (with both path and HTTP method matching). 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class MultipleMatchingEndpointsException extends RuntimeException { 14 | 15 | public final List matchingEndpointsDetails; 16 | public final String requestPath; 17 | public final String requestMethod; 18 | 19 | public MultipleMatchingEndpointsException(String message, List> fullyMatchingEndpoints, 20 | String requestPath, String requestMethod) { 21 | super(message); 22 | this.matchingEndpointsDetails = fullyMatchingEndpoints.stream() 23 | .map(e -> e.getClass().getName()) 24 | .collect(Collectors.toList()); 25 | this.requestPath = requestPath; 26 | this.requestMethod = requestMethod; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/Unauthorized401Exception.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.internal.util.Pair; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Thrown when a request does not have a valid authorization header. Represents a HTTP 401 response code. 10 | * 11 | * @author Florin Dragu 12 | */ 13 | public class Unauthorized401Exception extends RuntimeException { 14 | 15 | public final String requestPath; 16 | public final String authorizationHeader; 17 | public final List> extraDetailsForLogging; 18 | 19 | public Unauthorized401Exception(String message, String requestPath, String authorizationHeader) { 20 | this(message, requestPath, authorizationHeader, new ArrayList<>()); 21 | } 22 | 23 | public Unauthorized401Exception(String message, String requestPath, String authorizationHeader, 24 | List> extraDetailsForLogging) { 25 | super(message); 26 | 27 | this.requestPath = requestPath; 28 | this.authorizationHeader = authorizationHeader; 29 | this.extraDetailsForLogging = extraDetailsForLogging; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-spi"), 6 | "com.netflix.eureka:eureka-client:$eurekaClientVersion" 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | ) 11 | testImplementation ( 12 | project(":riposte-guice"), 13 | project(":riposte-core").sourceSets.test.output, 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "org.assertj:assertj-core:$assertJVersion", 16 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 17 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 18 | "junit:junit:$junitVersion", 19 | "org.mockito:mockito-core:$mockitoVersion", 20 | "io.rest-assured:rest-assured:$restAssuredVersion", 21 | "ch.qos.logback:logback-classic:$logbackVersion", 22 | "ch.qos.logback:logback-core:$logbackVersion", 23 | "uk.org.lidalia:slf4j-test:$slf4jTestVersion", 24 | "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion", 25 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/IncompleteHttpCallTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.config.ServerConfig; 4 | 5 | /** 6 | * This will be thrown when the server detects that too much idle time has passed after receiving the first HTTP chunk 7 | * in a call but before the last chunk is received (based on {@link ServerConfig#incompleteHttpCallTimeoutMillis()}. 8 | * See the javadocs for {@link ServerConfig#incompleteHttpCallTimeoutMillis()} for more information on when this 9 | * exception should be thrown and how the server handles it. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class IncompleteHttpCallTimeoutException extends RuntimeException { 14 | 15 | @SuppressWarnings("WeakerAccess") 16 | public final long timeoutMillis; 17 | 18 | public IncompleteHttpCallTimeoutException(long timeoutMillis) { 19 | super("Too much time passed without receiving any HTTP chunks from the caller after starting a request. The " 20 | + "HTTP request is incomplete and invalid, and the caller doesn't seem to be sending any more data. " 21 | + "Timeout value in millis: " + timeoutMillis); 22 | this.timeoutMillis = timeoutMillis; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/contrib/DefaultConsoleReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.nike.riposte.metrics.codahale.ReporterFactory; 4 | 5 | import com.codahale.metrics.ConsoleReporter; 6 | import com.codahale.metrics.MetricRegistry; 7 | import com.codahale.metrics.Reporter; 8 | 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * @author pevans 13 | */ 14 | @SuppressWarnings("WeakerAccess") 15 | public class DefaultConsoleReporterFactory implements ReporterFactory { 16 | 17 | private Reporter reporter; 18 | 19 | @Override 20 | public synchronized Reporter getReporter(MetricRegistry registry) { 21 | if (null == reporter) { 22 | reporter = ConsoleReporter.forRegistry(registry) 23 | .convertRatesTo(TimeUnit.SECONDS) 24 | .convertDurationsTo(TimeUnit.MILLISECONDS) 25 | .build(); 26 | } 27 | return reporter; 28 | } 29 | 30 | @Override 31 | public Long getInterval() { 32 | return 5L; 33 | } 34 | 35 | @Override 36 | public TimeUnit getTimeUnit() { 37 | return TimeUnit.SECONDS; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsListener.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics; 2 | 3 | import com.nike.riposte.server.metrics.ServerMetricsEvent; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Interface for handling {@link ServerMetricsEvent}s. Concrete implementations should track and report on metrics 10 | * around general server statistics and health (e.g. counters for inflight, processed, and failed requests, histograms 11 | * for request and response sizes, etc), as well as per-endpoint metrics. 12 | * 13 | *

Inspired by metrics handling in RxNetty and 14 | * 15 | * Dropwizard metrics Jetty instrumentation 16 | * 17 | */ 18 | public interface MetricsListener { 19 | 20 | /** 21 | * Handle the given event. 22 | * 23 | * @param event The event to handle. 24 | * @param value This should be a {@code HttpProcessingState} object, but may be null depending what happened during 25 | * the request. 26 | */ 27 | void onEvent(@NotNull ServerMetricsEvent event, @Nullable Object value); 28 | } 29 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBodySerializer.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | /** 6 | * Interface for a class that knows how to serialize a {@link ErrorResponseBody} to a string. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | @FunctionalInterface 11 | public interface ErrorResponseBodySerializer { 12 | 13 | /** 14 | * @param errorResponseBody 15 | * The error response body to serialize to a string - this method will use {@link 16 | * ErrorResponseBody#bodyToSerialize()} as the object for serialization. This parameter may be null. 17 | * If this parameter is null or if {@link ErrorResponseBody#bodyToSerialize()} is null then this method will 18 | * return null to indicate a blank response body is desired. 19 | * 20 | * @return The given {@link ErrorResponseBody} after being serialized to a string, or null if a blank response body 21 | * should be returned to the caller (null will be returned if {@link ErrorResponseBody#bodyToSerialize()} or 22 | * the {@code errorResponseBody} parameter itself is null). 23 | */ 24 | @Nullable String serializeErrorResponseBodyToString(@Nullable ErrorResponseBody errorResponseBody); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/contrib/RiposteGraphiteReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.nike.riposte.server.config.ServerConfig; 4 | 5 | import java.util.concurrent.ExecutionException; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @author pevans 10 | */ 11 | public class RiposteGraphiteReporterFactory extends DefaultGraphiteReporterFactory { 12 | 13 | @SuppressWarnings("unused") 14 | public RiposteGraphiteReporterFactory(ServerConfig config, String graphiteURL, Integer graphitePort) 15 | throws ExecutionException, InterruptedException { 16 | 17 | this( 18 | config.appInfo().get().appId() + "." + config.appInfo().get().dataCenter() + "." 19 | + config.appInfo().get().environment() + "." + config.appInfo().get().instanceId(), 20 | graphiteURL, 21 | graphitePort 22 | ); 23 | } 24 | 25 | public RiposteGraphiteReporterFactory(String prefix, String graphiteURL, Integer graphitePort) { 26 | super(prefix, graphiteURL, graphitePort); 27 | } 28 | 29 | @Override 30 | public Long getInterval() { 31 | return 10L; 32 | } 33 | 34 | @Override 35 | public TimeUnit getTimeUnit() { 36 | return TimeUnit.SECONDS; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-core"), 6 | "io.dropwizard.metrics:metrics-core:$codahaleMetricsVersion", 7 | "io.dropwizard.metrics:metrics-jvm:$codahaleMetricsVersion", 8 | "io.dropwizard.metrics:metrics-jmx:$codahaleMetricsVersion", 9 | "io.dropwizard.metrics:metrics-graphite:$codahaleMetricsVersion" 10 | ) 11 | compileOnly( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | ) 14 | testImplementation ( 15 | project(":riposte-guice"), 16 | project(":riposte-core").sourceSets.test.output, 17 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 20 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 21 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 22 | "junit:junit:$junitVersion", 23 | "org.mockito:mockito-core:$mockitoVersion", 24 | "io.rest-assured:rest-assured:$restAssuredVersion", 25 | "ch.qos.logback:logback-classic:$logbackVersion", 26 | "ch.qos.logback:logback-core:$logbackVersion" 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/Forbidden403Exception.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.nike.riposte.server.error.exception; 5 | 6 | import com.nike.internal.util.Pair; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Thrown when a request does not have a valid authorization header. Represents a HTTP 403 response code. 13 | * 14 | * @author pevans 15 | * 16 | */ 17 | public class Forbidden403Exception extends RuntimeException { 18 | 19 | public final String requestPath; 20 | public final String authorizationHeader; 21 | public final List> extraDetailsForLogging; 22 | 23 | public Forbidden403Exception(String message, String requestPath, String authorizationHeader) { 24 | this(message, requestPath, authorizationHeader, new ArrayList<>()); 25 | } 26 | 27 | public Forbidden403Exception(String message, String requestPath, String authorizationHeader, 28 | List> extraDetailsForLogging) { 29 | super(message); 30 | 31 | this.requestPath = requestPath; 32 | this.authorizationHeader = authorizationHeader; 33 | this.extraDetailsForLogging = extraDetailsForLogging; 34 | } 35 | private static final long serialVersionUID = 4921880566299500314L; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/vagrant/provision_eureka.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Taken and modified for ubuntu from 4 | # https://github.com/hudl/fargo/blob/master/provision_eureka.sh 5 | 6 | EUREKA_BIN="https://netflixoss.ci.cloudbees.com/job/eureka-master/lastSuccessfulBuild/artifact/*zip*/archive.zip" 7 | 8 | sysctl -w net.ipv6.conf.all.disable_ipv6=1 9 | 10 | apt-get update 11 | sleep 10 12 | 13 | apt-get install -y unzip tomcat7 htop vim 14 | 15 | echo "127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4" > /etc/hosts 16 | echo " 17 | 18 | 19 | " > /etc/tomcat7/tomcat-users.xml 20 | chown tomcat7:tomcat7 /etc/tomcat7/tomcat-users.xml 21 | chmod 644 /etc/tomcat7/tomcat-users.xml 22 | 23 | curl -s -o /tmp/eureka_archive.zip $EUREKA_BIN 24 | mkdir /tmp/eureka_archive 25 | unzip -o -d /tmp/eureka_archive /tmp/eureka_archive.zip 26 | war=$(find /tmp/eureka_archive -name "*war") 27 | unzip -o -d /var/lib/tomcat7/webapps/eureka $war 28 | rm -rf /tmp/eureka_archive* 29 | 30 | cp /vagrant/src/vagrant/*.properties /var/lib/tomcat7/webapps/eureka/WEB-INF/classes/ 31 | 32 | chown -R tomcat7:tomcat7 /var/lib/tomcat7/webapps/eureka 33 | 34 | service tomcat7 restart -------------------------------------------------------------------------------- /riposte-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | traceId=%X{traceId} %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} [%thread] |-%-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /riposte-guice-typesafe-config/src/test/java/com/nike/guice/typesafeconfig/TypesafeConfigPropertiesRegistrationGuiceModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.guice.typesafeconfig; 2 | 3 | import com.nike.internal.util.Pair; 4 | 5 | import com.typesafe.config.Config; 6 | import com.typesafe.config.ConfigFactory; 7 | 8 | import org.junit.Test; 9 | 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | /** 17 | * Tests the functionality of {@link TypesafeConfigPropertiesRegistrationGuiceModule}. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | public class TypesafeConfigPropertiesRegistrationGuiceModuleTest { 22 | 23 | @Test 24 | public void getPropertiesMap_returns_map_based_on_config_values() { 25 | // given 26 | Config config = ConfigFactory.load("testconfig"); 27 | TypesafeConfigPropertiesRegistrationGuiceModule module = new TypesafeConfigPropertiesRegistrationGuiceModule(config); 28 | Map expectedResult = Stream 29 | .of(Pair.of("foo", "bar"), Pair.of("someList", "[foo, bar]")) 30 | .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); 31 | 32 | // when 33 | Map result = module.getPropertiesMap(); 34 | 35 | // then 36 | assertThat(result).containsAllEntriesOf(expectedResult); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | traceId=%X{traceId} %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} [%thread] |-%-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | traceId=%X{traceId} %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} [%thread] |-%-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/DownstreamChannelClosedUnexpectedlyException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * This will be thrown when the channel being used to talk to a downstream system is closed unexpectedly before the call 7 | * has finished. This would normally happen because the downstream system force-closed the connection in a non-graceful 8 | * way, or some network glitch broke the connection, or some other similarly harsh event. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class DownstreamChannelClosedUnexpectedlyException extends RuntimeException { 13 | 14 | public final String channelId; 15 | 16 | public DownstreamChannelClosedUnexpectedlyException(Channel channel) { 17 | super("The channel used to talk to the downstream system was closed while the call was active - probably by " 18 | + "the downstream system, but also possibly by us if an unrecoverable error occurred and we " 19 | + "preemptively closed the channel. closed_channel_id=" + getChannelId(channel) 20 | ); 21 | this.channelId = getChannelId(channel); 22 | } 23 | 24 | @SuppressWarnings("WeakerAccess") 25 | protected static String getChannelId(Channel channel) { 26 | if (channel == null) 27 | return "null"; 28 | 29 | return channel.toString(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /riposte-service-registration-eureka/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | traceId=%X{traceId} %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} [%thread] |-%-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/DownstreamChannelClosedUnexpectedlyExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import io.netty.channel.Channel; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.mockito.Mockito.doReturn; 11 | import static org.mockito.Mockito.mock; 12 | 13 | /** 14 | * Tests the functionality of {@link DownstreamChannelClosedUnexpectedlyException}. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class DownstreamChannelClosedUnexpectedlyExceptionTest { 19 | 20 | @Test 21 | public void constructor_works_with_valid_channel() { 22 | // given 23 | Channel channelMock = mock(Channel.class); 24 | String channelToStringVal = UUID.randomUUID().toString(); 25 | doReturn(channelToStringVal).when(channelMock).toString(); 26 | 27 | // when 28 | DownstreamChannelClosedUnexpectedlyException ex = new DownstreamChannelClosedUnexpectedlyException(channelMock); 29 | 30 | // then 31 | assertThat(ex.channelId).isEqualTo(channelToStringVal); 32 | } 33 | 34 | @Test 35 | public void constructor_works_with_null_channel() { 36 | // when 37 | DownstreamChannelClosedUnexpectedlyException ex = new DownstreamChannelClosedUnexpectedlyException(null); 38 | 39 | // then 40 | assertThat(ex.channelId).isEqualTo("null"); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/header/accept/MimeMediaRangeTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | import com.nike.riposte.server.http.mimetype.MimeType; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.api.Assertions.catchThrowable; 9 | 10 | /** 11 | * Created by dpet22 on 8/3/16. 12 | */ 13 | public class MimeMediaRangeTypeTest { 14 | @Test 15 | public void test_media_range_type_constructor_throws_exceptin_from_null_value () { 16 | // when 17 | Throwable ex = catchThrowable(() -> new MimeMediaRangeType(null)); 18 | 19 | // then 20 | assertThat(ex) 21 | .isInstanceOf(IllegalArgumentException.class) 22 | .hasMessageStartingWith("type instance cannot be null."); 23 | } 24 | 25 | @Test 26 | public void equals_returns_true_for_same_instance() { 27 | // given 28 | MimeMediaRangeType instance = new MimeMediaRangeType(MimeType.Type.of("foo")); 29 | 30 | // expect 31 | assertThat(instance.equals(instance)).isTrue(); 32 | } 33 | 34 | @Test 35 | public void hashCode_equals_subtype_hashcode() { 36 | // given 37 | MimeType.Type type = MimeType.Type.of("foo"); 38 | MimeMediaRangeType instance = new MimeMediaRangeType(type); 39 | 40 | // expect 41 | assertThat(instance.hashCode()).isEqualTo(type.hashCode()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /riposte-spi/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | "io.netty:netty-codec-http:$nettyVersion", 6 | "com.nike.fastbreak:fastbreak:$fastbreakVersion", 7 | "com.nike.backstopper:backstopper-core:$backstopperVersion", 8 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 9 | "org.slf4j:slf4j-api:$slf4jVersion" 10 | ) 11 | compileOnly( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | ) 14 | testImplementation ( 15 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 16 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 17 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 18 | "junit:junit:$junitVersion", 19 | "org.mockito:mockito-core:$mockitoVersion", 20 | "org.assertj:assertj-core:$assertJVersion", 21 | "com.nike.backstopper:backstopper-reusable-tests:$backstopperVersion", 22 | "org.spockframework:spock-core:$spockVersion", 23 | "org.codehaus.groovy:groovy-all:$groovyVersion", 24 | "cglib:cglib:$cgLibVersion", //For Spock mocks 25 | "org.objenesis:objenesis:$objenesisVersion", //Also for Spock mocks 26 | "uk.org.lidalia:slf4j-test:$slf4jTestVersion", 27 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 28 | "com.nike.wingtips:wingtips-core:$wingtipsVersion" 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/src/main/java/com/nike/Main.java: -------------------------------------------------------------------------------- 1 | package com.nike; 2 | 3 | import com.nike.helloworld.HelloWorldEndpoint; 4 | import com.nike.riposte.server.Server; 5 | import com.nike.riposte.server.config.ServerConfig; 6 | import com.nike.riposte.server.http.Endpoint; 7 | import com.nike.riposte.server.logging.AccessLogger; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | 15 | /** 16 | * Typically trivial sample to demonstrate the use of the Riposte core framework. After this application starts up 17 | * you can hit http://localhost:8080/ to receive a "Hello, world" response from {@link HelloWorldEndpoint}. 18 | */ 19 | public class Main { 20 | 21 | public static class AppServerConfig implements ServerConfig { 22 | private final Collection> endpoints = Collections.singleton(new HelloWorldEndpoint()); 23 | private final AccessLogger accessLogger = new AccessLogger(); 24 | 25 | @Override 26 | public @NotNull Collection<@NotNull Endpoint> appEndpoints() { 27 | return endpoints; 28 | } 29 | 30 | @Override 31 | public @Nullable AccessLogger accessLogger() { 32 | return accessLogger; 33 | } 34 | } 35 | 36 | public static void main(String[] args) throws Exception { 37 | Server server = new Server(new AppServerConfig()); 38 | server.startup(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | traceId=%X{traceId} %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} [%thread] |-%-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /riposte-typesafe-config/src/test/java/com/nike/riposte/typesafeconfig/util/TypesafeConfigUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.typesafeconfig.util; 2 | 3 | import com.typesafe.config.Config; 4 | 5 | import org.junit.Test; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * Tests the functionality of {@link TypesafeConfigUtil}. 14 | * 15 | * @author Nic Munroe 16 | */ 17 | public class TypesafeConfigUtilTest { 18 | 19 | @Test 20 | public void exercise_private_constructor_for_code_coverage() throws NoSuchMethodException, IllegalAccessException, 21 | InvocationTargetException, InstantiationException { 22 | // given 23 | Constructor constructor = TypesafeConfigUtil.class.getDeclaredConstructor(); 24 | constructor.setAccessible(true); 25 | 26 | // when 27 | TypesafeConfigUtil instance = constructor.newInstance(); 28 | 29 | // expect 30 | assertThat(instance).isNotNull(); 31 | } 32 | 33 | @Test 34 | public void loadConfigForAppIdAndEnvironment_works_as_expected() { 35 | // when 36 | Config config = TypesafeConfigUtil.loadConfigForAppIdAndEnvironment("typesafeconfigserver", "compiletimetest"); 37 | 38 | // then 39 | assertThat(config.getString("typesafeConfigServer.foo")).isEqualTo("overridevalue"); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/contrib/DefaultSLF4jReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.nike.riposte.metrics.codahale.CodahaleMetricsEngine; 4 | import com.nike.riposte.metrics.codahale.ReporterFactory; 5 | 6 | import com.codahale.metrics.MetricRegistry; 7 | import com.codahale.metrics.Reporter; 8 | import com.codahale.metrics.Slf4jReporter; 9 | 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @author pevans 16 | */ 17 | @SuppressWarnings("WeakerAccess") 18 | public class DefaultSLF4jReporterFactory implements ReporterFactory { 19 | 20 | private final String prefix; 21 | 22 | private Reporter reporter; 23 | 24 | public DefaultSLF4jReporterFactory() { 25 | this(CodahaleMetricsEngine.class.getSimpleName()); 26 | } 27 | 28 | public DefaultSLF4jReporterFactory(String prefix) { 29 | this.prefix = prefix; 30 | } 31 | 32 | @Override 33 | public synchronized Reporter getReporter(MetricRegistry registry) { 34 | if (null == reporter) { 35 | reporter = Slf4jReporter.forRegistry(registry) 36 | .outputTo(LoggerFactory.getLogger(prefix)) 37 | .convertRatesTo(TimeUnit.SECONDS) 38 | .convertDurationsTo(TimeUnit.MILLISECONDS) 39 | .build(); 40 | } 41 | return reporter; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/RequestTooBigException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | /** 4 | * An exception that will be thrown when the total request size is larger than the max allowed. This may be a 5 | * short-circuit exception in the case where the request contained a content-length header that indicated the size was 6 | * too large and we could throw the error before accepting payload data, or it may not be thrown until after we've 7 | * received enough data that it went over the max (i.e. in the case of chunked transfer encoding where we don't know how 8 | * big the request is going to be until we've received the data). 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class RequestTooBigException extends RuntimeException { 13 | 14 | /** 15 | * Creates a new instance with no message or cause. 16 | */ 17 | public RequestTooBigException() { 18 | // Do nothing 19 | } 20 | 21 | /** 22 | * Creates a new instance with the given message and cause. 23 | */ 24 | public RequestTooBigException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | 28 | /** 29 | * Creates a new instance with the given message. 30 | */ 31 | public RequestTooBigException(String message) { 32 | super(message); 33 | } 34 | 35 | /** 36 | * Creates a new instance with the given cause. 37 | */ 38 | public RequestTooBigException(Throwable cause) { 39 | super(cause); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapter.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.service.riposte; 2 | 3 | import com.nike.backstopper.service.ClientDataValidationService; 4 | import com.nike.riposte.server.error.validation.RequestValidator; 5 | import com.nike.riposte.server.http.RequestInfo; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Singleton; 12 | 13 | /** 14 | * Adapter that lets {@link ClientDataValidationService} act as a {@link RequestValidator}. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | @SuppressWarnings("WeakerAccess") 19 | @Singleton 20 | public class BackstopperRiposteValidatorAdapter implements RequestValidator { 21 | 22 | protected final ClientDataValidationService clientDataValidationService; 23 | 24 | @Inject 25 | public BackstopperRiposteValidatorAdapter(ClientDataValidationService clientDataValidationService) { 26 | this.clientDataValidationService = clientDataValidationService; 27 | } 28 | 29 | @Override 30 | public void validateRequestContent(@NotNull RequestInfo request) { 31 | clientDataValidationService.validateObjectsFailFast(request.getContent()); 32 | } 33 | 34 | @Override 35 | public void validateRequestContent(@NotNull RequestInfo request, @Nullable Class... validationGroups) { 36 | clientDataValidationService.validateObjectsWithGroupsFailFast(validationGroups, request.getContent()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBodyTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler.impl; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.api.Assertions.catchThrowable; 9 | 10 | /** 11 | * Tests the functionality of {@link DelegatedErrorResponseBody}. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class DelegatedErrorResponseBodyTest { 16 | 17 | @Test 18 | public void constructor_sets_fields_as_expected() { 19 | // given 20 | String errorId = UUID.randomUUID().toString(); 21 | Object someObject = new Object(); 22 | 23 | // when 24 | DelegatedErrorResponseBody impl = new DelegatedErrorResponseBody(errorId, someObject); 25 | 26 | // then 27 | assertThat(impl.errorId).isEqualTo(errorId); 28 | assertThat(impl.errorId()).isEqualTo(errorId); 29 | assertThat(impl.bodyToSerialize).isSameAs(someObject); 30 | assertThat(impl.bodyToSerialize()).isSameAs(someObject); 31 | } 32 | 33 | @Test 34 | public void constructor_throws_IllegalArgumentException_if_passed_null_errorId() { 35 | // when 36 | Throwable ex = catchThrowable(() -> new DelegatedErrorResponseBody(null, new Object())); 37 | 38 | // then 39 | assertThat(ex) 40 | .isInstanceOf(IllegalArgumentException.class) 41 | .hasMessage("errorId cannot be null."); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/exception/DownstreamIdleChannelTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * This will be thrown when a Netty {@link io.netty.channel.Channel} used for an async downstream call sits idle beyond 7 | * the specified timeout value. This generally occurs in two situations: 8 | *

    9 | *
  1. An active downstream call takes too long to return and passes the timeout threshold.
  2. 10 | *
  3. 11 | * A pooled connection sits idle beyond the threshold (no active downstream call, just sitting in the pool 12 | * unused). 13 | *
  4. 14 | *
15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class DownstreamIdleChannelTimeoutException extends RuntimeException { 19 | 20 | public final long timeoutValueMillis; 21 | public final String channelId; 22 | 23 | public DownstreamIdleChannelTimeoutException(long timeoutValueMillis, Channel channel) { 24 | super("The downstream channel was idle too long. downstream_channel_timeout_value_millis=" + timeoutValueMillis 25 | + ", idle_channel_id=" + getChannelId(channel) 26 | ); 27 | this.timeoutValueMillis = timeoutValueMillis; 28 | this.channelId = getChannelId(channel); 29 | } 30 | 31 | @SuppressWarnings("WeakerAccess") 32 | protected static String getChannelId(Channel channel) { 33 | if (channel == null) 34 | return "null"; 35 | 36 | return channel.toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/channelpipeline/message/LastOutboundMessageSendFullResponseInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline.message; 2 | 3 | import com.nike.riposte.server.channelpipeline.ChannelAttributes; 4 | import com.nike.riposte.server.http.HttpProcessingState; 5 | import com.nike.riposte.server.http.ResponseInfo; 6 | 7 | import io.netty.channel.ChannelHandlerContext; 8 | 9 | /** 10 | * Implementation of {@link LastOutboundMessage} that indicates the response sender should use the current {@link 11 | * ResponseInfo} found in {@link ChannelAttributes#getHttpProcessingStateForChannel(ChannelHandlerContext)}'s {@link 12 | * HttpProcessingState#getResponseInfo()} as a full response. There will be no other response chunks coming - just use 13 | * the data contained in the state's response info as the full response. 14 | *

15 | * Since all the necessary info (including content) is contained in the current {@link 16 | * HttpProcessingState#getResponseInfo()} this message does not need to store anything. Users of this class should just 17 | * refer to {@link #INSTANCE} to avoid creating unnecessary objects. 18 | *

19 | * NOTE: This is intended to be used by endpoint handlers only when calling the various various {@code 20 | * io.netty.channel.ChannelHandlerContext#fire...(Object msg)} methods. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | public class LastOutboundMessageSendFullResponseInfo implements LastOutboundMessage { 25 | 26 | public static final LastOutboundMessageSendFullResponseInfo INSTANCE = 27 | new LastOutboundMessageSendFullResponseInfo(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /riposte-guice/src/test/java/com/nike/guice/PropertiesRegistrationGuiceModuleTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.guice; 2 | 3 | import com.nike.internal.util.MapBuilder; 4 | 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | 8 | import org.junit.Test; 9 | 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | import javax.inject.Inject; 14 | import javax.inject.Named; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | 18 | /** 19 | * Tests the functionality of {@link PropertiesRegistrationGuiceModule}. 20 | */ 21 | public class PropertiesRegistrationGuiceModuleTest { 22 | 23 | @Inject 24 | @Named("stringkey") 25 | private String injectedString; 26 | 27 | @Inject 28 | @Named("intkey") 29 | private int injectedInt; 30 | 31 | @Test 32 | public void verify_registration_works() { 33 | String stringKey = "stringkey"; 34 | String stringVal = UUID.randomUUID().toString(); 35 | String intKey = "intkey"; 36 | int intVal = 42; 37 | 38 | Injector injector = Guice.createInjector(new PropertiesRegistrationGuiceModule() { 39 | @Override 40 | protected Map getPropertiesMap() { 41 | return MapBuilder.builder(stringKey, stringVal) 42 | .put(intKey, String.valueOf(intVal)) 43 | .build(); 44 | } 45 | }); 46 | 47 | injector.injectMembers(this); 48 | 49 | assertThat(injectedString).isEqualTo(stringVal); 50 | assertThat(injectedInt).isEqualTo(intVal); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/header/AcceptHeaderParserTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header; 2 | 3 | import com.nike.riposte.server.http.header.accept.MediaRange; 4 | import com.nike.riposte.server.http.header.accept.MediaRangeFixture; 5 | import com.nike.riposte.util.text.parsercombinator.Parser; 6 | 7 | import org.junit.Test; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.stream.Collectors; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | /** 17 | * Created by dpet22 on 8/2/16. 18 | */ 19 | public class AcceptHeaderParserTest { 20 | 21 | @Test 22 | public void test_accept_parser_works () throws Parser.ParserFailure { 23 | final String acceptHeaderValue = Arrays.stream(MediaRangeFixture.fixtures).map(MediaRangeFixture::getMediaRangeString).collect(Collectors.joining(",")); 24 | final List expectedRanges = Arrays.stream(MediaRangeFixture.fixtures).map(MediaRangeFixture::getExpectedMediaRange).collect(Collectors.toList()); 25 | 26 | Optional oAcceptHeader = AcceptHeaderParser.parse(acceptHeaderValue); 27 | 28 | assertThat(oAcceptHeader).isNotNull(); 29 | assertThat(oAcceptHeader.isPresent()).isTrue(); 30 | 31 | final List parsedRanges = oAcceptHeader.get().mediaRanges; 32 | assertThat(parsedRanges).isNotNull(); 33 | assertThat(parsedRanges.size()).isEqualTo(expectedRanges.size()); 34 | expectedRanges.forEach( expectedRange -> assertThat(parsedRanges.contains(expectedRange)).isTrue() ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/NonblockingEndpointTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http; 2 | 3 | import com.nike.riposte.util.Matcher; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.Executor; 10 | 11 | import io.netty.channel.ChannelHandlerContext; 12 | 13 | import static org.hamcrest.CoreMatchers.nullValue; 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.mockito.Mockito.mock; 16 | 17 | /** 18 | * Tests the functionality of {@link NonblockingEndpoint}. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | public class NonblockingEndpointTest { 23 | 24 | @Test 25 | public void default_method_implementations_return_expected_values() { 26 | // given 27 | NonblockingEndpoint defaultImpl = new NonblockingEndpoint() { 28 | @Override 29 | public @NotNull CompletableFuture> execute( 30 | @NotNull RequestInfo request, 31 | @NotNull Executor longRunningTaskExecutor, 32 | @NotNull ChannelHandlerContext ctx 33 | ) { 34 | return null; 35 | } 36 | 37 | @Override 38 | public @NotNull Matcher requestMatcher() { 39 | return null; 40 | } 41 | }; 42 | 43 | // expect 44 | assertThat( 45 | defaultImpl.getCustomTimeoutExceptionCause(mock(RequestInfo.class), mock(ChannelHandlerContext.class)), 46 | nullValue() 47 | ); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | ext { 4 | } 5 | 6 | buildscript { 7 | ext.kotlin_version = '1.6.10' 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath ( "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") 13 | } 14 | 15 | } 16 | 17 | apply plugin: "application" 18 | apply plugin: 'kotlin' 19 | 20 | mainClassName = "com.nike.todoservice.MainKt" 21 | 22 | run { 23 | systemProperties = System.getProperties() 24 | } 25 | 26 | repositories { 27 | mavenCentral() 28 | } 29 | 30 | dependencies { 31 | implementation( 32 | project(":riposte-core"), 33 | "ch.qos.logback:logback-classic:$logbackVersion", 34 | "ch.qos.logback:logback-core:$logbackVersion", 35 | "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion", 36 | "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion", 37 | "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", 38 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 39 | ) 40 | testImplementation ( 41 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 42 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 43 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 44 | "junit:junit:$junitVersion", 45 | "io.rest-assured:rest-assured:$restAssuredVersion" 46 | ) 47 | } 48 | 49 | compileKotlin { 50 | kotlinOptions { 51 | jvmTarget = "1.8" 52 | } 53 | } 54 | compileTestKotlin { 55 | kotlinOptions { 56 | jvmTarget = "1.8" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteUnhandledErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import com.nike.backstopper.handler.riposte.RiposteUnhandledExceptionHandler; 4 | import com.nike.riposte.server.http.RequestInfo; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Interface describing a "backstop / last chance" error handler for Riposte that is guaranteed to handle the given error 10 | * by returning a generic error response. 11 | *

12 | * You can create your own instance of this class, however it's highly recommended that you just use the prebuilt {@link 13 | * RiposteUnhandledExceptionHandler} class which is part of the default error handling and validation system that is 14 | * designed to make error handling and validation easy and is based on Backstopper. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public interface RiposteUnhandledErrorHandler { 19 | 20 | /** 21 | * @param error 22 | * The error that ABSOLUTELY MUST be handled. 23 | * @param requestInfo 24 | * The {@link com.nike.riposte.server.http.RequestInfo} associated with the request that threw the error. Useful 25 | * for logging information to make debugging easier. 26 | * 27 | * @return A generic {@link com.nike.riposte.server.error.handler.ErrorResponseInfo} that should be used to send the 28 | * response back to the user. This should never return null. The various arguments should be used to log as much 29 | * info as possible on the request and the error to make debugging easier. 30 | */ 31 | @NotNull ErrorResponseInfo handleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/InvalidRipostePipelineExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Tests the functionality of {@link InvalidRipostePipelineException}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class InvalidRipostePipelineExceptionTest { 13 | 14 | @Test 15 | public void zero_arg_constructor_works() { 16 | // when 17 | InvalidRipostePipelineException ex = new InvalidRipostePipelineException(); 18 | 19 | // then 20 | assertThat(ex).hasMessage(null).hasCause(null); 21 | } 22 | 23 | @Test 24 | public void message_constructor_works() { 25 | // when 26 | InvalidRipostePipelineException ex = new InvalidRipostePipelineException("foo"); 27 | 28 | // then 29 | assertThat(ex).hasMessage("foo").hasCause(null); 30 | } 31 | 32 | @Test 33 | public void cause_constructor_works() { 34 | // given 35 | Throwable cause = new Exception("kaboom"); 36 | 37 | // when 38 | InvalidRipostePipelineException ex = new InvalidRipostePipelineException(cause); 39 | 40 | // then 41 | assertThat(ex).hasMessage(cause.toString()).hasCause(cause); 42 | } 43 | 44 | @Test 45 | public void message_and_cause_constructor_works() { 46 | // given 47 | Throwable cause = new Exception("kaboom"); 48 | 49 | // when 50 | InvalidRipostePipelineException ex = new InvalidRipostePipelineException("foo", cause); 51 | 52 | // then 53 | assertThat(ex).hasMessage("foo").hasCause(cause); 54 | } 55 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/header/AcceptHeader.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header; 2 | 3 | import com.nike.riposte.server.http.header.accept.MediaRange; 4 | 5 | import java.util.Collections; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Models an RFC-2616 14.1 Accept Header, which is a list of MediaRange instances, sorted most-significant-first. 12 | * 13 | * @author Kirk Peterson 14 | */ 15 | @SuppressWarnings("WeakerAccess") 16 | public class AcceptHeader implements Iterable { 17 | 18 | /** 19 | * the MediaRanges found in this AcceptHeader, sorted by most-significant-first order. 20 | */ 21 | public final List mediaRanges; 22 | 23 | /** 24 | * Constructs an AcceptHeader using the given list of MediaRanges, sorting them in most-significant-first order. 25 | * Note: the given list will have Collections.sort(mediaRanges) performed on it, ordering of the given list 26 | * could be modified. If you need to reuse the given list, you should pass in a copy instead. 27 | */ 28 | public AcceptHeader(final List mediaRanges) { 29 | Collections.sort(mediaRanges); 30 | this.mediaRanges = Collections.unmodifiableList(mediaRanges); 31 | } 32 | 33 | private String toStringCache = null; 34 | 35 | @Override 36 | public String toString() { 37 | if (toStringCache == null) { 38 | toStringCache = mediaRanges.stream().map(MediaRange::toString).collect(Collectors.joining(",")); 39 | } 40 | return toStringCache; 41 | } 42 | 43 | @Override 44 | public Iterator iterator() { 45 | return mediaRanges.iterator(); 46 | } 47 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/RequestContentDeserializationExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | import com.nike.riposte.server.http.impl.RequestInfoImpl; 5 | 6 | import com.fasterxml.jackson.core.type.TypeReference; 7 | 8 | import org.junit.Test; 9 | 10 | import java.util.UUID; 11 | 12 | import io.netty.handler.codec.http.HttpMethod; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.mockito.Mockito.mock; 17 | 18 | /** 19 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.RequestContentDeserializationException} 20 | */ 21 | public class RequestContentDeserializationExceptionTest { 22 | 23 | @Test 24 | public void should_honor_constructor_params() { 25 | //given 26 | String message = UUID.randomUUID().toString(); 27 | Throwable cause = new Exception("kaboom"); 28 | RequestInfo requestInfo = new RequestInfoImpl<>("/some/uri/path", HttpMethod.PATCH, null, null, null, null, null, null, null, false, true, false); 29 | TypeReference typeReferenceMock = mock(TypeReference.class); 30 | 31 | //when 32 | RequestContentDeserializationException ex = new RequestContentDeserializationException(message, cause, requestInfo, typeReferenceMock); 33 | 34 | //then 35 | assertThat(ex.getMessage(), is(message)); 36 | assertThat(ex.getCause(), is(cause)); 37 | assertThat(ex.httpMethod, is(requestInfo.getMethod().name())); 38 | assertThat(ex.requestPath, is(requestInfo.getPath())); 39 | assertThat(ex.desiredObjectType, is(typeReferenceMock)); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/header/accept/MimeMediaRangeType.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | import com.nike.riposte.server.http.mimetype.MimeType; 4 | import com.nike.riposte.server.http.mimetype.MimeType.Type; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Models a type that is a specific mime type, rather than a wildcard type. 10 | * 11 | * @author Kirk Peterson 12 | */ 13 | public class MimeMediaRangeType implements MediaRangeType { 14 | 15 | public final Type type; 16 | 17 | /** 18 | * Constructs a MediaRangeType instance using the given type. 19 | * 20 | * @throws IllegalArgumentException 21 | * If the given type is null. 22 | */ 23 | public MimeMediaRangeType(Type type) { 24 | if (type == null) { 25 | throw new IllegalArgumentException("type instance cannot be null."); 26 | } 27 | this.type = type; 28 | } 29 | 30 | /** 31 | * Returns the type used to describe this MediaRangeType. 32 | * 33 | * @return the type used to describe this MediaRangeType. 34 | */ 35 | public MimeType.Type getType() { 36 | return type; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) { 42 | return true; 43 | } 44 | 45 | if (!(o instanceof MimeMediaRangeType)) { 46 | return false; 47 | } 48 | 49 | MimeMediaRangeType that = (MimeMediaRangeType) o; 50 | 51 | return Objects.equals(type, that.type); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return type.hashCode(); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return type.getName(); 62 | } 63 | } -------------------------------------------------------------------------------- /riposte-core/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":riposte-spi"), 6 | // We pull in the native epoll transport dependency here so that if you are running on linux x86_64 you'll get the maximum performance possible from Netty. 7 | // If you're not on linux x86_64 then it will gracefully fall back to the standard java NIO transports. 8 | "io.netty:netty-transport-native-epoll:$nettyVersion:linux-x86_64", 9 | "com.nike.wingtips:wingtips-core:$wingtipsVersion", 10 | "com.nike.backstopper:backstopper-jackson:$backstopperVersion", 11 | ) 12 | compileOnly( 13 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 14 | ) 15 | testImplementation ( 16 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 17 | "org.assertj:assertj-core:$assertJVersion", 18 | "org.spockframework:spock-core:$spockVersion", 19 | "org.codehaus.groovy:groovy-all:$groovyVersion", 20 | "cglib:cglib:$cgLibVersion", //For Spock mocks 21 | "org.objenesis:objenesis:$objenesisVersion", //Also for Spock mocks 22 | "org.junit.jupiter:junit-jupiter-engine:$junit5Version", 23 | "org.junit.vintage:junit-vintage-engine:$junit5Version", 24 | "junit:junit:$junitVersion", 25 | "org.mockito:mockito-core:$mockitoVersion", 26 | "io.rest-assured:rest-assured:$restAssuredVersion", 27 | "uk.org.lidalia:slf4j-test:$slf4jTestVersion", 28 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 29 | "commons-io:commons-io:$apacheCommonsIoVersion", 30 | "com.nike.backstopper:backstopper-reusable-tests:$backstopperVersion" 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import com.nike.backstopper.model.riposte.ErrorResponseInfoImpl; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * The "response info" for an error response to send to the user. Contains the body content, the HTTP status code, and 13 | * any extra headers that should be returned to the user. 14 | *

15 | * You can create your own instance of this class, however it's highly recommended that you just use the prebuilt {@link 16 | * ErrorResponseInfoImpl} class which is part of the default error handling and validation system that is designed to 17 | * make error handling and validation easy and is based on Backstopper. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | public interface ErrorResponseInfo { 22 | 23 | /** 24 | * @return The response body content that should be sent to the user. This should never return null, because 25 | * {@link ErrorResponseBody#errorId()} is required - if you want an empty response body payload, then the 26 | * returned {@link ErrorResponseBody#bodyToSerialize()} can be null. 27 | */ 28 | @NotNull ErrorResponseBody getErrorResponseBody(); 29 | 30 | /** 31 | * @return The HTTP status code that should be returned to the user with the response. 32 | */ 33 | int getErrorHttpStatusCode(); 34 | 35 | /** 36 | * @return A map of any extra headers that should be added to the response sent to the user. You can safely return 37 | * null if there are no extra headers to add to the response. 38 | */ 39 | @Nullable Map> getExtraHeadersToAddToResponse(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/config/AppInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.config; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Interface for app & instance info required for metrics and healthcheck/service registry. See {@link 7 | * com.nike.riposte.server.config.impl.AppInfoImpl} for a basic DTO style implementation and static helper methods for 8 | * helping detect app ID and environment and factory methods for creating "local" instances. 9 | */ 10 | public interface AppInfo { 11 | 12 | /** 13 | * The value that should be returned by some methods of this interface if the correct value cannot be determined or 14 | * is otherwise unknown. This may or may not be a breaking error condition depending on your application's 15 | * requirements. 16 | */ 17 | String UNKNOWN_VALUE = "unknown"; 18 | 19 | /** 20 | * @return the AppId/name for this service (like {@code foo-svc}). This should always be a valid value - never 21 | * {@link #UNKNOWN_VALUE} if at all possible. 22 | */ 23 | @NotNull String appId(); 24 | 25 | /** 26 | * @return the environment for the AppId (like {@code test} or {@code prod}). This should always be a valid 27 | * value - never {@link #UNKNOWN_VALUE} if at all possible. 28 | */ 29 | @NotNull String environment(); 30 | 31 | /** 32 | * @return the datacenter/region for the AppId (like {@code us-west-2}), or {@link #UNKNOWN_VALUE} if the 33 | * datacenter/region could not be determined. 34 | */ 35 | @NotNull String dataCenter(); 36 | 37 | /** 38 | * @return the instanceId/ip/hostname of this machine/VM running the AppId service, or {@link #UNKNOWN_VALUE} if 39 | * the instanceId/ip/hostname could not be determined. 40 | */ 41 | @NotNull String instanceId(); 42 | } 43 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/contrib/DefaultGraphiteReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale.contrib; 2 | 3 | import com.nike.riposte.metrics.codahale.ReporterFactory; 4 | 5 | import com.codahale.metrics.MetricFilter; 6 | import com.codahale.metrics.MetricRegistry; 7 | import com.codahale.metrics.Reporter; 8 | import com.codahale.metrics.graphite.Graphite; 9 | import com.codahale.metrics.graphite.GraphiteReporter; 10 | 11 | import java.net.InetSocketAddress; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @author pevans 16 | */ 17 | @SuppressWarnings("WeakerAccess") 18 | public class DefaultGraphiteReporterFactory implements ReporterFactory { 19 | 20 | private final String prefix; 21 | private final String graphiteURL; 22 | private final Integer graphitePort; 23 | 24 | private Reporter reporter; 25 | 26 | public DefaultGraphiteReporterFactory(String prefix, String graphiteURL, Integer graphitePort) { 27 | this.prefix = prefix; 28 | this.graphiteURL = graphiteURL; 29 | this.graphitePort = graphitePort; 30 | } 31 | 32 | @Override 33 | public synchronized Reporter getReporter(MetricRegistry registry) { 34 | if (null == reporter) { 35 | Graphite graphite = new Graphite(new InetSocketAddress(graphiteURL, graphitePort)); 36 | reporter = GraphiteReporter.forRegistry(registry) 37 | .prefixedWith(prefix) 38 | .convertRatesTo(TimeUnit.SECONDS) 39 | .convertDurationsTo(TimeUnit.MILLISECONDS) 40 | .filter(MetricFilter.ALL) 41 | .build(graphite); 42 | } 43 | return reporter; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/error/ProjectApiErrorsImpl.kt: -------------------------------------------------------------------------------- 1 | package com.nike.todoservice.error 2 | 3 | import com.nike.backstopper.apierror.ApiError 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange 5 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase 6 | 7 | import java.util.Arrays 8 | 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Returns the project specific errors for this application. 13 | * 14 | * Individual projects should feel free to rename this class to something specific, e.g. MyProjectApiErrorsImpl 15 | * 16 | * NOTE: This extends [SampleProjectApiErrorsBase] for a reasonable base of "core errors". You may want to 17 | * create a similar reusable base class to be used by this project (and potentially others) that uses error codes and 18 | * messages of your choosing for the core errors. 19 | */ 20 | @Singleton 21 | class ProjectApiErrorsImpl : SampleProjectApiErrorsBase() { 22 | 23 | override fun getProjectSpecificApiErrors(): List { 24 | return PROJECT_SPECIFIC_API_ERRORS 25 | } 26 | 27 | /** 28 | * @return the range of errors for this project. This is used to verify that [.getProjectSpecificApiErrors] 29 | * * doesn't include any error codes outside the range you have reserved for your project. See the class javadocs for 30 | * * [ProjectSpecificErrorCodeRange] for suggestions on how to manage cross-project error ranges in the same 31 | * * org. 32 | */ 33 | override fun getProjectSpecificErrorCodeRange(): ProjectSpecificErrorCodeRange { 34 | return ProjectSpecificErrorCodeRange.ALLOW_ALL_ERROR_CODES 35 | } 36 | 37 | companion object { 38 | private val PROJECT_SPECIFIC_API_ERRORS = Arrays.asList(*ProjectApiError.values()) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.service.riposte; 2 | 3 | import com.nike.backstopper.service.ClientDataValidationService; 4 | import com.nike.riposte.server.http.RequestInfo; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import static org.mockito.Mockito.doReturn; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.verify; 12 | 13 | /** 14 | * Unit test for {@link BackstopperRiposteValidatorAdapter} 15 | */ 16 | public class BackstopperRiposteValidatorAdapterTest { 17 | 18 | private ClientDataValidationService validationServiceMock; 19 | private RequestInfo requestInfoMock; 20 | private Object requestContent = new Object(); 21 | private BackstopperRiposteValidatorAdapter adapter; 22 | 23 | @Before 24 | public void beforeMethod() { 25 | requestInfoMock = mock(RequestInfo.class); 26 | doReturn(requestContent).when(requestInfoMock).getContent(); 27 | validationServiceMock = mock(ClientDataValidationService.class); 28 | adapter = new BackstopperRiposteValidatorAdapter(validationServiceMock); 29 | } 30 | 31 | @Test 32 | public void validateRequestContentSingleArgDelegatesToInternalValidationService() { 33 | adapter.validateRequestContent(requestInfoMock); 34 | verify(validationServiceMock).validateObjectsFailFast(requestContent); 35 | } 36 | 37 | @Test 38 | public void validateRequestContentDoubleArgDelegatesToInternalValidationService() { 39 | Class[] validationGroups = new Class[] {String.class, Integer.class}; 40 | adapter.validateRequestContent(requestInfoMock, validationGroups); 41 | verify(validationServiceMock).validateObjectsWithGroupsFailFast(validationGroups, requestContent); 42 | } 43 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBody.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler.impl; 2 | 3 | import com.nike.riposte.server.error.handler.ErrorResponseBody; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * A basic implementation of {@link ErrorResponseBody} that delegates serialization ({@link #bodyToSerialize()}) to 10 | * some other object you specify. 11 | * 12 | * @author Nic Munroe 13 | */ 14 | @SuppressWarnings("WeakerAccess") 15 | public class DelegatedErrorResponseBody implements ErrorResponseBody { 16 | 17 | protected final @NotNull String errorId; 18 | protected final @Nullable Object bodyToSerialize; 19 | 20 | /** 21 | * Creates a new instance that uses the given errorId for {@link #errorId()} and the given bodyToSerialize for 22 | * {@link #bodyToSerialize()}. 23 | * 24 | * @param errorId This will be used as the {@link #errorId()}. It's recommended that you use a 25 | * {@link java.util.UUID#randomUUID()} for this. Cannot be null. 26 | * @param bodyToSerialize This will be used as the {@link #bodyToSerialize()}. Can be null - if you pass null 27 | * then an empty response body will be used. 28 | */ 29 | public DelegatedErrorResponseBody(@NotNull String errorId, @Nullable Object bodyToSerialize) { 30 | //noinspection ConstantConditions 31 | if (errorId == null) { 32 | throw new IllegalArgumentException("errorId cannot be null."); 33 | } 34 | this.errorId = errorId; 35 | this.bodyToSerialize = bodyToSerialize; 36 | } 37 | 38 | @Override 39 | public @NotNull String errorId() { 40 | return errorId; 41 | } 42 | 43 | @Override 44 | public @Nullable Object bodyToSerialize() { 45 | return bodyToSerialize; 46 | } 47 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/RequestTooBigExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.mockito.Mockito.mock; 9 | 10 | /** 11 | * Tests the functionality of {@link RequestTooBigException}. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class RequestTooBigExceptionTest { 16 | 17 | @Test 18 | public void no_arg_constructor_works_as_expected() { 19 | // given 20 | RequestTooBigException ex = new RequestTooBigException(); 21 | 22 | // expect 23 | assertThat(ex) 24 | .hasMessage(null) 25 | .hasNoCause(); 26 | } 27 | 28 | @Test 29 | public void message_only_constructor_works_as_expected() { 30 | // given 31 | String message = UUID.randomUUID().toString(); 32 | RequestTooBigException ex = new RequestTooBigException(message); 33 | 34 | // expect 35 | assertThat(ex) 36 | .hasMessage(message) 37 | .hasNoCause(); 38 | } 39 | 40 | @Test 41 | public void cause_only_constructor_works_as_expected() { 42 | // given 43 | Throwable cause = mock(Throwable.class); 44 | RequestTooBigException ex = new RequestTooBigException(cause); 45 | 46 | // expect 47 | assertThat(ex) 48 | .hasMessage(cause.toString()) 49 | .hasCause(cause); 50 | } 51 | 52 | @Test 53 | public void kitchen_sink_constructor_works_as_expected() { 54 | // given 55 | String message = UUID.randomUUID().toString(); 56 | Throwable cause = mock(Throwable.class); 57 | RequestTooBigException ex = new RequestTooBigException(message, cause); 58 | 59 | // expect 60 | assertThat(ex) 61 | .hasMessage(message) 62 | .hasCause(cause); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/Main.kt: -------------------------------------------------------------------------------- 1 | package com.nike.todoservice 2 | 3 | import com.nike.backstopper.handler.riposte.config.BackstopperRiposteConfigHelper.defaultErrorHandler 4 | import com.nike.backstopper.handler.riposte.config.BackstopperRiposteConfigHelper.defaultUnhandledErrorHandler 5 | import com.nike.riposte.server.Server 6 | import com.nike.riposte.server.config.ServerConfig 7 | import com.nike.riposte.server.error.handler.RiposteErrorHandler 8 | import com.nike.riposte.server.error.handler.RiposteUnhandledErrorHandler 9 | import com.nike.riposte.server.http.Endpoint 10 | import com.nike.riposte.server.logging.AccessLogger 11 | import com.nike.todoservice.endpoints.TodoItemsEndpoint 12 | import com.nike.todoservice.error.ProjectApiErrorsImpl 13 | import com.nike.backstopper.handler.ApiExceptionHandlerUtils.DEFAULT_IMPL as DEFAULT_API_EXCEPTION_HANDLER_UTILS 14 | 15 | fun main(args : Array) { 16 | val server = Server(AppServerConfig()) 17 | server.startup() 18 | } 19 | 20 | open class AppServerConfig : ServerConfig { 21 | private val endpoints = setOf>( 22 | TodoItemsEndpoint.Get(), 23 | TodoItemsEndpoint.Post(), 24 | TodoItemsEndpoint.Put(), 25 | TodoItemsEndpoint.Delete() 26 | ) 27 | private val accessLogger = AccessLogger() 28 | private val projectApiErrors = ProjectApiErrorsImpl() 29 | 30 | override fun appEndpoints(): Collection> { 31 | return endpoints 32 | } 33 | 34 | override fun accessLogger(): AccessLogger { 35 | return accessLogger 36 | } 37 | 38 | override fun riposteErrorHandler(): RiposteErrorHandler { 39 | return defaultErrorHandler(projectApiErrors, DEFAULT_API_EXCEPTION_HANDLER_UTILS) 40 | } 41 | 42 | override fun riposteUnhandledErrorHandler(): RiposteUnhandledErrorHandler { 43 | return defaultUnhandledErrorHandler(projectApiErrors, DEFAULT_API_EXCEPTION_HANDLER_UTILS) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-core/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.exception; 2 | 3 | import com.nike.riposte.server.http.Endpoint; 4 | import com.nike.riposte.util.Matcher; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.junit.Test; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | 16 | /** 17 | * Tests the functionality of {@link com.nike.riposte.server.error.exception.MultipleMatchingEndpointsException} 18 | */ 19 | public class MultipleMatchingEndpointsExceptionTest { 20 | 21 | @Test 22 | public void should_honor_constructor_params() { 23 | //given 24 | Endpoint endpoint1 = new EndpointOne(); 25 | Endpoint endpoint2 = new EndpointTwo(); 26 | 27 | List> endpointList = Arrays.asList(endpoint1, endpoint2); 28 | String requestPath = UUID.randomUUID().toString(); 29 | String requestMethod = UUID.randomUUID().toString(); 30 | String message = UUID.randomUUID().toString(); 31 | 32 | //when 33 | MultipleMatchingEndpointsException ex = new MultipleMatchingEndpointsException(message, endpointList, requestPath, requestMethod); 34 | 35 | //then 36 | assertThat(ex.getMessage(), is(message)); 37 | assertThat(ex.matchingEndpointsDetails, is(Arrays.asList(EndpointOne.class.getName(), EndpointTwo.class.getName()))); 38 | assertThat(ex.requestPath, is(requestPath)); 39 | assertThat(ex.requestMethod, is(requestMethod)); 40 | 41 | } 42 | 43 | public static class EndpointOne implements Endpoint { 44 | @Override 45 | public @NotNull Matcher requestMatcher() { 46 | return null; 47 | } 48 | } 49 | 50 | public static class EndpointTwo implements Endpoint { 51 | @Override 52 | public @NotNull Matcher requestMatcher() { 53 | return null; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /riposte-async-http-client/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-async-http-client2/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-servlet-api-adapter/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-metrics-codahale-signalfx/src/test/java/com/nike/riposte/testutils/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.testutils; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A copy of the Mockito 1.x Whitebox class - needed because they dropped this class in Mockito 2.x. 7 | */ 8 | @SuppressWarnings("unused") 9 | public class Whitebox { 10 | public static Object getInternalState(Object target, String field) { 11 | Class c = target.getClass(); 12 | try { 13 | Field f = getFieldFromHierarchy(c, field); 14 | f.setAccessible(true); 15 | return f.get(target); 16 | } catch (Exception e) { 17 | throw new RuntimeException("Unable to get internal state on a private field.", e); 18 | } 19 | } 20 | 21 | public static void setInternalState(Object target, String field, Object value) { 22 | Class c = target.getClass(); 23 | try { 24 | Field f = getFieldFromHierarchy(c, field); 25 | f.setAccessible(true); 26 | f.set(target, value); 27 | } catch (Exception e) { 28 | throw new RuntimeException("Unable to set internal state on a private field.", e); 29 | } 30 | } 31 | 32 | private static Field getFieldFromHierarchy(Class clazz, String field) { 33 | Field f = getField(clazz, field); 34 | while (f == null && clazz != Object.class) { 35 | clazz = clazz.getSuperclass(); 36 | f = getField(clazz, field); 37 | } 38 | if (f == null) { 39 | throw new RuntimeException( 40 | "You want me to get this field: '" + field + 41 | "' on this class: '" + clazz.getSimpleName() + 42 | "' but this field is not declared withing hierarchy of this class!"); 43 | } 44 | return f; 45 | } 46 | 47 | private static Field getField(Class clazz, String field) { 48 | try { 49 | return clazz.getDeclaredField(field); 50 | } catch (NoSuchFieldException e) { 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBody.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import com.nike.backstopper.model.riposte.ErrorResponseBodyImpl; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Represents the response body content for an error response. The only thing strictly required is {@link #errorId()}, 10 | * although it's recommended that you have a consistent error contract for all errors. {@link #errorId()} will be 11 | * returned as a response header to uniquely identify this error, and {@link #bodyToSerialize()} will be serialized 12 | * to provide the response payload. By default {@link #bodyToSerialize()} returns this instance, so this object is 13 | * what gets serialized if you don't override it, but you can override it to return {@code null} for a blank payload, 14 | * or have it return some other object if needed (i.e. delegating to an object from a third-party library that doesn't 15 | * implement this interface). 16 | * 17 | *

You can create your own instance of this class, however it's highly recommended that you just use the prebuilt 18 | * {@link ErrorResponseBodyImpl} class which is part of the default error handling and validation system (based on 19 | * Backstopper) and is designed to make error handling and validation easy and consistent. If you have some other object 20 | * that you want to serialize you can use {@link com.nike.riposte.server.error.handler.impl.DelegatedErrorResponseBody}. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | public interface ErrorResponseBody { 25 | 26 | /** 27 | * @return The unique ID associated with this error. This is usually just the string value of a {@link 28 | * java.util.UUID}. Should never be null. 29 | */ 30 | @NotNull String errorId(); 31 | 32 | /** 33 | * @return The object that should be serialized into the response body payload, or null if you want a blank/empty 34 | * response body payload. 35 | */ 36 | default @Nullable Object bodyToSerialize() { 37 | return this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | There are a few guidelines that we need contributors to follow so that we are able to process requests as efficiently as possible. If you have any questions or concerns please feel free to contact us at [opensource@nike.com](mailto:opensource@nike.com). 4 | 5 | ## Getting Started 6 | 7 | * Review our [Code of Conduct](https://github.com/Nike-Inc/nike-inc.github.io/blob/master/CONDUCT.md) 8 | * Submit the [Individual Contributor License Agreement](https://www.clahub.com/agreements/Nike-Inc/riposte) 9 | * Make sure you have a [GitHub account](https://github.com/signup/free) 10 | * Submit a ticket for your issue, assuming one does not already exist. 11 | * Clearly describe the issue including steps to reproduce when it is a bug. 12 | * Make sure you fill in the earliest version that you know has the issue. 13 | * Fork the repository on GitHub 14 | 15 | ## Making Changes 16 | 17 | * Create a topic branch off of `main` before you start your work. 18 | * Please avoid working directly on the `main` branch. 19 | * Make commits of logical units. 20 | * You may be asked to squash unnecessary commits down to logical units. 21 | * Check for unnecessary whitespace with `git diff --check` before committing. 22 | * Write meaningful, descriptive commit messages. 23 | * Please follow existing code conventions when working on a file. 24 | 25 | ## Submitting Changes 26 | 27 | * Push your changes to a topic branch in your fork of the repository. 28 | * Submit a pull request to the repository in the Nike-Inc organization. 29 | * After feedback has been given we expect responses within two weeks. After two weeks we may close the pull request if it isn't showing any activity. 30 | * Bug fixes or features that lack appropriate tests may not be considered for merge. 31 | * Changes that lower test coverage may not be considered for merge. 32 | 33 | # Additional Resources 34 | 35 | * [General GitHub documentation](https://help.github.com/) 36 | * [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 37 | * [Nike's Code of Conduct](https://github.com/Nike-Inc/nike-inc.github.io/blob/master/CONDUCT.md) 38 | * [Nike's Individual Contributor License Agreement](https://www.clahub.com/agreements/Nike-Inc/riposte) 39 | * [Nike OSS](https://nike-inc.github.io/) 40 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.handler; 2 | 3 | import com.nike.backstopper.handler.riposte.RiposteApiExceptionHandler; 4 | import com.nike.riposte.server.error.exception.UnexpectedMajorErrorHandlingError; 5 | import com.nike.riposte.server.http.RequestInfo; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | /** 11 | * Interface describing an error handler for Netty that takes in the error that occurred along with some info useful for 12 | * logging and returns an {@link ErrorResponseInfo} that can be used to build the response sent to the user (or null if 13 | * this handler doesn't know how to deal with the given error and {@link RiposteUnhandledErrorHandler} should take care 14 | * of it). 15 | *

16 | * You can create your own instance of this class, however it's highly recommended that you just use the prebuilt {@link 17 | * RiposteApiExceptionHandler} class which is part of the default error handling and validation system that is designed 18 | * to make error handling and validation easy and is based on Backstopper. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | public interface RiposteErrorHandler { 23 | 24 | /** 25 | * @param error 26 | * The error that may or may not be handled by this handler. 27 | * @param requestInfo 28 | * The {@link com.nike.riposte.server.http.RequestInfo} associated with the request that threw the error. Useful 29 | * for logging information to make debugging easier. 30 | * 31 | * @return The {@link com.nike.riposte.server.error.handler.ErrorResponseInfo} that should be used to build the 32 | * response for the user, or null if this handler doesn't know how to deal with the error. If this returns null then 33 | * you should have {@link RiposteUnhandledErrorHandler} handle the error instead. 34 | * 35 | * @throws UnexpectedMajorErrorHandlingError 36 | * This should never be thrown - if it is it indicates something major went wrong in the handler and is likely a 37 | * bug that should be fixed. 38 | */ 39 | @Nullable ErrorResponseInfo maybeHandleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo) 40 | throws UnexpectedMajorErrorHandlingError; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/header/accept/MimeMediaRangeSubType.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | import com.nike.riposte.server.http.mimetype.MimeType.SubType; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Models a subtype that is a specific mime subtype, rather than a wildcard subtype. 9 | * 10 | * @author Kirk Peterson 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public class MimeMediaRangeSubType implements MediaRangeSubType { 14 | 15 | public final SubType subType; 16 | 17 | /** 18 | * Creates a MediaRangeSubType instace using the given Mime SubType. 19 | * 20 | * @param subType 21 | * the sub type to construct a MimeMediaRangeSubType from. 22 | * 23 | * @throws IllegalArgumentException 24 | * If the given subType is null. 25 | */ 26 | public MimeMediaRangeSubType(SubType subType) { 27 | if (subType == null) { 28 | throw new IllegalArgumentException("subType instance cannot be null."); 29 | } 30 | this.subType = subType; 31 | } 32 | 33 | /** 34 | * returns the sub type used to construct this MimeMediaRangeSubType instance from. 35 | * 36 | * @return the sub type used to construct this MimeMediaRangeSubType instance from. 37 | */ 38 | public SubType getSubType() { 39 | return subType; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) { 45 | return true; 46 | } 47 | if (!(o instanceof MimeMediaRangeSubType)) { 48 | return false; 49 | } 50 | 51 | MimeMediaRangeSubType that = (MimeMediaRangeSubType) o; 52 | 53 | return Objects.equals(subType, that.subType); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return subType.hashCode(); 59 | } 60 | 61 | private String toStringCache = null; 62 | 63 | @Override 64 | public String toString() { 65 | if (toStringCache == null) { 66 | toStringCache = subType.getFacet().getRegistrationTreeName().map(tree -> tree + ".").orElse("") + 67 | subType.getName() + 68 | subType.getSuffix().map(suffix -> "+" + suffix).orElse(""); 69 | } 70 | return toStringCache; 71 | } 72 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/config/distributedtracing/ServerSpanNamingAndTaggingStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.config.distributedtracing; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | import com.nike.riposte.server.http.ResponseInfo; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.junit.Test; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.verifyNoInteractions; 13 | 14 | /** 15 | * Tests the default functionality of {@link ServerSpanNamingAndTaggingStrategy}. 16 | * 17 | * @author Nic Munroe 18 | */ 19 | public class ServerSpanNamingAndTaggingStrategyTest { 20 | 21 | @Test 22 | public void default_annotation_method_implementations_return_expected_values() { 23 | // given 24 | ServerSpanNamingAndTaggingStrategy defaultImpl = new DefaultServerSpanNamingAndTaggingStrategy(); 25 | ResponseInfo responseMock = mock(ResponseInfo.class); 26 | Throwable errorMock = mock(Throwable.class); 27 | 28 | // expect 29 | assertThat(defaultImpl.shouldAddEndpointStartAnnotation()).isTrue(); 30 | assertThat(defaultImpl.endpointStartAnnotationName()).isEqualTo("endpoint.start"); 31 | assertThat(defaultImpl.shouldAddEndpointFinishAnnotation()).isTrue(); 32 | assertThat(defaultImpl.endpointFinishAnnotationName()).isEqualTo("endpoint.finish"); 33 | 34 | verifyNoInteractions(responseMock, errorMock); 35 | } 36 | 37 | private static class DefaultServerSpanNamingAndTaggingStrategy extends ServerSpanNamingAndTaggingStrategy { 38 | 39 | @Override 40 | public @Nullable String doGetInitialSpanName(@NotNull RequestInfo request) { return null; } 41 | 42 | @Override 43 | public void doChangeSpanName(@NotNull S span, @NotNull String newName) { } 44 | 45 | @Override 46 | public void doHandleResponseTaggingAndFinalSpanName( 47 | @NotNull S span, @Nullable RequestInfo request, @Nullable ResponseInfo response, 48 | @Nullable Throwable error 49 | ) { } 50 | 51 | @Override 52 | public void doHandleRequestTagging(@NotNull S span, @NotNull RequestInfo request) { } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/impl/BaseResponseInfoBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.impl; 2 | 3 | import com.google.common.collect.Sets; 4 | 5 | import org.junit.Test; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.Set; 9 | import java.util.UUID; 10 | 11 | import io.netty.handler.codec.http.DefaultHttpHeaders; 12 | import io.netty.handler.codec.http.HttpHeaders; 13 | import io.netty.handler.codec.http.cookie.Cookie; 14 | import io.netty.handler.codec.http.cookie.DefaultCookie; 15 | import io.netty.util.CharsetUtil; 16 | 17 | import static org.hamcrest.CoreMatchers.is; 18 | import static org.hamcrest.MatcherAssert.assertThat; 19 | 20 | /** 21 | * Tests the functionality of {@link BaseResponseInfoBuilder} 22 | * 23 | * @author Nic Munroe 24 | */ 25 | public class BaseResponseInfoBuilderTest { 26 | 27 | @Test 28 | public void builder_stores_values_as_expected() { 29 | // given 30 | String content = UUID.randomUUID().toString(); 31 | int httpStatusCode = 200; 32 | HttpHeaders headers = new DefaultHttpHeaders(); 33 | String mimeType = "text/text"; 34 | Charset contentCharset = CharsetUtil.ISO_8859_1; 35 | Set cookies = Sets.newHashSet(new DefaultCookie("key1", "val1"), new DefaultCookie("key2", "val2")); 36 | boolean preventCompressedOutput = true; 37 | 38 | // when 39 | BaseResponseInfoBuilder responseInfoBuilder = new BaseResponseInfoBuilder(){} 40 | .withHttpStatusCode(httpStatusCode) 41 | .withHeaders(headers) 42 | .withDesiredContentWriterMimeType(mimeType) 43 | .withDesiredContentWriterEncoding(contentCharset).withCookies(cookies) 44 | .withPreventCompressedOutput(preventCompressedOutput); 45 | 46 | // then 47 | assertThat(responseInfoBuilder.getHttpStatusCode(), is(httpStatusCode)); 48 | assertThat(responseInfoBuilder.getHeaders(), is(headers)); 49 | assertThat(responseInfoBuilder.getDesiredContentWriterMimeType(), is(mimeType)); 50 | assertThat(responseInfoBuilder.getDesiredContentWriterEncoding(), is(contentCharset)); 51 | assertThat(responseInfoBuilder.getCookies(), is(cookies)); 52 | assertThat(responseInfoBuilder.isPreventCompressedOutput(), is(preventCompressedOutput)); 53 | } 54 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/impl/RiposteInternalRequestInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.impl; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.handler.codec.http.HttpContent; 7 | 8 | /** 9 | * Interface that implementations of {@link RequestInfo} should also implement - this interface covers some 10 | * under-the-hood stuff that implementations of {@link RequestInfo} need to support for Riposte internals to work 11 | * correctly, but app developers shouldn't need to worry about (which is why we hide them here in a separate interface). 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public interface RiposteInternalRequestInfo { 16 | 17 | /** 18 | * Indicates to this {@link RequestInfo} implementation that all content chunks will be handled externally, e.g. 19 | * {@code ProxyRouterEndpoint}s where the content chunks are immediately released after being sent downstream 20 | * to avoid pulling the full request into memory. In other words this should disable {@link 21 | * RequestInfo#releaseContentChunks()} so that when that method is called it does *not* call {@link 22 | * ByteBuf#release()} on any content chunks. This avoids releasing chunks too many times, thus causing reference 23 | * counting bugs. 24 | * 25 | *

IMPORTANT NOTE: Implementations should continue to {@link ByteBuf#retain()} chunks as they are added via 26 | * {@link RequestInfo#addContentChunk(HttpContent)}. This method only means they will be *released* externally, 27 | * but {@link RequestInfo} is still expected to retain the chunks so that they aren't prematurely destroyed. 28 | * 29 | *

ALSO NOTE: Since content chunks may be released externally early (even before the last content chunk arrives 30 | * from the original caller) {@link RequestInfo} must never allow {@link 31 | * RequestInfo#isCompleteRequestWithAllChunks()} to be set to true and must never allow any of the content-related 32 | * methods to return "real" values (e.g. all the {@code RequestInfo#get*Content()} methods should return null), 33 | * *UNLESS* the full request has already arrived and the content already pulled into heap memory at which point no 34 | * damage would be done by allowing those methods to continue to work. 35 | */ 36 | void contentChunksWillBeReleasedExternally(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/config/impl/DependencyInjectionProvidedServerConfigValuesBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.config.impl; 2 | 3 | import com.nike.riposte.server.http.Endpoint; 4 | 5 | import com.tngtech.java.junit.dataprovider.DataProvider; 6 | import com.tngtech.java.junit.dataprovider.DataProviderRunner; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import java.util.Set; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.mockito.Mockito.mock; 15 | 16 | /** 17 | * Tests the functionality of {@link DependencyInjectionProvidedServerConfigValuesBase}. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | @RunWith(DataProviderRunner.class) 22 | public class DependencyInjectionProvidedServerConfigValuesBaseTest { 23 | 24 | @DataProvider(value = { 25 | "true", 26 | "false" 27 | }) 28 | @Test 29 | public void constructor_sets_values_as_expected(boolean boolVal) { 30 | // given 31 | Integer port = 42; 32 | Integer sslPort = 43; 33 | Boolean useSsl = boolVal; 34 | Integer numBossThreads = 82; 35 | Integer numWorkerThreads = 83; 36 | Integer maxRequestSizeBytes = 4242; 37 | Set> endpoints = mock(Set.class); 38 | Boolean debugActionsEnabled = boolVal; 39 | Boolean debugChannelLifecycleLoggingEnabled = boolVal; 40 | 41 | // when 42 | DependencyInjectionProvidedServerConfigValuesBase base = new DependencyInjectionProvidedServerConfigValuesBase( 43 | port, sslPort, useSsl, numBossThreads, numWorkerThreads, maxRequestSizeBytes, endpoints, 44 | debugActionsEnabled, debugChannelLifecycleLoggingEnabled 45 | ); 46 | 47 | // then 48 | assertThat(base.endpointsPort).isEqualTo(port); 49 | assertThat(base.endpointsSslPort).isEqualTo(sslPort); 50 | assertThat(base.endpointsUseSsl).isEqualTo(useSsl); 51 | assertThat(base.numBossThreads).isEqualTo(numBossThreads); 52 | assertThat(base.numWorkerThreads).isEqualTo(numWorkerThreads); 53 | assertThat(base.maxRequestSizeInBytes).isEqualTo(maxRequestSizeBytes); 54 | assertThat(base.appEndpoints).isEqualTo(endpoints); 55 | assertThat(base.debugActionsEnabled).isEqualTo(debugActionsEnabled); 56 | assertThat(base.debugChannelLifecycleLoggingEnabled).isEqualTo(debugChannelLifecycleLoggingEnabled); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/error/ProjectApiError.kt: -------------------------------------------------------------------------------- 1 | package com.nike.todoservice.error 2 | 3 | import com.nike.backstopper.apierror.ApiError 4 | import com.nike.backstopper.apierror.ApiErrorBase 5 | import com.nike.todoservice.endpoints.TodoItemsEndpoint 6 | import io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND 7 | import java.util.UUID 8 | 9 | /** 10 | * Contains the application-specific errors that can occur. Each enum value maps an application-specific error to the 11 | * appropriate HTTP status code, and contains an appropriate human-readable message. 12 | * 13 | * These can be manually thrown with [com.nike.backstopper.exception.ApiException], or they can be used as the 14 | * `message` for any JSR 303 (Java Bean Validation) annotation (e.g. [javax.validation.constraints.NotNull.message] and 15 | * if that violation is triggered it will be converted by the Backstopper-powered error handling system into the 16 | * appropriate error instance below. 17 | * 18 | * NOTE: These codes are intended to be project-specific. Feel free to rename this class to something specific to the 19 | * project, e.g. MyProjectApiError. 20 | */ 21 | enum class ProjectApiError(private val delegate: ApiError) : ApiError { 22 | 23 | /** 24 | * These are just example errors. Each project should delete this and define their own project specific errors. But 25 | * take a look at how they are used in [TodoItemsEndpoint] first so you know how the validation and error 26 | * handling system works. 27 | */ 28 | TODO_ITEM_NOT_FOUND_1(99160, "The requested TODO was not found", NOT_FOUND.code()); 29 | 30 | @Suppress("unused") 31 | constructor(errorCode: Int, message: String, httpStatusCode: Int, metadata: Map? = null): 32 | this(ApiErrorBase( 33 | "delegated-to-enum-wrapper-" + UUID.randomUUID().toString(), errorCode, message, httpStatusCode, 34 | metadata 35 | )) 36 | 37 | override fun getName(): String { 38 | return this.name 39 | } 40 | 41 | override fun getErrorCode(): String { 42 | return delegate.errorCode 43 | } 44 | 45 | override fun getMessage(): String { 46 | return delegate.message 47 | } 48 | 49 | override fun getMetadata(): Map { 50 | return delegate.metadata 51 | } 52 | 53 | override fun getHttpStatusCode(): Int { 54 | return delegate.httpStatusCode 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImpl.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.model.riposte; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.model.DefaultErrorContractDTO; 5 | import com.nike.backstopper.model.DefaultErrorDTO; 6 | import com.nike.riposte.server.error.handler.ErrorResponseBody; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Collection; 11 | 12 | /** 13 | * Adapter that allows {@link DefaultErrorContractDTO} (the default Backstopper error contract) to be used 14 | * as a Riposte {@link ErrorResponseBody} for a Riposte project. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | @SuppressWarnings("WeakerAccess") 19 | public class ErrorResponseBodyImpl extends DefaultErrorContractDTO implements ErrorResponseBody { 20 | 21 | // Here for deserialization support only - usage in real code should involve one of the other constructors since 22 | // this class is immutable 23 | protected ErrorResponseBodyImpl() { 24 | super(); 25 | } 26 | 27 | public ErrorResponseBodyImpl(@NotNull DefaultErrorContractDTO copy) { 28 | super(ensureNonNullDtoAndErrorId(copy)); 29 | } 30 | 31 | public ErrorResponseBodyImpl(@NotNull String error_id, Collection apiErrors) { 32 | super(error_id, apiErrors); 33 | //noinspection ConstantConditions 34 | if (error_id == null) { 35 | throw new IllegalArgumentException("error_id cannot be null."); 36 | } 37 | } 38 | 39 | public ErrorResponseBodyImpl(@NotNull String error_id, Collection errorsToCopy, Void passInNullForThisArg) { 40 | super(error_id, errorsToCopy, passInNullForThisArg); 41 | //noinspection ConstantConditions 42 | if (error_id == null) { 43 | throw new IllegalArgumentException("error_id cannot be null."); 44 | } 45 | } 46 | 47 | private static DefaultErrorContractDTO ensureNonNullDtoAndErrorId(DefaultErrorContractDTO copy) { 48 | if (copy == null) { 49 | throw new IllegalArgumentException("The DefaultErrorContractDTO copy arg cannot be null."); 50 | } 51 | 52 | if (copy.error_id == null) { 53 | throw new IllegalArgumentException("The DefaultErrorContractDTO.error_id value cannot be null."); 54 | } 55 | 56 | return copy; 57 | } 58 | 59 | @Override 60 | public @NotNull String errorId() { 61 | return error_id; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/header/accept/MimeMediaRangeSubTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.header.accept; 2 | 3 | import com.nike.riposte.server.http.mimetype.MimeType; 4 | 5 | import org.junit.Test; 6 | import com.nike.riposte.testutils.Whitebox; 7 | 8 | import java.util.UUID; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.assertj.core.api.Assertions.catchThrowable; 12 | 13 | /** 14 | * Created by dpet22 on 8/3/16. 15 | */ 16 | public class MimeMediaRangeSubTypeTest { 17 | 18 | @Test 19 | public void test_media_range_subtype_constructor_throws_exceptin_from_null_value () { 20 | // when 21 | Throwable ex = catchThrowable(() -> new MimeMediaRangeSubType(null)); 22 | 23 | // then 24 | assertThat(ex) 25 | .isInstanceOf(IllegalArgumentException.class) 26 | .hasMessageStartingWith("subType instance cannot be null."); 27 | } 28 | 29 | @Test 30 | public void equals_returns_true_for_same_instance() { 31 | // given 32 | MimeMediaRangeSubType instance = new MimeMediaRangeSubType(MimeType.SubType.of("foo")); 33 | 34 | // expect 35 | assertThat(instance.equals(instance)).isTrue(); 36 | } 37 | 38 | @Test 39 | public void hashCode_equals_subtype_hashcode() { 40 | // given 41 | MimeType.SubType subtype = MimeType.SubType.of("foo"); 42 | MimeMediaRangeSubType instance = new MimeMediaRangeSubType(subtype); 43 | 44 | // expect 45 | assertThat(instance.hashCode()).isEqualTo(subtype.hashCode()); 46 | } 47 | 48 | private String getToStringCache(MimeMediaRangeSubType instance) { 49 | return (String) Whitebox.getInternalState(instance, "toStringCache"); 50 | } 51 | 52 | @Test 53 | public void toString_sets_and_uses_cache() { 54 | // given 55 | MimeMediaRangeSubType instance = new MimeMediaRangeSubType(MimeType.SubType.of("foo")); 56 | assertThat(getToStringCache(instance)).isNull(); 57 | 58 | // when 59 | String toStringVal = instance.toString(); 60 | 61 | // then 62 | assertThat(getToStringCache(instance)).isEqualTo(toStringVal); 63 | 64 | // and when 65 | String newCustomVal = UUID.randomUUID().toString(); 66 | Whitebox.setInternalState(instance, "toStringCache", newCustomVal); 67 | 68 | // then 69 | assertThat(instance.toString()).isEqualTo(newCustomVal); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/handler/IdleChannelTimeoutHandler.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.handler; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | 12 | /** 13 | * Handler that {@link io.netty.channel.Channel#close()}s idle channels after the specified number of milliseconds. This 14 | * should be added as one of the first handlers in the pipeline, but it should only be added in between requests so 15 | * that it doesn't squash long-running-but-valid requests. 16 | * 17 | * @author Nic Munroe 18 | */ 19 | @SuppressWarnings("WeakerAccess") 20 | public class IdleChannelTimeoutHandler extends IdleStateHandler { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(IdleChannelTimeoutHandler.class); 23 | 24 | protected final String customHandlerIdForLogs; 25 | protected final long idleTimeoutMillis; 26 | 27 | public IdleChannelTimeoutHandler(long idleTimeoutMillis, String customHandlerIdForLogs) { 28 | super(0, 0, (int) idleTimeoutMillis, TimeUnit.MILLISECONDS); 29 | this.customHandlerIdForLogs = customHandlerIdForLogs; 30 | this.idleTimeoutMillis = idleTimeoutMillis; 31 | } 32 | 33 | @Override 34 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { 35 | channelIdleTriggered(ctx, evt); 36 | 37 | // Close the channel. ChannelPipelineFinalizerHandler.channelInactive(...) will ensure that content is released. 38 | ctx.channel().close(); 39 | 40 | super.channelIdle(ctx, evt); 41 | } 42 | 43 | /** 44 | * Helper method that is called when the idle channel event is triggered, but before anything else happens 45 | * in {@link #channelIdle(ChannelHandlerContext, IdleStateEvent)}. This is usually used to add an appropriate 46 | * log message. 47 | */ 48 | protected void channelIdleTriggered(ChannelHandlerContext ctx, IdleStateEvent evt) { 49 | if (logger.isDebugEnabled()) { 50 | logger.debug( 51 | "Closing server channel due to idle timeout. " 52 | + "custom_handler_id={}, idle_timeout_millis={}, worker_channel_being_closed={}", 53 | customHandlerIdForLogs, idleTimeoutMillis, ctx.channel().toString() 54 | ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/server/handler/SmartHttpContentDecompressor.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.handler; 2 | 3 | import com.nike.riposte.server.channelpipeline.ChannelAttributes; 4 | import com.nike.riposte.server.http.Endpoint; 5 | import com.nike.riposte.server.http.HttpProcessingState; 6 | import com.nike.riposte.server.http.ProxyRouterEndpoint; 7 | import com.nike.riposte.server.http.RequestInfo; 8 | 9 | import io.netty.channel.embedded.EmbeddedChannel; 10 | import io.netty.handler.codec.http.HttpContentDecompressor; 11 | 12 | /** 13 | * Extension of {@link HttpContentDecompressor} that is smart about whether it decompresses responses or not. It 14 | * inspects the {@link HttpProcessingState#getEndpointForExecution()} to see what 15 | * {@link Endpoint#isDecompressRequestPayloadAllowed(RequestInfo)} returns, and only allows automatic decompression if 16 | * that methods returns true. 17 | * 18 | *

{@link ProxyRouterEndpoint}s return false by default for {@link 19 | * ProxyRouterEndpoint#isDecompressRequestPayloadAllowed(RequestInfo)}, so they will not auto-decompress unless that 20 | * method is overridden for a given {@link ProxyRouterEndpoint}. Other endpoint types default to true causing them to 21 | * auto-decompress unless that method is overridden to return false. 22 | * 23 | * @author Nic Munroe 24 | */ 25 | public class SmartHttpContentDecompressor extends HttpContentDecompressor { 26 | 27 | public SmartHttpContentDecompressor() { 28 | super(); 29 | } 30 | 31 | public SmartHttpContentDecompressor(boolean strict) { 32 | super(strict); 33 | } 34 | 35 | @Override 36 | protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Exception { 37 | // We only allow decompression if the endpoint allows it. 38 | HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); 39 | Endpoint endpoint = state.getEndpointForExecution(); 40 | 41 | if (endpointAllowsDecompression(endpoint, state)) { 42 | return super.newContentDecoder(contentEncoding); 43 | } 44 | 45 | // The endpoint does not allow decompression. Return null to indicate that this handler should not 46 | // auto-decompress this request's payload. 47 | return null; 48 | } 49 | 50 | protected boolean endpointAllowsDecompression(Endpoint endpoint, HttpProcessingState state) { 51 | if (endpoint == null) 52 | return true; 53 | 54 | return endpoint.isDecompressRequestPayloadAllowed(state.getRequestInfo()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /riposte-guice/src/main/java/com/nike/backstopper/handler/riposte/config/guice/BackstopperRiposteConfigGuiceModule.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.riposte.config.guice; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.ApiExceptionHandlerUtils; 5 | import com.nike.backstopper.handler.listener.ApiExceptionHandlerListener; 6 | import com.nike.backstopper.handler.riposte.RiposteApiExceptionHandler; 7 | import com.nike.backstopper.handler.riposte.RiposteUnhandledExceptionHandler; 8 | import com.nike.backstopper.handler.riposte.config.BackstopperRiposteConfigHelper; 9 | import com.nike.backstopper.service.riposte.BackstopperRiposteValidatorAdapter; 10 | import com.nike.riposte.server.config.ServerConfig; 11 | import com.nike.riposte.server.error.handler.RiposteErrorHandler; 12 | import com.nike.riposte.server.error.handler.RiposteUnhandledErrorHandler; 13 | import com.nike.riposte.server.error.validation.RequestValidator; 14 | 15 | import com.google.inject.AbstractModule; 16 | import com.google.inject.Provides; 17 | 18 | import java.util.List; 19 | 20 | import javax.inject.Singleton; 21 | 22 | /** 23 | * Wires the Backstopper error handling and validation system into a Riposte based project. Just make sure this Guice 24 | * module is used to generate the {@link ServerConfig#riposteErrorHandler()}, {@link 25 | * ServerConfig#riposteUnhandledErrorHandler()}, and {@link ServerConfig#requestContentValidationService()} for the 26 | * {@link ServerConfig} used by your application's {@link com.nike.riposte.server.Server}). 27 | * 28 | * @author Nic Munroe 29 | */ 30 | @SuppressWarnings("WeakerAccess") 31 | public class BackstopperRiposteConfigGuiceModule extends AbstractModule { 32 | 33 | @Override 34 | protected void configure() { 35 | bind(RiposteErrorHandler.class).to(RiposteApiExceptionHandler.class); 36 | bind(RiposteUnhandledErrorHandler.class).to(RiposteUnhandledExceptionHandler.class); 37 | bind(RequestValidator.class).to(BackstopperRiposteValidatorAdapter.class); 38 | } 39 | 40 | /** 41 | * @return The basic set of handler listeners that are appropriate for most Riposte applications. 42 | */ 43 | @Provides 44 | @Singleton 45 | @SuppressWarnings("unused") 46 | public List apiExceptionHandlerListeners(ProjectApiErrors projectApiErrors, 47 | ApiExceptionHandlerUtils utils) { 48 | return BackstopperRiposteConfigHelper.defaultHandlerListeners(projectApiErrors, utils); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/ReporterFactory.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.metrics.codahale; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.Reporter; 5 | import com.codahale.metrics.ScheduledReporter; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * An Interface for classes that can create configured Reporters. 11 | * 12 | * @author pevans 13 | */ 14 | public interface ReporterFactory { 15 | 16 | /** 17 | * Get the singleton instance of the Reporter wrapped by this ReporterFactory 18 | * 19 | * @param registry 20 | * The MetricRegistry that the Reporter will be reporting on 21 | */ 22 | Reporter getReporter(MetricRegistry registry); 23 | 24 | /** 25 | * True if this factory provides scheduled reporters, normally these reporters will implement ScheduledReporter 26 | * but the CodahaleMetricsEngine can still deal with reporters which don't implement ScheduledReporter but do have a 27 | * start(long interval, TimeUnit unit) method. 28 | * 29 | * @return true if Reporters created by this Factory are scheduled (and therefore have a 30 | * {@code start(long interval, TimeUnit unit)} method), otherwise false 31 | */ 32 | default boolean isScheduled() { 33 | return true; 34 | } 35 | 36 | /** 37 | * The interval at which Reporters produced by this Factory should report 38 | */ 39 | default Long getInterval() { 40 | if (isScheduled()) { 41 | return 1L; 42 | } 43 | else { 44 | return null; 45 | } 46 | } 47 | 48 | /** 49 | * The units associated with the interval 50 | */ 51 | default TimeUnit getTimeUnit() { 52 | if (isScheduled()) { 53 | return TimeUnit.MINUTES; 54 | } 55 | else { 56 | return null; 57 | } 58 | } 59 | 60 | /** 61 | * Override this method to control the Reporter startup. If not implemented the CodahaleMetricsEngine will start 62 | * the Reporter with standard means 63 | */ 64 | default void startReporter() { 65 | throw new UnsupportedOperationException("This ReporterFactory does not expose custom start behavior"); 66 | } 67 | 68 | /** 69 | * Override this method to control the Reporter shutdown. If not implemented the CodahaleMetricsEngine will stop 70 | * the Reporter with standard means 71 | */ 72 | default void stopReporter() { 73 | throw new UnsupportedOperationException("This ReporterFactory does not expose custom stop behavior"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /riposte-typesafe-config/src/main/java/com/nike/riposte/typesafeconfig/util/TypesafeConfigUtil.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.typesafeconfig.util; 2 | 3 | import com.typesafe.config.Config; 4 | import com.typesafe.config.ConfigFactory; 5 | import com.typesafe.config.ConfigMergeable; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Contains static helper methods for working with Typesafe Config. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class TypesafeConfigUtil { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(TypesafeConfigUtil.class); 18 | 19 | // Intentionally private - use the static methods 20 | private TypesafeConfigUtil() { /* Do nothing*/ } 21 | 22 | /** 23 | * Creates a {@link Config} from loading two files based on the given app ID and environment arguments. The base 24 | * file that will be loaded must be named {@code [appId].[conf/json/properties]}, e.g. if the appId was {@code foo} 25 | * then the base file loaded must be named foo.conf, foo.json, or foo.properties as per Typesafe Config's 26 | * specifications. Then the environment file is loaded, and it must be named {@code 27 | * [appId]-[environment].[conf/json/properties]}. Continuing the example, if appId was {@code foo} and environment 28 | * was {@code bar}, then the environment loaded must be named foo-bar.conf, foo-bar.json, or foo-bar.properties. The 29 | * returned {@link Config} object is just the environment file's {@link Config} setup with the base file's {@link 30 | * Config} used as a fallback via {@link Config#withFallback(ConfigMergeable)}. In other words, the environment file 31 | * and base file's properties are merged, with the environment file's properties winning any conflicts where a 32 | * property exists in both files. 33 | *

34 | * NOTE: {@link ConfigFactory#load(String)} is used to load the files, so System properties will override anything 35 | * specified in either the base or environment file. See that method for more detailed information about the file 36 | * loading process. 37 | */ 38 | public static Config loadConfigForAppIdAndEnvironment(String appId, String environment) { 39 | logger.info("Loading properties file: {}.[conf/json/properties]", appId); 40 | Config baseConfig = ConfigFactory.load(appId); 41 | String environmentFilenameNoExtension = appId + "-" + environment; 42 | logger.info("Loading properties file: {}.[conf/json/properties]", environmentFilenameNoExtension); 43 | Config environmentConfig = ConfigFactory.load(environmentFilenameNoExtension); 44 | return environmentConfig.withFallback(baseConfig); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /riposte-core/src/main/java/com/nike/riposte/util/ErrorContractSerializerHelper.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.util; 2 | 3 | import com.nike.backstopper.model.util.JsonUtilWithDefaultErrorContractDTOSupport; 4 | import com.nike.riposte.server.error.handler.ErrorResponseBodySerializer; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | /** 10 | * Helper class for error contract serializers. 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public class ErrorContractSerializerHelper { 14 | 15 | public static final ObjectMapper SMART_ERROR_MAPPER = generateErrorContractObjectMapper(true, true); 16 | public static final ErrorResponseBodySerializer SMART_ERROR_SERIALIZER = 17 | asErrorResponseBodySerializer(SMART_ERROR_MAPPER); 18 | 19 | public static ObjectMapper generateErrorContractObjectMapper(boolean excludeEmptyMetadataFromJson, 20 | boolean serializeErrorCodeFieldAsIntegerIfPossible) { 21 | return JsonUtilWithDefaultErrorContractDTOSupport.generateErrorContractObjectMapper( 22 | excludeEmptyMetadataFromJson, serializeErrorCodeFieldAsIntegerIfPossible 23 | ); 24 | } 25 | 26 | @SuppressWarnings("unused") 27 | public static ErrorResponseBodySerializer generateErrorContractSerializer( 28 | boolean excludeEmptyMetadataFromJson, boolean serializeErrorCodeFieldAsIntegerIfPossible 29 | ) { 30 | return asErrorResponseBodySerializer( 31 | generateErrorContractObjectMapper(excludeEmptyMetadataFromJson, serializeErrorCodeFieldAsIntegerIfPossible) 32 | ); 33 | } 34 | 35 | public static ErrorResponseBodySerializer asErrorResponseBodySerializer(ObjectMapper objectMapper) { 36 | return errorResponseBody -> { 37 | try { 38 | Object bodyToSerialize = (errorResponseBody == null) ? null : errorResponseBody.bodyToSerialize(); 39 | if (bodyToSerialize == null) { 40 | // errorResponseBody itself is null, or errorResponseBody.bodyToSerialize() is null. Either case 41 | // indicates empty response body payload, so we should return null. 42 | return null; 43 | } 44 | if(bodyToSerialize instanceof CharSequence) { 45 | return bodyToSerialize.toString(); 46 | } else { 47 | return objectMapper.writeValueAsString(bodyToSerialize); 48 | } 49 | } 50 | catch (JsonProcessingException e) { 51 | throw new RuntimeException("An error occurred while serializing an ErrorResponseBody to a string", e); 52 | } 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/mimetype/MimeTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.mimetype; 2 | 3 | import com.nike.riposte.server.http.mimetype.MimeType.Facet; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class MimeTypeTest { 14 | 15 | 16 | @Test 17 | public void test_mimetype_compares_two_indentical_instances_correctly () { 18 | final MimeType one = MimeType.of(MimeType.Type.APPLICATION, MimeType.SubType.of("json")); 19 | final MimeType two = MimeType.of(MimeType.Type.APPLICATION, MimeType.SubType.of("json")); 20 | assertThat(one).isEqualTo(two); 21 | assertThat(one.hashCode()).isEqualTo(two.hashCode()); 22 | } 23 | 24 | @Test 25 | public void test_mimetype_compares_two_different_instances_correctly () { 26 | final MimeType one = MimeType.of(MimeType.Type.APPLICATION, MimeType.SubType.of("json")); 27 | final MimeType two = MimeType.of(MimeType.Type.APPLICATION, MimeType.SubType.of(MimeType.Facet.STANDARD, "schema", Optional.of("json"))); 28 | assertThat(one).isNotEqualTo(two); 29 | assertThat(one.hashCode()).isNotEqualTo(two.hashCode()); 30 | } 31 | 32 | @Test 33 | public void test_mimetype_to_string_works () { 34 | final Map parameters = new HashMap<>(); 35 | parameters.put("key","value"); 36 | final MimeType complex = MimeType.of( 37 | MimeType.Type.EXAMPLE, 38 | MimeType.SubType.of(MimeType.Facet.PERSONAL, "niketest", Optional.of("json")), 39 | parameters 40 | ); 41 | assertThat(complex.toString()).isEqualTo("example/prs.niketest+json;key=value"); 42 | } 43 | 44 | 45 | @Test 46 | public void test_facet_lookup_works () { 47 | final Optional vendorFacet = Facet.forRegistrationTreeName("vnd"); 48 | assertThat(vendorFacet).isNotNull(); 49 | assertThat(vendorFacet.isPresent()).isTrue(); 50 | assertThat(vendorFacet.get()).isNotNull(); 51 | assertThat(vendorFacet.get()).isEqualTo(Facet.VENDOR); 52 | 53 | } 54 | 55 | 56 | @Test 57 | public void test_facet_lookup_with_null_name_fails () { 58 | final Optional vendorFacet = Facet.forRegistrationTreeName(null); 59 | assertThat(vendorFacet).isNotNull(); 60 | assertThat(vendorFacet.isPresent()).isFalse(); 61 | } 62 | 63 | 64 | @Test 65 | public void test_facet_lookup_with_unknown_name_fails () { 66 | final Optional vendorFacet = Facet.forRegistrationTreeName("tacos"); 67 | assertThat(vendorFacet).isNotNull(); 68 | assertThat(vendorFacet.isPresent()).isFalse(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /riposte-core/src/test/java/com/nike/riposte/server/channelpipeline/ChannelAttributesTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.channelpipeline; 2 | 3 | import com.nike.riposte.server.http.HttpProcessingState; 4 | 5 | import org.assertj.core.api.Assertions; 6 | import org.junit.Test; 7 | 8 | import java.lang.reflect.Constructor; 9 | import java.lang.reflect.InvocationTargetException; 10 | 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.util.Attribute; 14 | import io.netty.util.AttributeKey; 15 | 16 | import static org.assertj.core.api.Assertions.catchThrowable; 17 | import static org.hamcrest.CoreMatchers.is; 18 | import static org.hamcrest.CoreMatchers.notNullValue; 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.mockito.Mockito.doReturn; 21 | import static org.mockito.Mockito.mock; 22 | 23 | /** 24 | * Tests the functionality of {@link com.nike.riposte.server.channelpipeline.ChannelAttributes} 25 | */ 26 | public class ChannelAttributesTest { 27 | 28 | @Test 29 | public void getHttpProcessingStateForChannel_works_as_expected() { 30 | // given 31 | ChannelHandlerContext ctxMock = mock(ChannelHandlerContext.class); 32 | Channel channelMock = mock(Channel.class); 33 | @SuppressWarnings("unchecked") 34 | Attribute magicAttrMock = mock(Attribute.class); 35 | 36 | // and 37 | doReturn(channelMock).when(ctxMock).channel(); 38 | doReturn(magicAttrMock).when(channelMock).attr(ChannelAttributes.HTTP_PROCESSING_STATE_ATTRIBUTE_KEY); 39 | 40 | // expect 41 | assertThat(ChannelAttributes.getHttpProcessingStateForChannel(ctxMock), is(magicAttrMock)); 42 | } 43 | 44 | @Test 45 | public void exercise_private_constructor_for_code_coverage() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 46 | // given 47 | Constructor constructor = ChannelAttributes.class.getDeclaredConstructor(); 48 | constructor.setAccessible(true); 49 | 50 | // when 51 | ChannelAttributes channelAttributes = constructor.newInstance(); 52 | 53 | // expect 54 | assertThat(channelAttributes, notNullValue()); 55 | } 56 | 57 | @Test 58 | public void processingStateClassAndKeyPair_setValue_throws_UnsupportedOperationException() { 59 | // given 60 | ChannelAttributes.ProcessingStateClassAndKeyPair instance = 61 | new ChannelAttributes.ProcessingStateClassAndKeyPair(null, null); 62 | 63 | // when 64 | Throwable ex = catchThrowable(() -> instance.setValue(AttributeKey.newInstance("someattr"))); 65 | 66 | // then 67 | Assertions.assertThat(ex).isInstanceOf(UnsupportedOperationException.class); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilter.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.filter; 2 | 3 | import com.nike.internal.util.Pair; 4 | import com.nike.riposte.server.http.RequestInfo; 5 | import com.nike.riposte.server.http.ResponseInfo; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.Optional; 11 | 12 | import io.netty.channel.ChannelHandlerContext; 13 | 14 | /** 15 | * An extension of {@link RequestAndResponseFilter} that pre-configures the methods to work as a short-circuiting filter. Specifically 16 | * {@link #isShortCircuitRequestFilter()} will return true, and the non-short-circuiting methods are configured to throw a 17 | * {@link UnsupportedOperationException} since they should never be called when {@link #isShortCircuitRequestFilter()} is true. You just need 18 | * to implement the short circuiting request filter methods and the response filter method. See the javadocs for {@link RequestAndResponseFilter} 19 | * and its methods for more general information on request/response filtering. 20 | * 21 | * @author Nic Munroe 22 | */ 23 | public interface ShortCircuitingRequestAndResponseFilter extends RequestAndResponseFilter { 24 | 25 | @Override 26 | default @Nullable RequestInfo filterRequestFirstChunkNoPayload( 27 | @NotNull RequestInfo currentRequestInfo, 28 | @NotNull ChannelHandlerContext ctx 29 | ) { 30 | throw new UnsupportedOperationException( 31 | "This method should never be called for ShortCircuitingRequestAndResponseFilter classes " 32 | + "(where isShortCircuitRequestFilter() returns true)" 33 | ); 34 | } 35 | 36 | @Override 37 | default @Nullable RequestInfo filterRequestLastChunkWithFullPayload( 38 | @NotNull RequestInfo currentRequestInfo, 39 | @NotNull ChannelHandlerContext ctx 40 | ) { 41 | throw new UnsupportedOperationException( 42 | "This method should never be called for ShortCircuitingRequestAndResponseFilter classes " 43 | + "(where isShortCircuitRequestFilter() returns true)" 44 | ); 45 | } 46 | 47 | @Override 48 | default boolean isShortCircuitRequestFilter() { 49 | return true; 50 | } 51 | 52 | @Override 53 | @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( 54 | @NotNull RequestInfo currentRequestInfo, 55 | @NotNull ChannelHandlerContext ctx 56 | ); 57 | 58 | @Override 59 | @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( 60 | @NotNull RequestInfo currentRequestInfo, 61 | @NotNull ChannelHandlerContext ctx 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /riposte-guice/src/main/java/com/nike/guice/PropertiesRegistrationGuiceModule.java: -------------------------------------------------------------------------------- 1 | package com.nike.guice; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.name.Names; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * A Guice Module that registers all properties returned by {@link #getPropertiesMap()} as injectable values. This makes 10 | * bridging properties specified in properties files (*.properties, *.conf, etc) into Guice fairly trivial. Just load 11 | * the properties files and return the values from {@link #getPropertiesMap()}. Once this module runs, any properties 12 | * returned by that map are eligible for injection. You can then inject the values of those properties into your objects 13 | * like so: 14 | *

15 |  *      @Inject
16 |  *      @Named("some.integer.property.key.from.props.files")
17 |  *      private Integer someIntProp;
18 |  *
19 |  *      @Inject
20 |  *      @Named("some.string.property.key.from.props.files")
21 |  *      String someStringProp;
22 |  * 
23 | * You can also access them within your other Guice modules' @Provides methods, for example: 24 | *
25 |  *      @Provides
26 |  *      public CustomWidget customWidget(@Named("interesting.property.from.props.file") String propFromPropsFile) {
27 |  *          return new CustomWidget(propFromPropsFile);
28 |  *      }
29 |  * 
30 | *

31 | *

USING THIS IN TESTS

Part of the benefit of this class is that you can use it in your unit tests to inject 32 | * properties in the same way the code works in production. Just create a custom {@code 33 | * PropertiesRegistrationGuiceModuleForTesting} module that extends your production class, but overrides any properties 34 | * you need for testing, and have Guice use the testing module instead of the production module when unit testing. 35 | * 36 | * @author Nic Munroe 37 | */ 38 | public abstract class PropertiesRegistrationGuiceModule extends AbstractModule { 39 | 40 | /** 41 | * @return The map of property key/value pairs you want associated with Guice so that they can be {@code @Inject}-ed 42 | * simply by putting the property key into an {@code @Named} annotation. This method is usually implemented by just 43 | * loading your application's properties files and returning the data as a map, but there's nothing stopping you 44 | * from using a different mechanism to load your properties if you want. 45 | */ 46 | protected abstract Map getPropertiesMap(); 47 | 48 | @Override 49 | protected void configure() { 50 | /* 51 | Grab all the string properties and register them with Guice so they can be @Inject-ed 52 | without any further configuration. 53 | */ 54 | Map props = getPropertiesMap(); 55 | Names.bindProperties(binder(), props); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /riposte-spi/src/test/java/com/nike/riposte/server/http/filter/RequestAndResponseFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.http.filter; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | import com.nike.riposte.server.http.ResponseInfo; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.junit.Test; 9 | 10 | import io.netty.channel.ChannelHandlerContext; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.catchThrowable; 14 | import static org.mockito.Mockito.mock; 15 | 16 | /** 17 | * Tests the default method implementations of the {@link RequestAndResponseFilter} interface. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | public class RequestAndResponseFilterTest { 22 | 23 | @Test 24 | public void default_method_implementations_behave_as_expected() { 25 | // given 26 | RequestAndResponseFilter defaultImpl = new RequestAndResponseFilter() { 27 | @Override 28 | public @Nullable RequestInfo filterRequestFirstChunkNoPayload( 29 | @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx 30 | ) { 31 | return null; 32 | } 33 | 34 | @Override 35 | public @Nullable RequestInfo filterRequestLastChunkWithFullPayload( 36 | @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx 37 | ) { 38 | return null; 39 | } 40 | 41 | @Override 42 | public @Nullable ResponseInfo filterResponse( 43 | @NotNull ResponseInfo currentResponseInfo, 44 | @NotNull RequestInfo requestInfo, 45 | @NotNull ChannelHandlerContext ctx 46 | ) { 47 | return null; 48 | } 49 | }; 50 | 51 | // expect 52 | assertThat(defaultImpl.isShortCircuitRequestFilter()).isFalse(); 53 | Throwable firstChunkShortCircuitEx = catchThrowable(() -> defaultImpl.filterRequestFirstChunkWithOptionalShortCircuitResponse( 54 | mock(RequestInfo.class), mock(ChannelHandlerContext.class) 55 | )); 56 | assertThat(firstChunkShortCircuitEx) 57 | .isNotNull() 58 | .isInstanceOf(UnsupportedOperationException.class); 59 | Throwable lastChunkShortCircuitEx = catchThrowable(() -> defaultImpl.filterRequestLastChunkWithOptionalShortCircuitResponse( 60 | mock(RequestInfo.class), mock(ChannelHandlerContext.class) 61 | )); 62 | assertThat(lastChunkShortCircuitEx) 63 | .isNotNull() 64 | .isInstanceOf(UnsupportedOperationException.class); 65 | 66 | assertThat(defaultImpl.shouldExecuteBeforeSecurityValidation()) 67 | .isTrue(); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.nike.riposte.server.error.validation; 2 | 3 | import com.nike.backstopper.service.riposte.BackstopperRiposteValidatorAdapter; 4 | import com.nike.riposte.server.http.Endpoint; 5 | import com.nike.riposte.server.http.RequestInfo; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | /** 11 | * Interface for a request validator. Concrete implementations might perform JSR 303 validation, or custom validation 12 | * based on specific object types, or anything else your application needs. If an endpoint wants the request content 13 | * deserialized before it's called *and* your endpoint's {@link Endpoint#isValidateRequestContent(RequestInfo)} 14 | * method returns true, then the concrete implementation of this interface that you registered with your server will be 15 | * called. 16 | *

17 | * You can create your own instance of this class, however it's highly recommended that you just use the prebuilt {@link 18 | * BackstopperRiposteValidatorAdapter} class which is part of the default error handling and validation system that is 19 | * designed to make error handling and validation easy and is based on Backstopper. The {@link 20 | * BackstopperRiposteValidatorAdapter} implementation is based on JSR 303 (a.k.a. Java Bean Validation) and is linked 21 | * nicely with the Backstopper system to make it trivially easy to tie validation errors to error responses that conform 22 | * to a consistent error contract. 23 | * 24 | * @author Nic Munroe 25 | */ 26 | public interface RequestValidator { 27 | 28 | /** 29 | * Performs default validation on the given request's {@link RequestInfo#getContent()}. If this implementation uses 30 | * JSR 303 validation then this method call indicates using the default group. 31 | * 32 | * @param request The request whose {@link RequestInfo#getContent()} needs validating. Should never be null. 33 | */ 34 | void validateRequestContent(@NotNull RequestInfo request); 35 | 36 | /** 37 | * Performs validation on the given request's {@link RequestInfo#getContent()} using the given validation groups. If 38 | * this implementation uses JSR 303 validation then this method call indicates using the given groups with the 39 | * {@code javax.validation.Validator}. 40 | * 41 | * @param request The request whose {@link RequestInfo#getContent()} needs validating. Should never be null. 42 | * @param validationGroups The validation groups to use when validating the given request's content. This can 43 | * safely be null if you have no groups to apply, however in that case you should probably simply call 44 | * {@link #validateRequestContent(RequestInfo)} instead. 45 | */ 46 | void validateRequestContent(@NotNull RequestInfo request, @Nullable Class... validationGroups); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/src/main/java/com/nike/helloworld/HelloWorldEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.nike.helloworld; 2 | 3 | import com.nike.riposte.server.http.RequestInfo; 4 | import com.nike.riposte.server.http.ResponseInfo; 5 | import com.nike.riposte.server.http.StandardEndpoint; 6 | import com.nike.riposte.util.AsyncNettyHelper; 7 | import com.nike.riposte.util.Matcher; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.concurrent.Executor; 15 | import java.util.function.Supplier; 16 | 17 | import io.netty.channel.ChannelHandlerContext; 18 | import io.netty.handler.codec.http.HttpMethod; 19 | 20 | import static com.nike.riposte.util.AsyncNettyHelper.supplierWithTracingAndMdc; 21 | 22 | /** 23 | * A basic {@link StandardEndpoint} that listends for GET calls at the root path "/" and simply responds with 24 | * "Hello, world" in text/plain mime type. 25 | */ 26 | public class HelloWorldEndpoint extends StandardEndpoint { 27 | 28 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 29 | private final Matcher matcher = Matcher.match("/", HttpMethod.GET); 30 | 31 | /** 32 | * @return The {@link Matcher} that maps incoming requests to this endpoint. 33 | */ 34 | @Override 35 | public @NotNull Matcher requestMatcher() { 36 | return matcher; 37 | } 38 | 39 | /** 40 | * Sample service endpoint using CompletableFuture.supplyAsync to illustrate how to create an asynchronous 41 | * call. Where the service task will be completed without making external calls or compute-intensive code 42 | * it would be better to call: 43 | * 44 | *

45 |      * return CompletableFuture.completedFuture(ResponseInfo.newBuilder("Hello, world!")
46 |      *                                          .withDesiredContentWriterMimeType("text/plain")
47 |      *                                          .build();
48 |      * 
49 | * 50 | * Also note that because we use {@link AsyncNettyHelper#supplierWithTracingAndMdc(Supplier, ChannelHandlerContext)} 51 | * to wrap the endpoint logic, the log message will be automatically tagged with the trace ID associated with the 52 | * incoming request. 53 | */ 54 | @Override 55 | public @NotNull CompletableFuture> execute( 56 | @NotNull RequestInfo request, 57 | @NotNull Executor longRunningTaskExecutor, 58 | @NotNull ChannelHandlerContext ctx 59 | ) { 60 | return CompletableFuture.supplyAsync(supplierWithTracingAndMdc( 61 | () -> { 62 | logger.debug("Processing Request..."); 63 | return ResponseInfo.newBuilder("Hello, world!").withDesiredContentWriterMimeType("text/plain").build(); 64 | }, ctx) 65 | ); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /samples/sample-1-helloworld/src/test/java/com/nike/helloworld/HelloWorldEndpointTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.helloworld; 2 | 3 | import com.nike.Main; 4 | import com.nike.backstopper.apierror.sample.SampleCoreApiError; 5 | import com.nike.riposte.server.Server; 6 | 7 | import org.junit.AfterClass; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | import java.net.ServerSocket; 13 | 14 | import static io.restassured.RestAssured.given; 15 | import static io.restassured.http.ContentType.JSON; 16 | import static io.restassured.http.ContentType.TEXT; 17 | import static org.hamcrest.Matchers.equalTo; 18 | 19 | /** 20 | * Simple component tests for the hello world endpoint. 21 | */ 22 | public class HelloWorldEndpointTest { 23 | 24 | public static class AppServerConfigForTesting extends Main.AppServerConfig { 25 | private final int port; 26 | 27 | public AppServerConfigForTesting(int port) { 28 | this.port = port; 29 | } 30 | 31 | @Override 32 | public int endpointsPort() { 33 | return port; 34 | } 35 | } 36 | 37 | private static Server server; 38 | private static AppServerConfigForTesting serverConfig; 39 | 40 | private static int findFreePort() throws IOException { 41 | try (ServerSocket serverSocket = new ServerSocket(0)) { 42 | return serverSocket.getLocalPort(); 43 | } 44 | } 45 | 46 | @BeforeClass 47 | public static void setup() throws Exception { 48 | serverConfig = new AppServerConfigForTesting(findFreePort()); 49 | server = new Server(serverConfig); 50 | server.startup(); 51 | } 52 | 53 | @AfterClass 54 | public static void teardown() throws Exception { 55 | server.shutdown(); 56 | } 57 | 58 | /** 59 | * Sample integration test to validate the content type and body for a supported request. 60 | */ 61 | @Test 62 | public void success() { 63 | given() 64 | .port(serverConfig.endpointsPort()) 65 | .when() 66 | .get("/") 67 | .then() 68 | .assertThat() 69 | .contentType(TEXT) 70 | .body(equalTo("Hello, world!")); 71 | } 72 | 73 | /** 74 | * Sample integration test to validate the content type and body for an unsupported request This is unnecessary 75 | * really as this test is provided tbe core library, but is provided here as a reference for the developer. 76 | */ 77 | @Test 78 | public void pathNotfound() { 79 | given() 80 | .port(serverConfig.endpointsPort()) 81 | .when() 82 | .get("/fail") 83 | .then() 84 | .assertThat() 85 | .statusCode(404) 86 | .contentType(JSON) 87 | .body("errors[0].code", equalTo(Integer.parseInt(SampleCoreApiError.NOT_FOUND.getErrorCode()))); 88 | } 89 | 90 | 91 | } 92 | --------------------------------------------------------------------------------