I would like to write a custom directive for all get, post,put and delete requests, because all requests needs authorization. To keep code DRY, I want to abstract those boilerplate(I have to authorize more than 100 requests). I could handle all Get Requests as follows
def method1 = Try("hi") // controller methods
def method2 = Try("hello")
path("getData1") {
handleGetRequest(method1)
}
path("getData2") {
handleGetRequest(method2)
}
def customGetDirective: Directive0 = {
headerValueByName("token").flatMap { token =>
authorize(checkAuthorization(token, ""))
get.hflatMap {x=>
respondWithMediaType(`application/json`)
}
}
}
def handleGetRequest(fn: => Try[_]) = {
customGetDirective { ctx=>
val result = fn
val json = //result can be coverted to json
ctx.complete(json)
}
}
I want to write a similar Generic directive for POST request, so that I could use
path("postData1") {
handlePostRequest(saveToDb)
}
def handlePostRequest(fn: => Try[_]) = {
customPostDirective { (ctx, JsonData)=>
//.......
ctx.complete(json)
}
}
but, I dont know how to get RequestContext and JsonData as tuple
I can write like this,
entity(as[String]) { jsonData =>
customPostDirective{ ctx=>
}
}
If I get a tuple it will be more useful.
Thank you.
Related
I have the following routes definition
private val routes: Route =
concat(
pathEnd {
get {
handleErrorsAndReport("list_foos") {
}
}
},
path(Segment) { fooId =>
get {
handleErrorsAndReport("get_foo") {
rejectEmptyResponse(
complete(service.do(fooId))
)
}
}
},
pathEnd {
authenticateBasic(
realm = "Secure scope",
scopesAuthenticator
) { scopes =>
post {
handleErrorsAndReport("create_foo") {
}
}
}
},
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
concat(
put {
handleErrorsAndReport("update_foo") {
}
},
delete {
handleErrorsAndReport("delete_foo") {
}
}
)
}
}
)
I am trying to consume the get_foo endpoint. I have created a unit test for that and it looks like this
"allow get operation on foo without authentication present" in {
Get("/foos/{some_id}") ~> routes ~> check {
status shouldBe StatusCodes.NotFound
}
}
While debugging the test I can see that the route is correctly identified and I can access the code inside the route. The service code inside the get_foo route produces a None and complete(None) creates a rejection since it's an empty response and I have the rejectEmptyResponse directive. So I would expect that I would get a 404 response based on the handleErrorsAndReport directive that I have defined. The error handler looks like this
private def handleErrorsAndReport(endpoint: String): Directive0 = extractRequestContext.flatMap { ctx =>
val start = System.currentTimeMillis()
mapResponse { resp =>
// store response related metrics and return response
resp
} & handleExceptions(exceptionHandler)
}
private val exceptionHandler: ExceptionHandler = {
def handle(e: Throwable, responseCode: StatusCode, errorMessage: Option[String]): Route = {
extractRequest { request =>
val response = (responseCode, errorMessage) match {
case (InternalServerError, _) => "Internal Server Error"
case (_, Some(message)) => message
case _ => "Bad Request"
}
complete(HttpResponse(responseCode, entity = response))
}
}
ExceptionHandler {
case e#AError(description) => handle(e, BadRequest, Some(description))
case e: BError => handle(e, InternalServerError, Some(e.errorMessage))
case e: CError => handle(e, BadRequest, Some(e.errorMessage))
case e: DError => handle(e, BadRequest, Some(e.errorMessage))
case e: EError => handle(e, BadRequest, Some(e.errorMessage))
case e#FException(filter) => handle(e, BadRequest, Some(s"bla"))
case other => handle(other, InternalServerError, Option(other.getMessage))
}
}
What I am getting though is a 401 Unauthorized. How can this be?
As I was debugging the code I noticed that the control flow never enters my exception handler - I added breakpoints everywhere inside...
The problem with your code is that, after the rejection of request in the unauthenticated directive, the match happens against an authenticated route with the same url. So the authentication fails and results in 401 unauthorized instead of 404.
For solving it, you need to prevent this match against the authenticated route, after the failure of the unauthenticated route with the GET method, by wrapping it inside post, put, and delete routes so that the GET requests cannot reach it.
So it can be written as
private val routes: Route =
concat(
pathEnd {
get {
handleErrorsAndReport("list_foos") {
}
}
},
path(Segment) { fooId =>
get {
handleErrorsAndReport("get_foo") {
rejectEmptyResponse(
complete(service.do(fooId))
)
}
}
},
post {
pathEnd {
authenticateBasic(
realm = "Secure scope",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport("create_foo") {
}
}
}
},
put{
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport("update_foo") {
}
}
}
},
delete{
path(Segment) { fooId =>
authenticateBasic(
realm = "Secure scopes",
scopesAuthenticator
) { scopes =>
handleErrorsAndReport(“delete_foo") {
}
}
}
}
)
This question already has an answer here:
Akka-HTTP route not found?
(1 answer)
Closed 1 year ago.
I have the following route setup
val routes = protectedRoute("wallet") { user =>
concat {
path("wallet" / "deposit") {
get {
completeEitherT(walletService.deposit(user._id, "dollars", 50))
}
}
path("wallet") {
get {
completeEitherT(walletService.getForUser(user._id))
}
}
}
}
The completeEitherT function is as follows
def completeEitherT[T](t: EitherT[Future, DatabaseError, T])(implicit marsh: ToResponseMarshaller[T]): Route = {
onSuccess(t.value) {
case Left(x: DatabaseErrors.NotFound) =>
logger.error(x.toString)
complete(StatusCodes.NotFound)
case Left(error) => complete(StatusCodes.InternalServerError)
case Right(value) => complete(value)
}
}
The protectedRoute Directive is:
def protectedRoute(permissions: String*): Directive1[User] = new Directive1[User] {
override def tapply(f: (Tuple1[User]) => Route): Route = {
headerValueByName("Authorization") { jwt =>
Jwt.decode(jwt, jwtSecret, List(algo)) match {
case Failure(_) =>
logger.error("Unauthorized access from jwtToken:", jwt)
complete(StatusCodes.Unauthorized)
case Success(value) =>
val user = JsonParser(value.content).convertTo[User]
if (user.roles.flatMap(_.permissions).exists(permissions.contains)) f(Tuple1(user))
else complete(StatusCodes.Forbidden)
}
}
}
}
The issue that I am having is that whichever path is defined first results in a 404 when called via Postman.
The requested resource could not be found.
This is not a response from completeEitherT as it does not log the error
How do I define two or more paths inside the same directive?
Please mark as duplicate, I could not find this on Google Search but SO showed this as related.
Answer
Essentially I am missing a ~ between the paths
val routes = protectedRoute("wallet") { user =>
concat {
path("wallet" / "deposit") {
get {
completeEitherT(walletService.deposit(user._id, "dollars", 50))
}
} ~
path("wallet") {
get {
completeEitherT(walletService.getForUser(user._id))
}
}
}
}
Excellent Angular 2/Material Design framework, Teradata Covalent, provides a RESTService abstract class that wraps REST api calls here:
https://teradata.github.io/covalent/#/components/http
Code to incorporate the extension is easy, as follows:
export class CustomRESTService extends RESTService<any> {
constructor(private _http: Http /* or HttpInterceptorService */) {
super(_http, {
baseUrl: 'www.api.com',
path: '/path/to/endpoint',
headers: new Headers(),
dynamicHeaders: () => new Headers(),
transform: (res: Response): any => res.json(),
});
}
}
The "update" method in the RESTService abstract class is shown here:
public update(id: string | number, obj: T, transform?: IRestTransform): Observable<any> {
let requestOptions: RequestOptionsArgs = this.buildRequestOptions();
let request: Observable<Response> = this.http.patch(this.buildUrl(id), obj, requestOptions);
return request.map((res: Response) => {
if (res.status === 200) {
if (transform) {
return transform(res);
}
return this.transform(res);
} else {
return res;
}
}).catch((error: Response) => {
return new Observable<any>((subscriber: Subscriber<any>) => {
try {
subscriber.error(this.transform(error));
} catch (err) {
subscriber.error(error);
}
});
});
}
My question is: if the update method of the abstract class throws an exception, how can that be captured in the CustomRESTService class? I.e., what Typescript code might one use to display an error in the UI?
Thank you.
First thing's first - Why would you want to catch it inside the rest client and not inside the app's logic?
Assuming you have some good reason for doing that (some other infrastructure code that you're running in the CustomRESTClient class), I would override the update function and implement error handling there.
A simple example without observables:
abstract class Base {
update(n:number):number {
return n;
}
test():bool;
}
class Child extends Base {
update(n:number):number {
return super.update(n)*2;
}
test():bool {
return true;
}
}
I want to have a base route that receive a IntNumber and make some checks with the database to know if the values is correct or not and then If the value is correct I want to propagate the value to child routes.
Base Route
class BaseServiceRoute extends PathDirectives with SecurityDirectives {
val baseUserRoute = pathPrefix("user" / IntNumber)
}
How can I make some checks for the value IntNumber and the delegate the value to the child route? Directives?
Child Route
class CategoryServiceRoute(implicit executionContext: ExecutionContext) extends BaseServiceRoute {
val route = baseUserRoute { userId =>
pathPrefix("category") {
pathEndOrSingleSlash {
get {
complete(s"$userId")
}
} ~
path(LongNumber) { categoryId =>
get {
complete(s"$categoryId")
} ~
post {
complete("Hello category post")
}
}
}
}
}
Thanks
The best-practice suggestion would be just to nest the routes so that you can still access the value from the outer route like this:
pathPrefix("user" / IntNumber) { userId =>
pathPrefix("category") {
pathEndOrSingleSlash {
get {
complete(s"$userId")
}
}
}
}
However, you seem to want to separate routes into multiple parts which is perfectly fine. In that case just make the child route a def:
def childRoute(userId: Int): Route =
pathPrefix("category") {
pathEndOrSingleSlash {
get {
complete(s"$userId")
}
}
}
and then use it like this:
baseUserRoute(childRoute)
I'm novice to scala and Akka-Http. Experimenting Akka-Http for writing rest services. I have to return json or protobuf based on the Accept header.
optionalHeaderValueByName("Accept"){ contentType =>
if(contentType == Some(protoEncode)) {
complete {
NewsService.getNewsList().map {
case stories: List[Story] => HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray))
}
}
} else {
complete {
NewsService.getNewsList().map {
case stories: List[Story] => StoryList(stories)
}
}
}
As you can see the code repetition is happening can anyone suggest what could be the best way to optimise and generalise design to avoid such situation.
The simplest way is to move the check inside the body.
optionalHeaderValueByName("Accept"){ contentType =>
complete {
NewsService.getNewsList().map {
case stories: List[Story] =>
if(contentType == Some(protoEncode)) {
HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray))
} else
StoryList(stories)
}
}
}
Figured it out.
optionalHeaderValueByName("Accept") { contentType =>
onSuccess(NewsService.getNewsList()) {
case stories: List[Story] => contentType match {
case Some(protoEncodingString) => complete(HttpResponse(entity = HttpEntity(ContentType(protoEncoding), StoryList(stories).toProto().build().toByteArray)))
case _=> complete(StoryList(stories))
}
}
}