Dicas e truques para programar em Java com o SDK do Amazon Web Service (AWS)

Esse artigo descreve algumas dicas e truques úteis para o desenvolvimento de aplicações usando o SDK do AWS com o Java. Usando o SDK, você pode criar soluções para o Amazon Simple Storage Service (Amazon S3), Amazon Elastic Comput Cloud (Amazon EC2), Amazon SimpleDB, e outros.

O SDK do AWS para Java inclui a biblioteca Java do AWS, exemplos de código, e documentação de referência, mas onde encontrar informação sobre como usar a bibliotecas Java? Esse artigo contém dicas importantes para desenvolver software com o SDK do AWS para Java. As áreas primárias que falaremos aqui são:

  • Configuração do cliente
  • Controle de Logging
  • Manipulação de exceção

Configuração do cliente

O SDK do AWS para Java permite que você altere as configurações padrão do cliente, o que é útil quando você quer:

  • Mudar o ponto final da região
  • Conectar-se a Internet através de um proxy
  • Manipular as configurações de transporte do HTTP, como tempo de conexão e tentativas de requisição
  • Especificar o tamanho do buffer do socket TCP

Seleção da Região do AWS

Regiões permitem que você acesse serviços do AWS que fisicamente residem em uma região geográfica especifica. Isso pode ser útil tanto para redundância e manter seus dados e aplicações sendo executadas perto de onde você e seus usuários acessarão eles.

Cada cliente pode ser apontado para um ponto específico através do método setEndpoint(String endpointUrl).

Por exemplo, para apontar o cliente Java AWS EC2 para a região EU West, use o seguinte código:

AmazonEC2 ec2 = new  AmazonEC2(myCredentials);
ec2.setEndpoint("https://eu-west-1.ec2.amazonaws.com");

Certifique-se de que essas regiões sejam logicamente isoladas uma da outra, de forma que, por exemplo, você não possa acessar recursos do US East quando estiver se comunicando com o ponto do EU West.

Veja Available Region Endpoints for the AWS SDKs para uma lista de pontos válidos.

Configuração do Proxy

Quando estiver construindo um objeto cliente, pode passar um objeto opcional com.amazonaws.ClientConfiguration para personalizar a configuração do cliente.

Se você se conecta a Internet através de um servidor proxy, precisará configurar as configurações do servidor proxy (hospedeiro do proxy, porta e nome de usuário/senha) através do objeto ClientConfiguration.

Configuração do transporte HTTP

Muitas opções de transporte HTTP podem ser configuradas através do objeto com. amazonaws.ClientConfiguration. Valores padrão serão suficiente para a maioria dos usuários, mas alguns usuário irão querer mais controle:

  • tempo do Socket
  • tempo da conexão
  • Máximo de tentativas para erros recuperáveis
  • Máximo de conexões HTTP abertas

Dicas para ajustar o tamanho do buffer do socket TCP

Usuários avançados que desejem ajustar parâmetros TCP de baixo nível podem adicionalmente ajustar o tamanho do buffer TCP através do objeto ClientConfiguration. A maioria dos usuário nunca precisará mexer nesses valores, mas a opção existe para usuários avançados.

Tamanhos de buffer TCP otimizados para uma aplicação são altamente dependentes das configurações e capacidades da rede e do Sistema operacional. Por exemplo, muitos sistemas operacionais modernos fornecem um auto-ajuste lógico do tamanho do buffer TCP, o que pode ter um grande impacto na performance das conexões TCP que são mantidas abertas o suficiente para o auto-ajuste otimizar o tamanho do buffer.

Tamanhos de buffer grandes (ex.: 2MB) permitem que o sistema operacional mantenha mais dados na memória sem requerer que o servidor remoto tome conhecimento do recebimento dessa informação, de forma que isso pode ser particularmente útil quando a rede possuir alta latência.

Isso é apenas uma sugestão, e o sistema operacional pode escolher não honrá-la. Quando usar essa opção, os usuários devem sempre checar se o sistema operacional tem limites e padrões configurados. Muitos sistemas operacionais possuem um limite de tamanho de buffer TCP configurado, e não permitem que se vá além desse limite a menos que você explicitamente aumente o limite.

Muitos recursos disponíveis para ajudar com a configuração de tamanhos de buffer TCP e configurações TCP específicas do sistema operacional incluem:

Controle de Logging

O SDK do AWS para Java usa o Apache Commons Logging para registrar mensagens de diagnóstico. o ACL fornece uma interface genérica para registrar bibliotecas de forma que o usuário possa usar qualquer biblioteca compatível. O AWS recomenda o uso do Apache log4j como biblioteca de registro com o Commons Logging, e os exemplos abaixo são arquivos de propriedades do log4j.

Erros e avisos específicos do serviço

O AWS recomenda que você sempre deixe a hierarquia de log de “com.amazonaws” ajustada para “WARN” de forma que se pegue qualquer mensagem importante emitida pelas bibliotecas cliente. Por exemplo, se o cliente Amazon S3 detecta que sua aplicação não fechou de forma adequada um InputStream e pode ter deixado algum vazamento de recursos, reportará isso através de uma mensagem de aviso no log. Isso também garantirá que as mensagens serão registradas de o cliente tiver algum problema na manipulação de requisições e rspostas.

O arquivo log4j.properties a seguir ajusta o parâmetro rootLogger para WARN, o que fará com que mensagens de aviso e erro de todos os registros da hierarquia “com.amazonaws” sejam incluídos. Alternativamente, você pode explicitamente ajustar o registro de com.amazonaws para WARN.

log4j.rootLogger=WARN, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c -  %m%n
# Or you can explicitly enable WARN and ERROR messages for  the AWS Java clients
log4j.logger.com.amazonaws=WARN

Resumo das mensagens de Requisição/Resposta

Cada requisição a um serviço AWS gera uma única ID de resposta AWS que é útil se você executa-lo em um problema em que um serviço AWS esteja manipulando uma requisição. IDs de requisições AWS são acessíveis programaticamente através de objetos Exception no SDK para cada chamada falha a um serviço, e pode também ser reportada através do nível INFO no registro de “com.amazonaws.request”.

O arquivo log4j.properties ativa um resumo de requisições e respostas, incluindo IDs de requisição AWS.

log4j.rootLogger=WARN, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c -  %m%n
# Turn on INFO logging in com.amazonaws.request to log
# a summary of requests/responses with AWS request IDs
log4j.logger.com.amazonaws.request=INFO

Abaixo segue um exemplo da saída:

2009-12-17 09:53:04,269 [main] INFO com.amazonaws.request - Sending Request: POST https://rds.amazonaws.com / Parameters: (MaxRecords: 20, Action: DescribeEngineDefaultParameters, SignatureMethod: HmacSHA256, AWSAccessKeyId: AKIAJUHAG74EVMS3UVZA, Version: 2009-10-16, SignatureVersion: 2, Engine: mysql5.1, Timestamp: 2009-12-17T17:53:04.267Z, Signature: 4ydexGGkC77PovHhbfzAMA1H0nDnqIQxG9q+Yq3uw5s=, )
2009-12-17 09:53:04,464 [main] INFO com.amazonaws.request - Received successful response: 200, AWS Request ID: 06c12a39-eb35-11de-ae07-adb69edbb1e4
2009-12-17 09:53:04,469 [main] INFO com.amazonaws.request - Sending Request: POST https://rds.amazonaws.com / Parameters: (ResetAllParameters: true, Action: ResetDBParameterGroup, SignatureMethod: HmacSHA256, DBParameterGroupName: java-integ-test-param-group-1261072381023, AWSAccessKeyId: AKIAJUHAG74EVMS3UVZA, Version: 2009-10-16, SignatureVersion: 2, Timestamp: 2009-12-17T17:53:04.467Z, Signature: 9WcgfPwTobvLVcpyhbrdN7P7l3uH0oviYQ4yZ+TQjsQ=, )
2009-12-17 09:53:04,646 [main] INFO com.amazonaws.request - Received successful response: 200, AWS Request ID: 06e071cb-eb35-11de-81f7-01604e1b25ff

Registros detalhado

Em alguns casos, pode ser útil ver exatamente quais requisições e respostas estão sendo enviadas pelo SDK do AWS para Java. Esse tipo de registro não deve ser ativado em sistemas em produção já que escreveriam requisições grandes (ex.: um arquivo sendo carregado no Amazon S3) ou as respostas podem reduzir significativamente a resposta de um aplicação. Se você realmente precisa acessar essa informação, pode temporariamente ativar o registro de Apache HttpClient. Ative o nível DEBUG em “httpclient.wire” para ativar o registro de todas as requisições e respostas.

O arquivo log4j.properties a seguir liga o registro completo no Apache HttpClient e deve apenas ser usado temporariamente já que pode ter um impacto significante na performance da aplicação.

log4j.rootLogger=WARN, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c -  %m%n
# Log all HTTP content (headers, parameters, content, etc)  for
# all requests and responses. Use caution with this since it can
# be very expensive to log such verbose data!
log4j.logger.httpclient.wire=DEBUG

Manipulação de exceção

Entender como e quando o SDK do AWS para Java dispara exceções é importante para criar aplicações de alta qualidade usando o SDK. As seções a seguir descrevem os diferentes casos de exceções que são disparadas pelo SDK e como manipula-las de forma adequada.

Por quê exceções não verificadas?

O SDK do AWS para o Java  usa exceções em tempo de execução (ou não verificadas) ao invés de exceções verificadas por algumas razões:

  • Para permitir que os desenvolvedores tenham controle sobre os erros que eles quiserem manipular sem força-los a manipular casos excepcionais que não sejam de sua preocupação (tornando o código muito grande)
  • Para evitar problemas de escalabilidade inerentes a exceções verificadas em aplicações grandes

Em geral, exceções verificadas funcionam melhor em escalas menores,  mas podem tornar-se problemáticas a medida que as aplicações crescem e tornam-se mais complexas.

AmazonServiceException (e Sub-classes)

Esse é o tipo principal de exceção que você precisará lidar ao usar o SDK do AWS para Java. Essa exceção representa uma resposta a um erro de um serviço AWS. Por exemplo, se você solicita o encerramento de uma instância do Amazon EC2 que não exista, o EC2 retornará uma resposta de erro e todos os detalhes dessa resposta de erro será incluída dentro de AmazonServiceException. Em alguns casos, uma sub-classe de AmazonServiceException será disparada para permitir que os desenvolvedores tenham controle sobre a manipulação dos casos de erro através de blocos catch.

Quando você encontra uma AmazonServiceException, saberá que sua requisição foi enviada com sucesso para o serviço AWS, mas não pode ser processado com sucesso por causa de algum erro nos parâmetros da requisição ou por problemas no lado do serviço.

AmazonServiceException tem muitos campos úteis, que incluem:

  • O código de status HTTP retornado
  • O código de erro AWS retornado
  • A mensagem de erro detalhada do serviço
  • O ID da requisição AWS para a requisição que falhou

AmazonServiceException também inclui um campo que descreve se a requisição que falhou foi por culpa de quem a chamou (isto é, uma requisição com valores ilegais) ou por culpa do serviço AWS (isto é, um erro interno do serviço).

AmazonClientException

Essa exceção indica que um problema ocorreu dentro do código do cliente Java, seja durante o envio de uma requisição ao AWS ou traduzindo uma resposta do AWS. O AmazonClientException é mais severo do que AmazonServiceException e indica um problema maior que está impedindo o cliente de ser capaz de fazer chamadas aos serviços do AWS. Por exemplo, o SDK do AWS para Java irá disparar essa exceção se não houver uma conexão de rede disponível quando de tentar fazer uma chamada a um dos clientes.

IllegalArgumentException

Quando estiver chamando operações, se passar um argumento ilegal, o SDK do AWS irá disparar essa exceção. Por exemplo, se chamar um operador e passar um valor nulo para um dos parâmetros obrigatórios, o SDl irá disparar uma IllegalArgumentException descrevendo o argumento ilegal.

Usando políticas de controle de acesso

As políticas de controle de acesso do AWS permitem que você especifique controles de acesso detalhados aos recursos do AWS. Você pode permitir ou negar acesso aos recursos do AWS baseado em:

  • que recursos está sendo acessado
  • quem está acessando o recursos (isto é, o principal)
  • que action está sendo solicitada ao recursos
  • uma variedade de conditions que incluem restrições de data, endereço IP, etc

Políticas de controle de acesso são uma coleção de sentenças. Cada sentença toma a forma: “A tem permissão para fazer B em C onde D se aplica”.

  • A é o principal – a conta do AWS que está fazendo a requisição para acessar ou modificar um dos seus recursos no AWS.
  • B é a action – a forma em que seu recursos AWS está sendo acessado ou modificado, como o envio de uma mensagem a uma fila do Amazon SQS ou o armazenamento de um objeto no Amazon S3.
  • C é o resource – sua entidade do AWS que o principal quer acessar, como uma fila do Amazon SQS ou um objeto armazenado no Amazon S3.
  • D é um conjunto de conditions – restrições opcionais que especificam quando permitir ou negar acesso para que o principal acesse seu recurso. Muitas condições expressivas estão disponíveis, algumas específicas de cada serviço. Por exemplo, você pode usar condições de data para permitir acesso ao seu recursos apenas antes ou depois de uma data especifica.

Exemplo do Amazon S3

O exemplo a seguir demonstra uma política que permite que qualquer um tenha acesso para ler objetos de uma carteira, mas restrinja o acesso para carregar objetos a carteira a duas contas AWS específicas (além da conta dona da carteira).

Statement allowPublicReadStatement = new Statement(Effect.Allow)
         .withPrincipals(Principal.AllUsers)
         .withActions(S3Actions.GetObject)
         .withResources(new S3ObjectResource(myBucketName, "*"));
Statement allowRestrictedWriteStatement = new Statement(Effect.Allow)
         .withPrincipals(new Principal("123456789"), new Principal("876543210"))
         .withActions(S3Actions.PutObject)
         .withResources(new S3ObjectResource(myBucketName, "*"));

Policy policy = new Policy()
         .withStatements(allowPublicReadStatement, allowRestrictedWriteStatement);

AmazonS3 s3 = new AmazonS3Client(myAwsCredentials);
s3.setBucketPolicy(myBucketName, policy.toJson());

Exemplo do Amazon SQS

Um uso comum da políticas é autorizar uma fila do Amazon SQS a receber mensagens de um tópico do Amazon SNS.

/*
 * This policy allows an SNS topic to send messages to an SQS queue.
 * You can find your SNS topic's ARN through the SNS getTopicAttributes operation.
 */
Policy policy = new Policy().withStatements(
        new Statement(Effect.Allow)
            .withPrincipals(Principal.AllUsers)
            .withActions(SQSActions.SendMessage)
            .withConditions(ConditionFactory.newSourceArnCondition(myTopicArn)));

Map queueAttributes = new HashMap();
queueAttributes.put(QueueAttributeName.Policy.toString(), policy.toJson());

AmazonSQS sqs = new AmazonSQSClient(myAwsCredentials);
sqs.setQueueAttributes(new SetQueueAttributesRequest(myQueueUrl, queueAttributes));

Exemplo do Amazon SNS

Alguns serviços oferecem condições adicionais que podem ser usadas em políticas. O Amazon SNS fornece condições para permitir ou negar inscrições em tópicos SNS baseados no protocolo (ex.: e-mail, HTTP, HTTPS, SQS) e pontos finais (ex.: endereço de e-mail, URL, ARN do SQS) da requisição de inscrição em um tópico.

/*
 * This SNS condition allows you to restrict subscriptions to an Amazon SNS topic
 * based on the requested endpoint (email address, SQS queue ARN, etc) used when
 * someone tries to subscribe to your SNS topic.
 */
Condition endpointCondition =
        SNSConditionFactory.newEndpointCondition("*@mycompany.com");

Policy policy = new Policy().withStatements(
        new Statement(Effect.Allow)
            .withPrincipals(Principal.AllUsers)
            .withActions(SNSActions.Subscribe)
            .withConditions(endpointCondition));

AmazonSNS sns = new AmazonSNSClient(myAwsCredentials);
sns.setTopicAttributes(
        new SetTopicAttributesRequest(myTopicArn, "Policy", policy.toJson()));

Recursos adicionais