docs: private Request createRequest(Method method, String bucketName, String objectName,
                                  String region, Multimap<String,String> headerMap,
                                  Multimap<String,String> queryParamMap, final String contentType,
                                  Object body, int length)
      throws InvalidBucketNameException, NoSuchAlgorithmException, InvalidKeyException, InsufficientDataException,
             IOException, InternalException {
      if (bucketName == null && objectName != null) {
        throw new InvalidBucketNameException(NULL_STRING, "null bucket name for object '" + objectName + "'");
      }
  
      HttpUrl.Builder urlBuilder = this.baseUrl.newBuilder();
      if (bucketName != null) {
        checkBucketName(bucketName);
  
        String host = this.baseUrl.host();
        if (host.equals(S3_AMAZONAWS_COM)) {
          // special case: handle s3.amazonaws.com separately
          if (region != null) {
            host = AwsS3Endpoints.INSTANCE.endpoint(region);
          }
  
          boolean usePathStyle = false;
          if (method == Method.PUT && objectName == null && queryParamMap == null) {
            // use path style for make bucket to workaround "AuthorizationHeaderMalformed" error from s3.amazonaws.com
            usePathStyle = true;
          } else if (queryParamMap != null && queryParamMap.containsKey("location")) {
            // use path style for location query
            usePathStyle = true;
          } else if (bucketName.contains(".") && this.baseUrl.isHttps()) {
            // use path style where '.' in bucketName causes SSL certificate validation error
            usePathStyle = true;
          }
  
          if (usePathStyle) {
            urlBuilder.host(host);
            urlBuilder.addEncodedPathSegment(S3Escaper.encode(bucketName));
          } else {
            urlBuilder.host(bucketName + "." + host);
          }
        } else {
          urlBuilder.addEncodedPathSegment(S3Escaper.encode(bucketName));
        }
      }
  
      if (objectName != null) {
        // Limitation: OkHttp does not allow to add '.' and '..' as path segment.
        for (String pathSegment : objectName.split("/")) {
          urlBuilder.addEncodedPathSegment(S3Escaper.encode(pathSegment));
        }
        if (objectName.endsWith("/")) {
          // Fix issue #648: preserve trailing '/' to work with proxies like nginx.
          urlBuilder.addPathSegment("");
        }
      }
  
      if (queryParamMap != null) {
        for (Map.Entry<String,String> entry : queryParamMap.entries()) {
          urlBuilder.addEncodedQueryParameter(S3Escaper.encode(entry.getKey()), S3Escaper.encode(entry.getValue()));
        }
      }
  
      HttpUrl url = urlBuilder.build();
  
      Request.Builder requestBuilder = new Request.Builder();
      requestBuilder.url(url);
      if (headerMap != null) {
        for (Map.Entry<String,String> entry : headerMap.entries()) {
          requestBuilder.header(entry.getKey(), entry.getValue());
        }
      }
  
      String sha256Hash = null;
      String md5Hash = null;
      boolean chunkedUpload = false;
      if (this.accessKey != null && this.secretKey != null) {
        // Handle putobject specially to use chunked upload.
        if (method == Method.PUT && objectName != null && body != null && body instanceof InputStream && length > 0) {
          sha256Hash = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
  
          String contentEncoding = "aws-chunked";
          if (headerMap != null) {
            contentEncoding = Stream.concat(Stream.of("aws-chunked"),
                headerMap.get("Content-Encoding").stream()).distinct().filter(encoding -> !encoding.isEmpty())
                .collect(Collectors.joining(","));
          }
          requestBuilder.header("Content-Encoding", contentEncoding);
  
          requestBuilder.header("x-amz-decoded-content-length", Integer.toString(length));
          chunkedUpload = true;
        } else if (url.isHttps()) {
          // Fix issue #415: No need to compute sha256 if endpoint scheme is HTTPS.
          sha256Hash = "UNSIGNED-PAYLOAD";
          if (body != null) {
            md5Hash = Digest.md5Hash(body, length);
          }
        } else {
          Object data = body;
          int len = length;
          if (data == null) {
            data = new byte[0];
            len = 0;
          }
  
          if (method == Method.POST && queryParamMap != null && queryParamMap.containsKey("delete")) {
            // Fix issue #579: Treat 'Delete Multiple Objects' specially which requires MD5 hash.
            String[] hashes = Digest.sha256Md5Hashes(data, len);
            sha256Hash = hashes[0];
            md5Hash = hashes[1];
          } else if (method == Method.PUT && queryParamMap != null && queryParamMap.containsKey("lifecycle")) {
            String[] hashes = Digest.sha256Md5Hashes(data, len);
            sha256Hash = hashes[0];
            md5Hash = hashes[1];
          } else {
            // Fix issue #567: Compute SHA256 hash only.
            sha256Hash = Digest.sha256Hash(data, len);
          }
        }
      } else {
        // Fix issue #567: Compute MD5 hash only for anonymous access.
        if (body != null) {
          md5Hash = Digest.md5Hash(body, length);
        }
      }
  
      if (md5Hash != null) {
        requestBuilder.header("Content-MD5", md5Hash);
      }
      if (this.shouldOmitPortInHostHeader(url)) {
        requestBuilder.header("Host", url.host());
      } else {
        requestBuilder.header("Host", url.host() + ":" + url.port());
      }
      requestBuilder.header("User-Agent", this.userAgent);
      if (sha256Hash != null) {
        requestBuilder.header("x-amz-content-sha256", sha256Hash);
      }
      DateTime date = new DateTime();
      requestBuilder.header("x-amz-date", date.toString(DateFormat.AMZ_DATE_FORMAT));
  
      if (chunkedUpload) {
        // Add empty request body for calculating seed signature.
        // The actual request body is properly set below.
        requestBuilder.method(method.toString(), RequestBody.create(null, ""));
        Request request = requestBuilder.build();
        String seedSignature = Signer.getChunkSeedSignature(request, region, secretKey);
        requestBuilder = request.newBuilder();
  
        ChunkedInputStream cis = new ChunkedInputStream((InputStream) body, length, date, region, this.secretKey,
                                                        seedSignature);
        body = cis;
        length = cis.length();
      }
  
      RequestBody requestBody = null;
      if (body != null) {
        requestBody = new HttpRequestBody(contentType, body, length);
      }
  
      requestBuilder.method(method.toString(), requestBody);
      return requestBuilder.build();
    }