getInitParameterNames() {
+ return delegate.getInitParameterNames();
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxInputStream.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxInputStream.java
new file mode 100644
index 000000000000..526b8ddba507
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxInputStream.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import java.io.IOException;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+/**
+ * Exposes a {@link jakarta.servlet.ServletInputStream} as a
+ * {@code javax.servlet.ServletInputStream}. Blocking reads delegate to the
+ * wrapped stream; the non-blocking async read-listener API is not used by the
+ * bridged auth filters and is unsupported.
+ */
+class JakartaToJavaxInputStream extends ServletInputStream {
+
+ private final jakarta.servlet.ServletInputStream delegate;
+
+ JakartaToJavaxInputStream(jakarta.servlet.ServletInputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return delegate.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return delegate.read(b, off, len);
+ }
+
+ @Override
+ public int available() throws IOException {
+ return delegate.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return delegate.isFinished();
+ }
+
+ @Override
+ public boolean isReady() {
+ return delegate.isReady();
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ throw new UnsupportedOperationException("Async read listeners are not supported by the servlet bridge");
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxOutputStream.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxOutputStream.java
new file mode 100644
index 000000000000..517c998d99c3
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxOutputStream.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import java.io.IOException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+
+/**
+ * Exposes a {@link jakarta.servlet.ServletOutputStream} as a
+ * {@code javax.servlet.ServletOutputStream}. Blocking writes delegate to the
+ * wrapped stream; the non-blocking async write-listener API is not used by the
+ * bridged auth filters and is unsupported.
+ */
+class JakartaToJavaxOutputStream extends ServletOutputStream {
+
+ private final jakarta.servlet.ServletOutputStream delegate;
+
+ JakartaToJavaxOutputStream(jakarta.servlet.ServletOutputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ delegate.write(b, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public boolean isReady() {
+ return delegate.isReady();
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ throw new UnsupportedOperationException("Async write listeners are not supported by the servlet bridge");
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxRequest.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxRequest.java
new file mode 100644
index 000000000000..fcdd079656a1
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxRequest.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+/**
+ * Exposes a {@link jakarta.servlet.http.HttpServletRequest} as a
+ * {@code javax.servlet.http.HttpServletRequest} so that hadoop's
+ * {@code javax.servlet}-based authentication filters can read the request
+ * inside a Jetty EE10 (jakarta) servlet chain. See {@link JavaxFilterBridge}.
+ *
+ * The methods used by the hadoop-auth filter chain (headers, cookies,
+ * method, URL/URI, remote address, attributes, scheme, body) delegate to the
+ * wrapped jakarta request. Async, upgrade, multipart, session and dispatcher
+ * operations are not part of that chain and throw
+ * {@link UnsupportedOperationException}.
+ */
+public class JakartaToJavaxRequest implements HttpServletRequest {
+
+ private final jakarta.servlet.http.HttpServletRequest delegate;
+
+ public JakartaToJavaxRequest(jakarta.servlet.http.HttpServletRequest delegate) {
+ this.delegate = delegate;
+ }
+
+ jakarta.servlet.http.HttpServletRequest getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public String getAuthType() {
+ return delegate.getAuthType();
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ jakarta.servlet.http.Cookie[] source = delegate.getCookies();
+ if (source == null) {
+ return null;
+ }
+ Cookie[] result = new Cookie[source.length];
+ for (int i = 0; i < source.length; i++) {
+ result[i] = ServletBridgeUtils.toJavax(source[i]);
+ }
+ return result;
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ return delegate.getDateHeader(name);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return delegate.getHeader(name);
+ }
+
+ @Override
+ public Enumeration getHeaders(String name) {
+ return delegate.getHeaders(name);
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return delegate.getHeaderNames();
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ return delegate.getIntHeader(name);
+ }
+
+ @Override
+ public String getMethod() {
+ return delegate.getMethod();
+ }
+
+ @Override
+ public String getPathInfo() {
+ return delegate.getPathInfo();
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return delegate.getPathTranslated();
+ }
+
+ @Override
+ public String getContextPath() {
+ return delegate.getContextPath();
+ }
+
+ @Override
+ public String getQueryString() {
+ return delegate.getQueryString();
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return delegate.getRemoteUser();
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return delegate.isUserInRole(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return delegate.getUserPrincipal();
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return delegate.getRequestedSessionId();
+ }
+
+ @Override
+ public String getRequestURI() {
+ return delegate.getRequestURI();
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return delegate.getRequestURL();
+ }
+
+ @Override
+ public String getServletPath() {
+ return delegate.getServletPath();
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ throw new UnsupportedOperationException("getSession is not supported by the servlet bridge");
+ }
+
+ @Override
+ public HttpSession getSession() {
+ throw new UnsupportedOperationException("getSession is not supported by the servlet bridge");
+ }
+
+ @Override
+ public String changeSessionId() {
+ throw new UnsupportedOperationException("changeSessionId is not supported by the servlet bridge");
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return delegate.isRequestedSessionIdValid();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return delegate.isRequestedSessionIdFromCookie();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return delegate.isRequestedSessionIdFromURL();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public boolean isRequestedSessionIdFromUrl() {
+ return delegate.isRequestedSessionIdFromURL();
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
+ throw new UnsupportedOperationException("authenticate is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void login(String username, String password) throws ServletException {
+ throw new UnsupportedOperationException("login is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ throw new UnsupportedOperationException("logout is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Collection getParts() throws IOException, ServletException {
+ throw new UnsupportedOperationException("getParts is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Part getPart(String name) throws IOException, ServletException {
+ throw new UnsupportedOperationException("getPart is not supported by the servlet bridge");
+ }
+
+ @Override
+ public T upgrade(Class handlerClass) throws IOException, ServletException {
+ throw new UnsupportedOperationException("upgrade is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return delegate.getAttribute(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return delegate.getAttributeNames();
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return delegate.getCharacterEncoding();
+ }
+
+ @Override
+ public void setCharacterEncoding(String env) throws java.io.UnsupportedEncodingException {
+ delegate.setCharacterEncoding(env);
+ }
+
+ @Override
+ public int getContentLength() {
+ return delegate.getContentLength();
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return delegate.getContentLengthLong();
+ }
+
+ @Override
+ public String getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return new JakartaToJavaxInputStream(delegate.getInputStream());
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return delegate.getParameter(name);
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ return delegate.getParameterNames();
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ return delegate.getParameterValues(name);
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return delegate.getParameterMap();
+ }
+
+ @Override
+ public String getProtocol() {
+ return delegate.getProtocol();
+ }
+
+ @Override
+ public String getScheme() {
+ return delegate.getScheme();
+ }
+
+ @Override
+ public String getServerName() {
+ return delegate.getServerName();
+ }
+
+ @Override
+ public int getServerPort() {
+ return delegate.getServerPort();
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return delegate.getReader();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return delegate.getRemoteAddr();
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return delegate.getRemoteHost();
+ }
+
+ @Override
+ public void setAttribute(String name, Object o) {
+ delegate.setAttribute(name, o);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ delegate.removeAttribute(name);
+ }
+
+ @Override
+ public Locale getLocale() {
+ return delegate.getLocale();
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ return delegate.getLocales();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return delegate.isSecure();
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ throw new UnsupportedOperationException("getRequestDispatcher is not supported by the servlet bridge");
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public String getRealPath(String path) {
+ throw new UnsupportedOperationException("getRealPath is not supported by the servlet bridge");
+ }
+
+ @Override
+ public int getRemotePort() {
+ return delegate.getRemotePort();
+ }
+
+ @Override
+ public String getLocalName() {
+ return delegate.getLocalName();
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return delegate.getLocalAddr();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return delegate.getLocalPort();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ throw new UnsupportedOperationException("getServletContext is not supported on the request bridge");
+ }
+
+ @Override
+ public AsyncContext startAsync() {
+ throw new UnsupportedOperationException("startAsync is not supported by the servlet bridge");
+ }
+
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
+ throw new UnsupportedOperationException("startAsync is not supported by the servlet bridge");
+ }
+
+ @Override
+ public boolean isAsyncStarted() {
+ return delegate.isAsyncStarted();
+ }
+
+ @Override
+ public boolean isAsyncSupported() {
+ return delegate.isAsyncSupported();
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ throw new UnsupportedOperationException("getAsyncContext is not supported by the servlet bridge");
+ }
+
+ @Override
+ public DispatcherType getDispatcherType() {
+ return DispatcherType.valueOf(delegate.getDispatcherType().name());
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxResponse.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxResponse.java
new file mode 100644
index 000000000000..9c749990eb0a
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxResponse.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Locale;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Exposes a {@link jakarta.servlet.http.HttpServletResponse} as a
+ * {@code javax.servlet.http.HttpServletResponse} so that hadoop's
+ * {@code javax.servlet}-based authentication filters can write the response
+ * (status, headers, cookies, error) inside a Jetty EE10 servlet chain.
+ * See {@link JavaxFilterBridge}.
+ */
+public class JakartaToJavaxResponse implements HttpServletResponse {
+
+ private final jakarta.servlet.http.HttpServletResponse delegate;
+
+ public JakartaToJavaxResponse(jakarta.servlet.http.HttpServletResponse delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void addCookie(Cookie cookie) {
+ delegate.addCookie(ServletBridgeUtils.toJakarta(cookie));
+ }
+
+ @Override
+ public boolean containsHeader(String name) {
+ return delegate.containsHeader(name);
+ }
+
+ @Override
+ public String encodeURL(String url) {
+ return delegate.encodeURL(url);
+ }
+
+ @Override
+ public String encodeRedirectURL(String url) {
+ return delegate.encodeRedirectURL(url);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public String encodeUrl(String url) {
+ return delegate.encodeURL(url);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public String encodeRedirectUrl(String url) {
+ return delegate.encodeRedirectURL(url);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ delegate.sendError(sc, msg);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ delegate.sendError(sc);
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException {
+ delegate.sendRedirect(location);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date) {
+ delegate.setDateHeader(name, date);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date) {
+ delegate.addDateHeader(name, date);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ delegate.setHeader(name, value);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ delegate.addHeader(name, value);
+ }
+
+ @Override
+ public void setIntHeader(String name, int value) {
+ delegate.setIntHeader(name, value);
+ }
+
+ @Override
+ public void addIntHeader(String name, int value) {
+ delegate.addIntHeader(name, value);
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ delegate.setStatus(sc);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void setStatus(int sc, String sm) {
+ // Servlet 6.0 removed the message form; the status code carries the meaning.
+ delegate.setStatus(sc);
+ }
+
+ @Override
+ public int getStatus() {
+ return delegate.getStatus();
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return delegate.getHeader(name);
+ }
+
+ @Override
+ public Collection getHeaders(String name) {
+ return delegate.getHeaders(name);
+ }
+
+ @Override
+ public Collection getHeaderNames() {
+ return delegate.getHeaderNames();
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return delegate.getCharacterEncoding();
+ }
+
+ @Override
+ public String getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return new JakartaToJavaxOutputStream(delegate.getOutputStream());
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ return delegate.getWriter();
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset) {
+ delegate.setCharacterEncoding(charset);
+ }
+
+ @Override
+ public void setContentLength(int len) {
+ delegate.setContentLength(len);
+ }
+
+ @Override
+ public void setContentLengthLong(long len) {
+ delegate.setContentLengthLong(len);
+ }
+
+ @Override
+ public void setContentType(String type) {
+ delegate.setContentType(type);
+ }
+
+ @Override
+ public void setBufferSize(int size) {
+ delegate.setBufferSize(size);
+ }
+
+ @Override
+ public int getBufferSize() {
+ return delegate.getBufferSize();
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ delegate.flushBuffer();
+ }
+
+ @Override
+ public void resetBuffer() {
+ delegate.resetBuffer();
+ }
+
+ @Override
+ public boolean isCommitted() {
+ return delegate.isCommitted();
+ }
+
+ @Override
+ public void reset() {
+ delegate.reset();
+ }
+
+ @Override
+ public void setLocale(Locale loc) {
+ delegate.setLocale(loc);
+ }
+
+ @Override
+ public Locale getLocale() {
+ return delegate.getLocale();
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxServletContext.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxServletContext.java
new file mode 100644
index 000000000000..c1d1a1630b5a
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JakartaToJavaxServletContext.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+/**
+ * Exposes a {@link jakarta.servlet.ServletContext} as a
+ * {@code javax.servlet.ServletContext}. Only the operations used by hadoop's
+ * authentication filter chain and secret-provider construction are delegated
+ * (attributes, init parameters, logging, resources, version). Attributes are
+ * read from and written to the underlying jakarta context, so a value set on
+ * the real context (for example the signer secret provider) is visible through
+ * this view. Servlet/filter registration, dispatchers, listeners, session
+ * configuration and JSP descriptors are not part of that chain and throw
+ * {@link UnsupportedOperationException}.
+ */
+public class JakartaToJavaxServletContext implements ServletContext {
+
+ private final jakarta.servlet.ServletContext delegate;
+
+ public JakartaToJavaxServletContext(jakarta.servlet.ServletContext delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getContextPath() {
+ return delegate.getContextPath();
+ }
+
+ @Override
+ public ServletContext getContext(String uripath) {
+ throw new UnsupportedOperationException("getContext is not supported by the servlet bridge");
+ }
+
+ @Override
+ public int getMajorVersion() {
+ return delegate.getMajorVersion();
+ }
+
+ @Override
+ public int getMinorVersion() {
+ return delegate.getMinorVersion();
+ }
+
+ @Override
+ public int getEffectiveMajorVersion() {
+ return delegate.getEffectiveMajorVersion();
+ }
+
+ @Override
+ public int getEffectiveMinorVersion() {
+ return delegate.getEffectiveMinorVersion();
+ }
+
+ @Override
+ public String getMimeType(String file) {
+ return delegate.getMimeType(file);
+ }
+
+ @Override
+ public Set getResourcePaths(String path) {
+ return delegate.getResourcePaths(path);
+ }
+
+ @Override
+ public URL getResource(String path) throws MalformedURLException {
+ return delegate.getResource(path);
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String path) {
+ return delegate.getResourceAsStream(path);
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ throw new UnsupportedOperationException("getRequestDispatcher is not supported by the servlet bridge");
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name) {
+ throw new UnsupportedOperationException("getNamedDispatcher is not supported by the servlet bridge");
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Servlet getServlet(String name) throws ServletException {
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Enumeration getServlets() {
+ return Collections.emptyEnumeration();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Enumeration getServletNames() {
+ return Collections.emptyEnumeration();
+ }
+
+ @Override
+ public void log(String msg) {
+ delegate.log(msg);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void log(Exception exception, String msg) {
+ delegate.log(msg, exception);
+ }
+
+ @Override
+ public void log(String message, Throwable throwable) {
+ delegate.log(message, throwable);
+ }
+
+ @Override
+ public String getRealPath(String path) {
+ return delegate.getRealPath(path);
+ }
+
+ @Override
+ public String getServerInfo() {
+ return delegate.getServerInfo();
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return delegate.getInitParameter(name);
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ return delegate.getInitParameterNames();
+ }
+
+ @Override
+ public boolean setInitParameter(String name, String value) {
+ return delegate.setInitParameter(name, value);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return delegate.getAttribute(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return delegate.getAttributeNames();
+ }
+
+ @Override
+ public void setAttribute(String name, Object object) {
+ delegate.setAttribute(name, object);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ delegate.removeAttribute(name);
+ }
+
+ @Override
+ public String getServletContextName() {
+ return delegate.getServletContextName();
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, String className) {
+ throw new UnsupportedOperationException("addServlet is not supported by the servlet bridge");
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
+ throw new UnsupportedOperationException("addServlet is not supported by the servlet bridge");
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, Class extends Servlet> servletClass) {
+ throw new UnsupportedOperationException("addServlet is not supported by the servlet bridge");
+ }
+
+ @Override
+ public T createServlet(Class clazz) throws ServletException {
+ throw new UnsupportedOperationException("createServlet is not supported by the servlet bridge");
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName) {
+ throw new UnsupportedOperationException("getServletRegistration is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Map getServletRegistrations() {
+ throw new UnsupportedOperationException("getServletRegistrations is not supported by the servlet bridge");
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, String className) {
+ throw new UnsupportedOperationException("addFilter is not supported by the servlet bridge");
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
+ throw new UnsupportedOperationException("addFilter is not supported by the servlet bridge");
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, Class extends Filter> filterClass) {
+ throw new UnsupportedOperationException("addFilter is not supported by the servlet bridge");
+ }
+
+ @Override
+ public T createFilter(Class clazz) throws ServletException {
+ throw new UnsupportedOperationException("createFilter is not supported by the servlet bridge");
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName) {
+ throw new UnsupportedOperationException("getFilterRegistration is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Map getFilterRegistrations() {
+ throw new UnsupportedOperationException("getFilterRegistrations is not supported by the servlet bridge");
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig() {
+ throw new UnsupportedOperationException("getSessionCookieConfig is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void setSessionTrackingModes(Set sessionTrackingModes) {
+ throw new UnsupportedOperationException("setSessionTrackingModes is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Set getDefaultSessionTrackingModes() {
+ throw new UnsupportedOperationException("getDefaultSessionTrackingModes is not supported by the servlet bridge");
+ }
+
+ @Override
+ public Set getEffectiveSessionTrackingModes() {
+ throw new UnsupportedOperationException("getEffectiveSessionTrackingModes is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void addListener(String className) {
+ throw new UnsupportedOperationException("addListener is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void addListener(T t) {
+ throw new UnsupportedOperationException("addListener is not supported by the servlet bridge");
+ }
+
+ @Override
+ public void addListener(Class extends EventListener> listenerClass) {
+ throw new UnsupportedOperationException("addListener is not supported by the servlet bridge");
+ }
+
+ @Override
+ public T createListener(Class clazz) throws ServletException {
+ throw new UnsupportedOperationException("createListener is not supported by the servlet bridge");
+ }
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor() {
+ throw new UnsupportedOperationException("getJspConfigDescriptor is not supported by the servlet bridge");
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return delegate.getClassLoader();
+ }
+
+ @Override
+ public void declareRoles(String... roleNames) {
+ throw new UnsupportedOperationException("declareRoles is not supported by the servlet bridge");
+ }
+
+ @Override
+ public String getVirtualServerName() {
+ return delegate.getVirtualServerName();
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java
new file mode 100644
index 000000000000..8a9076de26ec
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+
+/**
+ * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet
+ * chain by wrapping the jakarta request/response as javax views for the
+ * delegate, and re-presenting the delegate's authenticated request to the
+ * downstream jakarta chain.
+ *
+ * This is the single choke point that lets Ozone keep hadoop's
+ * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos /
+ * delegation token) as the source of truth while the rest of the HTTP stack
+ * runs on jakarta. When the delegate authenticates and calls its chain, the
+ * authenticated principal it established (remote user, user principal, auth
+ * type) is overlaid onto the original jakarta request and passed downstream;
+ * when the delegate short-circuits (for example writing a 401), the response
+ * has already been written through to the jakarta response and the downstream
+ * chain is not invoked.
+ */
+public class JavaxFilterBridge implements Filter {
+
+ private final javax.servlet.Filter delegate;
+
+ public JavaxFilterBridge(javax.servlet.Filter delegate) {
+ this.delegate = delegate;
+ }
+
+ /** The wrapped javax filter, exposed for configuration by the registrar. */
+ public javax.servlet.Filter getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ try {
+ delegate.init(new JakartaToJavaxFilterConfig(filterConfig));
+ } catch (javax.servlet.ServletException e) {
+ throw new ServletException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
+ throw new ServletException("JavaxFilterBridge only supports HTTP requests");
+ }
+ final HttpServletRequest jakartaRequest = (HttpServletRequest) request;
+ final HttpServletResponse jakartaResponse = (HttpServletResponse) response;
+
+ JakartaToJavaxRequest javaxRequest = new JakartaToJavaxRequest(jakartaRequest);
+ JakartaToJavaxResponse javaxResponse = new JakartaToJavaxResponse(jakartaResponse);
+
+ javax.servlet.FilterChain javaxChain = (downstreamRequest, downstreamResponse) -> {
+ // The delegate authenticated the request and may have wrapped it to carry
+ // the principal. Overlay that principal onto the original jakarta request
+ // and continue the jakarta chain.
+ HttpServletRequest authenticated = jakartaRequest;
+ if (downstreamRequest instanceof javax.servlet.http.HttpServletRequest) {
+ authenticated = new AuthenticatedRequest(jakartaRequest,
+ (javax.servlet.http.HttpServletRequest) downstreamRequest);
+ }
+ try {
+ chain.doFilter(authenticated, jakartaResponse);
+ } catch (ServletException e) {
+ throw new javax.servlet.ServletException(e.getMessage(), e);
+ }
+ };
+
+ try {
+ delegate.doFilter(javaxRequest, javaxResponse, javaxChain);
+ } catch (javax.servlet.ServletException e) {
+ throw new ServletException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ delegate.destroy();
+ }
+
+ /**
+ * Jakarta request that carries the authentication result (remote user,
+ * principal, auth type, roles) the delegate established on its javax request,
+ * while delegating everything else to the original jakarta request.
+ */
+ private static final class AuthenticatedRequest extends HttpServletRequestWrapper {
+
+ private final javax.servlet.http.HttpServletRequest authenticated;
+
+ private AuthenticatedRequest(HttpServletRequest original,
+ javax.servlet.http.HttpServletRequest authenticated) {
+ super(original);
+ this.authenticated = authenticated;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return authenticated.getRemoteUser();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return authenticated.getUserPrincipal();
+ }
+
+ @Override
+ public String getAuthType() {
+ return authenticated.getAuthType();
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return authenticated.isUserInRole(role);
+ }
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/ServletBridgeUtils.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/ServletBridgeUtils.java
new file mode 100644
index 000000000000..61e4ee012364
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/ServletBridgeUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.server.http.servletbridge;
+
+/**
+ * Value-type conversions between the {@code javax.servlet} and
+ * {@code jakarta.servlet} namespaces used by the servlet bridge.
+ */
+final class ServletBridgeUtils {
+
+ private ServletBridgeUtils() {
+ }
+
+ /** Convert a jakarta cookie to its javax equivalent. */
+ static javax.servlet.http.Cookie toJavax(jakarta.servlet.http.Cookie source) {
+ javax.servlet.http.Cookie result =
+ new javax.servlet.http.Cookie(source.getName(), source.getValue());
+ if (source.getDomain() != null) {
+ result.setDomain(source.getDomain());
+ }
+ if (source.getPath() != null) {
+ result.setPath(source.getPath());
+ }
+ result.setMaxAge(source.getMaxAge());
+ result.setSecure(source.getSecure());
+ result.setHttpOnly(source.isHttpOnly());
+ return result;
+ }
+
+ /** Convert a javax cookie to its jakarta equivalent. */
+ static jakarta.servlet.http.Cookie toJakarta(javax.servlet.http.Cookie source) {
+ jakarta.servlet.http.Cookie result =
+ new jakarta.servlet.http.Cookie(source.getName(), source.getValue());
+ if (source.getDomain() != null) {
+ result.setDomain(source.getDomain());
+ }
+ if (source.getPath() != null) {
+ result.setPath(source.getPath());
+ }
+ result.setMaxAge(source.getMaxAge());
+ result.setSecure(source.getSecure());
+ result.setHttpOnly(source.isHttpOnly());
+ return result;
+ }
+}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/package-info.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/package-info.java
new file mode 100644
index 000000000000..84fcd84d3d4f
--- /dev/null
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A minimal {@code javax.servlet} to {@code jakarta.servlet} bridge that lets
+ * hadoop's {@code javax.servlet}-based authentication filters run inside
+ * Ozone's Jetty EE10 (jakarta) HTTP server. See
+ * {@link org.apache.hadoop.hdds.server.http.servletbridge.JavaxFilterBridge}.
+ */
+package org.apache.hadoop.hdds.server.http.servletbridge;
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
index 6941ea3d537c..fbc6320cbe5a 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
@@ -23,6 +23,10 @@
import static org.apache.hadoop.ozone.OzoneConsts.ROCKSDB_SST_SUFFIX;
import com.google.common.annotations.VisibleForTesting;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -38,10 +42,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
@@ -49,6 +49,7 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hdds.server.OzoneAdmins;
+import org.apache.hadoop.hdds.server.http.servletbridge.JakartaToJavaxRequest;
import org.apache.hadoop.hdds.utils.db.DBCheckpoint;
import org.apache.hadoop.hdds.utils.db.DBStore;
import org.apache.hadoop.ozone.lock.BootstrapStateHandler;
@@ -313,7 +314,8 @@ protected static String[] parseFormDataParameters(HttpServletRequest request) {
List sstParam = new ArrayList<>();
try {
- FileItemIterator iter = upload.getItemIterator(request);
+ FileItemIterator iter =
+ upload.getItemIterator(new JakartaToJavaxRequest(request));
while (iter.hasNext()) {
FileItemStream item = iter.next();
if (!item.isFormField() || !FIELD_NAME.equals(item.getFieldName())) {
@@ -352,7 +354,8 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) {
public void doPost(HttpServletRequest request, HttpServletResponse response) {
LOG.info("Received POST request to obtain DB checkpoint snapshot");
- if (!ServletFileUpload.isMultipartContent(request)) {
+ if (!ServletFileUpload.isMultipartContent(
+ new JakartaToJavaxRequest(request))) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HttpServletUtils.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HttpServletUtils.java
index 11b933f3a975..5be883755ba2 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HttpServletUtils.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/HttpServletUtils.java
@@ -17,16 +17,16 @@
package org.apache.hadoop.hdds.utils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/LogLevel.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/LogLevel.java
index de228d8dcc2b..8231216cedc3 100644
--- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/LogLevel.java
+++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/LogLevel.java
@@ -18,6 +18,10 @@
package org.apache.hadoop.hdds.utils;
import com.google.common.annotations.VisibleForTesting;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -25,14 +29,11 @@
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
@@ -42,7 +43,6 @@
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.util.GenericOptionsParser;
-import org.apache.hadoop.util.ServletUtil;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.log4j.Level;
@@ -67,6 +67,11 @@ public final class LogLevel {
private static final String MARKER = "";
private static final Pattern TAG = Pattern.compile("<[^>]*>");
+ private static final String HTML_TAIL = "
\n"
+ + "Hadoop, "
+ + Calendar.getInstance().get(Calendar.YEAR) + ".\n"
+ + "