如何在Controller上工作之前操作Play2中的RequestHeader

时间:2014-05-22 14:21:30

标签: scala playframework playframework-2.1

我想添加一个tracingId来跟踪在WebApp中调用的Play2和Services中的请求。 在Controller处理请求之前,应该可以在请求中添加tracingId。

到目前为止,我认为这可以通过过滤器实现。

object AddTraceIdFilter extends Filter  {
    override def apply(next: (RequestHeader) => Result)(rh: RequestHeader): Result = {

    val traceId = TraceId.fromRequestHeader(rh) getOrElse addAnTraceId
    next(rh).withHeaders( TraceId.traceKey -> traceId )
  }
}

但是使用这种方法,Headers会附加到Result上,而我认为请求不会被触及。

在Controller中,我想做一些像

这样的事情
val traceId = traceIdfromReuest(request)

有没有办法修改RequestHeader,以便每个传入的请求在Controller获取它之前添加一个traceId-Header ? 我已经看过拦截器 http://www.playframework.com/documentation/2.0/ScalaInterceptors,但我没有找到如何在那里操纵请求标头的主角。

3 个答案:

答案 0 :(得分:3)

您应该从RequestHeader

构建一个新的rh
val rhWithTraceId = rh....

例如:

val rhWithTraceId = rh.copy(tags = rh.tags + ("traceId" -> traceId))

然后你可以用它来完成下一个功能

next(rhWithTraceId)

在控制器中,您可以从

访问traceId
request.tags.get("traceId")

另一种解决方案是使用ActionBuilder

答案 1 :(得分:3)

如@Yann Simon所述,最简单的解决方案是使用标记,而不是修改请求中的 http-headers

如果您确实要修改RequestHeader中的http-header,可以执行以下操作:

oldHeader.copy(
  headers =
    new Headers{
      val data : Seq[(String, Seq[String])]  = 
         oldHeader.headers.toMap.toSeq ++ additionalKeys.mapValues(Seq(_))
    }
)

要记住的重要部分是,如果要在过滤器中修改RequestHeader中的数据,那么归结为给定方法的参数,签名(RequestHeader) => Result通常称为下一页在示例中。

object AddTraceIdFilter extends Filter  {
  override def apply(next: (RequestHeader) => Result)(rh: RequestHeader): Result = {

    val traceId = TraceId.fromRequestHeader(rh) getOrElse addAnTraceId
    next(
      rh.copy(tags = tags + ("traceId" -> traceId)) // <- Headers modified here will be readable from Reuest and the next filters
    ).withHeaders( "traceId" -> traceId ) // <- This just sets the httpHeaders for the Result given to the User after the filter has been applied, the values can not be read from within a controller
  }
}

答案 2 :(得分:0)

首先,我想直接回答这个问题,对于Java人员来说:

这是您在触摸控制器之前使用过滤器操作请求标头的方法:

class TracingFilterAction extends Action<TracingFilter> {

    @Override
    public Promise<SimpleResult> call(Context ctx) throws Throwable {

        ctx.request().headers().put(key, value)

因为你问了一个关于&#34;追踪&#34;的问题。请求,我觉得分享我的LoggingFilter的冲动。

对于Play Framework Java社区,我开发了一个出色的LoggingFilter,它将记录完整的请求,标题和文件。有效载荷。

对于Scala社区,它应该仍适用于您,但您必须在Scala项目中使用一些额外的Java代码:(

用法

这将启用类级别的请求记录

@LoggingFilter
public final class MyController extends Controller {   

}

这将启用方法级别的请求记录

@LoggingFilter
public Result index() {
    ...
}

这是2.2.x(可能还有2.2.3)

的实现
import java.io.UnsupportedEncodingException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang3.StringUtils;

import play.Logger;
import play.Logger.ALogger;
import play.libs.F.Function0;
import play.libs.F.Promise;
import play.mvc.Action;
import play.mvc.Http.Context;
import play.mvc.Http.Request;
import play.mvc.SimpleResult;
import play.mvc.With;

@With(LoggingFilterAction.class)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggingFilter {

}

class LoggingFilterAction extends Action<LoggingFilter> {

    private static final String NOTIFICATION_PREFIX = "* ";
    private static final String REQUEST_PREFIX = "> ";
    private static final String RESPONSE_PREFIX = "< ";

    private final ALogger logger = Logger.of(getClass());
    private final AtomicLong _id = new AtomicLong(0);
    private final String newLine = System.getProperty("line.separator");

    @Override
    public Promise<SimpleResult> call(Context ctx) throws Throwable {
        long id = _id.incrementAndGet();
        StringBuilder b = new StringBuilder();
        printRequestLine(b, id, ctx.request().method(), ctx.request().uri());
        printRequestHeaders(b, id, REQUEST_PREFIX, ctx.request().headers());
        String requestContent = contentAsString(ctx.request(), b);
        if (!StringUtils.isEmpty(requestContent)) {
            b.append(requestContent);
        }
        b.append(newLine);
        final SimpleResult result = delegate.call(ctx).get(5, TimeUnit.SECONDS);
        printResponseLine(b, id, getStatus(result));
        printResponseHeaders(b, id, RESPONSE_PREFIX, headers(result));
        String responseContent = contentAsString(result);
        if (!StringUtils.isEmpty(responseContent)) {
            b.append(responseContent);
        }
        b.append(newLine);
        logger.debug(b.toString());

        return Promise.promise(new Function0<SimpleResult>() {
            public SimpleResult apply() throws Throwable {
                return result;
            }
        });
    }

    private String header(String header, SimpleResult result) {
        return play.core.j.JavaResultExtractor.getHeaders(result).get(header);
    }

    private String charset(SimpleResult result) {
        String h = header("Content-Type", result);
        if (h == null) return null;
        if (h.contains("; charset=")) {
            return h.substring(h.indexOf("; charset=") + 10, h.length()).trim();
        }
        else {
            return null;
        }
    }

    private byte[] contentAsBytes(SimpleResult result) {
        return play.core.j.JavaResultExtractor.getBody(result);
    }

    private String contentAsString(Request request, StringBuilder b) {
        try {
            if (request.body() == null) {
                return "request has no body";
            }
        } catch (ClassCastException e) {
            return "request has no body";
        }
        if (request.body().asText() != null) {
            logger.debug("getting request body as text");
            return request.body().asText();
        }
        if (request.body().asRaw() != null) {
            logger.debug("getting request body as raw");
            try {
                return new String(request.body().asRaw().asBytes(), "utf-8");
            }
            catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
        if (request.body().asJson() != null) {
            logger.debug("getting request body as json");
            return request.body().asJson().toString();
        }
        if (request.body().asFormUrlEncoded() != null) {
            logger.debug("getting request body as as formUrlEncoded");
            return toString(request.body().asFormUrlEncoded());
        }
        if (request.body().asMultipartFormData() != null) {
            logger.debug("getting request body as multipartFormData");
            return request.body().asMultipartFormData().toString();
        }
        if (request.body().asXml() != null) {
            logger.debug("getting request body as xml");
            return request.body().asXml().toString();
        }
        logger.debug("getting request body as default");
        return request.body().toString();
    }

    private String toString(Map<String, String[]> data) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String[]> e : data.entrySet()) {
            String[] val = e.getValue();
            String key = e.getKey();
            if (sb.length() > 0) {
                sb.append("&");
            }
            try {
                sb.append(key).append("=").append(URLEncoder.encode(val[0], "utf-8"));
            }
            catch (UnsupportedEncodingException e1) {
                throw new RuntimeException(e1);
            }
        }
        return sb.toString();
    }

    private String contentAsString(SimpleResult result) {
        try {
            String charset = charset(result);
            if (charset == null) {
                charset = "utf-8";
            }
            return new String(contentAsBytes(result), charset);
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    private Map<String, String> headers(SimpleResult result) {
        return play.core.j.JavaResultExtractor.getHeaders(result);
    }

    private int getStatus(SimpleResult result) {
        return result.getWrappedSimpleResult().header().status();
    }

    private StringBuilder prefixId(StringBuilder b, long id) {
        b.append(Long.toString(id)).append(" ");
        return b;
    }

    private void printRequestLine(StringBuilder b, long id, String method, String uri) {
        prefixId(b, id).append(NOTIFICATION_PREFIX)
                .append("LoggingFilter - Request received on thread ")
                .append(Thread.currentThread().getName()).append("\n");
        prefixId(b, id).append(REQUEST_PREFIX).append(method).append(" ").append(uri).append("\n");
    }

    private void printResponseLine(StringBuilder b, long id, int status) {
        prefixId(b, id).append(NOTIFICATION_PREFIX)
                .append("LoggingFilter - Response received on thread ")
                .append(Thread.currentThread().getName()).append("\n");
        prefixId(b, id).append(RESPONSE_PREFIX).append(Integer.toString(status)).append("\n");
    }

    private void printResponseHeaders(StringBuilder b, long id, final String prefix,
            Map<String, String> headers) {
        for (Map.Entry<String, String> e : headers.entrySet()) {
            String val = e.getValue();
            String header = e.getKey();
            printPrefixedHeader(b, id, prefix, header, new String[] { val });
        }

    }

    private void printRequestHeaders(StringBuilder b, long id, final String prefix,
            Map<String, String[]> headers) {
        for (Map.Entry<String, String[]> e : headers.entrySet()) {
            String[] val = e.getValue();
            String header = e.getKey();
            printPrefixedHeader(b, id, prefix, header, val);
        }
    }

    private void printPrefixedHeader(StringBuilder b, long id, final String prefix, String header,
            String[] val) {
        if (val.length == 1) {
            prefixId(b, id).append(prefix).append(header).append(": ").append(val[0]).append("\n");
        }
        else {
            StringBuilder sb = new StringBuilder();
            boolean add = false;
            for (Object s : val) {
                if (add) {
                    sb.append(',');
                }
                add = true;
                sb.append(s);
            }
            prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString())
                    .append("\n");
        }
    }

}

根据读取布尔配置属性添加禁用日志记录的功能应该是一项微不足道的练习。

然后在生产/暂存/任何模式下,您可以轻松地禁用LoggingFilter。

干杯!