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.

pkg12restapi/restapi3RestCrudApi.java
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}