• File: MiniExpress.java
  • Full Path: /home/lmnet/fm-site/files/JavaProgramming/JAVAPROGRAMS/HTTP_SERVER_JAVA/MiniExpress.java
  • Date Modified: 12/10/2025 1:59 AM
  • File size: 9.72 KB
  • MIME-type: text/plain
  • Charset: utf-8
 
Open Back
// MiniExpress.java
// A tiny Express-like framework in pure Java (no external dependencies)

import com.sun.net.httpserver.*;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;

public class MiniExpress {

    /* ========================== */
    /*           TYPES            */
    /* ========================== */

    @FunctionalInterface
    public interface Next {
        void next() throws IOException;
    }

    @FunctionalInterface
    public interface Middleware {
        void handle(Request req, Response res, Next next) throws IOException;
    }

    @FunctionalInterface
    public interface Handler {
        void handle(Request req, Response res, Next next) throws IOException;
    }

    /* ========================== */
    /*          REQUEST           */
    /* ========================== */

    public static class Request {
        private final HttpExchange ex;
        private String cachedBody = null;
        private final Map<String, Object> attributes = new HashMap<>();

        public Request(HttpExchange ex) {
            this.ex = ex;
        }

        public String method() {
            return ex.getRequestMethod();
        }

        public String path() {
            return ex.getRequestURI().getPath();
        }

        public Headers headers() {
            return ex.getRequestHeaders();
        }

        public Map<String, Object> attributes() {
            return attributes;
        }

        public Map<String, String> query() {
            return parseQuery(ex.getRequestURI().getRawQuery());
        }

        public synchronized String body() throws IOException {
            if (cachedBody != null) return cachedBody;

            InputStream is = ex.getRequestBody();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buf = new byte[2048];
            int len;

            while ((len = is.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }

            cachedBody = baos.toString(StandardCharsets.UTF_8);
            return cachedBody;
        }

        private Map<String, String> parseQuery(String q) {
            Map<String, String> map = new HashMap<>();
            if (q == null || q.isEmpty()) return map;

            for (String part : q.split("&")) {
                int idx = part.indexOf('=');
                try {
                    if (idx >= 0) {
                        String k = URLDecoder.decode(part.substring(0, idx), "UTF-8");
                        String v = URLDecoder.decode(part.substring(idx + 1), "UTF-8");
                        map.put(k, v);
                    } else {
                        map.put(URLDecoder.decode(part, "UTF-8"), "");
                    }
                } catch (Exception ignored) {}
            }
            return map;
        }
    }

    /* ========================== */
    /*          RESPONSE          */
    /* ========================== */

    public static class Response {
        private final HttpExchange ex;
        private int status = 200;

        public Response(HttpExchange ex) {
            this.ex = ex;
        }

        public void status(int code) {
            this.status = code;
        }

        public Headers headers() {
            return ex.getResponseHeaders();
        }

        public void set(String key, String value) {
            headers().set(key, value);
        }

        public void send(String body) throws IOException {
            byte[] bytes = body.getBytes(StandardCharsets.UTF_8);

            if (!headers().containsKey("Content-Type")) {
                headers().set("Content-Type", "text/plain; charset=utf-8");
            }

            ex.sendResponseHeaders(status, bytes.length);
            try (OutputStream os = ex.getResponseBody()) {
                os.write(bytes);
            }
        }

        public void json(String json) throws IOException {
            set("Content-Type", "application/json; charset=utf-8");
            send(json);
        }
    }

    /* ========================== */
    /*          ROUTER            */
    /* ========================== */

    private static class Route {
        final String method;
        final String path;
        final Handler handler;

        public Route(String method, String path, Handler handler) {
            this.method = method.toUpperCase();
            this.path = path;
            this.handler = handler;
        }
    }

    /* ========================== */
    /*           APP              */
    /* ========================== */

    public static class App {
        private final List<Middleware> middlewares = new ArrayList<>();
        private final List<Route> routes = new ArrayList<>();
        private HttpServer server;

        // Inside App class:
public App staticFiles(String directory) {
    File root = new File(directory);

    this.use((req, res, next) -> {
        String path = req.path();
        if (path.equals("/")) {
            next.next(); // allow real route "/" to work
            return;
        }

        File file = new File(root, path);
        if (!file.exists() || file.isDirectory()) {
            next.next(); // continue to routes
            return;
        }

        // Detect content-type
             String mime = "text/plain";
                if (path.endsWith(".html")) mime = "text/html";
                if (path.endsWith(".css")) mime = "text/css";
                if (path.endsWith(".js")) mime = "application/javascript";
                if (path.endsWith(".png")) mime = "image/png";
                if (path.endsWith(".jpg") || path.endsWith(".jpeg")) mime = "image/jpeg";
                if (path.endsWith(".gif")) mime = "image/gif";

                res.set("Content-Type", mime);

                byte[] bytes = java.nio.file.Files.readAllBytes(file.toPath());
                 res.status(200);
                 res.ex.sendResponseHeaders(200, bytes.length);
                OutputStream os = res.ex.getResponseBody();
                os.write(bytes);
                os.close();
            });

            return this;
        }

        public App use(Middleware m) {
            middlewares.add(m);
            return this;
        }

        public App get(String path, Handler h) {
            routes.add(new Route("GET", path, h));
            return this;
        }

        public App post(String path, Handler h) {
            routes.add(new Route("POST", path, h));
            return this;
        }

        private void handle(HttpExchange ex) throws IOException {
            Request req = new Request(ex);
            Response res = new Response(ex);

            List<Middleware> chain = new ArrayList<>(middlewares);

            // find the matching route
            Route matched = null;
            for (Route r : routes) {
                if (r.method.equals(ex.getRequestMethod()) && r.path.equals(ex.getRequestURI().getPath())) {
                    matched = r;
                    break;
                }
            }

            if (matched != null) {
                Route finalRoute = matched;
                chain.add((rq, rs, next) -> finalRoute.handler.handle(rq, rs, next));
            } else {
                chain.add((rq, rs, next) -> {
                    rs.status(404);
                    rs.send("Not Found");
                });
            }
            

            runMiddleware(chain, 0, req, res);
        }

        private void runMiddleware(List<Middleware> chain, int idx, Request req, Response res) throws IOException {
            if (idx >= chain.size()) return;

            Middleware m = chain.get(idx);
            Next next = () -> runMiddleware(chain, idx + 1, req, res);

            m.handle(req, res, next);
        }

        public void listen(int port) throws IOException {
            server = HttpServer.create(new InetSocketAddress(port), 0);
            server.createContext("/", this::handle);
            server.setExecutor(Executors.newCachedThreadPool());
            server.start();
            System.out.println("MiniExpress running at: http://localhost:" + port);
        }
    }

    /* ========================== */
    /*       JSON QUOTER          */
    /* ========================== */

    static class JSONObjectSafe {
        static String quote(String s) {
            if (s == null) return "\"\"";
            return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
        }
    }

    /* ========================== */
    /*       DEMO SERVER          */
    /* ========================== */

   public static void main(String[] args) throws Exception {
    App app = new App();

    // serve static files in /public
    app.staticFiles("public");

    // Logger
    app.use((req, res, next) -> {
        System.out.println(req.method() + " " + req.path());
        next.next();
    });

    // Body parser
    app.use((req, res, next) -> {
        if (req.method().equals("POST")) {
            req.attributes().put("body", req.body());
        }
        next.next();
    });

    // GET /
    app.get("/", (req, res, next) -> {
        // serve /public/index.html when user visits "/"
        File index = new File("public/index.html");
        if (index.exists()) {
            String html = new String(java.nio.file.Files.readAllBytes(index.toPath()));
            res.set("Content-Type", "text/html");
            res.send(html);
        } else {
            res.send("Hello from MiniExpress!");
        }
    });

    app.listen(80);
}
}