Problem with AppSync GraphQL HTTP API call from Lambda function when upgrading from Java SDK V1 to V2

0

Hello all! I have recently migrated all AWS API clients used in my Lambda functions from Java SDK V1 to V2; and ALMOST everything works! Unfortunately, calling AppSync graphql endpoints does not have a built in API client, and requires using signed HTTP requests to the endpoint. I thought I had figured out how to correctly replicate this in SDK V2, but all the calls return unsuccessfully with the same response:

{ "errors" : [ { "errorType" : "UnknownOperationException", "message" : "Unknown Operation Request." } ] }

This would seem to indicate an issue with the GraphQL request itself, however it is the exact same request used successfully with SDK v1, and the response is the same regardless of the mutation submitted in the request. Regardless, a detailed inspection found nothing wrong with the request or the GQL schema. I have tried switching HTTP client implementations, but I'm not sure what else to try to debug this issue. Any insight would be appreciated!

Here is the V2 version of the code:

public static HttpExecuteResponse executeQuery(GQLQuery query) throws JsonProcessingException 
    {
    	// get creds 
    	DefaultCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();
        AwsCredentials credentials = credentialsProvider.resolveCredentials();

        // create URI 
        URI uri = URI.create(APPSYNC_API_ENDPOINT);
        
        ObjectMapper mapper = JsonUtils.mapper();
        byte[] inputBytes = mapper.writeValueAsBytes(query);
        LOGGER.debug("GQL Query: " + new String(inputBytes));
           

        // create the request
        final SdkHttpFullRequest request = SdkHttpFullRequest.builder()
                .method(SdkHttpMethod.PUT)
                .putHeader("type", "AWS_IAM")
                .putHeader("Content-Type", "application/json")
                .putHeader("Content-Length", String.valueOf(inputBytes.length))
                .contentStreamProvider(RequestBody.fromBytes(inputBytes).contentStreamProvider())
                .uri(uri)
                .build(); 

        //Sign it...
        Aws4Signer signer = Aws4Signer.create();
        Aws4SignerParams signerParams = Aws4SignerParams.builder()
                .signingRegion(Region.of(AWS_REGION))
                .awsCredentials(credentials)
                .signingName(APPSYNC_SERVICE)
                .build();
        SdkHttpFullRequest signedRequest = signer.sign(request, signerParams);
      

        // create httpClient - set timeout and proxy config on this if necessary
        SdkHttpClient httpClient =  UrlConnectionHttpClient.builder().build();

        // Create executable request
        HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
        		                                              .request(signedRequest)
        		                                              .contentStreamProvider(request.contentStreamProvider().orElse(null))
        		                                              .build();
        
        ExecutableHttpRequest executableHttpRequest = httpClient.prepareRequest(executeRequest);

        // Execute the request
        try {
        	HttpExecuteResponse resp= executableHttpRequest.call();
        	SdkHttpResponse sdkResp= resp.httpResponse();
        	if (sdkResp.isSuccessful()) return resp;
        	else {
        		if (LOGGER.isDebugEnabled()) {
        			String response= IoUtils.toUtf8String(resp.responseBody().orElse(AbortableInputStream.createEmpty()));
        			System.out.println(response);
        		}
        		throw new RuntimeException("AppSync API call unsuccessful (" + sdkResp.statusCode() + ") with message: " + sdkResp.statusText().orElse("null"));
        	}
        	
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }

For comparison, here is the SDK V1 version of the call:

public static void executeQuery(GQLQuery query) throws JsonProcessingException 
    {
    	Request<AmazonWebServiceRequest> request = new DefaultRequest<AmazonWebServiceRequest>(APPSYNC_SERVICE);
        request.setHttpMethod(HttpMethodName.POST);
        request.setEndpoint(URI.create(APPSYNC_API_ENDPOINT)); 
        
        ObjectMapper mapper = JsonUtils.mapper();
        byte[] inputBytes = mapper.writeValueAsBytes(query);
        LOGGER.debug("GQL Query: " + new String(inputBytes));

        request.setContent(new ByteArrayInputStream(inputBytes));
        request.addHeader("type", "AWS_IAM");
        request.addHeader("Content-Type", "application/json");
        request.addHeader("Content-Length", String.valueOf(inputBytes.length));
        
        //Signing with V4 authentication
        AWS4Signer signer = new AWS4Signer();
        signer.setServiceName(APPSYNC_SERVICE); 
        signer.setRegionName(AWS_REGION);
        DefaultAWSCredentialsProviderChain credentials = new DefaultAWSCredentialsProviderChain();
        signer.sign(request, credentials.getCredentials());
        
		Response resp = new AmazonHttpClient(new ClientConfiguration())
				                             .requestExecutionBuilder()
											 .request(request)
											 .execute();
		if (resp.getHttpResponse().getStatusCode() != 200) {
    		throw new RuntimeException("AppSync API call unsuccessful (" + resp.getHttpResponse().getStatusCode() + ") with message: " + resp.getHttpResponse().getStatusText());
		}	
    }

I just recently validated that the V1 version works correctly, while the V2 version returns the error message above, with no other changes in the system or environment. Also, in the V2 version the AppSync API logs do not show any activity, so the request is not even making it that far.

Thanks in advance for any help!

No Answers

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions