-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Chris Birchall
committed
Sep 4, 2014
0 parents
commit 6818616
Showing
293 changed files
with
33,912 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
.settings | ||
.classpath | ||
.project | ||
*.iml | ||
*.ipr | ||
*.iws | ||
dist/ | ||
lib_managed/ | ||
project/boot/ | ||
project/plugins/project/ | ||
project/target/ | ||
project/project/ | ||
target/ | ||
|
||
# use glob syntax. | ||
syntax: glob | ||
*.ser | ||
*.class | ||
*~ | ||
*.bak | ||
#*.off | ||
*.old | ||
|
||
# eclipse conf file | ||
.settings | ||
.classpath | ||
.project | ||
.manager | ||
.scala_dependencies | ||
|
||
# idea | ||
.idea | ||
*.iml | ||
|
||
# building | ||
target | ||
build | ||
null | ||
dist | ||
test-output | ||
build.log | ||
|
||
# other scm | ||
.svn | ||
.CVS | ||
.hg* | ||
|
||
# switch to regexp syntax. | ||
# syntax: regexp | ||
# ^\.pc/ | ||
|
||
# Naughty output not in target directory | ||
build.log | ||
.DS_Store | ||
derby.log | ||
*.db | ||
.lib | ||
logs | ||
|
||
# Vagrant | ||
.vagrant | ||
vagrant_ansible_inventory_default | ||
tmp | ||
|
||
# Development environment files | ||
conf/application.dev.conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash | ||
java_major_version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F'.' '{ print $2 }') | ||
if [ $java_major_version -ge 8 ]; then | ||
PERM_OPT="-XX:MaxMetaspaceSize=386M" | ||
else | ||
PERM_OPT="-XX:MaxPermSize=256M" | ||
fi | ||
export SBT_OPTS="-XX:+CMSClassUnloadingEnabled ${PERM_OPT}" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Octoparts | ||
|
||
[See documentation](http://m3dev.github.io/octoparts-site/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.m3.octoparts | ||
|
||
import java.io.File | ||
import java.util.concurrent.TimeUnit | ||
|
||
import _root_.controllers.ControllersModule | ||
import com.kenshoo.play.metrics.MetricsFilter | ||
import com.m3.octoparts.cache.CacheModule | ||
import com.m3.octoparts.http.HttpModule | ||
import com.m3.octoparts.hystrix.{ HystrixMetricsLogger, HystrixModule } | ||
import com.m3.octoparts.logging.PartRequestLogger | ||
import com.m3.octoparts.repository.RepositoriesModule | ||
import com.typesafe.config.ConfigFactory | ||
import com.wordnik.swagger.config.{ ConfigFactory => SwaggerConfigFactory } | ||
import com.wordnik.swagger.model.ApiInfo | ||
import play.api._ | ||
import play.api.libs.concurrent.Akka | ||
import play.api.mvc._ | ||
import scaldi.Module | ||
import scaldi.play.ScaldiSupport | ||
|
||
import scala.collection.concurrent.TrieMap | ||
import scala.concurrent.duration._ | ||
|
||
object Global extends WithFilters(MetricsFilter) with ScaldiSupport { | ||
|
||
val info = ApiInfo( | ||
title = "Octoparts", | ||
description = """Octoparts is an API aggregator service for your backend HTTP services.""", | ||
termsOfServiceUrl = "<Choose your own terms of service url>", | ||
contact = "<Put your own contact info here>", | ||
license = "<Choose your own licence>", | ||
licenseUrl = "<Choose your own licence URL>") | ||
|
||
SwaggerConfigFactory.config.setApiInfo(info) | ||
|
||
def applicationModule = | ||
aggregator.module :: | ||
new RepositoriesModule :: | ||
new CacheModule :: | ||
new HystrixModule :: | ||
new HttpModule :: | ||
new ControllersModule :: | ||
new Module { | ||
// Random stuff that doesn't belong in other modules | ||
bind[PartRequestLogger] to PartRequestLogger | ||
} | ||
|
||
/** | ||
* For each entry, V.getClass == K | ||
*/ | ||
private val controllerCache = TrieMap[Class[_], Any]() | ||
|
||
/** | ||
* Caches controller instantiation which was shown to be expensive because of ScalDI. | ||
*/ | ||
override def getControllerInstance[A](clazz: Class[A]): A = { | ||
controllerCache.getOrElseUpdate(clazz, super.getControllerInstance(clazz)).asInstanceOf[A] | ||
} | ||
|
||
override def onStop(app: Application) = { | ||
controllerCache.clear() | ||
super.onStop(app) | ||
} | ||
|
||
// Load environment-specific application.${env}.conf, merged with the generic application.conf | ||
override def onLoadConfig(config: Configuration, path: File, classloader: ClassLoader, mode: Mode.Mode): Configuration = { | ||
val playEnv = config.getString("application.env").fold(mode.toString) { parsedEnv => | ||
// "test" mode should cause the environment to be "test" except when the parsedEnv is "ci", | ||
// since CI/Jenkins needs its own test environment configuration | ||
(mode.toString.toLowerCase, parsedEnv.toLowerCase) match { | ||
case ("test", env) if env != "ci" => "test" | ||
case (_, env) => env | ||
} | ||
} | ||
Logger.debug(s"Play environment = $playEnv (mode = $mode, application.env = ${config.getString("application.env")}). Loading extra config from application.$playEnv.conf, if it exists.") | ||
val modeSpecificConfig = config ++ Configuration(ConfigFactory.load(s"application.$playEnv.conf")) | ||
super.onLoadConfig(modeSpecificConfig, path, classloader, mode) | ||
} | ||
|
||
override def onStart(app: Application) = { | ||
super.onStart(app) | ||
|
||
startPeriodicTasks(app) | ||
} | ||
|
||
/** | ||
* Register any tasks that should be run on the global Akka scheduler. | ||
* These tasks will automatically stop running when the app shuts down. | ||
*/ | ||
def startPeriodicTasks(implicit app: Application): Unit = { | ||
import play.api.libs.concurrent.Execution.Implicits.defaultContext | ||
|
||
val hystrixLoggingInterval = app.configuration.underlying.getDuration("hystrix.logging.intervalMs", TimeUnit.MILLISECONDS).toInt.millis | ||
Akka.system.scheduler.schedule(hystrixLoggingInterval, hystrixLoggingInterval) { | ||
HystrixMetricsLogger.logHystrixMetrics() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.m3.octoparts.aggregator | ||
|
||
import com.m3.octoparts.model.{ PartRequest, RequestMeta } | ||
|
||
/** | ||
* All the information needed to request a given part | ||
* | ||
* @param requestMeta The common meta info that was supplied to the AggregateRequest | ||
* @param partRequest Information about the individual part being requested | ||
*/ | ||
case class PartRequestInfo( | ||
requestMeta: RequestMeta, | ||
partRequest: PartRequest, | ||
noCache: Boolean = false) { | ||
|
||
val partRequestId = partRequest.id.getOrElse(partRequest.partId) | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
app/com/m3/octoparts/aggregator/handler/AggregatorHandlersModule.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.m3.octoparts.aggregator.handler | ||
|
||
import com.m3.octoparts.http.HttpClientPool | ||
import scaldi.Module | ||
|
||
class AggregatorHandlersModule extends Module { | ||
|
||
implicit val glueContext = play.api.libs.concurrent.Execution.Implicits.defaultContext | ||
|
||
bind[HttpHandlerFactory] to new SimpleHttpHandlerFactory(inject[HttpClientPool]) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.m3.octoparts.aggregator.handler | ||
|
||
import com.m3.octoparts.model.PartResponse | ||
import com.m3.octoparts.model.config._ | ||
|
||
import scala.concurrent.Future | ||
|
||
/** | ||
* A Handler simply maps a Map[Param, String] to a Future[PartResponse] | ||
* via it's #process method | ||
* | ||
* A Handler makes use of configuration data (partId, URI, registered params, etc.) | ||
* and request-specific data (PartRequestInfo) to form a request to the external dependency. | ||
* In most cases, this is done by parsing the PartRequestInfo | ||
* into a form that can be consumed by a generic HystrixCommand (see [[HttpPartRequestHandler]]) | ||
*/ | ||
trait Handler { | ||
|
||
type HandlerArguments = Map[ShortPartParam, String] | ||
|
||
// Used primarily for creating a PartResponse, but also for logging purposes | ||
def partId: String | ||
|
||
def process(arguments: HandlerArguments): Future[PartResponse] | ||
} |
13 changes: 13 additions & 0 deletions
13
app/com/m3/octoparts/aggregator/handler/HttpHandlerFactory.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.m3.octoparts.aggregator.handler | ||
|
||
import com.m3.octoparts.model.config.HttpPartConfig | ||
|
||
trait HttpHandlerFactory { | ||
|
||
/** | ||
* @param config a HttpCommandConfig entry | ||
* @return a handler ready to be used | ||
*/ | ||
def makeHandler(config: HttpPartConfig): Handler | ||
|
||
} |
142 changes: 142 additions & 0 deletions
142
app/com/m3/octoparts/aggregator/handler/HttpPartRequestHandler.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package com.m3.octoparts.aggregator.handler | ||
|
||
import java.net.{ URI, URLEncoder } | ||
|
||
import com.m3.octoparts.http.{ HttpResponse, _ } | ||
import com.m3.octoparts.hystrix._ | ||
import com.m3.octoparts.model.PartResponse | ||
import com.m3.octoparts.model.config._ | ||
import com.netaporter.uri.Uri | ||
import com.netaporter.uri.dsl._ | ||
|
||
import scala.concurrent.{ ExecutionContext, Future } | ||
import scala.util.matching.Regex | ||
|
||
/** | ||
* Trait describing a handler for processing of generic HTTP PartRequestInfo requests | ||
* that correspond to an external dependency | ||
*/ | ||
trait HttpPartRequestHandler extends Handler { | ||
handler => | ||
|
||
implicit def executionContext: ExecutionContext | ||
|
||
def httpClient: HttpClientLike | ||
|
||
def uriToInterpolate: String | ||
|
||
def httpMethod: HttpMethod.Value | ||
|
||
def additionalValidStatuses: Set[Int] | ||
|
||
def hystrixExecutor: HystrixExecutor | ||
|
||
/** | ||
* A regex for matching "${...}" placeholders in strings | ||
*/ | ||
private val PlaceholderReplacer: Regex = """\$\{([^\}]+)\}""".r | ||
|
||
// def registeredParams: Set[PartParam] | ||
|
||
/** | ||
* Given arguments for this handler, builds a blocking HTTP request with the proper | ||
* URI, header and body params and sends it asynchronously in the context of | ||
* a Hystrix Command. | ||
* | ||
* Note that this Future should be used with care because it may | ||
* contain a Failure instead of Success. Make sure to transform with | ||
* .recover | ||
* | ||
* @param hArgs Preparsed HystrixArguments | ||
* @return Future[PartResponse] | ||
*/ | ||
def process(hArgs: HandlerArguments): Future[PartResponse] = { | ||
hystrixExecutor.future { | ||
createBlockingHttpRetrieve(hArgs).retrieve() | ||
}.map { | ||
createPartResponse | ||
} | ||
} | ||
|
||
/** | ||
* Returns a BlockingHttpRetrieve command | ||
* | ||
* @param hArgs Handler arguments | ||
* @return a command object that will perform an HTTP request on demand | ||
*/ | ||
def createBlockingHttpRetrieve(hArgs: HandlerArguments): BlockingHttpRetrieve = { | ||
new BlockingHttpRetrieve { | ||
val httpClient = handler.httpClient | ||
val method = httpMethod | ||
val uri = new URI(buildUri(hArgs)) | ||
val maybeBody = hArgs.collectFirst { | ||
case (p, v) if p.paramType == ParamType.Body => v | ||
} | ||
val headers = collectHeaders(hArgs) | ||
} | ||
} | ||
|
||
/** | ||
* Transforms a HttpResponse case class into a PartResponse | ||
* @param httpResp HttpResponse | ||
* @return PartREsponse | ||
*/ | ||
def createPartResponse(httpResp: HttpResponse) = PartResponse( | ||
partId, | ||
id = partId, | ||
cookies = httpResp.cookies, | ||
statusCode = Some(httpResp.status), | ||
mimeType = httpResp.mimeType, | ||
charset = httpResp.charset, | ||
cacheControl = httpResp.cacheControl, | ||
contents = httpResp.body, | ||
errors = if (httpResp.status < 400 || additionalValidStatuses.contains(httpResp.status)) Nil else Seq(httpResp.message) | ||
) | ||
|
||
/** | ||
* Turns a string into an escaped string for cookies | ||
* @param c Cookie name or value | ||
* @return escaped cookie string | ||
*/ | ||
def escapeCookie(c: String) = URLEncoder.encode(c, "UTF-8") | ||
|
||
/** | ||
* Isolates the header-related arguments, taking care to escape Cookie headers | ||
* @param hArgs arguments | ||
* @return Map[String, String] | ||
*/ | ||
def collectHeaders(hArgs: HandlerArguments): Seq[(String, String)] = { | ||
hArgs.toSeq.collect { | ||
case (p, v) if p.paramType == ParamType.Header => p.outputName -> v | ||
case (p, v) if p.paramType == ParamType.Cookie => "Cookie" -> (escapeCookie(p.outputName) + "=" + escapeCookie(v)) | ||
} | ||
} | ||
|
||
/** | ||
* Takes a "base" URI string in the format of "http://example.com/${hello}" and returns an interpolated | ||
* string | ||
* | ||
* @param hArgs HttpArguments | ||
* @return Uri | ||
*/ | ||
private[handler] def buildUri(hArgs: HandlerArguments): Uri = { | ||
val baseUri = interpolate(uriToInterpolate) { key => | ||
val maybeParamsVal: Option[String] = hArgs.collectFirst { | ||
case (p, v) if p.paramType == ParamType.Path && p.outputName == key => v | ||
} | ||
maybeParamsVal.getOrElse("") | ||
} | ||
baseUri.addParams(hArgs.collect { case (p, v) if p.paramType == ParamType.Query => (p.outputName, v) }.toSeq) | ||
} | ||
|
||
/** | ||
* Replace all instances of "${...}" placeholders in the given string | ||
* | ||
* @param stringToInterpolate the string that includes placeholders | ||
* @param replacer a function that replaces the contents of the placeholder (excluding braces) with a string | ||
* @return the interpolated string | ||
*/ | ||
private def interpolate(stringToInterpolate: String)(replacer: String => String) = | ||
PlaceholderReplacer.replaceAllIn(stringToInterpolate, { m => replacer(m.group(1)) }) | ||
|
||
} |
Oops, something went wrong.