Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openjdk/test/jdk/java/net/httpclient/   (Sun/Oracle ©)  Datei vom 13.11.2022 mit Größe 26 kB image not shown  

Quelle  ShortResponseBody.java   Sprache: JAVA

 
/*
 * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */



import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jdk.test.lib.net.SimpleSSLContext;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import static java.lang.System.out;
import static java.net.http.HttpClient.Builder.NO_PROXY;
import static java.net.http.HttpResponse.BodyHandlers.ofString;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.stream.Collectors.toList;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

public abstract class ShortResponseBody {

    Server closeImmediatelyServer;
    Server closeImmediatelyHttpsServer;
    Server variableLengthServer;
    Server variableLengthHttpsServer;
    Server fixedLengthServer;

    String httpURIClsImed;
    String httpsURIClsImed;
    String httpURIVarLen;
    String httpsURIVarLen;
    String httpURIFixLen;

    SSLContext sslContext;
    SSLParameters sslParameters;
    HttpClient client;
    int numberOfRequests;

    static final int REQUESTS_PER_CLIENT = 10; // create new client every 10 requests
    static final long PAUSE_FOR_GC = 5; // 5ms to let gc work
    static final long PAUSE_FOR_PEER = 5; // 5ms to let server react

    static final String EXPECTED_RESPONSE_BODY =
            "

Heading

Some Text

"
;

    // A request number used to replace %reqnb% in URLs with a unique
    // number for better log analysis
    static final AtomicLong reqnb = new AtomicLong();

    static final AtomicLong ids = new AtomicLong();
    final ThreadFactory factory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r,  "HttpClient-Worker-" + ids.incrementAndGet());
            thread.setDaemon(true);
            return thread;
        }
    };
    final ExecutorService service = Executors.newCachedThreadPool(factory);

    final AtomicReference<SkipException> skiptests = new AtomicReference<>();
    void checkSkip() {
        var skip = skiptests.get();
        if (skip != nullthrow skip;
    }
    static String name(ITestResult result) {
        var params = result.getParameters();
        return result.getName()
                + (params == null ? "()" : Arrays.toString(result.getParameters()));
    }

    @BeforeMethod
    void beforeMethod(ITestContext context) {
        if (client == null || numberOfRequests == REQUESTS_PER_CLIENT) {
            numberOfRequests = 0;
            out.println("--- new client");
            client = newHttpClient();
            System.gc();
            try {
                Thread.sleep(PAUSE_FOR_GC);
            } catch (InterruptedException x) {

            }
        }
        numberOfRequests++;
        if (context.getFailedTests().size() > 0) {
            if (skiptests.get() == null) {
                SkipException skip = new SkipException("some tests failed");
                skip.setStackTrace(new StackTraceElement[0]);
                skiptests.compareAndSet(null, skip);
            }
        }
    }

    @AfterClass
    static final void printFailedTests(ITestContext context) {
        out.println("\n=========================\n");
        try {
            var FAILURES = context.getFailedTests().getAllResults().stream()
                    .collect(Collectors.toMap(r -> name(r), ITestResult::getThrowable));

            if (FAILURES.isEmpty()) return;
            out.println("Failed tests: ");
            FAILURES.entrySet().forEach((e) -> {
                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
                e.getValue().printStackTrace(out);
                e.getValue().printStackTrace();
            });
         } finally {
            out.println("\n=========================\n");
        }
    }

    @DataProvider(name = "sanity")
    public Object[][] sanity() {
        return new Object[][]{
            { httpURIVarLen  + "?length=all" },
            { httpsURIVarLen + "?length=all" },
            { httpURIFixLen  + "?length=all" },
        };
    }

    public static String uniqueURL(String url) {
        return url.replace("%reqnb%", String.valueOf(reqnb.incrementAndGet()));
    }

    @Test(dataProvider = "sanity")
    void sanity(String url) throws Exception {
        url = uniqueURL(url);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
        out.println("Request: " + request);
        HttpResponse<String> response = client.send(request, ofString());
        String body = response.body();
        assertEquals(body, EXPECTED_RESPONSE_BODY);
        client.sendAsync(request, ofString())
                .thenApply(resp -> resp.body())
                .thenAccept(b -> assertEquals(b, EXPECTED_RESPONSE_BODY))
                .join();
    }

    @DataProvider(name = "sanityBadRequest")
    public Object[][] sanityBadRequest() {
        return new Object[][]{
                { httpURIVarLen  }, // no query string
                { httpsURIVarLen },
                { httpURIFixLen  },
        };
    }

    @Test(dataProvider = "sanityBadRequest")
    void sanityBadRequest(String url) throws Exception {
        url = uniqueURL(url);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
        out.println("Request: " + request);
        HttpResponse<String> response = client.send(request, ofString());
        assertEquals(response.statusCode(), 400);
        assertEquals(response.body(), "");
    }

    @DataProvider(name = "uris")
    public Object[][] variants(ITestContext context) {
        String[][] cases = new String[][] {
            // The length query string is the total number of bytes in the reply,
            // including headers, before the server closes the connection. The
            // second arg is a partial-expected-detail message in the exception.
            { httpURIVarLen + "?length=0",   "no bytes"     }, // EOF without receiving anything
            { httpURIVarLen + "?length=1",   "status line"  }, // EOF during status-line
            { httpURIVarLen + "?length=2",   "status line"  },
            { httpURIVarLen + "?length=10",  "status line"  },
            { httpURIVarLen + "?length=19",  "header"       }, // EOF during Content-Type header
            { httpURIVarLen + "?length=30",  "header"       },
            { httpURIVarLen + "?length=45",  "header"       },
            { httpURIVarLen + "?length=48",  "header"       },
            { httpURIVarLen + "?length=51",  "header"       },
            { httpURIVarLen + "?length=98",  "header"       }, // EOF during Connection header
            { httpURIVarLen + "?length=100""header"       },
            { httpURIVarLen + "?length=101""header"       },
            { httpURIVarLen + "?length=104""header"       },
            { httpURIVarLen + "?length=106""chunked transfer encoding" }, // EOF during chunk header ( length )
            { httpURIVarLen + "?length=110""chunked transfer encoding" }, // EOF during chunk response body data

            { httpsURIVarLen + "?length=0",   "no bytes"    },
            { httpsURIVarLen + "?length=1",   "status line" },
            { httpsURIVarLen + "?length=2",   "status line" },
            { httpsURIVarLen + "?length=10",  "status line" },
            { httpsURIVarLen + "?length=19",  "header"      },
            { httpsURIVarLen + "?length=30",  "header"      },
            { httpsURIVarLen + "?length=45",  "header"      },
            { httpsURIVarLen + "?length=48",  "header"      },
            { httpsURIVarLen + "?length=51",  "header"      },
            { httpsURIVarLen + "?length=98",  "header"      },
            { httpsURIVarLen + "?length=100""header"      },
            { httpsURIVarLen + "?length=101""header"      },
            { httpsURIVarLen + "?length=104""header"      },
            { httpsURIVarLen + "?length=106""chunked transfer encoding" },
            { httpsURIVarLen + "?length=110""chunked transfer encoding" },

            { httpURIFixLen + "?length=0",   "no bytes"    }, // EOF without receiving anything
            { httpURIFixLen + "?length=1",   "status line" }, // EOF during status-line
            { httpURIFixLen + "?length=2",   "status line" },
            { httpURIFixLen + "?length=10",  "status line" },
            { httpURIFixLen + "?length=19",  "header"      }, // EOF during Content-Type header
            { httpURIFixLen + "?length=30",  "header"      },
            { httpURIFixLen + "?length=45",  "header"      },
            { httpURIFixLen + "?length=48",  "header"      },
            { httpURIFixLen + "?length=51",  "header"      },
            { httpURIFixLen + "?length=78",  "header"      }, // EOF during Connection header
            { httpURIFixLen + "?length=79",  "header"      },
            { httpURIFixLen + "?length=86",  "header"      },
            { httpURIFixLen + "?length=104""fixed content-length" }, // EOF during body
            { httpURIFixLen + "?length=106""fixed content-length" },
            { httpURIFixLen + "?length=110""fixed content-length" },

            // ## ADD https fixed

            { httpURIClsImed,  "no bytes"},
            { httpsURIClsImed, "no bytes"},
        };

        if (context.getFailedTests().size() > 0) {
            // Shorten the log output by preventing useless
            // skip traces to be printed for subsequent methods
            // if one of the previous @Test method has failed.
            return new Object[0][];
        }

        return cases;
    }

    HttpClient newHttpClient() {
        return HttpClient.newBuilder()
                .proxy(NO_PROXY)
                .sslContext(sslContext)
                .sslParameters(sslParameters)
                .executor(service)
                .build();
    }

    // can be used to prolong request body publication
    static final class InfiniteInputStream extends InputStream {
        int count = 0;
        int k16 = 0;
        @Override
        public int read() throws IOException {
            if (++count == 1) {
                System.out.println("Start sending 1 byte");
            }
            if (count > 16 * 1024) {
                k16++;
                System.out.println("... 16K sent.");
                count = count % (16 * 1024);
                try {
                    Thread.sleep(PAUSE_FOR_PEER);
                } catch (InterruptedException x) {
                    // ignore
                }
            }
            if (k16 > 128) {
                System.out.println("WARNING: InfiniteInputStream: " +
                        "more than 128 16k buffers generated: returning EOF");
                return -1;
            }
            return 1;
        }

        @Override
        public int read(byte[] buf, int offset, int length) {
            //int count = offset;
            length = Math.max(0, Math.min(buf.length - offset, length));
            //for (; count < length; count++)
            //    buf[offset++] = 0x01;
            //return count;
            if (count == 0) {
                System.out.println("Start sending " + length);
            } else if (count > 16 * 1024) {
                k16++;
                System.out.println("... 16K sent.");
                count = count % (16 * 1024);
                try {
                    Thread.sleep(PAUSE_FOR_PEER);
                } catch (InterruptedException x) {
                    // ignore
                }
            }
            if (k16 > 128) {
                System.out.println("WARNING: InfiniteInputStream: " +
                        "more than 128 16k buffers generated: returning EOF");
                return -1;
            }
            count += length;
            return length;
        }
    }

    void assertExpectedMessage(HttpRequest request, Throwable t, String expected) {
        if (request.uri().getScheme().equalsIgnoreCase("https")
                && (t instanceof SSLHandshakeException)) {
            // OK
            out.println("Skipping expected " + t);
        } else {
            String msg = t.getMessage();
            assertTrue(msg.contains(expected),
                    "exception msg:[" + msg + "]");
        }
    }

    void assertExpectedMessage(HttpRequest request, Throwable t, List<String> expected) {
        if (request.uri().getScheme().equalsIgnoreCase("https")
                && (t instanceof SSLHandshakeException)) {
            // OK
            out.println("Skipping expected " + t);
        } else {
            String msg = t.getMessage();
            assertTrue(expected.stream().anyMatch(msg::contains),
                    "exception msg:[" + msg + "] not in " + Arrays.asList(expected));
        }
    }

    // Asserts that the "send" method appears in the stack of the given
    // exception. The synchronous API must contain the send method on the stack.
    static void assertSendMethodOnStack(IOException ioe) {
        final String cn = "jdk.internal.net.http.HttpClientImpl";
        List<StackTraceElement> list = Stream.of(ioe.getStackTrace())
                .filter(ste -> ste.getClassName().equals(cn)
                        && ste.getMethodName().equals("send"))
                .collect(toList());
        if (list.size() != 1) {
            ioe.printStackTrace(out);
            fail(cn + ".send method not found in stack.");
        }
    }

    // Asserts that the implementation-specific ConnectionExpiredException does
    // NOT appear anywhere in the exception or its causal chain.
    static void assertNoConnectionExpiredException(IOException ioe) {
        Throwable throwable = ioe;
        do {
            String cn = throwable.getClass().getSimpleName();
            if (cn.equals("ConnectionExpiredException")) {
                ioe.printStackTrace(out);
                fail("UNEXPECTED ConnectionExpiredException in:[" + ioe + "]");
            }
        } while ((throwable = throwable.getCause()) != null);
    }

    // -- infra

    /**
     * A server that, listens on a port, accepts new connections, and can be
     * closed.
     */

    static abstract class Server extends Thread implements AutoCloseable {
        protected final ServerSocket ss;
        protected volatile boolean closed;

        Server(String name) throws IOException {
            super(name);
            ss = newServerSocket();
            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
            this.start();
        }

        protected ServerSocket newServerSocket() throws IOException {
            return new ServerSocket();
        }

        public int getPort() { return ss.getLocalPort(); }

        @Override
        public void close() {
            if (closed)
                return;
            closed = true;
            try {
                ss.close();
            } catch (IOException e) {
                out.println("Unexpected exception while closing server: " + e);
                e.printStackTrace(out);
                throw new UncheckedIOException("Unexpected: ", e);
            }
        }
    }

    /**
     * A server that closes the connection immediately, without reading or writing.
     */

    static class PlainCloseImmediatelyServer extends Server {
        PlainCloseImmediatelyServer() throws IOException {
            super("PlainCloseImmediatelyServer");
        }

        protected PlainCloseImmediatelyServer(String name) throws IOException {
            super(name);
        }

        @Override
        public void run() {
            while (!closed) {
                try (Socket s = ss.accept()) {
                    if (s instanceof SSLSocket) {
                        ((SSLSocket)s).startHandshake();
                    }
                    out.println("Server: got connection, closing immediately ");
                } catch (Throwable e) {
                    if (!closed) {
                        out.println("Unexpected exception in server: " + e);
                        e.printStackTrace(out);
                        throw new RuntimeException("Unexpected: ", e);
                    }
                }
            }
        }
    }

    /**
     * A server that closes the connection immediately, without reading or writing,
     * after completing the SSL handshake.
     */

    static final class SSLCloseImmediatelyServer extends PlainCloseImmediatelyServer {
        SSLCloseImmediatelyServer() throws IOException {
            super("SSLCloseImmediatelyServer");
        }
        @Override
        public ServerSocket newServerSocket() throws IOException {
            return SSLServerSocketFactory.getDefault().createServerSocket();
        }
    }

    /**
     * A server that replies with headers and a, possibly partial, reply, before
     * closing the connection. The number of bytes of written ( header + body),
     * is controllable through the "length" query string param in the requested
     * URI.
     */

    static abstract class ReplyingServer extends Server {

        private final String name;

        ReplyingServer(String name) throws IOException {
            super(name);
            this.name = name;
        }

        private static final String BAD_REQUEST_RESPONSE =
                "HTTP/1.1 400 Bad Request\r\n" +
                        "Content-Length: 0\r\n" +
                        "Connection: close\r\n\r\n";

        abstract String response();

        @Override
        public void run() {
            while (!closed) {
                try (Socket s = ss.accept()) {
                    out.print(name + ": got connection ");
                    InputStream is = s.getInputStream();
                    URI requestMethod = readRequestMethod(is);
                    out.print(requestMethod + " ");
                    URI uriPath = readRequestPath(is);
                    out.println(uriPath);
                    String headers = readRequestHeaders(is);

                    String query = uriPath.getRawQuery();
                    if (query == null) {
                        out.println("Unexpected request without query string received. Got headers: [" + headers + "]");
                        out.println("Replying with 400 Bad Request");
                        writeResponse(s, BAD_REQUEST_RESPONSE, BAD_REQUEST_RESPONSE.length());
                        continue;
                    }
                    String qv = query.split("=")[1];
                    int len;
                    if (qv.equals("all")) {
                        len = response().getBytes(US_ASCII).length;
                    } else {
                        len = Integer.parseInt(query.split("=")[1]);
                    }

                    out.println(name + ": writing " + len  + " bytes");
                    writeResponse(s, response(), len);
                } catch (Throwable e) {
                    if (!closed) {
                        out.println("Unexpected exception in server: " + e);
                        e.printStackTrace(out);
                        throw new RuntimeException("Unexpected: " + e, e);
                    }
                }
            }
        }

        private static void writeResponse(Socket socket, String response, int len) throws IOException {
            OutputStream os = socket.getOutputStream();
            byte[] responseBytes = response.getBytes(US_ASCII);
            os.write(responseBytes, 0, len);
            os.flush();
        }

        static final byte[] requestEnd = new byte[] { '\r''\n''\r''\n' };

        // Read the request method
        static URI readRequestMethod(InputStream is) throws IOException {
            StringBuilder sb = new StringBuilder();
            int r;
            while ((r = is.read()) != -1 && r != 0x20) {
                sb.append((char)r);
            }
            return URI.create(sb.toString());
        }

        // Read the request URI path
        static URI readRequestPath(InputStream is) throws IOException {
            StringBuilder sb = new StringBuilder();
            int r;
            while ((r = is.read()) != -1 && r != 0x20) {
                sb.append((char)r);
            }
            return URI.create(sb.toString());
        }

        // Read until the end of a HTTP request headers
        static String readRequestHeaders(InputStream is) throws IOException {
            int requestEndCount = 0, r;
            StringBuilder sb = new StringBuilder();
            while ((r = is.read()) != -1) {
                sb.append((char) r);
                if (r == requestEnd[requestEndCount]) {
                    requestEndCount++;
                    if (requestEndCount == 4) {
                        break;
                    }
                } else {
                    requestEndCount = 0;
                }
            }
            return sb.toString();
        }
    }

    /** A server that issues a, possibly-partial, chunked reply. */
    static class PlainVariableLengthServer extends ReplyingServer {

        static final String CHUNKED_RESPONSE_BODY =
                "6\r\n""\r\n" +
                "6\r\n""\r\n" +
                "10\r\n""

Heading

\r\n"
 +
                "10\r\n""

Some Text

\r\n"
 +
                "7\r\n""\r\n" +
                "7\r\n""\r\n" +
                "0\r\n""\r\n";

        static final String RESPONSE_HEADERS =
                "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html; charset=utf-8\r\n" +
                "Transfer-Encoding: chunked\r\n" +
                "Connection: close\r\n\r\n";

        static final String RESPONSE = RESPONSE_HEADERS + CHUNKED_RESPONSE_BODY;

        PlainVariableLengthServer() throws IOException {
            super("PlainVariableLengthServer");
        }

        protected PlainVariableLengthServer(String name) throws IOException {
            super(name);
        }

        @Override
        String response( ) { return RESPONSE; }
    }

    /** A server that issues a, possibly-partial, chunked reply over SSL. */
    static final class SSLVariableLengthServer extends PlainVariableLengthServer {
        SSLVariableLengthServer() throws IOException {
            super("SSLVariableLengthServer");
        }
        @Override
        public ServerSocket newServerSocket() throws IOException {
            return SSLServerSocketFactory.getDefault().createServerSocket();
        }
    }

    /** A server that issues a fixed-length reply. */
    static final class FixedLengthServer extends ReplyingServer {

        static final String RESPONSE_BODY = EXPECTED_RESPONSE_BODY;

        static final String RESPONSE_HEADERS =
                "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html; charset=utf-8\r\n" +
                "Content-Length: " + RESPONSE_BODY.length() + "\r\n" +
                "Connection: close\r\n\r\n";

        static final String RESPONSE = RESPONSE_HEADERS + RESPONSE_BODY;

        FixedLengthServer() throws IOException {
            super("FixedLengthServer");
        }

        @Override
        String response( ) { return RESPONSE; }
    }

    static String serverAuthority(Server server) {
        return InetAddress.getLoopbackAddress().getHostName() + ":"
                + server.getPort();
    }

    @BeforeTest
    public void setup() throws Exception {
        sslContext = new SimpleSSLContext().get();
        if (sslContext == null)
            throw new AssertionError("Unexpected null sslContext");
        SSLContext.setDefault(sslContext);

        sslParameters = new SSLParameters();

        closeImmediatelyServer = new PlainCloseImmediatelyServer();
        httpURIClsImed = "http://" + serverAuthority(closeImmediatelyServer)
                + "/http1/closeImmediately/req=%reqnb%/foo";

        closeImmediatelyHttpsServer = new SSLCloseImmediatelyServer();
        httpsURIClsImed = "https://" + serverAuthority(closeImmediatelyHttpsServer)
                + "/https1/closeImmediately/req=%reqnb%/foo";

        variableLengthServer = new PlainVariableLengthServer();
        httpURIVarLen = "http://" + serverAuthority(variableLengthServer)
                + "/http1/variable/req=%reqnb%/bar";

        variableLengthHttpsServer = new SSLVariableLengthServer();
        httpsURIVarLen = "https://" + serverAuthority(variableLengthHttpsServer)
                + "/https1/variable/req=%reqnb%/bar";

        fixedLengthServer = new FixedLengthServer();
        httpURIFixLen = "http://" + serverAuthority(fixedLengthServer)
                + "/http1/fixed/req=%reqnb%/baz";
    }

    @AfterTest
    public void teardown() throws Exception {
        closeImmediatelyServer.close();
        closeImmediatelyHttpsServer.close();
        variableLengthServer.close();
        variableLengthHttpsServer.close();
        fixedLengthServer.close();
    }
}

86%


¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.