Description
I'll help you craft a GitHub issue for the CORS and 404 errors you're experiencing. Here's a template you can use:
## Issue: AWS Textract Service - 404 in OPTIONS request and CORS errors
### Description
I'm experiencing CORS errors when trying to use the AWS Textract service from a Flutter application. The browser is sending an OPTIONS preflight request which is resulting in a 404 error, followed by CORS errors that prevent the actual Textract API call from succeeding.
### Environment
- Flutter version: Flutter 3.29.0
- AWS SDK packages:
- aws_common:
- aws_signature_v4:
- Browser : [Chrome.]
- Platform: [Web]
### Reproduction Steps
1. Initialize the AwsTextractService with valid AWS credentials
2. Call the `analyzeDocumentClarity` method with document bytes
3. The browser sends an OPTIONS preflight request to the Textract endpoint
4. The OPTIONS request receives a 404 response
5. Subsequent POST request fails with CORS error
### Code Example
```dart
final service = AwsTextractService.getInstance(
accessKey: 'MY_ACCESS_KEY',
secretKey: 'MY_SECRET_KEY',
region: 'ap-south-1',
);
try {
final result = await service.analyzeDocumentClarity(documentBytes);
// Never gets here due to CORS error
} catch (e) {
print('Error: $e');
}
Error Messages
Error analyzing document: POST https://textract.ap-south-1.amazonaws.com? failed: TypeError: Failed to fetch
DartError: Bad state: Future already completed
Expected Behavior
The AWS Textract API call should complete successfully without CORS errors.
Attempted Solutions
{"__type":"InvalidSignatureException","message":"The request signature we calculated does not match the signature you
provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}
- I've also tried to compare the client sdk for react and how it does things but couldn't replicate it.
- I've verified the credentials
- I've confirmed direct API calls (e.g., from Postman) work correctly.
Would appreciate guidance on:
- Is there a different approach needed for browser-based Textract calls?
- Are there additional headers needed for CORS support?
What I am trying to do.
import 'dart:convert';
import 'dart:typed_data';
import 'package:aws_textract_api/textract-2018-06-27.dart';
import 'package:aws_common/aws_common.dart';
import 'package:aws_signature_v4/aws_signature_v4.dart';
import 'package:crypto/crypto.dart';
import 'package:esc_pos_utils_plus/dart_hex/hex.dart';
import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';
/// AWS Textract service that uses the aws_signature_v4 package for authentication
class AwsTextractService {
static AwsTextractService? _instance;
final String accessKey;
final String secretKey;
final String region;
final AWSCredentials _credentials;
final AWSCredentialsProvider _credentialsProvider;
late final AWSSigV4Signer _signer;
final Uuid _uuid = const Uuid();
// Private constructor for singleton
AwsTextractService._({
required this.accessKey,
required this.secretKey,
required this.region,
}) : _credentials = AWSCredentials(accessKey, secretKey),
_credentialsProvider =
AWSCredentialsProvider(AWSCredentials(accessKey, secretKey)) {
_signer = AWSSigV4Signer(
credentialsProvider: _credentialsProvider,
);
}
/// Get the singleton instance
static AwsTextractService getInstance({
required String accessKey,
required String secretKey,
String region = 'ap-south-1',
}) {
_instance ??= AwsTextractService._(
accessKey: accessKey,
secretKey: secretKey,
region: region,
);
return _instance!;
}
String _formatDate(DateTime dateTime) {
return '${dateTime.year}${dateTime.month.toString().padLeft(2, '0')}${dateTime.day.toString().padLeft(2, '0')}';
}
String _formatAmzDate(DateTime dateTime) {
return '${_formatDate(dateTime)}T${dateTime.hour.toString().padLeft(2, '0')}${dateTime.minute.toString().padLeft(2, '0')}${dateTime.second.toString().padLeft(2, '0')}Z';
}
/// Analyze document clarity using AWS Textract
/// Returns a confidence score between 0.0 and 1.0
Future<double> analyzeDocumentClarity(Uint8List documentBytes) async {
try {
// Host and endpoint information
final host = 'textract.$region.amazonaws.com';
final endpoint = Uri.parse('https://$host');
final timestamp = DateTime.now().toUtc();
final amzDate = _formatAmzDate(timestamp);
// Prepare the request body
final requestBody = jsonEncode({
'Document': {
'Bytes': base64Encode(documentBytes),
},
});
// Create a unique request ID
final requestId = _uuid.v4();
// Convert request body to bytes
final requestBodyBytes = Uint8List.fromList(utf8.encode(requestBody));
// Calculate SHA256 hash of request body
final bodySha256 = sha256.convert(requestBodyBytes).bytes;
final hexEncodedBodyHash = const HexEncoder().convert(bodySha256);
// Prepare headers
final headers = {
'Content-Type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'Textract.DetectDocumentText',
'amz-sdk-invocation-id': requestId,
'amz-sdk-request': 'attempt=1; max=3',
'Accept': '*/*',
'X-Amz-Date': amzDate,
'X-Amz-Content-Sha256': hexEncodedBodyHash,
};
debugPrint('Headers: $headers');
// Create the AWS request
final awsRequest = AWSHttpRequest(
method: AWSHttpMethod.post,
uri: endpoint,
headers: headers,
body: requestBodyBytes,
);
// Define the credential scope
final scope = AWSCredentialScope(
region: region,
service: AWSService.textract,
);
debugPrint('Request: $awsRequest');
// Sign the request
final AWSSignedRequest signedRequest = await _signer.sign(
awsRequest,
credentialScope: scope,
serviceConfiguration: S3ServiceConfiguration(), // Using S3 config as fallback
);
debugPrint('Signed Headers: ${signedRequest.headers}');
// Send the request using AWS HTTP client
final operation = signedRequest.send();
// Handle the response
final response = await operation.response;
if (response.statusCode != 200) {
final errorBody = await _decodeResponse(response);
throw Exception(
'AWS Textract request failed with status ${response.statusCode}: $errorBody',
);
}
// Parse the response
final responseBody = await _decodeResponse(response);
final responseJson = jsonDecode(responseBody);
final textractResponse = DetectDocumentTextResponse.fromJson(responseJson);
// Calculate and return the document clarity score
return _calculateDocumentClarityScore(textractResponse);
} catch (e) {
debugPrint('Error analyzing document: $e');
rethrow;
}
}
/// Helper method to decode the AWS HTTP response
Future<String> _decodeResponse(AWSBaseHttpResponse response) async {
return await utf8.decodeStream(response.split());
}
/// Calculate document clarity score based on Textract response
double _calculateDocumentClarityScore(DetectDocumentTextResponse response) {
final blocks = response.blocks;
if (blocks == null || blocks.isEmpty) {
return 0.0; // No text detected
}
// Calculate average confidence across all detected text blocks
double totalConfidence = 0.0;
int textBlockCount = 0;
for (final block in blocks) {
if (block.blockType == BlockType.line ||
block.blockType == BlockType.word) {
if (block.confidence != null) {
totalConfidence += block.confidence!;
textBlockCount++;
}
}
}
// If no text blocks found, document might be blank or an image
if (textBlockCount == 0) {
return 0.3; // Assign a low default score
}
// Return average confidence as a value between 0.0 and 1.0
return totalConfidence / textBlockCount / 100;
}
}
Categories
Steps to Reproduce
Here's a detailed "Steps to Reproduce" section you can add to your GitHub issue:
### Steps to Reproduce
1. Create a new Flutter web project or use an existing one
2. Add the following dependencies to your pubspec.yaml:
```yaml
dependencies:
aws_signature_v4: ^0.6.3
aws_common: ^0.7.5
uuid: ^4.5.1
crypto: ^3.0.6
dotenv: ^4.2.0
esc_pos_utils_plus: ^2.0.4
-
Create a service class for AWS Textract (as shown in code example)
-
Set up a simple UI with:
- A button to select/capture a document image
- A button to analyze the document using Textract
- A text area to display results or errors
-
Implement the document analysis workflow:
// In your widget's event handler
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
final bytes = await image.readAsBytes();
try {
final service = AwsTextractService.getInstance(
accessKey: 'YOUR_ACCESS_KEY',
secretKey: 'YOUR_SECRET_KEY',
region: 'ap-south-1',
);
final clarity = await service.analyzeDocumentClarity(bytes);
setState(() {
resultText = "Document clarity score: ${clarity.toStringAsFixed(2)}";
});
} catch (e) {
setState(() {
resultText = "Error: $e";
});
print('Full error: $e');
}
}
-
Select a document image and click the analyze button
-
Observe in the Network tab:
- An OPTIONS request to the Textract endpoint failing with a 404 status
- The subsequent POST request failing with a CORS error
-
Check the Console tab for the complete error message about CORS policy violation
### Screenshots
<img width="1088" alt="Image" src="https://github.com/user-attachments/assets/e1738002-fa29-4dbf-902d-8c17dc0dbb9b" />
<img width="1081" alt="Image" src="https://github.com/user-attachments/assets/10aa6a7c-098a-4370-8eb4-9d359fc09663" />
### Platforms
- [ ] iOS
- [ ] Android
- [x] Web
- [ ] macOS
- [ ] Windows
- [ ] Linux
### Flutter Version
3.29.0
### Amplify Flutter Version
aws_common: ^0.7.5
### Deployment Method
Amplify Gen 2
### Schema
```GraphQL
Description
I'll help you craft a GitHub issue for the CORS and 404 errors you're experiencing. Here's a template you can use:
Error Messages
Error analyzing document: POST https://textract.ap-south-1.amazonaws.com? failed: TypeError: Failed to fetch
DartError: Bad state: Future already completed
Expected Behavior
The AWS Textract API call should complete successfully without CORS errors.
Attempted Solutions
Would appreciate guidance on:
What I am trying to do.
Categories
Steps to Reproduce
Here's a detailed "Steps to Reproduce" section you can add to your GitHub issue:
Create a service class for AWS Textract (as shown in code example)
Set up a simple UI with:
Implement the document analysis workflow:
Select a document image and click the analyze button
Observe in the Network tab:
Check the Console tab for the complete error message about CORS policy violation