String createCanonicalRequest(HttpUriRequest request) {
    StringBuilder result = new StringBuilder();
    result.append(request.getMethod()).append('\n');
    String path = request.getURI().getPath();
    if (path.isEmpty()) {
      path = "/";
    }
    result.append(path).append('\n');
    String queryString = request.getURI().getQuery();
    queryString = queryString != null ? queryString : "";
    addCanonicalQueryString(queryString, result).append('\n');
    addCanonicalHeaders(request, result).append('\n');

    HttpEntity entity = null;
    try {
      if (request instanceof HttpEntityEnclosingRequestBase) {
        entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
      } else {
        entity = new StringEntity("");
      }
      InputStream content = entity.getContent();
      addHashedPayload(content, result);
    } catch (IOException e) {
      throw new RuntimeException("Could not create hash for entity " + entity, e);
    }
    return result.toString();
  }
 StringBuilder addSignedHeaders(
     SortedMap<String, String> sortedFormattedHeaders, StringBuilder builder) {
   int startingLength = builder.length();
   for (String headerName : sortedFormattedHeaders.keySet()) {
     if (builder.length() > startingLength) {
       builder.append(';');
     }
     builder.append(headerName);
   }
   return builder;
 }
 StringBuilder addCanonicalHeaders(
     SortedMap<String, String> sortedFormattedHeaders, StringBuilder builder) {
   for (Map.Entry<String, String> entry : sortedFormattedHeaders.entrySet()) {
     builder.append(entry.getKey()).append(':').append(entry.getValue()).append('\n');
   }
   return builder;
 }
 StringBuilder addCanonicalQueryString(String queryString, StringBuilder builder) {
   int startingLength = builder.length();
   SortedMap<String, String> encodedParams = new TreeMap<>();
   for (String queryParam : queryString.split("&")) {
     if (!queryParam.isEmpty()) {
       String[] parts = queryParam.split("=", 2);
       encodedParams.put(encodeQueryStringValue(parts[0]), encodeQueryStringValue(parts[1]));
     }
   }
   for (Map.Entry<String, String> entry : encodedParams.entrySet()) {
     if (builder.length() > startingLength) {
       builder.append('&');
     }
     builder.append(entry.getKey()).append('=').append(entry.getValue());
   }
   return builder;
 }
 StringBuilder addHashedPayload(InputStream payload, StringBuilder builder) throws IOException {
   return builder.append(toHexString(sha256(payload)));
 }