Как изменить заголовки ответа после успешного формирования ответа?
Всем привет! Многим знаком класс javax.servlet.Filter, который позволяет встраивать в жизненный цикл запроса свою логику и у меня с ним проблема:
public class CorsFilter implements Filter {
private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
private static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
private static final String ALLOW = "Allow";
@Override
public void init(FilterConfig filterConfig) {
// NO LOGIC
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
chain.doFilter(req, resp);
final HttpServletResponse response = (HttpServletResponse) resp;
final Collection<String> headers = response.getHeaderNames();
if (notContainHeaderWithIgnoreCase(headers, ACCESS_CONTROL_ALLOW_ORIGIN)) {
response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
}
if (notContainHeaderWithIgnoreCase(headers, ACCESS_CONTROL_ALLOW_HEADERS)) {
response.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, "*");
}
if (notContainHeaderWithIgnoreCase(headers, ACCESS_CONTROL_ALLOW_METHODS)) {
response.addHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
}
if (notContainHeaderWithIgnoreCase(headers, ALLOW)) {
response.addHeader(ALLOW, "GET, POST, PUT, DELETE, OPTIONS");
}
}
private boolean notContainHeaderWithIgnoreCase(Collection<String> headers, String name) {
return headers.stream().noneMatch(header -> header.equalsIgnoreCase(name));
}
@Override
public void destroy() {
// NO LOGIC
}
}
В данном фильтре я пытаюсь после формирования ответа посмотреть: есть ли в нём CORS заголовки и, если нет, то проставить их. Сделать это ДО формирования ответа я не могу, так как сервис иногда проксирует запрос и сторонние сервисы тоже проставляют CORS тем самым получается дублирование.
Такая реализация не работает, так как после формирования ответа он помечается как commited и изменять его нельзя. Как быть?
Ответы (1 шт):
Автор решения: ulxanxv
→ Ссылка
С проблемой разобрался, получилось вот так (может быть кому то будет нужно):
public class CorsFilter implements Filter {
private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
private static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
private static final String ALLOW = "Allow";
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
final ResponseWrapper wrapper = new ResponseWrapper(
(HttpServletResponse) resp);
filterChain.doFilter(req, wrapper);
setCorsHeaders((HttpServletResponse) resp);
copyResponseToOriginalResponse(wrapper, resp);
}
private void setCorsHeaders(HttpServletResponse response) {
final HeaderNames headerNames = new HeaderNames(response.getHeaderNames());
if (headerNames.notContainsIgnoreCase(ACCESS_CONTROL_ALLOW_ORIGIN)) {
response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
}
if (headerNames.notContainsIgnoreCase(ACCESS_CONTROL_ALLOW_HEADERS)) {
response.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, "*");
}
if (headerNames.notContainsIgnoreCase(ACCESS_CONTROL_ALLOW_METHODS)) {
response.addHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
}
if (headerNames.notContainsIgnoreCase(ALLOW)) {
response.addHeader(ALLOW, "GET, POST, PUT, DELETE, OPTIONS");
}
}
private void copyResponseToOriginalResponse(ResponseWrapper wrapper, ServletResponse response) throws IOException {
final ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(wrapper.getCaptureAsBytes());
}
private static final class ResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream capture;
private ServletOutputStream output;
private PrintWriter writer;
public ResponseWrapper(HttpServletResponse response) {
super(response);
capture = new ByteArrayOutputStream(response.getBufferSize());
}
@Override
public ServletOutputStream getOutputStream() {
if (writer != null) {
throw new IllegalStateException("getWriter() has already been called on this response.");
}
if (output == null) {
output = new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
capture.write(b);
}
@Override
public void flush() throws IOException {
capture.flush();
}
@Override
public void close() throws IOException {
capture.close();
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
}
};
}
return output;
}
@Override
public PrintWriter getWriter() throws IOException {
if (output != null) {
throw new IllegalStateException("getOutputStream() has already been called on this response.");
}
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(capture, getCharacterEncoding()));
}
return writer;
}
@Override
public void flushBuffer() throws IOException {
super.flushBuffer();
if (writer != null) {
writer.flush();
} else if (output != null) {
output.flush();
}
}
public byte[] getCaptureAsBytes() throws IOException {
if (writer != null) {
writer.close();
} else if (output != null) {
output.close();
}
return capture.toByteArray();
}
}
@Override
public void init(FilterConfig filterConfig) {
/* NO LOGIC */
}
@Override
public void destroy() {
/* NO LOGIC */
}
}