Question

I have a route snippet that I want to reuse in multiple scenarios:

val dirSegment = "licenses"
path( dirSegment ~ PathEnd ) {
  redirect( dirSegment + "/", StatusCodes.MovedPermanently ) 
} ~ 
pathPrefix(dirSegment) { 
  path("") {
    /* do something */
  }
}

I'd like to turn this in to a directive (or parameterizable route?) where I can specify the value of the dirSegment val and arbitrary further routing/code in place of path("") { /* do something */ } white retaining the redirect behavior, looking something like the following:

directoryPath("licenses") {
  path("") {
    /* do something */
  }
} ~ 
directoryPath("about") {
  path("") {
    /* do somthing else */
  }
}

Whereas that would have equivalent behavior to the following without all the repetition:

val dirSegment = "licenses"
val anotherDir = "About"

path( dirSegment ~ PathEnd ) {
  redirect(dirSegment + "/", StatusCodes.MovedPermanently ) 
} ~ 
pathPrefix(dirSegment) { 
  path("") {
    /* do something */
  }
} ~
path( anotherDir ~ PathEnd ) {
  redirect(anotherDir + "/", StatusCodes.MovedPermanently ) 
} ~ 
pathPrefix(anotherDir) { 
  path("") {
    /* do something else */
  }
}

Note that this question was inspired by some of the discussion in How do I automatically add slash to end of a url in spray routing?

Était-ce utile?

La solution

You'll need to write a custom directive for this.

// additional imports you may need
import shapeless.HNil
import spray.http.StatusCodes
import spray.routing.Directive0
import spray.routing.PathMatcher

Now that that's out of the way:

/**
 * Spray's PathEnd matches trailing optional slashes... we can't have that
 * otherwise it will cause a redirect loop.
 */
object PathEndNoSlash extends PathMatcher[HNil] {
  def apply(path: Path) = path match {
    case Path.Empty ⇒ PathMatcher.Matched.Empty
    case _          ⇒ PathMatcher.Unmatched
  }
}

/**
 * Custom directive that uses a redirect to add a trailing slashe to segment
 * if the slash isn't present.
 */
def directoryPath(segment: String) = new Directive0 {
  def happly(f: HNil ⇒ Route) =
    // first, the redirect
    pathPrefix(segment ~ PathEndNoSlash) {
      redirect("/" + segment + "/", StatusCodes.MovedPermanently) } ~
    // delegate actual work to nested directives
    pathPrefix(segment).happly(f)
}

Usage:

directoryPath("about") {
  path("a") {
    complete {
      "this is /about/a"
    }
  } ~ path("b") {
    complete {
      "this is /about/b"
    }
  } ~ path(PathEnd) {
    complete {
      "this is /about/"
    }
  }
}

If a user accesses /about, they will be forwarded to /about/ and see "this is /about/". The nested paths a and b work as expected (that is, without redirects of their own).

Note: This solution is for Spray 1.2.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top