@Override
  public String getSignedUrl(RestRequest restRequest) throws RestServiceException {
    Map<String, Object> args = restRequest.getArgs();
    String restMethodInSignedUrlStr = RestUtils.getHeader(args, RestUtils.Headers.URL_TYPE, true);
    Account account = RestUtils.getAccountFromArgs(args);
    Container container = RestUtils.getContainerFromArgs(args);
    RestMethod restMethodInSignedUrl;
    try {
      restMethodInSignedUrl = RestMethod.valueOf(restMethodInSignedUrlStr);
    } catch (IllegalArgumentException e) {
      throw new RestServiceException("Unrecognized RestMethod: " + restMethodInSignedUrlStr,
          RestServiceErrorCode.InvalidArgs);
    }
    StringBuilder urlBuilder = new StringBuilder();
    switch (restMethodInSignedUrl) {
      case GET:
        urlBuilder.append(downloadEndpoint);
        break;
      case POST:
        urlBuilder.append(uploadEndpoint);
        break;
      default:
        throw new RestServiceException("Signing request for " + restMethodInSignedUrl + " is not supported",
            RestServiceErrorCode.InvalidArgs);
    }

    long urlTtlSecs = defaultUrlTtlSecs;
    long maxUploadSize = defaultMaxUploadSize;
    boolean chunkUpload = false;
    Map<String, Object> argsForUrl = new HashMap<>();
    for (Map.Entry<String, Object> entry : args.entrySet()) {
      String name = entry.getKey();
      Object value = entry.getValue();
      if (name.regionMatches(true, 0, AMBRY_HEADER_PREFIX, 0, AMBRY_HEADER_PREFIX.length())
          && value instanceof String) {
        switch (name) {
          case RestUtils.Headers.URL_TTL:
            urlTtlSecs = Math.min(maxUrlTtlSecs, RestUtils.getLongHeader(args, RestUtils.Headers.URL_TTL, true));
            break;
          case RestUtils.Headers.MAX_UPLOAD_SIZE:
            maxUploadSize = RestUtils.getLongHeader(args, RestUtils.Headers.MAX_UPLOAD_SIZE, true);
            break;
          case RestUtils.Headers.CHUNK_UPLOAD:
            chunkUpload = RestUtils.getBooleanHeader(args, RestUtils.Headers.CHUNK_UPLOAD, true);
            break;
          default:
            argsForUrl.put(name, value);
            break;
        }
      }
    }
    if (RestMethod.POST.equals(restMethodInSignedUrl)) {
      if (chunkUpload) {
        // Chunks of a stitched blob have a fixed max size to ensure that the router does not do further chunking as
        // this is not supported by the current metadata format.
        maxUploadSize = chunkUploadMaxChunkSize;
        // They also have a non-optional blob TTL to ensure that chunks that were not stitched within a reasonable time
        // span are cleaned up.
        if (RestUtils.getLongHeader(args, RestUtils.Headers.TTL, false) == null) {
          argsForUrl.put(RestUtils.Headers.TTL, chunkUploadInitialChunkTtlSecs);
        }
        argsForUrl.put(RestUtils.Headers.CHUNK_UPLOAD, true);
        argsForUrl.put(RestUtils.Headers.SESSION, UUID.randomUUID().toString());
        BlobId reservedMetadataBlobId = ClusterMapUtils.reserveMetadataBlobId(
            ClusterMapUtils.getPartitionClass(account, container, clusterMapConfig.clusterMapDefaultPartitionClass),
            null, ReservedMetadataIdMetrics.getReservedMetadataIdMetrics(clusterMap.getMetricRegistry()), clusterMap,
            account.getId(), container.getId(), container.isEncrypted(), routerConfig);
        if (reservedMetadataBlobId == null) {
          ReservedMetadataIdMetrics.getReservedMetadataIdMetrics(
              clusterMap.getMetricRegistry()).reserveMetadataIdFailedForPostSignedUrlCount.inc();
        }
        if (isReservedMetadataEnabled) {
          if (reservedMetadataBlobId == null) {
            throw new RestServiceException("Reserving metadata id failed", RestServiceErrorCode.InternalServerError);
          }
          argsForUrl.put(RestUtils.Headers.RESERVED_METADATA_ID, reservedMetadataBlobId);
        }
      }
      argsForUrl.put(RestUtils.Headers.MAX_UPLOAD_SIZE, maxUploadSize);
    }
    argsForUrl.put(LINK_EXPIRY_TIME, time.seconds() + urlTtlSecs);

    String nextSeparator = QUERY_STRING_START;
    for (Map.Entry<String, Object> entry : argsForUrl.entrySet()) {
      String value = urlEncode(entry.getValue());
      urlBuilder.append(nextSeparator).append(entry.getKey()).append(PARAMETER_ASSIGN).append(value);
      nextSeparator = PARAMETER_SEPARATOR;
    }
    return urlBuilder.toString();
  }
Constructor
@param uploadEndpoint the endpoint in all signed POST URLs.
@param downloadEndpoint the endpoint in all signed GET URLs.
@param defaultUrlTtlSecs the default ttl of signed URLs if the request does not customize it.
@param defaultMaxUploadSize the default max size of upload from signed POST URLs if the request does not customize
                            it.
@param maxUrlTtlSecs the maximum ttl of signed URLs. If the request specifies a higher TTL, it will be lowered
@param chunkUploadInitialChunkTtlSecs the preconfigured blob TTL for chunks of a stitched upload. These chunks
                                      will be made permanent once the blob is stitched together.
@param chunkUploadMaxChunkSize the preconfigured max size for chunks of a stitched upload.
@param time the {@link Time} instance to use.
@param clusterMap the {@link ClusterMap} object.
@param clusterMapConfig the {@link ClusterMapConfig} object.
@param routerConfig the {@link RouterConfig} object.
ambry-frontend/src/main/java/com/github/ambry/frontend/AmbryUrlSigningService.java