如何从Scala-Play应用程序中的URL中提取路径变量的值?

时间:2014-09-06 11:45:14

标签: scala playframework-2.3

我正在为Play Framework编写一个模块。在我的模块的一部分,我有以下代码

abstract class SecurityFiltering extends GlobalSettings{
  override def onRequestReceived(request: RequestHeader) = {
    play.Logger.debug("onRequestReceived: " + request)
    super.onRequestReceived(request)
  }

  override def doFilter(next: RequestHeader => Handler): (RequestHeader => Handler) = {
    request => {
      play.Logger.debug("doFilter: " + request)
      super.doFilter(next)(request)
    }
  }

  override def onRouteRequest(request: RequestHeader): Option[Handler] = {
    play.Logger.debug("onRouteRequest: " + request)
    super.onRouteRequest(request)
  }
}

在doFilter方法中,我能够确定以下有用信息

ROUTE_PATTERN = /x/$name<[^/]+>/$age<[^/]+>
ROUTE_CONTROLLER = controllers.Application
ROUTE_ACTION_METHOD = tester
ROUTE_VERB = GET
path = /x/hello

除此之外,我需要的是在之前之前的URL 的命名部分的值。因此,在我的测试应用程序中给出以下路由,我需要检索Name = Pete和Age = 41

localhost:9000/x/Pete/41

Play Framework中肯定有一些代码可以执行此操作,但我无法找到它。有人可以建议我如何实现这个目标,或者指出哪个Play类提取这些值?

2 个答案:

答案 0 :(得分:2)

package models.com.encentral.tattara.web.util;

 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;


 public class RouteExtractor {

//something like "/foo/$id<[^/]+>/edit/$james<[^/]+>"
private String routePattern;
private String path;

//something like /$id<[^/]+>
private static final String INDEX_PATTERN = "\\$(.+?)\\<\\[\\^\\/\\]\\+\\>";

public RouteExtractor(String routePattern, String path) {

    this.routePattern = routePattern;
    this.path = path;
}

private Map<Integer, String> extractPositions() {
    Pattern pattern = Pattern.compile(INDEX_PATTERN);
    Matcher matcher = pattern.matcher(this.routePattern);
    Map<Integer, String> results = new HashMap<>();
    int index = 0;
    while (matcher.find()) {
        results.put(index++, matcher.group(1));
    }
    return results;
}

private String replaceRoutePatternWithGroup() {
    Pattern pattern = Pattern.compile(INDEX_PATTERN);
    Matcher matcher = pattern.matcher(this.routePattern);
    return matcher.replaceAll("([^/]+)");
}

public Map<String, String> extract() {
    Pattern pattern = Pattern.compile(this.replaceRoutePatternWithGroup());
    Matcher matcher = pattern.matcher(this.path);
    final Map<String, String> results = new HashMap<>();
    if (matcher.find()) {
        this.extractPositions().entrySet().stream().forEach(s -> {
            results.put(s.getValue(), matcher.group(s.getKey() + 1));
        });
    }
    return results;
}

}

答案 1 :(得分:1)

根据this GitHub issue通过JRoper回复

onRequestReceived是执行路由并标记请求的东西,因此当它首次被调用时,它不会有任何路由信息,只有在它被调用之后。

val p = """\$([^<]+)<([^>]+)>""".r
override def onRequestReceived(request: RequestHeader) = {
  val (taggedRequest, handler) = super.onRequestReceived(request)
  val pattern = taggedRequest.tags("ROUTE_PATTERN")
  val paramNames = p.findAllMatchIn(pattern).map(m => m.group(1)).toList
  val pathRegex = ("^" + p.replaceAllIn(pattern, m => "(" + m.group(2) + ")") + "$").r
  val paramValues = pathRegex.findFirstMatchIn(request.path).get.subgroups  
  val params: Map[String, String] = paramNames.zip(paramValues).toMap
  // ^ your params map, will be Map("name" -> "Pete", "age" -> "41")
  (taggedRequest, handler)
}

也就是说,通常有更好,更安全的方式来实现你想要达到的目标。如果您依赖于URL中存在特定参数,则过滤器不是正确的,因为过滤器适用于所有请求,无论它们是否具有这些参数。相反,您应该使用动作合成或自定义动作构建器,如下所示:

case class MyAction(name: String, age: Int) extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    // Do your filtering here, you have access to both the name and age above
    block(request)
  }
}

def foo(name: String, age: Int) = MyAction(name, age) { request =>
  Ok("Hello world")
}

def bar(name: String, age: Int) = MyAction(name, age).async { request =>
  Future.successful(Ok("Hello world"))
}