-
Notifications
You must be signed in to change notification settings - Fork 265
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and simplify API dsl (#1690)
Refactor the API handlers. Split handlers and directives in several files to make them more composable. Co-authored-by: Pierre-Marie Padiou <[email protected]> Co-authored-by: Bastien Teinturier <[email protected]>
- Loading branch information
1 parent
ea8f940
commit 9ff2f83
Showing
22 changed files
with
1,037 additions
and
430 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
366 changes: 26 additions & 340 deletions
366
eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala
Large diffs are not rendered by default.
Oops, something went wrong.
43 changes: 43 additions & 0 deletions
43
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/AuthDirective.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,43 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
import akka.http.scaladsl.server.Directive0 | ||
import akka.http.scaladsl.server.directives.Credentials | ||
import fr.acinq.eclair.api.Service | ||
|
||
import scala.concurrent.Future | ||
import scala.concurrent.duration.DurationInt | ||
|
||
trait AuthDirective { | ||
this: Service with EclairDirectives => | ||
|
||
/** | ||
* A directive0 that passes whenever valid basic credentials are provided. We | ||
* are not interested in the extracted username. | ||
* | ||
* @return | ||
*/ | ||
def authenticated: Directive0 = authenticateBasicAsync(realm = "Access restricted", userPassAuthenticator).tflatMap { _ => pass } | ||
|
||
|
||
private def userPassAuthenticator(credentials: Credentials): Future[Option[String]] = credentials match { | ||
case p@Credentials.Provided(id) if p.verify(password) => Future.successful(Some(id)) | ||
case _ => akka.pattern.after(1 second, using = actorSystem.scheduler)(Future.successful(None))(actorSystem.dispatcher) // force a 1 sec pause to deter brute force | ||
} | ||
|
||
} |
36 changes: 36 additions & 0 deletions
36
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/DefaultHeaders.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,36 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
import akka.http.scaladsl.model.HttpMethods.POST | ||
import akka.http.scaladsl.model.headers.CacheDirectives.{`max-age`, `no-store`, public} | ||
import akka.http.scaladsl.model.headers._ | ||
import akka.http.scaladsl.server.Directive0 | ||
import akka.http.scaladsl.server.Directives.respondWithDefaultHeaders | ||
|
||
trait DefaultHeaders { | ||
|
||
/** | ||
* Adds customHeaders to all http responses. | ||
*/ | ||
def eclairHeaders:Directive0 = respondWithDefaultHeaders(customHeaders) | ||
|
||
|
||
private val customHeaders = `Access-Control-Allow-Headers`("Content-Type, Authorization") :: | ||
`Access-Control-Allow-Methods`(POST) :: | ||
`Cache-Control`(public, `no-store`, `max-age`(0)) :: Nil | ||
} |
47 changes: 47 additions & 0 deletions
47
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/EclairDirectives.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,47 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
import akka.http.scaladsl.server.{Directive0, Directive1, Directives} | ||
import akka.util.Timeout | ||
import fr.acinq.eclair.api.Service | ||
|
||
import scala.concurrent.duration.DurationInt | ||
|
||
class EclairDirectives extends Directives with TimeoutDirective with ErrorDirective with AuthDirective with DefaultHeaders with ExtraDirectives { this: Service => | ||
|
||
/** | ||
* Prepares inner routes to be exposed as public API with default headers, error handlers and basic authentication. | ||
*/ | ||
private def securedHandler:Directive0 = eclairHeaders & handled & authenticated | ||
|
||
/** | ||
* Provides a Timeout to the inner route either from request param or the default. | ||
*/ | ||
private def standardHandler:Directive1[Timeout] = toStrictEntity(5 seconds) & withTimeout | ||
|
||
/** | ||
* Handles POST requests with given simple path. The inner route is wrapped in a standard handler and provides a Timeout as parameter. | ||
*/ | ||
def postRequest(p:String):Directive1[Timeout] = securedHandler & post & path(p) & standardHandler | ||
|
||
/** | ||
* Handles GET requests with given simple path. The inner route is wrapped in a standard handler and provides a Timeout as parameter. | ||
*/ | ||
def getRequest(p:String):Directive1[Timeout] = securedHandler & get & path(p) & standardHandler | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ErrorDirective.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,53 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpResponse, StatusCodes} | ||
import akka.http.scaladsl.server.{Directive0, ExceptionHandler, RejectionHandler} | ||
import fr.acinq.eclair.api.Service | ||
|
||
trait ErrorDirective { | ||
this: Service with EclairDirectives => | ||
|
||
/** | ||
* Handles API exceptions and rejections. Produces json formatted | ||
* error responses. | ||
*/ | ||
def handled: Directive0 = handleExceptions(apiExceptionHandler) & | ||
handleRejections(apiRejectionHandler) | ||
|
||
|
||
import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization} | ||
|
||
private val apiExceptionHandler = ExceptionHandler { | ||
case t: IllegalArgumentException => | ||
logger.error(s"API call failed with cause=${t.getMessage}", t) | ||
complete(StatusCodes.BadRequest, ErrorResponse(t.getMessage)) | ||
case t: Throwable => | ||
logger.error(s"API call failed with cause=${t.getMessage}", t) | ||
complete(StatusCodes.InternalServerError, ErrorResponse(t.getMessage)) | ||
} | ||
|
||
// map all the rejections to a JSON error object ErrorResponse | ||
private val apiRejectionHandler = RejectionHandler.default.mapRejectionResponse { | ||
case res@HttpResponse(_, _, ent: HttpEntity.Strict, _) => | ||
res.withEntity( | ||
HttpEntity(ContentTypes.`application/json`, serialization.writePretty(ErrorResponse(ent.data.utf8String))) | ||
) | ||
} | ||
|
||
} |
19 changes: 19 additions & 0 deletions
19
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ErrorResponse.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,19 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
case class ErrorResponse(error: String) |
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
54 changes: 54 additions & 0 deletions
54
eclair-node/src/main/scala/fr/acinq/eclair/api/directives/TimeoutDirective.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,54 @@ | ||
/* | ||
* Copyright 2019 ACINQ SAS | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package fr.acinq.eclair.api.directives | ||
|
||
import akka.http.scaladsl.model.{ContentTypes, HttpRequest, HttpResponse, StatusCodes} | ||
import akka.http.scaladsl.server.{Directive0, Directive1, Directives} | ||
import akka.util.Timeout | ||
import fr.acinq.eclair.api.serde.FormParamExtractors._ | ||
import fr.acinq.eclair.api.serde.JsonSupport._ | ||
import fr.acinq.eclair.api.serde.JsonSupport | ||
|
||
import scala.concurrent.duration.DurationInt | ||
|
||
trait TimeoutDirective extends Directives { | ||
|
||
import JsonSupport.{formats, serialization} | ||
|
||
|
||
/** | ||
* Extracts a given request timeout from an optional form field. Provides either the | ||
* extracted Timeout or a default Timeout to the inner route. | ||
*/ | ||
def withTimeout:Directive1[Timeout] = extractTimeout.tflatMap { timeout => | ||
withTimeoutRequest(timeout._1) & provide(timeout._1) | ||
} | ||
|
||
private val timeoutResponse: HttpRequest => HttpResponse = { _ => | ||
HttpResponse(StatusCodes.RequestTimeout).withEntity( | ||
ContentTypes.`application/json`, serialization.writePretty(ErrorResponse("request timed out")) | ||
) | ||
} | ||
|
||
private def withTimeoutRequest(t: Timeout): Directive0 = withRequestTimeout(t.duration + 2.seconds) & | ||
withRequestTimeoutResponse(timeoutResponse) | ||
|
||
private def extractTimeout: Directive1[Timeout] = formField("timeoutSeconds".as[Timeout].?).tflatMap { opt => | ||
provide(opt._1.getOrElse(Timeout(30 seconds))) | ||
} | ||
|
||
} |
Oops, something went wrong.