HttpCallerServiceImplByJ11.java

package space.sunqian.common.net.http;

import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.Fs;
import space.sunqian.common.io.IOKit;

import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
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.Map;
import java.util.Objects;

enum HttpCallerServiceImplByJ11 implements HttpCallerService {
    INST;

    @Override
    public @Nonnull HttpCaller newCaller(int bufSize, @Nonnull Proxy proxy) throws HttpNetException {
        return new CallerImpl(proxy);
    }

    private static final class CallerImpl implements HttpCaller {

        private final @Nonnull HttpClient httpClient;

        CallerImpl(@Nonnull Proxy proxy) throws HttpNetException {
            this.httpClient = Fs.uncheck(() ->
                    HttpClient.newBuilder().proxy(ProxySelector.of((InetSocketAddress) proxy.address())).build(),
                HttpNetException::new);
        }

        @Override
        public @Nonnull HttpResp request(@Nonnull HttpReq req) throws HttpNetException {
            return Fs.uncheck(() -> request0(req), HttpNetException::new);
        }

        //@SuppressWarnings("resource")
        private @Nonnull HttpResp request0(@Nonnull HttpReq req) throws Exception {
            String[] headers = req.headers().entrySet().stream().flatMap(entry -> {
                    List<String> values = entry.getValue();
                    String[] kvs = new String[values.size() * 2];
                    int i = 0;
                    for (String value : values) {
                        kvs[i++] = entry.getKey();
                        kvs[i++] = value;
                    }
                    return Arrays.stream(kvs);
                }
            ).toArray(String[]::new);
            HttpReq.Body body = req.body();
            HttpRequest.BodyPublisher bodyPublisher = body == null ?
                HttpRequest.BodyPublishers.noBody() : toBodyPublisher(body);
            HttpRequest request = HttpRequest.newBuilder()
                .uri(req.url().toURI())
                .headers(headers)
                .method(req.method(), bodyPublisher)
                .timeout(req.timeout())
                .build();
            HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
            return new HttpResp() {

                @Override
                public @Nonnull String protocolVersion() {
                    return findString(response.version(), 0);
                }

                @Override
                public @Nonnull String statusCode() {
                    return Integer.toString(response.statusCode());
                }

                @Override
                public @Nonnull String statusText() {
                    return findString(response.statusCode(), 4);
                }

                @Override
                public @Nonnull Map<String, List<String>> headers() {
                    return response.headers().map();
                }

                @Override
                public @Nonnull InputStream body() {
                    return Fs.nonnull(response.body(), IOKit.emptyInputStream());
                }

                @Override
                public @Nullable String contentType() {
                    return response.headers().firstValue("Content-Type").orElse(null);
                }
            };
        }

        private @Nonnull HttpRequest.BodyPublisher toBodyPublisher(@Nonnull HttpReq.Body body) {
            switch (body.type()) {
                case INPUT_STREAM:
                    return HttpRequest.BodyPublishers.ofInputStream(body::toInputStream);
                case TEXT:
                    return HttpRequest.BodyPublishers.ofString(body.toText());
                default:
                    return HttpRequest.BodyPublishers.ofByteArray(body.toByteArray());
            }
        }

        private static final @Nonnull Object @Nonnull [] TABLE = {
            HttpClient.Version.HTTP_1_1, "HTTP/1.1",
            HttpClient.Version.HTTP_2, "HTTP/2",
            200, "OK",
            201, "Created",
            204, "No Content",
            301, "Moved Permanently",
            302, "Found",
            304, "Not Modified",
            400, "Bad Request",
            401, "Unauthorized",
            403, "Forbidden",
            404, "Not Found",
            405, "Method Not Allowed",
            500, "Internal Server Error",
            502, "Bad Gateway",
            503, "Service Unavailable",
            504, "Gateway Timeout"
        };

        private String findString(@Nonnull Object key, int start) {
            for (int i = start; i < TABLE.length; i++) {
                if (Objects.equals(TABLE[i], key)) {
                    return TABLE[i + 1].toString();
                }
            }
            return "Unknown";
        }
    }
}