Skip to content

Commit 7a7e81b

Browse files
Merge pull request #16 from conductor-oss/hedge
Few changes to HelloController
2 parents 69aec76 + 8c87aa4 commit 7a7e81b

4 files changed

Lines changed: 178 additions & 3 deletions

File tree

src/main/java/org/conductoross/controller/CircuitBreakerController.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,16 @@ public String faultInjection(
108108
@ApiResponse(responseCode = "200", description = "Successful operation"),
109109
@ApiResponse(responseCode = "Multiple", description = "Various status codes as specified in pattern")
110110
})
111+
111112
@GetMapping("/pattern")
112113
public ResponseEntity<String> patternResponse(
113114
@Parameter(description = "Comma-separated list of response codes", required = false,
114-
example = "200,500,500,500,200,200,200")
115-
@RequestParam(defaultValue = "200,500,500,500,200,200,200") String responseCode) {
115+
example = "500,500,500,200")
116+
@RequestParam(defaultValue = "500,500,500,200") String statusCode,
117+
118+
@Parameter(description = "Request identifier for pattern tracking", required = true)
119+
@RequestParam String requestId) {
116120

117-
return circuitBreakerService.simulatePatternResponse(responseCode);
121+
return circuitBreakerService.simulatePatternResponse(statusCode, requestId);
118122
}
119123
}

src/main/java/org/conductoross/controller/HelloController.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
import org.springframework.http.ResponseEntity;
1414
import org.springframework.web.bind.annotation.*;
1515

16+
import java.util.HashMap;
17+
import java.util.Map;
18+
1619
@RestController
1720
@RequestMapping("/api/hello")
1821
@Tag(name = "HttpBin Service", description = "HttpBin is a microservice designed for testing HTTP-related scenarios")
1922
public class HelloController {
2023

2124
private final HelloService helloService;
25+
private static final String EXPECTED_SECRET = "blahSecret";
2226

2327
public HelloController(HelloService helloService) {
2428
this.helloService = helloService;
@@ -180,4 +184,68 @@ public String handleHedgeRequest(
180184
// Process the request through service
181185
return helloService.processHedgeRequest(number, delayMs);
182186
}
187+
188+
@GetMapping("/validate-secret")
189+
public ResponseEntity<?> validateSecret(
190+
@RequestHeader Map<String, String> headers) {
191+
192+
String authHeader = headers.getOrDefault("X-Authorization",
193+
headers.getOrDefault("x-authorization", null));
194+
195+
Map<String, Object> response = new HashMap<>();
196+
response.put("received_headers", headers);
197+
response.put("auth_status", EXPECTED_SECRET.equals(authHeader) ? "valid" : "invalid");
198+
response.put("timestamp", System.currentTimeMillis());
199+
200+
201+
if (EXPECTED_SECRET.equals(authHeader)) {
202+
response.put("message", "Secret was correctly substituted");
203+
return ResponseEntity.ok(response);
204+
} else {
205+
response.put("message", "Invalid or missing secret");
206+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
207+
}
208+
}
209+
@Operation(summary = "Hello with sequential status codes",
210+
description = "Returns responses with status codes in the specified sequence. " +
211+
"Each subsequent call will return the next status code in the sequence.")
212+
@ApiResponses(value = {
213+
@ApiResponse(responseCode = "200", description = "Successful operation"),
214+
@ApiResponse(responseCode = "400", description = "Bad Request - Invalid status codes"),
215+
@ApiResponse(responseCode = "XXX", description = "Various status codes as specified in sequence")
216+
})
217+
@GetMapping("/sequential-status")
218+
public ResponseEntity<Map<String, Object>> sayHelloWithSequentialStatus(
219+
@Parameter(description = "Name to be greeted", required = true, example = "John")
220+
@RequestParam String name,
221+
222+
@Parameter(description = "Comma-separated list of status codes to cycle through",
223+
required = true,
224+
example = "200,400,500,304,200")
225+
@RequestParam String codes) {
226+
227+
try {
228+
// Parse the status codes
229+
String[] statusCodeStrings = codes.split(",");
230+
int[] statusCodes = new int[statusCodeStrings.length];
231+
232+
for (int i = 0; i < statusCodeStrings.length; i++) {
233+
statusCodes[i] = Integer.parseInt(statusCodeStrings[i].trim());
234+
// Validate status code range
235+
if (statusCodes[i] < 100 || statusCodes[i] > 599) {
236+
Map<String, Object> errorResponse = new HashMap<>();
237+
errorResponse.put("error", "Invalid HTTP status code: " + statusCodes[i] + ". Valid range is 100-599.");
238+
return ResponseEntity.badRequest().body(errorResponse);
239+
}
240+
}
241+
242+
// Delegate to service to handle the sequential logic
243+
return helloService.sayHelloWithSequentialStatus(name, statusCodes);
244+
245+
} catch (NumberFormatException e) {
246+
Map<String, Object> errorResponse = new HashMap<>();
247+
errorResponse.put("error", "Invalid status code format. Use comma-separated integers.");
248+
return ResponseEntity.badRequest().body(errorResponse);
249+
}
250+
}
183251
}

src/main/java/org/conductoross/service/CircuitBreakerService.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.*;
99
import java.util.concurrent.ConcurrentHashMap;
1010
import java.util.concurrent.atomic.AtomicInteger;
11+
import java.util.concurrent.locks.ReentrantLock;
1112
import java.util.stream.Collectors;
1213

1314
@Service
@@ -21,6 +22,21 @@ public class CircuitBreakerService {
2122
// Track the current delay for progressive degradation
2223
private final ConcurrentHashMap<String, Integer> currentDelays = new ConcurrentHashMap<>();
2324

25+
// Map: requestId -> (pattern list, lock)
26+
private final Map<String, PatternEntry> patternMap = new ConcurrentHashMap<>();
27+
28+
/**
29+
* Thread-safe class to hold pattern and lock
30+
*/
31+
private static class PatternEntry {
32+
final LinkedList<Integer> pattern;
33+
final ReentrantLock lock = new ReentrantLock();
34+
35+
PatternEntry(LinkedList<Integer> pattern) {
36+
this.pattern = pattern;
37+
}
38+
}
39+
2440
/**
2541
* Simulates a service with time-based lifecycle phases
2642
* (normal -> degrading -> failing -> recovering -> normal)
@@ -183,6 +199,10 @@ public ResponseEntity<String> simulatePatternResponse(String responseCodePattern
183199
int position = counter.getAndIncrement() % pattern.size();
184200
int statusCode = pattern.get(position);
185201

202+
if (statusCode == 200) {
203+
counter.set(0);
204+
}
205+
186206
// Create a meaningful response
187207
String responseBody = String.format(
188208
"Pattern response: step %d of %d, returning status %d",
@@ -339,4 +359,55 @@ public String getExpectedCircuitBreakerState() {
339359
}
340360
}
341361
}
362+
363+
/**
364+
* Returns responses according to a specified pattern of status codes
365+
* Thread-safe implementation that handles concurrent requests with the same requestId
366+
*/
367+
public ResponseEntity<String> simulatePatternResponse(String responseCodePattern, String requestId) {
368+
// Get or create pattern entry for this requestId
369+
PatternEntry entry = patternMap.computeIfAbsent(requestId, k -> {
370+
LinkedList<Integer> codes = new LinkedList<>();
371+
try {
372+
Arrays.stream(responseCodePattern.split(","))
373+
.map(String::trim)
374+
.map(Integer::parseInt)
375+
.forEach(codes::add);
376+
377+
if (codes.isEmpty()) {
378+
return null; // Will trigger bad request response
379+
}
380+
381+
return new PatternEntry(codes);
382+
} catch (NumberFormatException e) {
383+
return null; // Will trigger bad request response
384+
}
385+
});
386+
387+
// Handle validation errors
388+
if (entry == null) {
389+
return ResponseEntity.badRequest().body("Invalid pattern format or empty pattern");
390+
}
391+
392+
// Use lock to ensure thread safety when modifying the pattern list
393+
entry.lock.lock();
394+
try {
395+
// Get the status code from the front of the list
396+
int statusCode = entry.pattern.isEmpty() ? 500 : entry.pattern.removeFirst();
397+
398+
// Create a response message
399+
String responseBody = String.format(
400+
"Pattern response for request %s: returning status %d, remaining pattern: %s",
401+
requestId, statusCode, entry.pattern);
402+
403+
// If we've returned a 200 or the list is now empty, remove from the map
404+
if (statusCode == 200 || entry.pattern.isEmpty()) {
405+
patternMap.remove(requestId);
406+
}
407+
System.out.println("For request ID:"+requestId+" response code is "+ statusCode);
408+
return ResponseEntity.status(statusCode).body(responseBody);
409+
} finally {
410+
entry.lock.unlock();
411+
}
412+
}
342413
}

src/main/java/org/conductoross/service/HelloService.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
import org.conductoross.exception.ExternalDependencyException;
44
import org.conductoross.exception.OverloadedServiceException;
5+
import org.springframework.http.ResponseEntity;
56
import org.springframework.stereotype.Service;
67

8+
import java.util.Arrays;
9+
import java.util.HashMap;
10+
import java.util.Map;
711
import java.util.Random;
812
import java.util.concurrent.ConcurrentHashMap;
913
import java.util.concurrent.Semaphore;
@@ -219,4 +223,32 @@ public String processRequest(String clientIp, int requestedDelay) {
219223
// Return the response
220224
return "Hello, HedgeReq. Seq is #" + currentSeq + ". Returning response after " + actualDelay + "ms.";
221225
}
226+
227+
private final Map<String, Integer> sequentialCounters = new ConcurrentHashMap<>();
228+
229+
public ResponseEntity<Map<String, Object>> sayHelloWithSequentialStatus(String name, int[] statusCodes) {
230+
// Create a unique key for this sequence (you might want to include more context)
231+
String sequenceKey = Arrays.toString(statusCodes);
232+
233+
// Get current position in sequence (thread-safe)
234+
int currentIndex = sequentialCounters.compute(sequenceKey, (key, currentCount) -> {
235+
if (currentCount == null) {
236+
return 0;
237+
}
238+
return (currentCount + 1) % statusCodes.length;
239+
});
240+
241+
int statusCode = statusCodes[currentIndex];
242+
243+
// Create response body
244+
Map<String, Object> response = new HashMap<>();
245+
response.put("message", "Hello, " + name + "!");
246+
response.put("current_status_code", statusCode);
247+
response.put("sequence_position", currentIndex + 1);
248+
response.put("total_codes_in_sequence", statusCodes.length);
249+
response.put("full_sequence", statusCodes);
250+
response.put("timestamp", System.currentTimeMillis());
251+
252+
return ResponseEntity.status(statusCode).body(response);
253+
}
222254
}

0 commit comments

Comments
 (0)