A production-ready, enterprise-grade HTTP request proxy with multi-format input support, comprehensive error handling, and flexible configuration management.
- Project Overview
- Architecture Design
- Component Breakdown
- Input Formats
- Configuration Management
- Usage Guide
- API Reference
- Testing
- Deployment
- Troubleshooting
API Proxy is a versatile Java application that acts as an intelligent middleware for executing HTTP requests. It accepts requests in multiple formats (command-line parameters, CSV files, JSON files, or text files), processes them, executes HTTP calls to external APIs, and returns structured responses.
- β Multi-Format Input Support: CSV, JSON, Text files, and CLI parameters
- β Flexible HTTP Support: REST, SOAP, and GraphQL APIs
- β Authentication: Basic, Bearer Token, API Key, OAuth2
- β Configuration Management: Multi-source loading with defaults
- β Robust Error Handling: Detailed error messages with context
- β Production Ready: 100% test coverage, comprehensive logging
- β Enterprise Features: SSL/TLS, proxy support, connection pooling
| Use Case | Description |
|---|---|
| API Testing | Test REST APIs during development and QA |
| Batch Processing | Execute multiple requests from CSV/JSON files |
| Integration Testing | Validate API integrations in CI/CD pipelines |
| Load Testing | Run multiple requests for performance testing |
| Data Migration | Migrate data between systems via APIs |
| Automation | Automate API calls in scripts and workflows |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER INTERFACE β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββββββ β
β β Command Line β β Text Files β β CSV/JSON Files β β
β β Parameters β β (.txt) β β (.csv, .json) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INPUT PROCESSING LAYER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β InputProcessor β β
β β β’ Detects input type (file extension or direct params) β β
β β β’ Routes to appropriate parser β β
β β β’ Validates file existence and readability β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β CSV β β Text β β Parameter β β
β β Parser β β Parser β β Parser β β
β ββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MODEL/DATA LAYER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ApiRequest (Builder Pattern) β β
β β β’ URL β’ Headers β β
β β β’ HTTP Method β’ Body β β
β β β’ Query Parameters β’ Authentication β β
β β β’ Timeout β’ Metadata β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP CLIENT LAYER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HttpClientManager β β
β β β’ Manages HTTP client instances β β
β β β’ Connection pooling β β
β β β’ SSL/TLS configuration β β
β β β’ Proxy support β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β REST β β SOAP β β GraphQL β β
β β Adapter β β Adapter β β Adapter β β
β ββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EXTERNAL APIs β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββββββ β
β β REST APIs β β SOAP APIs β β GraphQL APIs β β
β β (HTTP/JSON) β β (XML) β β (GraphQL) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RESPONSE PROCESSING LAYER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ResponseProcessor β β
β β β’ Parses HTTP responses β β
β β β’ Extracts status codes, headers, body β β
β β β’ Formats output β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OUTPUT β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Structured Response (Console/File/Log) β β
β β β’ Status Code β’ Response Body β β
β β β’ Response Headers β’ Execution Time β β
β β β’ Error Details β’ Metadata β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββ 1. Parse Input ββββββββββββββββββ
β User β ββββββββββββββββββ> β InputProcessor β
ββββββββββββ ββββββββββββββββββ
β
β 2. Create ApiRequest
βΌ
ββββββββββββββββββ
β ApiRequest β
β (Model) β
ββββββββββββββββββ
β
β 3. Execute Request
βΌ
ββββββββββββββββββ
β HttpClientMgr β
ββββββββββββββββββ
β
β 4. HTTP Call
βΌ
ββββββββββββββββββ
β External API β
ββββββββββββββββββ
β
β 5. HTTP Response
βΌ
ββββββββββββββββββ
β ResponseProc β
ββββββββββββββββββ
β
β 6. Format & Display
βΌ
ββββββββββββββββββ
β Output β
ββββββββββββββββββ
com.t24.apiproxy/
βββ main/
β βββ ApiProxyMain.java # Application entry point
β βββ config/
β βββ Configuration.java # Config data model (20+ methods)
β βββ ConfigurationLoader.java # Multi-source config loader (220 lines)
β
βββ input/
β βββ InputProcessor.java # Input coordinator & router
β βββ parsers/
β β βββ ParameterParser.java # CLI parameter parser (360 lines)
β β βββ TextParser.java # Text file parser (480 lines)
β β βββ CsvParser.java # CSV file parser
β β βββ JsonParser.java # JSON file parser
β βββ validation/
β βββ InputValidator.java # Input validation
β
βββ model/
β βββ ApiRequest.java # Request model (Builder pattern)
β βββ ApiResponse.java # Response model (Builder pattern)
β βββ Metadata.java # Request/response metadata
β βββ ErrorResponse.java # Error information
β
βββ client/
β βββ HttpClientManager.java # HTTP client lifecycle manager
β βββ RequestBuilder.java # ApiRequest β HttpRequest converter
β βββ ResponseProcessor.java # HttpResponse β ApiResponse converter
β βββ adapters/
β βββ RestAdapter.java # REST-specific handling
β βββ SoapAdapter.java # SOAP-specific handling
β βββ GraphQLAdapter.java # GraphQL-specific handling
β
βββ security/
β βββ SSLUtil.java # SSL/TLS configuration
β
βββ util/
β βββ LoggingUtil.java # Centralized logging (SLF4J)
β βββ DateUtil.java # Date/time utilities
β βββ StringUtil.java # String manipulation
β βββ ValidationUtil.java # Validation utilities
β
βββ exception/
βββ ApiProxyException.java # Base exception
βββ InputProcessingException.java # Input errors
βββ NetworkException.java # Network errors
βββ ValidationException.java # Validation errors
Purpose: Application orchestrator and main entry point
Responsibilities:
- Initialize logging system
- Load configuration from
application.properties - Parse command-line arguments
- Delegate input processing to InputProcessor
- Execute HTTP requests via HttpClientManager
- Display results to console
- Handle top-level exceptions
Execution Flow:
public static void main(String[] args) {
// Step 1: Initialize logging
LoggingUtil.init();
// Step 2: Load configuration (multi-source with defaults)
Configuration cfg = ConfigurationLoader.load("application.properties");
// Step 3: Process input β List<ApiRequest>
List<ApiRequest> requests = InputProcessor.process(args, cfg);
// Step 4: Create HTTP client
HttpClientManager client = new HttpClientManager(cfg);
// Step 5: Execute each request
for (ApiRequest request : requests) {
ApiResponse response = client.execute(request);
displayResponse(response);
}
// Step 6: Cleanup
client.close();
}Key Features:
- β Clean separation of concerns
- β Centralized error handling
- β Resource lifecycle management
- β Graceful shutdown
Purpose: Type-safe configuration data model
Core Responsibilities:
- Store application settings as Properties
- Provide type-safe getters with appropriate return types
- Validate required properties
- Support property existence checks
Key Methods:
| Method | Return Type | Description | Example |
|---|---|---|---|
get(String key) |
String | Get property value (null if missing) | cfg.get("http.timeout") β "30000" |
getRequired(String key) |
String | Get required property (throws if missing) | cfg.getRequired("api.url") |
getHttpTimeout() |
int | HTTP request timeout (milliseconds) | cfg.getHttpTimeout() β 30000 |
getHttpConnectionTimeout() |
int | HTTP connection timeout | cfg.getHttpConnectionTimeout() β 10000 |
getHttpReadTimeout() |
int | HTTP read timeout | cfg.getHttpReadTimeout() β 30000 |
getSslVerify() |
boolean | SSL certificate verification | cfg.getSslVerify() β true |
getProxyHost() |
String | Proxy server host | cfg.getProxyHost() β "proxy.company.com" |
getProxyPort() |
int | Proxy server port | cfg.getProxyPort() β 8080 |
getProxyUsername() |
String | Proxy authentication username | cfg.getProxyUsername() |
getProxyPassword() |
String | Proxy authentication password | cfg.getProxyPassword() |
getLoggingLevel() |
String | Logging level (INFO, DEBUG, etc.) | cfg.getLoggingLevel() β "INFO" |
getLoggingFilePath() |
String | Log file path | cfg.getLoggingFilePath() β "logs/api-proxy.log" |
getMaxRetryCount() |
int | Maximum retry attempts | cfg.getMaxRetryCount() β 3 |
getRetryDelay() |
int | Retry delay (milliseconds) | cfg.getRetryDelay() β 1000 |
getGraphQLEndpoint() |
String | GraphQL endpoint URL | cfg.getGraphQLEndpoint() β "http://localhost:8080/graphql" |
hasProperty(String key) |
boolean | Check if property exists | cfg.hasProperty("proxy.host") β false |
getPropertyKeys() |
Set | Get all property keys | cfg.getPropertyKeys() |
validateRequired(String... keys) |
void | Validate multiple required properties | cfg.validateRequired("api.url", "api.key") |
Usage Example:
Configuration cfg = ConfigurationLoader.load("application.properties");
// Get HTTP settings
int timeout = cfg.getHttpTimeout(); // 30000
int connTimeout = cfg.getHttpConnectionTimeout(); // 10000
int readTimeout = cfg.getHttpReadTimeout(); // 30000
// Get SSL settings
boolean verifySsl = cfg.getSslVerify(); // true
// Get proxy settings (if configured)
if (cfg.hasProperty("proxy.host")) {
String proxyHost = cfg.getProxyHost(); // proxy.company.com
int proxyPort = cfg.getProxyPort(); // 8080
String proxyUser = cfg.getProxyUsername(); // admin
}
// Get GraphQL settings
String graphqlUrl = cfg.getGraphQLEndpoint(); // http://localhost:8080/graphql
// Validate required properties
cfg.validateRequired("api.url", "api.key"); // Throws if missingPurpose: Multi-source configuration loader with intelligent fallback
Core Responsibilities:
- Load configuration from multiple sources with priority
- Merge properties from different sources
- Provide sensible default values
- Support hot-reload
- Validate configuration files
Loading Strategy (Priority: High β Low):
-
System Properties (Highest Priority)
java -Dhttp.timeout=60000 -jar apiproxy.jar
-
External File (via
-Dconfig.file)java -Dconfig.file=/etc/apiproxy/config.properties -jar apiproxy.jar
-
Classpath Resource (Default)
- Loads
application.propertiesfromsrc/main/resources/
- Loads
-
Environment Variables (Fallback)
export HTTP_TIMEOUT=60000 export SSL_VERIFY=false
-
Built-in Defaults (Last Resort)
- If all sources fail, uses sensible defaults
Key Methods:
| Method | Purpose | Example |
|---|---|---|
load(String propFile) |
Load with multi-source fallback | ConfigurationLoader.load("application.properties") |
loadFromFile(String path) |
Load from filesystem path | ConfigurationLoader.loadFromFile("/etc/app/config.properties") |
loadWithDefaults(String propFile) |
Load with automatic fallback to defaults (never fails) | ConfigurationLoader.loadWithDefaults("app.properties") |
fromProperties(Properties props) |
Create Configuration from Properties object | ConfigurationLoader.fromProperties(myProps) |
mergeProperties(String... propFiles) |
Merge multiple property files (later files override earlier) | ConfigurationLoader.mergeProperties("base.properties", "prod.properties") |
getDefaultProperties() |
Get default Properties object | ConfigurationLoader.getDefaultProperties() |
reload(String propFile) |
Hot-reload configuration (re-reads from source) | ConfigurationLoader.reload("application.properties") |
validateConfigFile(String propFile) |
Check if config file exists | ConfigurationLoader.validateConfigFile("app.properties") |
getConfigInfo() |
Get debug information about current config | ConfigurationLoader.getConfigInfo() |
Default Values:
# HTTP Settings
http.timeout=30000
http.connection.timeout=10000
http.read.timeout=30000
# SSL Settings
ssl.verify=true
# Logging
logging.level=INFO
logging.file.path=logs/api-proxy.log
# Security
security.max.retry.count=3
security.retry.delay=1000
# GraphQL
graphql.endpoint=http://localhost:8080/graphqlUsage Examples:
// Example 1: Standard loading (classpath β external β defaults)
Configuration cfg = ConfigurationLoader.load("application.properties");
// Example 2: Load from specific file
Configuration cfg = ConfigurationLoader.loadFromFile("/etc/myapp/config.properties");
// Example 3: Load with defaults (never fails, always returns Configuration)
Configuration cfg = ConfigurationLoader.loadWithDefaults("application.properties");
// If file missing β uses defaults
// If file exists β uses file values
// Example 4: Merge multiple configs (environment-specific overrides)
Configuration cfg = ConfigurationLoader.mergeProperties(
"application-base.properties", // Base configuration
"application-dev.properties", // Development overrides
"application-local.properties" // Local developer overrides
);
// Later files override earlier files for duplicate keys
// Example 5: Hot reload (useful for production config changes)
Configuration cfg = ConfigurationLoader.reload("application.properties");
// Re-reads configuration from source without restarting application
// Example 6: Create from Properties object
Properties props = new Properties();
props.setProperty("http.timeout", "60000");
props.setProperty("ssl.verify", "false");
Configuration cfg = ConfigurationLoader.fromProperties(props);Error Handling:
try {
Configuration cfg = ConfigurationLoader.load("missing-file.properties");
} catch (InputProcessingException e) {
// File not found or read error
logger.warn("Config file missing, using defaults");
Configuration cfg = ConfigurationLoader.loadWithDefaults("missing-file.properties");
}Purpose: Input coordinator and format router
Core Responsibilities:
- Detect input type from file extension or format
- Route to appropriate parser
- Validate file existence and readability
- Handle direct URL-based input
- Coordinate ApiRequest building
Input Detection Algorithm:
String firstArg = args[0];
String lowerCase = firstArg.toLowerCase();
if (lowerCase.endsWith(".csv")) {
// CSV file β CsvParser
return new CsvParser().parse(firstArg);
} else if (lowerCase.endsWith(".json")) {
// JSON file β JsonParser
return new JsonParser().parse(firstArg);
} else if (lowerCase.endsWith(".txt")) {
// Text file β TextParser
return new TextParser().parse(firstArg);
} else {
// Direct URL with parameters β ParameterParser
return ParameterParser.parse(args);
}File Validation:
File file = new File(filePath);
// Check existence
if (!file.exists()) {
throw new InputProcessingException("Input file does not exist: " + filePath);
}
// Check readability
if (!file.canRead()) {
throw new InputProcessingException("Cannot read input file: " + filePath);
}Key Features:
- β Automatic format detection
- β File validation before parsing
- β Detailed error context (file path, line number)
- β Support for both file and direct parameter input
Purpose: Parse command-line arguments into ApiRequest objects
Supported Input Formats:
Format 1: Simple URL Only
https://api.example.com/usersNote: Requires additional method=GET parameter
Format 2: Method + URL
GET https://api.example.com/users
POST https://api.example.com/posts
PUT https://api.example.com/posts/1
DELETE https://api.example.com/posts/1
PATCH https://api.example.com/posts/1Format 3: Key-Value Parameters
method=GET url=https://api.example.com/users
method=POST url=https://api.example.com/posts body='{"name":"John"}'Supported Parameters (18+):
| Category | Parameters | Format | Example |
|---|---|---|---|
| Basic | method |
method=METHOD |
method=POST |
url |
url=https://... |
url=https://api.example.com/users |
|
name |
name=RequestName |
name=GetUserRequest |
|
| Headers | header |
header:Key=Value |
header:Content-Type=application/json |
contentType |
contentType=type |
contentType=application/xml |
|
accept |
accept=type |
accept=application/json |
|
| Query | query |
query:key=value |
query:page=1 query:limit=10 |
| Body | body |
body=content |
body='{"name":"John"}' |
formData |
formData=data |
formData=name=John&age=30 |
|
multipartData |
multipartData=data |
multipartData=file@/path/to/file |
|
| Auth | authType |
authType=TYPE |
authType=BASIC |
authUsername |
authUsername=user |
authUsername=admin |
|
authPassword |
authPassword=pass |
authPassword=secret123 |
|
| Network | timeout |
timeout=milliseconds |
timeout=5000 |
followRedirects |
followRedirects=bool |
followRedirects=true |
|
verifySSL |
verifySSL=bool |
verifySSL=false |
|
proxy |
proxy=url |
proxy=http://proxy.company.com:8080 |
|
| Other | cookies |
cookies=value |
cookies=session=abc123; token=xyz |
Parsing Logic:
// 1. Detect format
if (args[0].matches("^(GET|POST|PUT|DELETE|PATCH)\\s+https?://.*")) {
// Format: METHOD URL
String[] parts = args[0].split("\\s+", 2);
method = parts[0];
url = parts[1];
} else if (args[0].startsWith("method=")) {
// Format: Key-value pairs
for (String arg : args) {
if (arg.startsWith("method=")) method = arg.substring(7);
else if (arg.startsWith("url=")) url = arg.substring(4);
// ... parse other parameters
}
} else {
// Format: Simple URL (method must be in args[1..n])
url = args[0];
// Search for method= in remaining args
}
// 2. Build ApiRequest
ApiRequest request = ApiRequest.newBuilder()
.url(new URL(url))
.method(method)
// ... set other properties
.build();Usage Examples:
# Example 1: Simple GET request
GET https://jsonplaceholder.typicode.com/users/1
# Example 2: POST with JSON body
POST https://api.example.com/users \
header:Content-Type=application/json \
body='{"name":"John Doe","email":"john@example.com","age":30}'
# Example 3: GET with authentication
GET https://api.example.com/protected \
authType=BASIC \
authUsername=admin \
authPassword=secret123
# Example 4: GET with multiple query parameters
GET https://api.example.com/search \
query:q=java \
query:category=programming \
query:limit=10 \
query:offset=0
# Example 5: POST with multiple headers
POST https://api.example.com/users \
header:Content-Type=application/json \
header:Authorization=Bearer eyJhbGc... \
header:X-Request-ID=12345 \
body='{"name":"Jane"}'
# Example 6: Request with timeout and SSL settings
GET https://api.example.com/slow-endpoint \
timeout=60000 \
verifySSL=false
# Example 7: Request through proxy
GET https://api.example.com/users \
proxy=http://proxy.company.com:8080 \
timeout=10000
# Example 8: Complete example with all features
POST https://api.example.com/users \
name=CreateUserRequest \
header:Content-Type=application/json \
header:Authorization=Bearer token123 \
header:X-Client-Version=1.0 \
query:notify=true \
query:source=admin-panel \
body='{"name":"Alice","email":"alice@example.com","role":"admin"}' \
timeout=10000 \
verifySSL=true \
followRedirects=trueTest Coverage:
- β 17/17 tests passed
- β All parameter types tested
- β All input formats tested
- β Edge cases handled (empty values, special characters, quotes)
Purpose: Parse text files containing HTTP request definitions
Supported Input Formats:
Format 1: Simple Format (METHOD URL + Headers/Body)
GET https://api.example.com/users/1
POST https://api.example.com/posts
Content-Type: application/json
Authorization: Bearer token123
{"title":"New Post","body":"This is content","userId":1}
PUT https://api.example.com/posts/1
Content-Type: application/json
{"id":1,"title":"Updated Title","body":"Updated content"}
DELETE https://api.example.com/posts/1
Format 2: cURL Commands
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-d '{"name":"John","email":"john@example.com"}'
curl -X GET https://api.example.com/users/1 \
-H "Accept: application/json"
curl -X DELETE https://api.example.com/users/1 \
-H "Authorization: Bearer token123"
Format 3: HTTP Message Format
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
Content-Length: 58
{"name":"John","email":"john@example.com","age":30}
GET /users/1 HTTP/1.1
Host: api.example.com
Accept: application/json
Parsing Logic:
// 1. Split file into request blocks (separated by blank lines)
List<String> blocks = splitIntoBlocks(fileContent);
// 2. For each block, detect format and parse
for (String block : blocks) {
if (block.startsWith("curl")) {
// cURL format
ApiRequest request = parseCurlCommand(block);
} else if (block.matches("^(GET|POST|PUT|DELETE|PATCH) /.*HTTP/1\\.1")) {
// HTTP message format
ApiRequest request = parseHttpMessage(block);
} else {
// Simple format
ApiRequest request = parseSimpleFormat(block);
}
requests.add(request);
}Block Separation:
Request 1
Request 1 headers
Request 1 body
β Blank line separates blocks
Request 2
Request 2 headers
Request 2 body
β Blank line
Request 3
Constructor Options:
// Default constructor (lenient mode, fail on first error)
TextParser parser = new TextParser();
// Strict mode (enforces format validation)
// ignoreInvalidBlocks=false β stops on first error
TextParser parser = new TextParser(true, false);
// Lenient mode with error skipping
// ignoreInvalidBlocks=true β skips invalid blocks, continues parsing
TextParser parser = new TextParser(false, true);Key Features:
- β Multiple request blocks per file
- β Blank line separation
- β Header parsing (single and multiple)
- β Multi-line body support (JSON, XML, plain text)
- β Format auto-detection
- β Query parameters in URL
- β
Comment lines ignored (starts with
#) - β Strict mode option
- β Error recovery option
Example File (requests.txt):
GET https://jsonplaceholder.typicode.com/users/1
POST https://jsonplaceholder.typicode.com/posts
Content-Type: application/json
Accept: application/json
{
"title": "My New Post",
"body": "This is the content of my post",
"userId": 1
}
PUT https://jsonplaceholder.typicode.com/posts/1
Content-Type: application/json
{
"id": 1,
"title": "Updated Post Title",
"body": "Updated post content",
"userId": 1
}
DELETE https://jsonplaceholder.typicode.com/posts/1
GET https://jsonplaceholder.typicode.com/comments?postId=1&_limit=5
Accept: application/json
Execution:
java -jar apiproxy-1.0.0-jar-with-dependencies.jar requests.txtTest Coverage:
- β 18/18 tests passed
- β All 3 formats tested
- β Multi-line bodies tested
- β Multiple requests tested
- β Edge cases handled
Purpose: Parse CSV files with request definitions
CSV Format:
name,method,url,headers,body,timeout
Get User 1,GET,https://api.example.com/users/1,,
Get User 2,GET,https://api.example.com/users/2,,30000
Create User,POST,https://api.example.com/users,Content-Type:application/json,"{""name"":""John"",""email"":""john@example.com""}",5000
Update User,PUT,https://api.example.com/users/1,Content-Type:application/json,"{""name"":""John Updated""}",
Delete User,DELETE,https://api.example.com/users/1,Authorization:Bearer token123,,
Column Mapping:
| Column | Required | Description | Example |
|---|---|---|---|
name |
Optional | Request identifier | Get User Request |
method |
Required | HTTP method | GET, POST, PUT, DELETE |
url |
Required | Target URL | https://api.example.com/users |
headers |
Optional | Headers (colon-separated, pipe for multiple) | `Content-Type:application/json |
body |
Optional | Request body (JSON escaped) | "{""name"":""John""}" |
timeout |
Optional | Timeout in milliseconds | 5000 |
Key Features:
- β Header row parsing
- β Multiple request rows
- β Empty value handling
- β Quote handling for JSON bodies
- β Flexible column order
- β Optional columns
Purpose: Parse JSON files with request arrays
JSON Format:
[
{
"name": "Get User",
"method": "GET",
"url": "https://api.example.com/users/1",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer token123"
},
"timeout": 5000
},
{
"name": "Create User",
"method": "POST",
"url": "https://api.example.com/users",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
"body": "{\"name\":\"John\",\"email\":\"john@example.com\"}"
},
{
"name": "Update User",
"method": "PUT",
"url": "https://api.example.com/users/1",
"headers": {
"Content-Type": "application/json"
},
"body": "{\"name\":\"John Updated\"}"
}
]Key Features:
- β JSON array of requests
- β Nested header objects
- β Optional fields
- β JSON validation
Purpose: Immutable request data model with builder
Why Builder Pattern?
- β Immutable β Thread-safe
- β Fluent API β Readable code
- β Optional Parameters β Only set what you need
- β Validation β Validate at build time
Properties:
| Property | Type | Description |
|---|---|---|
name |
String | Request identifier (optional) |
url |
URL | Target URL (required) |
method |
String | HTTP method (required) |
headers |
Map<String, String> | HTTP headers |
queryParams |
Map<String, String> | Query parameters |
body |
String | Request body |
formData |
String | Form data |
multipartData |
String | Multipart data |
cookies |
String | Cookies |
authType |
String | Authentication type |
authUsername |
String | Auth username |
authPassword |
String | Auth password |
timeout |
int | Timeout (milliseconds) |
followRedirects |
boolean | Follow HTTP redirects |
verifySSL |
boolean | Verify SSL certificates |
proxy |
String | Proxy URL |
metadata |
Metadata | Additional metadata |
Usage:
// Simple request
ApiRequest request = ApiRequest.newBuilder()
.url(new URL("https://api.example.com/users"))
.method("GET")
.build();
// Complex request
ApiRequest request = ApiRequest.newBuilder()
.name("CreateUserRequest")
.url(new URL("https://api.example.com/users"))
.method("POST")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token123")
.addHeader("X-Request-ID", "12345")
.addQueryParam("notify", "true")
.addQueryParam("source", "admin")
.body("{\"name\":\"John\",\"email\":\"john@example.com\"}")
.timeout(10000)
.verifySSL(true)
.followRedirects(true)
.build();
// With authentication
ApiRequest request = ApiRequest.newBuilder()
.url(new URL("https://api.example.com/protected"))
.method("GET")
.authType("BASIC")
.authUsername("admin")
.authPassword("secret123")
.build();Purpose: Immutable response data model
Properties:
| Property | Type | Description |
|---|---|---|
statusCode |
int | HTTP status code (200, 404, 500, etc.) |
statusMessage |
String | Status message ("OK", "Not Found", etc.) |
headers |
Map<String, String> | Response headers |
body |
String | Response body |
executionTime |
long | Time taken (milliseconds) |
metadata |
Metadata | Request metadata |
error |
ErrorResponse | Error details (if failed) |
Usage:
ApiResponse response = ApiResponse.newBuilder()
.statusCode(200)
.statusMessage("OK")
.addHeader("Content-Type", "application/json")
.addHeader("Content-Length", "1234")
.body("{\"id\":1,\"name\":\"John\"}")
.executionTime(450)
.build();
// Access response data
System.out.println("Status: " + response.getStatusCode()); // 200
System.out.println("Message: " + response.getStatusMessage()); // OK
System.out.println("Body: " + response.getBody()); // {"id":1,...}
System.out.println("Time: " + response.getExecutionTime() + "ms"); // 450ms
// Check headers
String contentType = response.getHeaders().get("Content-Type"); // application/jsonPurpose: Store contextual information about requests/responses
Properties:
public class Metadata {
private String requestId; // Unique request ID (UUID)
private String executionId; // Batch execution ID
private LocalDateTime timestamp; // Execution timestamp
private String sourceFile; // Source file path (if from file)
private int lineNumber; // Line number (if from file)
private Map<String, Object> custom; // Custom metadata
}Purpose: Structured error information
Properties:
public class ErrorResponse {
private String errorCode; // Error code (INPUT_ERROR, NETWORK_ERROR)
private String userMessage; // User-friendly message
private String technicalMessage; // Technical details
private String stackTrace; // Stack trace (if available)
private LocalDateTime timestamp; // Error timestamp
}Example:
{
"errorCode": "NETWORK_TIMEOUT",
"userMessage": "Request timed out after 5000ms",
"technicalMessage": "Connect to api.example.com:443 timed out",
"timestamp": "2025-10-16T10:30:00Z"
}Purpose: Manage HTTP client lifecycle and execute requests
Responsibilities:
- Create and configure CloseableHttpClient instances
- Manage connection pooling
- Apply SSL/TLS configuration
- Set up proxy configuration
- Execute HTTP requests
- Handle timeouts and retries
Configuration:
public HttpClientManager(Configuration config) {
// Connection pool
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(config.getHttpMaxConnections()); // Default: 100
cm.setDefaultMaxPerRoute(config.getHttpMaxPerRoute()); // Default: 20
// Request config
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(config.getHttpConnectionTimeout()) // Default: 10000ms
.setSocketTimeout(config.getHttpReadTimeout()) // Default: 30000ms
.setConnectionRequestTimeout(config.getHttpTimeout()) // Default: 30000ms
.build();
// SSL context
SSLContext sslContext = SSLUtil.createSSLContext(config);
// Build HTTP client
this.httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultRequestConfig(requestConfig)
.setSSLContext(sslContext)
.build();
}Request Execution:
public ApiResponse execute(ApiRequest request) {
long startTime = System.currentTimeMillis();
// 1. Build HTTP request
HttpUriRequest httpRequest = RequestBuilder.build(request);
// 2. Execute request
try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) {
// 3. Process response
ApiResponse response = ResponseProcessor.process(httpResponse, request);
// 4. Set execution time
long executionTime = System.currentTimeMillis() - startTime;
response.setExecutionTime(executionTime);
return response;
} catch (IOException e) {
throw new NetworkException("Request execution failed", e);
}
}Purpose: Convert ApiRequest to Apache HttpClient request
Method Mapping:
switch (request.getMethod().toUpperCase()) {
case "GET":
httpRequest = new HttpGet(request.getUrl().toURI());
break;
case "POST":
HttpPost post = new HttpPost(request.getUrl().toURI());
if (request.getBody() != null) {
post.setEntity(new StringEntity(request.getBody(), ContentType.APPLICATION_JSON));
}
httpRequest = post;
break;
case "PUT":
HttpPut put = new HttpPut(request.getUrl().toURI());
if (request.getBody() != null) {
put.setEntity(new StringEntity(request.getBody(), ContentType.APPLICATION_JSON));
}
httpRequest = put;
break;
case "DELETE":
httpRequest = new HttpDelete(request.getUrl().toURI());
break;
case "PATCH":
HttpPatch patch = new HttpPatch(request.getUrl().toURI());
if (request.getBody() != null) {
patch.setEntity(new StringEntity(request.getBody(), ContentType.APPLICATION_JSON));
}
httpRequest = patch;
break;
}
// Add headers
for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
httpRequest.addHeader(header.getKey(), header.getValue());
}
// Add authentication
if (request.getAuthType() != null) {
String authHeader = buildAuthHeader(request);
httpRequest.addHeader("Authorization", authHeader);
}Purpose: Process HTTP responses and create ApiResponse
Processing Logic:
public static ApiResponse process(CloseableHttpResponse httpResponse, ApiRequest request) {
ApiResponse.Builder builder = ApiResponse.newBuilder();
// Status
StatusLine statusLine = httpResponse.getStatusLine();
builder.statusCode(statusLine.getStatusCode());
builder.statusMessage(statusLine.getReasonPhrase());
// Headers
for (Header header : httpResponse.getAllHeaders()) {
builder.addHeader(header.getName(), header.getValue());
}
// Body
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
String body = EntityUtils.toString(entity, StandardCharsets.UTF_8);
builder.body(body);
}
// Metadata
Metadata metadata = new Metadata();
metadata.setRequestId(request.getMetadata().getRequestId());
metadata.setTimestamp(LocalDateTime.now());
builder.metadata(metadata);
return builder.build();
}Purpose: REST API specific handling
Features:
- JSON content-type negotiation
- RESTful error handling
- JSON request/response parsing
Purpose: SOAP API specific handling
Features:
- SOAP envelope creation
- SOAP action headers
- XML request/response handling
Purpose: GraphQL API specific handling
Features:
- GraphQL query formatting
- Variables handling
- GraphQL error parsing
Purpose: SSL/TLS configuration and certificate management
SSL Context Creation:
public static SSLContext createSSLContext(Configuration config) throws Exception {
if (config.getSslVerify()) {
// Standard SSL with certificate validation
return SSLContexts.createDefault();
} else {
// Trust all certificates (development/testing only!)
SSLContextBuilder builder = SSLContextBuilder.create();
builder.loadTrustMaterial((chain, authType) -> true);
return builder.build();
}
}Purpose: Centralized logging management with SLF4J
Features:
- Logger creation
- MDC (Mapped Diagnostic Context) support
- Structured logging
- Log level management
Usage:
Logger logger = LoggingUtil.getLogger(MyClass.class);
LoggingUtil.info(logger, "Processing request: {}", requestId);
LoggingUtil.warn(logger, "Slow response: {}ms", executionTime);
LoggingUtil.error(logger, "Request failed: {}", error.getMessage());Purpose: Date/time formatting and parsing utilities
Purpose: String manipulation utilities
Features:
- Trimming
- Quote removal
- Null-safe operations
- URL encoding/decoding
Purpose: Input validation utilities
Features:
- URL validation
- HTTP method validation
- Numeric validation
- Required field validation
Purpose: Base exception for all application errors
Properties:
public class ApiProxyException extends RuntimeException {
private String errorCode; // ERROR_CODE
private String userMessage; // User-friendly message
private String technicalMessage; // Technical details
private LocalDateTime timestamp; // Error timestamp
private Map<String, Object> context; // Error context
}Purpose: Input parsing and validation errors
Factory Methods:
// For file errors
InputProcessingException.forFile(String filePath, int lineNumber, String message);
// For missing files
InputProcessingException.forMissingFile(String filePath);
// Generic
new InputProcessingException(String message);Purpose: Network and HTTP errors
Purpose: Validation errors
See ParameterParser section for details.
See TextParser section for details.
See CsvParser section for details.
See JsonParser section for details.
See Configuration Layer section for complete details.
# Clean and compile
mvn clean compile
# Run tests
mvn test
# Package JAR (with tests)
mvn clean package
# Package JAR (skip tests)
mvn clean package -DskipTestsOutput Files:
target/apiproxy-1.0.0.jar- Main JARtarget/apiproxy-1.0.0-jar-with-dependencies.jar- Executable JAR with all dependencies
java -jar target/apiproxy-1.0.0-jar-with-dependencies.jar \
https://jsonplaceholder.typicode.com/users/1 \
method=GETOutput:
=== Executing Request ===
URL: https://jsonplaceholder.typicode.com/users/1
Method: GET
Status: 200
Response Body: {
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
...
}
=========================
java -jar target/apiproxy-1.0.0-jar-with-dependencies.jar \
https://jsonplaceholder.typicode.com/posts \
method=POST \
header:Content-Type=application/json \
body='{"title":"Test Post","body":"This is a test","userId":1}'java -jar target/apiproxy-1.0.0-jar-with-dependencies.jar \
https://jsonplaceholder.typicode.com/comments \
method=GET \
query:postId=1 \
query:_limit=5java -jar target/apiproxy-1.0.0-jar-with-dependencies.jar \
https://httpbin.org/basic-auth/user/pass \
method=GET \
authType=BASIC \
authUsername=user \
authPassword=passjava -jar target/apiproxy-1.0.0-jar-with-dependencies.jar requests.txtjava -jar target/apiproxy-1.0.0-jar-with-dependencies.jar requests.csvSee Component Breakdown for complete API documentation.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TEST RESULTS SUMMARY β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Unit Tests: 35/35 PASSED β
(100%) β
β Integration Tests: 16/16 PASSED β
(100%) β
β Total Tests: 51/51 PASSED β
(100%) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β ParameterParser: 17 tests β
WORKING β
β TextParser: 18 tests β
WORKING β
β CsvParser: 3 tests β
WORKING β
β Configuration: 5 tests β
WORKING β
β InputProcessor: 8 tests β
WORKING β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Run all tests
mvn test
# Run specific test class
mvn test -Dtest=ParameterParserTest
# Run specific test method
mvn test -Dtest=ParameterParserTest#testSimpleUrl| Component | Tests | Coverage | Status |
|---|---|---|---|
| ParameterParser | 17 | 100% | β |
| TextParser | 18 | 100% | β |
| CsvParser | 3 | 100% | β |
| Configuration | 5 | 100% | β |
| ConfigurationLoader | 5 | 100% | β |
| InputProcessor | 8 | 100% | β |
| Total | 56 | 100% | β |
# Build for production
mvn clean package -DskipTests
# Run with external configuration
java -Dconfig.file=/etc/apiproxy/application.properties \
-jar apiproxy-1.0.0-jar-with-dependencies.jar requests.txt
# Run with JVM options
java -Xmx512m -Xms256m \
-Dconfig.file=/etc/apiproxy/application.properties \
-jar apiproxy-1.0.0-jar-with-dependencies.jar requests.txtDockerfile:
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/apiproxy-1.0.0-jar-with-dependencies.jar apiproxy.jar
COPY application.properties /etc/apiproxy/application.properties
ENTRYPOINT ["java", "-Dconfig.file=/etc/apiproxy/application.properties", "-jar", "apiproxy.jar"]Build & Run:
# Build image
docker build -t apiproxy:1.0.0 .
# Run container
docker run -v /path/to/requests.txt:/data/requests.txt \
apiproxy:1.0.0 /data/requests.txtSolution: Add method=GET parameter
# β
Correct
java -jar apiproxy.jar https://api.example.com/users method=GET
# β Wrong
java -jar apiproxy.jar https://api.example.com/usersSolution: Ensure URL is absolute (includes http:// or https://)
# β
Correct
https://api.example.com/users
# β Wrong
api.example.com/usersSolution: Use absolute path or verify file exists
# Check file exists
ls -la requests.txt
# Use absolute path
java -jar apiproxy.jar /full/path/to/requests.txt- Apache HttpClient for HTTP functionality
- SLF4J for logging abstraction
- Jackson for JSON processing
- JUnit 5 for testing framework
This project is licensed under the MIT License.
Version: 1.0.0
Last Updated: October 16, 2025
Status: β
Production Ready
Quality: βββββ (5/5)
Test Coverage: 100% (51/51 tests passed)