Como he buscado durante mucho tiempo para encontrar el error, quiero compartirlo aquí, en caso de que alguien se encuentre con el mismo problema: he implementado el siguiente fragmento de código para cargar datos en S3:
public class S3FileUploadHandler { private static final String USER_META_DATA_KEY_MODIFIED = "last-modified"; private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); private final AmazonS3Client s3; private final TransferManager tm; private final String bucketName = "myTestBucket"; ... public void run() { File file = ... // retrieve file to upload FileInputStream fis = null; try { fis = new FileInputStream(file); final String key = ... // some path on S3 final ObjectMetadata objectMetadata = buildObjectMetadata(file); final PutObjectRequest req = new PutObjectRequest(bucketName, key, fis, objectMetadata); final Upload upload = tm.upload(req); upload.addProgressListener(new MyProgressListener(upload)); upload.waitForCompletion(); } catch (final AmazonServiceExceptionase) { logger.debug("Couldn't put file {}. AmazonServiceException {}", file, ase); logger.error("Couldn't put file {}. AmazonServiceException Error Message: {}", file, ase.getMessage()); logger.error("HTTP Status Code: " + ase.getStatusCode()); logger.error("AWS Error Code: " + ase.getErrorCode()); logger.error("Error Type: " + ase.getErrorType()); logger.error("Request ID: " + ase.getRequestId()); } catch (final Exception e) { ... } finally { if (fis != null) { fis.close(); } } } private ObjectMetadata buildObjectMetadata(final File file) { final ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(file.length()); final Date lastModifiedDate = new Date(file.lastModified()); final String dateStr = sdf.format(lastModifiedDate); metadata.addUserMetadata(USER_META_DATA_KEY_MODIFIED, dateStr); return metadata; } }
El código cortado carga un archivo en AWS S3 y, para conservar la última marca de tiempo modificada del archivo, configuro esta información como metadatos de usuario.
A veces el código anterior funciona, a veces la carga se interrumpe con la siguiente excepción:
Couldn't put file C:\Temp\xyz.txt. AmazonServiceException Error Message: The request signature we calculated does not match the signature you provided. Check your key and signing method. (Service: Amazon S3; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: B8BC3251B9C1A3F5) HTTP Status Code: 403 AWS Error Code: SignatureDoesNotMatch Error Type: Client Request ID: B8BC3251B9C1A3F5
El error en el código anterior es que SimpleDateFormat contiene potencialmente caracteres que no son ASCII.
Por ejemplo, la fecha de la última modificación en una localidad alemana puede ser
Di Mär 21 10:29:00 MEZ 2017
La documentación de AWS establece que
Los metadatos definidos por el usuario son un conjunto de pares clave-valor. Amazon S3 almacena claves de metadatos definidas por el usuario en minúsculas. Cada par clave-valor debe cumplir con US-ASCII cuando se usa REST y UTF-8 cuando se usa SOAP o cargas basadas en navegador a través de POST.
AWS realmente podría funcionar en un mejor texto de excepción aquí, pero con la información anterior, la solución es simple:
private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
... lo que da como resultado cadenas de metadatos (siempre) aceptadas
Tue Mar 21 10:29:00 CET 2017