REST
restapi3RestCrudApi
- Path
- pkg12restapi/restapi3RestCrudApi.java
- Package
- pkg12restapi
- Study order
- 3
- Run
- Single-file source launch
- Command
- java pkg12restapi/restapi3RestCrudApi.java
- Dependencies
- com.sun.net.httpserver.HttpExchange, com.sun.net.httpserver.HttpServer
There is no in-browser runner. This is the file from the curriculum, unchanged.
1package pkg12restapi;2 3import com.sun.net.httpserver.HttpExchange;4import com.sun.net.httpserver.HttpServer;5import java.io.IOException;6import java.net.InetSocketAddress;7import java.net.URI;8import java.net.http.HttpClient;9import java.net.http.HttpRequest;10import java.net.http.HttpResponse;11import java.nio.charset.StandardCharsets;12import java.util.Map;13import java.util.concurrent.ConcurrentHashMap;14import java.util.concurrent.atomic.AtomicInteger;15 16/*17 * restapi3RestCrudApi.java18 * ------------------------19 * A complete in-memory REST CRUD API: routing by method + path, JSON in/out.20 *21 * DEFINITION:22 * This is restapi2 taken to a real resource: /users supports23 * GET (list), POST (create), GET /users/{id} (read), DELETE /users/{id}.24 * Data lives in a thread-safe map. A client then exercises every route.25 *26 * KEY POINTS:27 * - Dispatch on getRequestMethod() + path segments.28 * - Return correct status codes (200/201/404) and JSON bodies.29 * - Use a ConcurrentHashMap because the server is multi-threaded.30 * - Everything runs in-process over loopback — no external services.31 */32public class restapi3RestCrudApi {33 34 static final Map<Integer, String> USERS = new ConcurrentHashMap<>();35 static final AtomicInteger SEQ = new AtomicInteger();36 37 public static void main(String[] args) throws Exception {38 HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);39 server.createContext("/users", restapi3RestCrudApi::handleUsers);40 server.start();41 String base = "http://127.0.0.1:" + server.getAddress().getPort();42 43 HttpClient client = HttpClient.newHttpClient();44 45 // CREATE two users (POST)46 send(client, "POST", base + "/users", "{\"name\":\"Ada\"}");47 send(client, "POST", base + "/users", "{\"name\":\"Linus\"}");48 // LIST (GET collection)49 send(client, "GET", base + "/users", null);50 // READ one (GET item)51 send(client, "GET", base + "/users/1", null);52 // READ missing -> 40453 send(client, "GET", base + "/users/999", null);54 // DELETE one55 send(client, "DELETE", base + "/users/1", null);56 // LIST again57 send(client, "GET", base + "/users", null);58 59 server.stop(0);60 }61 62 static void handleUsers(HttpExchange ex) throws IOException {63 String method = ex.getRequestMethod();64 String path = ex.getRequestURI().getPath(); // /users or /users/{id}65 String[] parts = path.split("/"); // ["", "users", "{id}"?]66 Integer id = parts.length == 3 ? tryParse(parts[2]) : null;67 68 if (method.equals("POST") && id == null) { // create69 String body = new String(ex.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);70 String name = extractName(body);71 int newId = SEQ.incrementAndGet();72 USERS.put(newId, name);73 respond(ex, 201, "{\"id\":" + newId + ",\"name\":\"" + name + "\"}");74 } else if (method.equals("GET") && id == null) { // list75 respond(ex, 200, toJsonArray());76 } else if (method.equals("GET")) { // read one77 String name = USERS.get(id);78 if (name == null) respond(ex, 404, "{\"error\":\"not found\"}");79 else respond(ex, 200, "{\"id\":" + id + ",\"name\":\"" + name + "\"}");80 } else if (method.equals("DELETE") && id != null) { // delete81 respond(ex, USERS.remove(id) != null ? 204 : 404, "");82 } else {83 respond(ex, 405, "{\"error\":\"method not allowed\"}");84 }85 }86 87 static String toJsonArray() {88 StringBuilder sb = new StringBuilder("[");89 boolean first = true;90 for (var e : USERS.entrySet()) {91 if (!first) sb.append(',');92 sb.append("{\"id\":").append(e.getKey()).append(",\"name\":\"").append(e.getValue()).append("\"}");93 first = false;94 }95 return sb.append(']').toString();96 }97 98 static String extractName(String json) {99 int k = json.indexOf("\"name\"");100 if (k < 0) return "unknown";101 int q1 = json.indexOf('"', json.indexOf(':', k) + 1);102 int q2 = json.indexOf('"', q1 + 1);103 return (q1 < 0 || q2 < 0) ? "unknown" : json.substring(q1 + 1, q2);104 }105 106 static Integer tryParse(String s) { try { return Integer.valueOf(s); } catch (Exception e) { return null; } }107 108 static void respond(HttpExchange ex, int status, String body) throws IOException {109 byte[] bytes = body.getBytes(StandardCharsets.UTF_8);110 ex.getResponseHeaders().add("Content-Type", "application/json");111 ex.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);112 if (bytes.length > 0) ex.getResponseBody().write(bytes);113 ex.close();114 }115 116 static void send(HttpClient client, String method, String url, String body) throws Exception {117 HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(url))118 .header("Content-Type", "application/json");119 b = switch (method) {120 case "POST" -> b.POST(HttpRequest.BodyPublishers.ofString(body));121 case "DELETE" -> b.DELETE();122 default -> b.GET();123 };124 HttpResponse<String> r = client.send(b.build(), HttpResponse.BodyHandlers.ofString());125 System.out.printf("%-6s %-28s -> %d %s%n", method, url, r.statusCode(), r.body());126 }127}