All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aoju.bus.http.Builder Maven / Gradle / Ivy

/*********************************************************************************
 *                                                                               *
 * The MIT License (MIT)                                                         *
 *                                                                               *
 * Copyright (c) 2015-2022 aoju.org and other contributors.                      *
 *                                                                               *
 * Permission is hereby granted, free of charge, to any person obtaining a copy  *
 * of this software and associated documentation files (the "Software"), to deal *
 * in the Software without restriction, including without limitation the rights  *
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell     *
 * copies of the Software, and to permit persons to whom the Software is         *
 * furnished to do so, subject to the following conditions:                      *
 *                                                                               *
 * The above copyright notice and this permission notice shall be included in    *
 * all copies or substantial portions of the Software.                           *
 *                                                                               *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR    *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,      *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE   *
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER        *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN     *
 * THE SOFTWARE.                                                                 *
 *                                                                               *
 ********************************************************************************/
package org.aoju.bus.http;

import org.aoju.bus.core.io.Blending;
import org.aoju.bus.core.io.ByteString;
import org.aoju.bus.core.io.buffer.Buffer;
import org.aoju.bus.core.io.source.BufferSource;
import org.aoju.bus.core.io.source.Source;
import org.aoju.bus.core.lang.Normal;
import org.aoju.bus.core.lang.RegEx;
import org.aoju.bus.core.lang.Symbol;
import org.aoju.bus.http.bodys.ResponseBody;
import org.aoju.bus.http.metric.Internal;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.IDN;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * 实用方法工具
 *
 * @author Kimi Liu
 * @since Java 17+
 */
public class Builder {

    /**
     * 最后一个四位数的年份:"Fri, 31 Dec 9999 23:59:59 GMT"
     */
    public static final long MAX_DATE = 253402300799999L;

    public static final Headers EMPTY_HEADERS = Headers.of();
    public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, Normal.EMPTY_BYTE_ARRAY);
    /**
     * GMT and UTC are equivalent for our purposes
     */
    public static final TimeZone UTC = TimeZone.getTimeZone("GMT");
    public static final Comparator NATURAL_ORDER = String::compareTo;
    public static final ByteString QUOTED_STRING_DELIMITERS = ByteString.encodeUtf8("\"\\");
    public static final ByteString TOKEN_DELIMITERS = ByteString.encodeUtf8("\t ,=");
    /**
     * Byte order marks.
     */
    private static final Blending UNICODE_BOMS = Blending.of(
            ByteString.decodeHex("efbbbf"),   // UTF-8
            ByteString.decodeHex("feff"),     // UTF-16BE
            ByteString.decodeHex("fffe"),     // UTF-16LE
            ByteString.decodeHex("0000ffff"), // UTF-32BE
            ByteString.decodeHex("ffff0000")  // UTF-32LE
    );

    private static final Method addSuppressedExceptionMethod;

    /**
     * 如果我们未能以非标准格式解析日期,请依次尝试这些格式.
     */
    public static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[]{
            "EEE, dd MMM yyyy HH:mm:ss zzz",
            "EEEE, dd-MMM-yy HH:mm:ss zzz",
            "EEE MMM d HH:mm:ss yyyy",
            "EEE, dd-MMM-yyyy HH:mm:ss z",
            "EEE, dd-MMM-yyyy HH-mm-ss z",
            "EEE, dd MMM yy HH:mm:ss z",
            "EEE dd-MMM-yyyy HH:mm:ss z",
            "EEE dd MMM yyyy HH:mm:ss z",
            "EEE dd-MMM-yyyy HH-mm-ss z",
            "EEE dd-MMM-yy HH:mm:ss z",
            "EEE dd MMM yy HH:mm:ss z",
            "EEE,dd-MMM-yy HH:mm:ss z",
            "EEE,dd-MMM-yyyy HH:mm:ss z",
            "EEE, dd-MM-yyyy HH:mm:ss z",
            "EEE MMM d yyyy HH:mm:ss z",
    };

    public static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS =
            new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length];

    /**
     * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
     * cookies are on the fast path.
     */
    private static final ThreadLocal STANDARD_DATE_FORMAT =
            ThreadLocal.withInitial(() -> {
                // Date format specified by RFC 7231 section 7.1.1.1.
                DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
                rfc1123.setLenient(false);
                rfc1123.setTimeZone(UTC);
                return rfc1123;
            });

    public static final String CONNECT = "CONNECT";
    public static final String CONNECTED = "CONNECTED";
    public static final String SEND = "SEND";
    public static final String MESSAGE = "MESSAGE";
    public static final String SUBSCRIBE = "SUBSCRIBE";
    public static final String UNSUBSCRIBE = "UNSUBSCRIBE";
    public static final String ACK = "ACK";
    public static final String UNKNOWN = "UNKNOWN";
    public static final String ERROR = "ERROR";

    static {
        Method m;
        try {
            m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
        } catch (Exception e) {
            m = null;
        }
        addSuppressedExceptionMethod = m;
    }

    public Builder() {

    }

    public static void addSuppressedIfPossible(Throwable e, Throwable suppressed) {
        if (addSuppressedExceptionMethod != null) {
            try {
                addSuppressedExceptionMethod.invoke(e, suppressed);
            } catch (InvocationTargetException | IllegalAccessException ignored) {
            }
        }
    }

    public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
            throw new ArrayIndexOutOfBoundsException();
        }
    }

    /**
     * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading a
     * complete source is helpful, such as when doing so completes a cache body or frees a socket
     * connection for reuse.
     */
    public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
        try {
            return skipAll(source, timeout, timeUnit);
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
     * extend the deadline if one exists already.
     */
    public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
        long now = System.nanoTime();
        long originalDuration = source.timeout().hasDeadline()
                ? source.timeout().deadlineNanoTime() - now
                : Long.MAX_VALUE;
        source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
        try {
            Buffer skipBuffer = new Buffer();
            while (source.read(skipBuffer, 8192) != -1) {
                skipBuffer.clear();
            }
            return true;
        } catch (InterruptedIOException e) {
            return false;
        } finally {
            if (originalDuration == Long.MAX_VALUE) {
                source.timeout().clearDeadline();
            } else {
                source.timeout().deadlineNanoTime(now + originalDuration);
            }
        }
    }

    /**
     * Returns an immutable copy of {@code list}.
     */
    public static  List immutableList(List list) {
        return Collections.unmodifiableList(new ArrayList<>(list));
    }

    /**
     * Returns an immutable copy of {@code map}.
     */
    public static  Map immutableMap(Map map) {
        return map.isEmpty()
                ? Collections.emptyMap()
                : Collections.unmodifiableMap(new LinkedHashMap<>(map));
    }

    /**
     * Returns an immutable list containing {@code elements}.
     */
    public static  List immutableList(T... elements) {
        return Collections.unmodifiableList(Arrays.asList(elements.clone()));
    }

    public static ThreadFactory threadFactory(String name, boolean daemon) {
        return runnable -> {
            Thread result = new Thread(runnable, name);
            result.setDaemon(daemon);
            return result;
        };
    }

    /**
     * Returns an array containing only elements found in {@code first} and also in {@code
     * second}. The returned elements are in the same order as in {@code first}.
     */
    public static String[] intersect(
            Comparator comparator, String[] first, String[] second) {
        List result = new ArrayList<>();
        for (String a : first) {
            for (String b : second) {
                if (comparator.compare(a, b) == 0) {
                    result.add(a);
                    break;
                }
            }
        }
        return result.toArray(new String[result.size()]);
    }

    /**
     * Returns true if there is an element in {@code first} that is also in {@code second}. This
     * method terminates if any intersection is found. The sizes of both arguments are assumed to be
     * so small, and the likelihood of an intersection so great, that it is not worth the CPU cost of
     * sorting or the memory cost of hashing.
     */
    public static boolean nonEmptyIntersection(
            Comparator comparator, String[] first, String[] second) {
        if (first == null || second == null || first.length == 0 || second.length == 0) {
            return false;
        }
        for (String a : first) {
            for (String b : second) {
                if (comparator.compare(a, b) == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    public static String hostHeader(UnoUrl url, boolean includeDefaultPort) {
        String host = url.host().contains(":")
                ? "[" + url.host() + "]"
                : url.host();
        return includeDefaultPort || url.port() != UnoUrl.defaultPort(url.scheme())
                ? host + ":" + url.port()
                : host;
    }

    /**
     * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
     * https://code.google.com/p/android/issues/detail?id=54072
     */
    public static boolean isAndroidGetsocknameError(AssertionError e) {
        return e.getCause() != null && e.getMessage() != null
                && e.getMessage().contains("getsockname failed");
    }

    public static int indexOf(Comparator comparator, String[] array, String value) {
        for (int i = 0, size = array.length; i < size; i++) {
            if (comparator.compare(array[i], value) == 0) return i;
        }
        return -1;
    }

    public static String[] concat(String[] array, String value) {
        String[] result = new String[array.length + 1];
        System.arraycopy(array, 0, result, 0, array.length);
        result[result.length - 1] = value;
        return result;
    }

    /**
     * Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code
     * limit}.
     */
    public static int skipLeadingAsciiWhitespace(String input, int pos, int limit) {
        for (int i = pos; i < limit; i++) {
            switch (input.charAt(i)) {
                case Symbol.C_HT:
                case Symbol.C_LF:
                case '\f':
                case Symbol.C_CR:
                case Symbol.C_SPACE:
                    continue;
                default:
                    return i;
            }
        }
        return limit;
    }

    /**
     * Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at
     * {@code pos}.
     */
    public static int skipTrailingAsciiWhitespace(String input, int pos, int limit) {
        for (int i = limit - 1; i >= pos; i--) {
            switch (input.charAt(i)) {
                case Symbol.C_HT:
                case Symbol.C_LF:
                case '\f':
                case Symbol.C_CR:
                case Symbol.C_SPACE:
                    continue;
                default:
                    return i + 1;
            }
        }
        return pos;
    }

    /**
     * Equivalent to {@code string.substring(pos, limit).trim()}.
     */
    public static String trimSubstring(String string, int pos, int limit) {
        int start = skipLeadingAsciiWhitespace(string, pos, limit);
        int end = skipTrailingAsciiWhitespace(string, start, limit);
        return string.substring(start, end);
    }

    /**
     * Returns the index of the first character in {@code input} that contains a character in {@code
     * delimiters}. Returns limit if there is no such character.
     */
    public static int delimiterOffset(String input, int pos, int limit, String delimiters) {
        for (int i = pos; i < limit; i++) {
            if (delimiters.indexOf(input.charAt(i)) != -1) return i;
        }
        return limit;
    }

    /**
     * Returns the index of the first character in {@code input} that is {@code delimiter}. Returns
     * limit if there is no such character.
     */
    public static int delimiterOffset(String input, int pos, int limit, char delimiter) {
        for (int i = pos; i < limit; i++) {
            if (input.charAt(i) == delimiter) return i;
        }
        return limit;
    }

    /**
     * If {@code host} is an IP address, this returns the IP address in canonical form.
     * 

* Otherwise this performs IDN ToASCII encoding and canonicalize the result to lowercase. For * example this converts {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to * {@code www.google.com}. {@code null} will be returned if the host cannot be ToASCII encoded or * if the result contains unsupported ASCII characters. */ public static String canonicalizeHost(String host) { // If the input contains a :, it’s an IPv6 address. if (host.contains(":")) { InetAddress inetAddress = host.startsWith("[") && host.endsWith("]") ? decodeIpv6(host, 1, host.length() - 1) : decodeIpv6(host, 0, host.length()); if (inetAddress == null) return null; byte[] address = inetAddress.getAddress(); if (address.length == 16) return inet6AddressToAscii(address); if (address.length == 4) return inetAddress.getHostAddress(); // An IPv4-mapped IPv6 address. throw new AssertionError("Invalid IPv6 address: '" + host + "'"); } try { String result = IDN.toASCII(host).toLowerCase(Locale.US); if (result.isEmpty()) return null; if (containsInvalidHostnameAsciiCodes(result)) { return null; } return result; } catch (IllegalArgumentException e) { return null; } } private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) { for (int i = 0; i < hostnameAscii.length(); i++) { char c = hostnameAscii.charAt(i); if (c <= '\u001f' || c >= '\u007f') { return true; } if (" #%/:?@[\\]".indexOf(c) != -1) { return true; } } return false; } /** * Returns the index of the first character in {@code input} that is either a control character * (like {@code \u0000 or \n}) or a non-ASCII character. Returns -1 if {@code input} has no such * characters. */ public static int indexOfControlOrNonAscii(String input) { for (int i = 0, length = input.length(); i < length; i++) { char c = input.charAt(i); if (c <= '\u001f' || c >= '\u007f') { return i; } } return -1; } /** * Returns true if {@code host} is not a host name and might be an IP address. */ public static boolean verifyAsIpAddress(String host) { return RegEx.IP_ADDRESS.matcher(host).matches(); } public static Charset bomAwareCharset(BufferSource source, Charset charset) throws IOException { switch (source.select(UNICODE_BOMS)) { case 0: return org.aoju.bus.core.lang.Charset.UTF_8; case 1: return org.aoju.bus.core.lang.Charset.UTF_16_BE; case 2: return org.aoju.bus.core.lang.Charset.UTF_16_LE; case 3: return org.aoju.bus.core.lang.Charset.UTF_32_BE; case 4: return org.aoju.bus.core.lang.Charset.UTF_32_LE; case -1: return charset; default: throw new AssertionError(); } } public static int checkDuration(String name, long duration, TimeUnit unit) { if (duration < 0) throw new IllegalArgumentException(name + " < 0"); if (null == unit) throw new NullPointerException("unit == null"); long millis = unit.toMillis(duration); if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException(name + " too large."); if (millis == 0 && duration > 0) throw new IllegalArgumentException(name + " too small."); return (int) millis; } public static int decodeHexDigit(char c) { if (c >= Symbol.C_ZERO && c <= Symbol.C_NINE) return c - Symbol.C_ZERO; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } /** * Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */ private static InetAddress decodeIpv6(String input, int pos, int limit) { byte[] address = new byte[Normal._16]; int b = 0; int compress = -1; int groupOffset = -1; for (int i = pos; i < limit; ) { if (b == address.length) return null; if (i + 2 <= limit && input.regionMatches(i, Symbol.COLON + Symbol.COLON, 0, 2)) { if (compress != -1) return null; i += 2; b += 2; compress = b; if (i == limit) break; } else if (b != 0) { if (input.regionMatches(i, Symbol.COLON, 0, 1)) { i++; } else if (input.regionMatches(i, Symbol.DOT, 0, 1)) { // If we see a '.', rewind to the beginning of the previous group and parse as IPv4. if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null; b += 2; break; } else { return null; } } int value = 0; groupOffset = i; for (; i < limit; i++) { char c = input.charAt(i); int hexDigit = decodeHexDigit(c); if (hexDigit == -1) break; value = (value << 4) + hexDigit; } int groupLength = i - groupOffset; if (groupLength == 0 || groupLength > 4) return null; address[b++] = (byte) ((value >>> 8) & 0xff); address[b++] = (byte) (value & 0xff); } if (b != address.length) { if (compress == -1) return null; System.arraycopy(address, compress, address, address.length - (b - compress), b - compress); Arrays.fill(address, compress, compress + (address.length - b), (byte) 0); } try { return InetAddress.getByAddress(address); } catch (UnknownHostException e) { throw new AssertionError(); } } /** * Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */ private static boolean decodeIpv4Suffix( String input, int pos, int limit, byte[] address, int addressOffset) { int b = addressOffset; for (int i = pos; i < limit; ) { if (b == address.length) return false; // Read a delimiter. if (b != addressOffset) { if (input.charAt(i) != Symbol.C_DOT) return false; i++; } int value = 0; int groupOffset = i; for (; i < limit; i++) { char c = input.charAt(i); if (c < Symbol.C_ZERO || c > Symbol.C_NINE) break; if (value == 0 && groupOffset != i) return false; value = (value * 10) + c - Symbol.C_ZERO; if (value > 255) return false; } int groupLength = i - groupOffset; if (groupLength == 0) return false; address[b++] = (byte) value; } if (b != addressOffset + 4) return false; return true; } /** * Encodes an IPv6 address in canonical form according to RFC 5952. */ private static String inet6AddressToAscii(byte[] address) { int longestRunOffset = -1; int longestRunLength = 0; for (int i = 0; i < address.length; i += 2) { int currentRunOffset = i; while (i < Normal._16 && address[i] == 0 && address[i + 1] == 0) { i += 2; } int currentRunLength = i - currentRunOffset; if (currentRunLength > longestRunLength && currentRunLength >= 4) { longestRunOffset = currentRunOffset; longestRunLength = currentRunLength; } } Buffer result = new Buffer(); for (int i = 0; i < address.length; ) { if (i == longestRunOffset) { result.writeByte(Symbol.C_COLON); i += longestRunLength; if (i == Normal._16) result.writeByte(Symbol.C_COLON); } else { if (i > 0) result.writeByte(Symbol.C_COLON); int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff; result.writeHexadecimalUnsignedLong(group); i += 2; } } return result.readUtf8(); } public static Headers toHeaders(List headerBlock) { Headers.Builder builder = new Headers.Builder(); for (Headers.Header header : headerBlock) { Internal.instance.addLenient(builder, header.name.utf8(), header.value.utf8()); } return builder.build(); } public static List toHeaderBlock(Headers headers) { List result = new ArrayList<>(); for (int i = 0; i < headers.size(); i++) { result.add(new Headers.Header(headers.name(i), headers.value(i))); } return result; } /** * Returns true if an HTTP request for {@code a} and {@code b} can reuse a connection. */ public static boolean sameConnection(UnoUrl a, UnoUrl b) { return a.host().equals(b.host()) && a.port() == b.port() && a.scheme().equals(b.scheme()); } /** * Returns the date for {@code value}. Returns null if the value couldn't be parsed. */ public static Date parse(String value) { if (value.length() == 0) { return null; } ParsePosition position = new ParsePosition(0); Date result = STANDARD_DATE_FORMAT.get().parse(value, position); if (position.getIndex() == value.length()) { // STANDARD_DATE_FORMAT must match exactly; all text must be consumed, e.g. no ignored // non-standard trailing "+01:00". Those cases are covered below. return result; } synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) { for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) { DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i]; if (format == null) { format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US); // Set the timezone to use when interpreting formats that don't have a timezone. GMT is // specified by RFC 7231. format.setTimeZone(UTC); BROWSER_COMPATIBLE_DATE_FORMATS[i] = format; } position.setIndex(0); result = format.parse(value, position); if (position.getIndex() != 0) { // Something was parsed. It's possible the entire string was not consumed but we ignore // that. If any of the BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS ended in "'GMT'" we'd have // to also check that position.getIndex() == value.length() otherwise parsing might have // terminated early, ignoring things like "+01:00". Leaving this as != 0 means that any // trailing junk is ignored. return result; } } } return null; } /** * Returns the string for {@code value}. */ public static String format(Date value) { return STANDARD_DATE_FORMAT.get().format(value); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy