カスタムモジュールを用いたレスポンスヘッダーの変更

カスタムモジュールを変更し、HTTP レスポンスヘッダーの追加や編集を行えます。レスポンスヘッダーの変更に対応することは、Fastly Client-Side Protection の要件の一つです。この機能を使用して、複数の Web アプリケーション間でレスポンスヘッダーを正規化することも可能です。

レスポンスヘッダーの変更に対応できるよう、カスタムモジュールを以下のように更新します。

制約と考慮事項

カスタムモジュールが統合されるフレームワークには、レスポンスヘッダーの変更に影響する制約がある場合があります。たとえば、一部の Web サーバーやフレームワークでは、レスポンスのヘッダーキーの削除に対応していません。このような場合は、値を空の文字列に設定しています。

リクエストフロー

Next-Gen WAF を使用してレスポンスヘッダーを変更する場合のリクエストフローは以下のとおりです。

  1. Web サーバーまたはフレームワークは、リクエストをカスタムの Next-Gen WAF モジュールに渡します。

  2. モジュールは、PreRequest 関数を Next-Gen WAF エージェントで呼び出します。エージェントは、RPCMsgOut 構造体を返します。ここには、レスポンスに対して実行する1つ以上のアクションが含まれる場合があります。ここでのアクションは、モジュールに追加または変化するレスポンスヘッダーを指示します。

  3. モジュールは、エージェントがリクエストを処理している間にこれらのアクションを保存します。

  4. モジュールは、リクエストをエージェントに渡して評価を行います。

  5. リクエストが許可された場合、エージェントはリクエストをオリジンに転送します。オリジンはエージェントにレスポンスを送信し、エージェントはそのレスポンスをモジュールに転送します。

  6. ステップ2でモジュールが保存したレスポンスアクションに基づき、モジュールは適切なレスポンスヘッダーの追加や編集を行います。

  7. モジュールは、リクエストを開始したウェブクライアントにレスポンスを送信します。

レスポンスアクションの保存

カスタムモジュールでは、PreRequest 関数は RPCMsgOut 構造体のレスポンスアクションを RespActions 配列として返します。エージェントがリクエストを処理中している間にこれらのアクションを保存するには、以下をの手順を実施します。

  1. Java モジュール
  2. Goモジュール

カスタム Java モジュールの場合、MessagePack レスポンスを展開する必要があります。以下の例では、レスポンスからアクションを展開する方法を示しています。

public class RPCMsgOut {
public Action[] responseActions;
void unpack(MessageUnpacker u) throws Exception {
int mlen = u.unpackMapHeader();
for (int j = 0; j < mlen; j++) {
String value = u.unpackString();
switch (value) {
case "RespActions":
if (u.tryUnpackNil())
break;
int actionsLen = u.unpackArrayHeader();
if (actionsLen == 0)
break;
responseActions = new Action[actionsLen];
for (int k = 0; k < actionsLen; k++) {
if (u.unpackArrayHeader() != 2) {
throw new IllegalArgumentException("Invalid tuple length for RespActions");
}
int code = u.unpackInt();
int argsLen = u.unpackArrayHeader();
String[] args = new String[argsLen];
for (int i = 0; i < argsLen; i++) {
args[i] = u.unpackString();
}
responseActions[k] = new Action(code, args);
}
break;
default:
u.skipValue();
break;
}
}
}
}

Action クラスは以下のとおりです。

public class Action {
public static final int AddHdr = 1;
public static final int SetHdr = 2;
public static final int SetNEHdr = 3;
public static final int DelHdr = 4;
private int code;
public String[] args;
public Action() {
// Default constructor
}
public Action(int code, String[] args) {
this.code = code;
this.args = args;
}
public String[] getArgs() {
return args;
}
public int getCode() {
return code;
}
}

レスポンスヘッダーの設定

保存したレスポンスアクションを実行するようカスタムモジュールを更新するには、以下の手順を実施します。

  1. Javaモジュール
  2. Go モジュール

カスタム Java モジュールの場合、以下のコードスニペットに示すように、レスポンスヘッダーの変更には Jakarta ハンドラーを実装する必要があります。

重要: Java 以外のフレームワークを使用している場合、このコードを変更する必要があります。

@Override
public void handle(String target, Request request,
HttpServletRequest httpRequest, HttpServletResponse httpResponse)
throws IOException, ServletException {
if (isInvalidRequest(target, request, httpRequest, httpResponse)) {
return;
}
RPCMsgIn msgIn = filterService.newRPCMsgIn(httpRequest);
if (request.isSecure()) {
SSLSession sslSession = (SSLSession) request.getAttribute("org.eclipse.jetty.servlet.request.ssl_session");
msgIn.TLSCipher = sslSession.getCipherSuite();
msgIn.TLSProtocol = sslSession.getProtocol();
}
httpRequest = filterService.readRequestBody(httpRequest, httpResponse, msgIn);
RPCMsgOut rpcResponse = filterService.getPreRequest(httpRequest, msgIn);
try {
/* Fail safe on no response from agent */
if (rpcResponse == null) {
getHandler().handle(target, request, httpRequest, httpResponse);
return;
}
/*
* Add any request headers returned from agent to the http request
* Set the rpc request/response attributes on http request
*/
if (rpcResponse.requestHeaders != null && rpcResponse.requestHeaders.length > 0) {
httpRequest = new HeaderDecoratorRequestWrapper(httpRequest, rpcResponse.requestHeaders);
}
/*
* Check if there are response actions to apply to the response and then apply them.
*/
if (rpcResponse.responseActions != null) {
// Use CustomRequest to handle response actions in onResponseCommit
com.signalsciences.jetty.JettyRequestWrapper customRequest = new JettyRequestWrapper(
HttpConnection.getCurrentConnection().getHttpChannel().getRequest(),
rpcResponse.responseActions
);
httpResponse = new ResponseWrapper(httpResponse, rpcResponse.responseActions);
request = customRequest;
}
} catch (Exception e) {
// Handle exceptions
}
}

ResponseWrapper クラスは、レスポンスヘッダーのアクションを実装する役割を担います。

public class ResponseWrapper extends HttpServletResponseWrapper {
private boolean headersModified = false;
private Action[] actions;
public ResponseWrapper(HttpServletResponse resp, Action[] actions) {
super(resp);
this.actions = actions;
}
@Override
public void flushBuffer() throws IOException {
ensureHeadersHandled();
super.flushBuffer();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
ensureHeadersHandled();
return super.getOutputStream();
}
@Override
public PrintWriter getWriter() throws IOException {
ensureHeadersHandled();
return super.getWriter();
}
public void ensureHeadersHandled() {
if (!headersModified) {
headersModified = true;
handleResponseActions();
}
}
private void handleResponseActions() {
for (Action action : actions) {
String[] args = action.args;
if (action.getCode() == Action.AddHdr) {
super.addHeader(args[0], args[1]);
} else if (action.getCode() == Action.SetHdr) {
super.setHeader(args[0], args[1]);
} else if (action.getCode() == Action.SetNEHdr) {
if (!super.containsHeader(args[0])) {
super.addHeader(args[0], args[1]);
}
} else if (action.getCode() == Action.DelHdr) {
super.setHeader(args[0], "");
}
}
}
}

レスポンスアクションの受信と実行に対応する方法については、以下の実装例をご確認ください。