From e2c5c8831516fa54b4eb60f23fc7375e86dde19c Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Fri, 4 Apr 2025 02:21:35 +0530 Subject: [PATCH 1/4] Tried writing a reference server using jdk httpServer --- .idea/misc.xml | 2 +- app/src/processing/app/ReferenceServer.java | 176 ++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 app/src/processing/app/ReferenceServer.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 43b163889..c10c228a5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/app/src/processing/app/ReferenceServer.java b/app/src/processing/app/ReferenceServer.java new file mode 100644 index 000000000..2a7516dc2 --- /dev/null +++ b/app/src/processing/app/ReferenceServer.java @@ -0,0 +1,176 @@ +package processing.app; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.*; +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.zip.*; + +/** + * A simple HTTP server that serves files from a zip archive. + * Replaces the previous custom WebServer implementation. + */ +public class ReferenceServer { + private final HttpServer server; + private final ZipFile zip; + private final Map entries; + private final int port; + + /** mapping of file extensions to content-types */ + static final Map contentTypes = new ConcurrentHashMap<>(); + + static { + contentTypes.put("", "content/unknown"); + contentTypes.put(".css", "text/css"); + contentTypes.put(".csv", "text/csv"); + contentTypes.put(".eot", "application/vnd.ms-fontobject"); + contentTypes.put(".gif", "image/gif"); + contentTypes.put(".html", "text/html"); + contentTypes.put(".ico", "image/x-icon"); + contentTypes.put(".jpeg", "image/jpeg"); + contentTypes.put(".jpg", "image/jpeg"); + contentTypes.put(".js", "text/javascript"); + contentTypes.put(".json", "application/json"); + contentTypes.put(".md", "text/markdown"); + contentTypes.put(".mdx", "text/mdx"); + contentTypes.put(".mtl", "text/plain"); + contentTypes.put(".obj", "text/plain"); + contentTypes.put(".otf", "font/otf"); + contentTypes.put(".pde", "text/plain"); + contentTypes.put(".png", "image/png"); + contentTypes.put(".svg", "image/svg+xml"); + contentTypes.put(".tsv", "text/tab-separated-values"); + contentTypes.put(".ttf", "font/ttf"); + contentTypes.put(".txt", "text/plain"); + contentTypes.put(".vlw", "application/octet-stream"); + contentTypes.put(".woff", "font/woff"); + contentTypes.put(".woff2", "font/woff2"); + contentTypes.put(".xml", "application/xml"); + contentTypes.put(".yml", "text/yaml"); + contentTypes.put(".zip", "application/zip"); + } + + /** + * Creates a new reference server that serves files from the specified zip file. + * + * @param zipFile The zip file containing reference documentation + * @param port The port to serve on + * @throws IOException If there is an error starting the server + */ + public ReferenceServer(File zipFile, int port) throws IOException { + this.zip = new ZipFile(zipFile); + this.port = port; + + // Index all entries in the zip file + entries = new HashMap<>(); + Enumeration en = zip.entries(); + while (en.hasMoreElements()) { + ZipEntry entry = en.nextElement(); + entries.put(entry.getName(), entry); + } + + // Create and configure the server + server = HttpServer.create(new InetSocketAddress(port), 0); + server.createContext("/", new ReferenceHandler()); + server.setExecutor(Executors.newFixedThreadPool(10)); + server.start(); + + Messages.log("Reference server started on port " + port); + } + + /** + * Gets the base URL for the server. + * + * @return The base URL + */ + public String getPrefix() { + return "http://localhost:" + port + "/"; + } + + /** + * Stops the server. + */ + public void stop() { + server.stop(0); + try { + zip.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Handler for reference documentation requests. + */ + class ReferenceHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + String path = exchange.getRequestURI().getPath(); + + // Remove leading slash to match zip entry paths + if (path.startsWith("/")) { + path = path.substring(1); + } + + // Handle empty paths or directory requests + if (path.isEmpty() || path.endsWith("/")) { + path = path + "index.html"; + } + + ZipEntry entry = entries.get(path); + + if (entry != null) { + // Determine content type + String contentType = "application/octet-stream"; + int dotIndex = path.lastIndexOf('.'); + if (dotIndex > 0) { + String extension = path.substring(dotIndex); + contentType = contentTypes.getOrDefault(extension, "application/octet-stream"); + } + + // Send the file + exchange.getResponseHeaders().set("Content-Type", contentType); + exchange.getResponseHeaders().set("Content-Length", String.valueOf(entry.getSize())); + exchange.sendResponseHeaders(200, entry.getSize()); + + try (OutputStream os = exchange.getResponseBody(); + InputStream is = zip.getInputStream(entry)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } + + Messages.log("Serving: " + path + " (" + contentType + ")"); + } else { + // Send 404 + String response = "

404 Not Found

The requested resource was not found.

"; + exchange.getResponseHeaders().set("Content-Type", "text/html"); + exchange.sendResponseHeaders(404, response.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + + Messages.log("404 Not Found: " + path); + } + } + } + + /** + * A main() method for testing. + */ + static public void main(String[] args) { + try { + new ReferenceServer(new File(args[0]), 8053); + System.out.println("Server running at http://localhost:8053/"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file From 57ac5089eed18a26fb6c97824a7323c60931aefa Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Fri, 18 Apr 2025 02:26:47 +0530 Subject: [PATCH 2/4] updated javaeditor and reverted requested change --- .idea/misc.xml | 2 +- java/src/processing/mode/java/JavaEditor.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index c10c228a5..43b163889 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 815792955..ffe41da7f 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -100,9 +100,10 @@ public class JavaEditor extends Editor { static final int REFERENCE_PORT = 8053; // weird to link to a specific location like this, but it's versioned, so: static final String REFERENCE_URL = - "https://github.com/processing/processing-website/releases/download/2022-10-05-1459/reference.zip"; + "https://github.com/processing/processing4/releases/tag/processing-1300-4.4.0"; + static final String REFERENCE_URL_2 = "https://github.com/processing/processing4/releases/download/processing-1300-4.4.0/processing-4.4.0-reference.zip"; Boolean useReferenceServer; - WebServer referenceServer; + ReferenceServer referenceServer; protected JavaEditor(Base base, String path, EditorState state, @@ -846,7 +847,7 @@ public void showReference(String name) { } if (referenceZip.exists()) { try { - referenceServer = new WebServer(referenceZip, REFERENCE_PORT); + referenceServer = new ReferenceServer(referenceZip, REFERENCE_PORT); useReferenceServer = true; } catch (IOException e) { @@ -889,7 +890,7 @@ private boolean isReferenceDownloaded() { private void downloadReference() { try { - URL source = new URL(REFERENCE_URL); + URL source = new URL(REFERENCE_URL_2); HttpURLConnection conn = (HttpURLConnection) source.openConnection(); HttpURLConnection.setFollowRedirects(true); conn.setConnectTimeout(15 * 1000); From 13183b9cdbbff6719a8d32989eabdf6d1e38d44b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sat, 19 Apr 2025 16:10:51 +0530 Subject: [PATCH 3/4] added base functions to get version details --- java/src/processing/mode/java/JavaEditor.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index ffe41da7f..6048a272c 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -887,10 +887,25 @@ private boolean isReferenceDownloaded() { } */ + private String getReferenceDownloadUrl() { + String versionName = Base.getVersionName(); + int revisionInt = Base.getRevision(); + String revision = String.valueOf(revisionInt); + + if ("unspecified".equals(versionName) || revisionInt == Integer.MAX_VALUE) { + return "https://github.com/processing/processing4/releases/download/processing-1300-4.4.0/processing-4.4.0-reference.zip"; + } + + String url = String.format( + "https://github.com/processing/processing4/releases/download/processing-%s-%s/processing-%s-reference.zip", + revision, versionName, versionName); + System.out.println("Generated URL: " + url); + return url; + } private void downloadReference() { try { - URL source = new URL(REFERENCE_URL_2); + URL source = new URL(getReferenceDownloadUrl()); HttpURLConnection conn = (HttpURLConnection) source.openConnection(); HttpURLConnection.setFollowRedirects(true); conn.setConnectTimeout(15 * 1000); From 866bc2b6ccfb7ce3866d63770ebbb9fc2c2dd0ba Mon Sep 17 00:00:00 2001 From: Vaivaswat Dubey <113991324+Vaivaswat2244@users.noreply.github.com> Date: Sat, 19 Apr 2025 23:45:37 +0530 Subject: [PATCH 4/4] Delete WebServer.java --- app/src/processing/app/WebServer.java | 335 -------------------------- 1 file changed, 335 deletions(-) delete mode 100644 app/src/processing/app/WebServer.java diff --git a/app/src/processing/app/WebServer.java b/app/src/processing/app/WebServer.java deleted file mode 100644 index bd13f59b4..000000000 --- a/app/src/processing/app/WebServer.java +++ /dev/null @@ -1,335 +0,0 @@ -package processing.app; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.zip.*; - - -/** - * This code is placed here in anticipation of running the reference from an - * internal web server that reads the docs from a zip file, instead of using - * thousands of .html files on the disk, which is really inefficient. - *

- * This is a very simple, multi-threaded HTTP server, originally based on - * this article on java.sun.com. - */ -public class WebServer { - static final int HTTP_OK = 200; - static final int HTTP_NOT_FOUND = 404; - static final int HTTP_BAD_METHOD = 405; - - /** where worker threads stand idle */ - private final Vector threads = new Vector<>(); - - /** max # worker threads */ - static final int WORKERS = 5; - - private final int port; - private final ZipFile zip; - private final Map entries; - - static final int BUFFER_SIZE = 8192; - - static final byte[] EOL = { (byte) '\r', (byte) '\n' }; - - - public WebServer(File zipFile, int port) throws IOException { - this.zip = new ZipFile(zipFile); - this.port = port; - - entries = new HashMap<>(); - Enumeration en = zip.entries(); - while (en.hasMoreElements()) { - ZipEntry entry = en.nextElement(); - entries.put(entry.getName(), entry); - } - - // start worker threads - for (int i = 0; i < WORKERS; ++i) { - Worker w = new Worker(); - Thread t = new Thread(w, "Web Server Worker #" + i); - t.start(); - threads.addElement(w); - } - - new Thread(() -> { - try { - ServerSocket ss = new ServerSocket(port); - while (true) { - Socket s = ss.accept(); - synchronized (threads) { - if (threads.isEmpty()) { - Worker ws = new Worker(); - ws.setSocket(s); - (new Thread(ws, "additional worker")).start(); - } else { - Worker w = threads.elementAt(0); - threads.removeElementAt(0); - w.setSocket(s); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - } - - - public String getPrefix() { - return "http://localhost:" + port + "/"; - } - - - class Worker implements Runnable { - // buffer to use for requests - byte[] buffer; - private Socket socket; - - Worker() { - buffer = new byte[BUFFER_SIZE]; - socket = null; - } - - synchronized void setSocket(Socket s) { - this.socket = s; - notify(); - } - - public synchronized void run() { - while (true) { - if (socket == null) { - try { - wait(); - } catch (InterruptedException e) { - continue; - } - } - try { - handleClient(); - } catch (Exception e) { - e.printStackTrace(); - } - // go back in wait queue if there's fewer - // than numHandler connections. - socket = null; - synchronized (threads) { - if (threads.size() >= WebServer.WORKERS) { - // too many threads, exit this one - return; - } else { - threads.addElement(this); - } - } - } - } - - void handleClient() throws IOException { - InputStream is = new BufferedInputStream(socket.getInputStream()); - PrintStream ps = new PrintStream(socket.getOutputStream()); - // we will only block in read for this many milliseconds - // before we fail with java.io.InterruptedIOException, - // at which point we will abandon the connection. - socket.setSoTimeout(10000); - socket.setTcpNoDelay(true); - // zero out the buffer from last time - for (int i = 0; i < BUFFER_SIZE; i++) { - buffer[i] = 0; - } - try { - // We only support HTTP GET/HEAD, and don't support any fancy HTTP - // options, so we're only interested really in the first line. - int length = 0; - - outerLoop: - while (length < BUFFER_SIZE) { - int r = is.read(buffer, length, BUFFER_SIZE - length); - if (r == -1) { - return; // EOF - } - int i = length; - length += r; - for (; i < length; i++) { - if (buffer[i] == (byte) '\n' || buffer[i] == (byte) '\r') { - break outerLoop; // read one line - } - } - } - - // are we doing a GET or just a HEAD - boolean doingGet; - // beginning of file name - int index; - if (buffer[0] == (byte) 'G' && - buffer[1] == (byte) 'E' && - buffer[2] == (byte) 'T' && - buffer[3] == (byte) ' ') { - doingGet = true; - index = 4; - } else if (buffer[0] == (byte) 'H' && - buffer[1] == (byte) 'E' && - buffer[2] == (byte) 'A' && - buffer[3] == (byte) 'D' && - buffer[4] == (byte) ' ') { - doingGet = false; - index = 5; - } else { - // we don't support this method - ps.print("HTTP/1.0 " + HTTP_BAD_METHOD + " unsupported method type: "); - ps.write(buffer, 0, 5); - ps.write(EOL); - ps.flush(); - socket.close(); - return; - } - - int i; - // find the file name, from: - // GET /foo/bar.html HTTP/1.0 - // extract "/foo/bar.html" - for (i = index; i < length; i++) { - if (buffer[i] == (byte) ' ') { - break; - } - } - - String path = new String(buffer, index, i - index); - // get the zip entry, remove the front slash - ZipEntry entry = entries.get(path.substring(1)); - boolean ok = printHeaders(ps, path, entry); - if (entry != null) { - InputStream stream = zip.getInputStream(entry); - if (doingGet && ok) { - sendFile(stream, ps); - } - } else { - send404(ps); - } - } finally { - socket.close(); - } - } - - boolean printHeaders(PrintStream ps, String path, ZipEntry entry) throws IOException { - int status; - if (entry == null) { - status = HTTP_NOT_FOUND; - ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found"); - } else { - status = HTTP_OK; - ps.print("HTTP/1.0 " + HTTP_OK + " OK"); - } - ps.write(EOL); - Messages.log("From " + socket.getInetAddress().getHostAddress() + ": GET " + path + " --> " + status); - - ps.print("Server: Processing Reference Server"); - ps.write(EOL); - ps.print("Date: " + new Date()); - ps.write(EOL); - - if (entry != null) { - if (!entry.isDirectory()) { - ps.print("Content-length: " + entry.getSize()); - ps.write(EOL); - ps.print("Last Modified: " + new Date(entry.getTime())); - ps.write(EOL); - String name = entry.getName(); - int ind = name.lastIndexOf('.'); - String contentType = "application/x-unknown-content-type"; - if (ind > 0) { - contentType = contentTypes.getOrDefault(name.substring(ind), contentType); - } - ps.print("Content-type: " + contentType); - } else { - ps.print("Content-type: text/html"); - } - ps.write(EOL); - } - ps.write(EOL); // adding another newline here [fry] - - // indicates whether to send a file on return - return status == HTTP_OK; - } - - void send404(PrintStream ps) throws IOException { - ps.write(EOL); - ps.write(EOL); - ps.print("

404 Not Found

"); - ps.print("The requested resource was not found."); - ps.write(EOL); - ps.write(EOL); - } - - void sendFile(InputStream is, PrintStream ps) throws IOException { - try (is) { - int n; - while ((n = is.read(buffer)) > 0) { - ps.write(buffer, 0, n); - } - } - } - } - - - /** mapping of file extensions to content-types */ - static final Map contentTypes = new ConcurrentHashMap<>(); - - // get list of extensions to support (https://superuser.com/a/232101) - // find . -type f | sed -En 's|.*/[^/]+\.([^/.]+)$|\1|p' | sort -u - // -E is for macOS, use -r on Linux - static { - contentTypes.put("", "content/unknown"); - - contentTypes.put(".css", "text/css"); - contentTypes.put(".csv", "text/csv"); - contentTypes.put(".eot", "application/vnd.ms-fontobject"); // only in 3.x - contentTypes.put(".gif", "image/gif"); - contentTypes.put(".html", "text/html"); - contentTypes.put(".ico", "image/x-icon"); // only in 3.x? - contentTypes.put(".jpeg", "image/jpeg"); - contentTypes.put(".jpg", "image/jpeg"); - contentTypes.put(".js", "text/javascript"); - contentTypes.put(".json", "application/json"); - contentTypes.put(".md", "text/markdown"); - contentTypes.put(".mdx", "text/mdx"); - contentTypes.put(".mtl", "text/plain"); // https://stackoverflow.com/a/19304383 - contentTypes.put(".obj", "text/plain"); // https://stackoverflow.com/a/19304383 - contentTypes.put(".otf", "font/otf"); - contentTypes.put(".pde", "text/plain"); - contentTypes.put(".png", "image/png"); - contentTypes.put(".svg", "image/svg+xml"); - contentTypes.put(".tsv", "text/tab-separated-values"); - contentTypes.put(".ttf", "font/ttf"); - contentTypes.put(".txt", "text/plain"); - contentTypes.put(".vlw", "application/octet-stream"); // or maybe font/x-vlw - contentTypes.put(".woff", "font/woff"); - contentTypes.put(".woff2", "font/woff2"); - contentTypes.put(".xml", "application/xml"); // https://datatracker.ietf.org/doc/html/rfc3023 - contentTypes.put(".yml", "text/yaml"); - contentTypes.put(".zip", "application/zip"); - } - - - /** - * A main() method for testing. - * - *
-   * cd app
-   * ant
-   * open http://localhost:8053/reference/index.html
-   * java -cp pde.jar processing.app.WebServer ../java/reference.zip
-   * 
- */ - static public void main(String[] args) { - try { - new WebServer(new File(args[0]), 8053); - } catch (IOException e) { - e.printStackTrace(); - } - } -} - - -