HttpTransport.java

// SPDX-License-Identifier: Apache-2.0
package com.shengzhiai.yugu.stcompat.internal;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

/** HTTP abstraction so the retry and error logic can be tested without sockets. */
public interface HttpTransport {

    /** One HTTP attempt. */
    final class Request {
        public final String method;
        public final String url;
        public final Map<String, String> headers;
        public final Multipart body;
        public final int connectTimeoutMs;
        public final int readTimeoutMs;

        public Request(String method, String url, Map<String, String> headers, Multipart body,
                       int connectTimeoutMs, int readTimeoutMs) {
            this.method = method;
            this.url = url;
            this.headers = Collections.unmodifiableMap(new LinkedHashMap<String, String>(headers));
            this.body = body;
            this.connectTimeoutMs = connectTimeoutMs;
            this.readTimeoutMs = readTimeoutMs;
        }
    }

    /**
     * Phase times of one attempt on the {@link System#nanoTime()} clock, 0 when unknown, and sizes,
     * -1 when unknown (SDK 2.1.1 timing log).
     */
    final class Metrics {
        public long startNanos;
        /** The request body was handed to the socket. */
        public long uploadDoneNanos;
        /** Status line and headers of the answer were read. */
        public long headersNanos;
        /** The body was read and decompressed. */
        public long bodyDoneNanos;
        public long uploadBytes = -1;
        /** Body bytes as received (compressed when {@link #contentEncoding} is set). */
        public long wireBytes = -1;
        /** Body bytes after decompression. */
        public long bodyBytes = -1;
        /** Content-Encoding of the answer in lower case, null for none. */
        public String contentEncoding;
    }

    /** Response of one attempt. Header names are lower case. */
    final class Response {
        public final int status;
        public final Map<String, String> headers;
        public final String body;
        /** Phase times and sizes of the attempt, null when the transport does not measure them. */
        public final Metrics metrics;

        public Response(int status, Map<String, String> headers, String body) {
            this(status, headers, body, null);
        }

        public Response(int status, Map<String, String> headers, String body, Metrics metrics) {
            this.metrics = metrics;
            this.status = status;
            Map<String, String> h = new LinkedHashMap<String, String>();
            if (headers != null) {
                for (Map.Entry<String, String> e : headers.entrySet()) {
                    if (e.getKey() != null) {
                        h.put(e.getKey().toLowerCase(Locale.ROOT), e.getValue());
                    }
                }
            }
            this.headers = Collections.unmodifiableMap(h);
            this.body = body == null ? "" : body;
        }

        public String header(String name) {
            return headers.get(name.toLowerCase(Locale.ROOT));
        }
    }

    /**
     * Executes one attempt. Implementations register an abort handle with {@code token} and throw
     * {@link IOException} on transport failure (SocketTimeoutException for timeouts).
     */
    Response execute(Request request, CancelToken token) throws IOException;
}