EchoHandler.java

/*
 *    Copyright (c) 2015-2023 AsyncHttpClient Project. All rights reserved.
 *
 *    Licensed 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.asynchttpclient.test;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.zip.Deflater;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_ENCODING;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING;
import static io.netty.handler.codec.http.HttpHeaderValues.CHUNKED;
import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE;

public class EchoHandler extends AbstractHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(EchoHandler.class);

    @Override
    public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException {

        LOGGER.debug("Echo received request {} on path {}", request, pathInContext);

        if (httpRequest.getHeader("X-HEAD") != null) {
            httpResponse.setContentLength(1);
        }

        if (httpRequest.getHeader("X-ISO") != null) {
            httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET);
        } else {
            httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET);
        }

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            httpResponse.addHeader("Allow", "GET,HEAD,POST,OPTIONS,TRACE");
        }

        Enumeration<String> e = httpRequest.getHeaderNames();
        String headerName;
        while (e.hasMoreElements()) {
            headerName = e.nextElement();
            if (headerName.startsWith("LockThread")) {
                final int sleepTime = httpRequest.getIntHeader(headerName);
                try {
                    Thread.sleep(sleepTime == -1 ? 40 : sleepTime * 1000L);
                } catch (InterruptedException ex) {
                    //
                }
            }

            if (headerName.startsWith("X-redirect")) {
                httpResponse.sendRedirect(httpRequest.getHeader("X-redirect"));
                return;
            }
            if (headerName.startsWith("X-fail")) {
                byte[] body = "custom error message".getBytes(StandardCharsets.US_ASCII);
                httpResponse.addHeader(CONTENT_LENGTH.toString(), String.valueOf(body.length));
                httpResponse.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
                httpResponse.getOutputStream().write(body);
                httpResponse.getOutputStream().flush();
                httpResponse.getOutputStream().close();
                return;
            }
            httpResponse.addHeader("X-" + headerName, httpRequest.getHeader(headerName));
        }

        String pathInfo = httpRequest.getPathInfo();
        if (pathInfo != null) {
            httpResponse.addHeader("X-pathInfo", pathInfo);
        }

        String queryString = httpRequest.getQueryString();
        if (queryString != null) {
            httpResponse.addHeader("X-queryString", queryString);
        }

        httpResponse.addHeader("X-KEEP-ALIVE", httpRequest.getRemoteAddr() + ':' + httpRequest.getRemotePort());

        Cookie[] cs = httpRequest.getCookies();
        if (cs != null) {
            for (Cookie c : cs) {
                httpResponse.addCookie(c);
            }
        }

        Enumeration<String> i = httpRequest.getParameterNames();
        if (i.hasMoreElements()) {
            StringBuilder requestBody = new StringBuilder();
            while (i.hasMoreElements()) {
                headerName = i.nextElement();
                httpResponse.addHeader("X-" + headerName, httpRequest.getParameter(headerName));
                requestBody.append(headerName);
                requestBody.append('_');
            }

            if (requestBody.length() > 0) {
                String body = requestBody.toString();
                httpResponse.getOutputStream().write(body.getBytes());
            }
        }

        if (httpRequest.getHeader("X-COMPRESS") != null) {
            byte[] compressed = deflate(IOUtils.toByteArray(httpRequest.getInputStream()));
            httpResponse.addIntHeader(CONTENT_LENGTH.toString(), compressed.length);
            httpResponse.addHeader(CONTENT_ENCODING.toString(), DEFLATE.toString());
            httpResponse.getOutputStream().write(compressed, 0, compressed.length);

        } else {
            httpResponse.addHeader(TRANSFER_ENCODING.toString(), CHUNKED.toString());
            int size = 16384;
            if (httpRequest.getContentLength() > 0) {
                size = httpRequest.getContentLength();
            }
            if (size > 0) {
                int read = 0;
                while (read > -1) {
                    byte[] bytes = new byte[size];
                    read = httpRequest.getInputStream().read(bytes);
                    if (read > 0) {
                        httpResponse.getOutputStream().write(bytes, 0, read);
                    }
                }
            }
        }

        request.setHandled(true);
        httpResponse.getOutputStream().flush();
        // FIXME don't always close, depends on the test, cf ReactiveStreamsTest
        httpResponse.getOutputStream().close();
    }

    private static byte[] deflate(byte[] input) throws IOException {
        Deflater compressor = new Deflater();
        compressor.setLevel(Deflater.BEST_COMPRESSION);

        compressor.setInput(input);
        compressor.finish();

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length)) {
            byte[] buf = new byte[1024];
            while (!compressor.finished()) {
                int count = compressor.deflate(buf);
                bos.write(buf, 0, count);
            }
            return bos.toByteArray();
        }
    }
}