Kilka dni temu mój zespół miał za zadanie zintegrować naszą aplikację Java z punktami końcowymi Amazon API Gateway. Jak zapewne wiesz, Amazon API Gateway wymaga podpisywania żądań.
W naszym przypadku uwierzytelnianie opierało się na kluczu dostępu użytkownika IAM i tajnym kluczu dostępu. Podpisanie żądania to kryptograficzna funkcja skrótu zwracająca wartość skrótu na podstawie danych wejściowych. Amazon API Gateway obsługuje uwierzytelnianie przy użyciu AWS Signature Version 4.
Gotowy do rozpoczęcia? Wywołanie AWS API Gateway z poziomu języka Java
Podczas burzy mózgów rozważaliśmy następujące pomysły:
- Wygenerowanie AWS SDK dla konkretnej definicji API Gateway - najbardziej wystarczający sposób zalecany przez Amazon.
- Generowanie Klient HTTP z
OpenAPI
lubSwagger
(obie definicje są gotowe do pobrania z API Gateway). - Użyj niestandardowego Klient HTTPnp.
RestTemplate
i zaimplementować sygnatariusza samodzielnie w oparciu o Przykład w języku Python. - Użyj AWS SDK, AWS4Signer i czystego żądania/odpowiedzi JSON, aby wywołać API Gateway.
Wiedzieliśmy również, że to API nie jest stabilne i dostawca API może je zmienić. W tych okolicznościach zdecydowaliśmy się nie używać wstępnie wygenerowanego AWS SDK lub klienta HTTP z OpenAPI (pamiętasz kod boilerplate wygenerowany z WSDL i mapowań DTO?). Dodatkowo, próba wygenerowania dedykowanego AWS SDK dała nam błąd w AWS Console. Chore!!!
Naturalną decyzją było wdrożenie trzeciego lub czwartego pomysłu. Zorganizowałem wewnętrzny konkurs, w którym zwycięskie rozwiązanie zostanie wykorzystane w naszym projekcie. Mój kolega z zespołu zaczął wdrażać AWS signer
i wywołać API Gateway ręcznie za pomocą RestTemplate
. Rozpocząłem badania nad tym, jak to zrobić, używając AWS SDK
oraz AWS4Signer
wdrożenie. Podczas implementacji obaj narzekaliśmy. Mój kolega miał problem z debugowaniem, dlaczego suma kontrolna (kod skrótu) jest nieprawidłowa. Nie byłem w stanie znaleźć przykładu użycia AWS SDK
oraz AWS4Signer
w oficjalnej dokumentacji AWS i StackOverflow. Było tylko jedno miejsce w Apache NiFi
projekt, w którym podobne implementacja została wykonana.
Ostatecznie najszybszym rozwiązaniem okazała się niestandardowa implementacja wywołania API Gateway oparta na AWS SDK
oraz AWS4Signer
.
W tym wpisie na blogu chciałbym opisać wymagane kroki, aby zaimplementować wywołanie API Gateway przy użyciu surowego żądania i odpowiedzi JSON. Jeśli jesteś niecierpliwy, możesz przejść bezpośrednio do przykładu GitHub i sprawdzić moją niestandardową implementację opartą na Apache NiFi.
Jak wywołać AWS API Gateway z poziomu kodu Java?
1. Zebranie podstawowych informacji
Przede wszystkim należy zebrać następujące dane od dostawcy API Gateway:
AWS_IAM_ACCESS_KEY
(użytkownik IAM),AWS_IAM_SECRET_ACCESS_KEY
(hasło IAM),AWS_REGION
(region, w którym wdrożona jest brama API),AWS_API_GATEWAY_ENDPOINT
(adres URL do punktu końcowego API Gateway).
2. Dołącz zestaw AWS SDK do swojego projektu
W moim przypadku dodałem do kodu następującą zależność Maven:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>{version}</version>
</dependency>
3. Utwórz klasę JsonApiGatewayCaller rozszerzającą klasę AmazonWebServiceClient
.
public class JsonApiGatewayCaller extends AmazonWebServiceClient
The AmazonWebServiceClient
abstrakcyjna klasa bazowa jest odpowiedzialna za podstawowe możliwości klienta, które są takie same dla wszystkich klientów AWS SDK Java, na przykład ustawianie punktu końcowego klienta.
4. Utwórz JsonApiGatewayCaller
konstruktor
public JsonApiGatewayCaller(String accessKey, String secretAccessKey, String apiKey, String region, URI endpoint) {
super(new ClientConfiguration());
this.credentials = new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretAccessKey));
this.apiKey = apiKey;
this.endpoint = endpoint;
this.signer = new AWS4Signer();
this.signer.setServiceName(API_GATEWAY_SERVICE_NAME);
this.signer.setRegionName(region);
final JsonOperationMetadata metadata = new JsonOperationMetadata().withHasStreamingSuccessResponse(false).withPayloadJson(false);
final Unmarshaller<ApiGatewayResponse, JsonUnmarshallerContext> responseUnmarshaller = in -> new ApiGatewayResponse(in.getHttpResponse());
this.responseHandler = SdkStructuredPlainJsonFactory.SDK_JSON_FACTORY.createResponseHandler(metadata, responseUnmarshaller);
JsonErrorUnmarshaller defaultErrorUnmarshaller = new JsonErrorUnmarshaller(ApiGatewayException.class, null) {
@Override
public AmazonServiceException unmarshall(JsonNode jsonContent) throws Exception {
return new ApiGatewayException(jsonContent.toString());
}
};
this.errorResponseHandler = SdkStructuredPlainJsonFactory.SDK_JSON_FACTORY.createErrorResponseHandler(
Collections.singletonList(defaultErrorUnmarshaller), null);
}
- The
clientConfiguration
obiekt zawiera podstawowe opcje HTTP, takie jak ustawienia proxy, ciąg agenta użytkownika, maksymalna liczba ponawianych prób itp. - The
credentials
obiekt reprezentujeAWSCredentialsProvider
. Istnieje kilka sposobów dostarczania poświadczeń IAM do kodu, zobacz oficjalną dokumentację. W naszym uproszczonym przykładzie używamy statycznego dostawcy poświadczeń. - The
apiKey
reprezentuje ustawienie autoryzacji w naszej bramie API. Zobacz oficjalną dokumentację. - The
endpoint
obiekt jest reprezentowany przez adres URL do naszej bramy API, na przykładhttps://234n34k5678k.execute-api.eu-west-1.amazonaws.com/TEST
. - Następne trzy linie tworzą
AWS4Signer
obiekt do podpisywania żądań za pomocąAWS4
protokół podpisywania. - Ostatnia sekcja konstruktora jest odpowiedzialna za funkcję
responseHandler
orazerrorResponseHandler
creation. Jest to kluczowa część pobierania odpowiedzi w czystej reprezentacji JSON.
5. Utwórz ExecutionContext
private ExecutionContext createExecutionContext() {
final ExecutionContext executionContext = ExecutionContext.builder().withSignerProvider(
new DefaultSignerProvider(this, signer)).build();
executionContext.setCredentialsProvider(credentials);
return executionContext;
}
The ExecutionContext
pochodzi z com.amazonws.http
i agreguje obsługę żądań, klienta usługi sieciowej, dostawcę sygnatariuszy i AWS Request Metrics. Należy pamiętać, że ta klasa nie jest bezpieczna dla wątków.
6. Utwórz DefaultRequest
private DefaultRequest prepareRequest(HttpMethodName method, String resourcePath, InputStream content) {
DefaultRequest request = new DefaultRequest(API_GATEWAY_SERVICE_NAME);
request.setHttpMethod(method);
request.setContent(content);
request.setEndpoint(this.endpoint);
request.setResourcePath(resourcePath);
request.setHeaders(Collections.singletonMap("Content-type", "application/json"));
return request;
}
Ta metoda tworzy DefaultRequest
w dość prosty sposób. Kluczową częścią jest tutaj ustawienie Content-type: application/json
aby poprawnie "rozmawiać" w formacie JSON.
7. Wykonanie metody HTTP
public ApiGatewayResponse execute(HttpMethodName method, String resourcePath, InputStream content) {
final ExecutionContext executionContext = createExecutionContext();
DefaultRequest request = prepareRequest(method, resourcePath, content);
RequestConfig requestConfig = new AmazonWebServiceRequestAdapter(request.getOriginalRequest());
return this.client.execute(request, responseHandler, errorResponseHandler, executionContext, requestConfig).getAwsResponse();
}
Ta metoda jest sercem naszej klasy. Jest ona odpowiedzialna za wywołanie punktu końcowego API Gateway poprzez przygotowanie kontekstu wykonania, żądania, konfiguracji żądania i wywołanie funkcji execute
metoda z AmazonHttpClient
. Można tutaj zauważyć, że metoda ta wymaga przekazania responseHandler
oraz errorResponseandler
aby poprawnie usunąć wyniki. Implementacja wykorzystuje nasze własne ApiGatewayResponse oraz Wyjątek ApiGatewayException.
8. Końcowe użycie
Załóżmy, że chcemy wysłać metodę POST do punktu końcowego /pets z następującym żądaniem JSON:
{
"type": "dog",
"price": 249.99
}
Nasz kod Java może wyglądać jak na poniższym przykładzie:
JsonApiGatewayCaller caller = new JsonApiGatewayCaller(
AWS_IAM_ACCESS_KEY,
AWS_IAM_SECRET_ACCESS_KEY,
null,
AWS_REGION,
new URI(AWS_API_GATEWAY_ENPOINT)
);
ApiGatewayResponse response = caller.execute(HttpMethodName.POST, "/pets", new ByteArrayInputStream(exampleJsonRequest.getBytes()));
Wnioski
- Korzystanie z AWS SDK nie jest zbyt trywialne, a czasami nie jest dobrze udokumentowane.
- Podpisywanie żądań przez
AWS4
protokół może powodować problemy. - Aby po prostu wywołać API Gateway, trzeba stworzyć wiele standardowego kodu.
- Pełny
Java
kod jest dostępny na stronie GitHub.
Potrzebujesz dedykowanego zespołu programistów do wynajęcia?
Chcesz porozmawiać z naszymi ekspertami o niestandardowych rozwiązaniach programistycznych dla Twojej firmy?
P: Jakie są niektóre z korzyści migracji do chmury wspomnianych w tej treści?
Niektóre z korzyści płynących z migracji do chmury wymienionych w tej treści to skalowalność, niezawodność, odzyskiwanie danych po awarii i automatyczne tworzenie kopii zapasowych, bezpieczeństwo, niższe koszty, lepsze monitorowanie i analiza oraz lepsze zarządzanie zasobami IT.