tahamajs/IE / Projects /scripts /refactor_servers.sh
tahamajs's picture
download
raw
45.4 kB
#!/usr/bin/env bash
set -e
BASE_DIR="src/main/java/ir/ac/ut/ece/ie/server"
# Create directories
mkdir -p "$BASE_DIR/staticcontent"
mkdir -p "$BASE_DIR/dynamic/routehandlers"
mkdir -p "$BASE_DIR/dynamic/pages" # اگر قبلاً نبوده باشد
#################################
# Static server files
#################################
cat > "$BASE_DIR/staticcontent/StaticContentServer.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Static Content Server with reverse proxy to dynamic server
*/
public class StaticContentServer {
private ServerSocket serverSocket;
static String BASE_PATH;
private final ExecutorService threadPool;
private boolean running = false;
public static final String DYNAMIC_SERVER_HOST = "localhost";
public static final int DYNAMIC_SERVER_PORT = 9092;
public StaticContentServer() {
BASE_PATH = findBasePath();
this.threadPool = Executors.newFixedThreadPool(20);
System.out.println("Static Server Base Path: " + BASE_PATH);
}
private String findBasePath() {
String[] possiblePaths = {
"./src/main/resources/Templates/",
"src/main/resources/Templates/",
"./Templates/",
"Templates/",
System.getProperty("user.dir") + "/src/main/resources/Templates/"
};
for (String path : possiblePaths) {
Path p = Paths.get(path).toAbsolutePath().normalize();
if (Files.exists(p) && Files.isDirectory(p)) {
return p.toString() + File.separator;
}
}
return Paths.get(".").toAbsolutePath().normalize().toString() +
File.separator + "src" + File.separator + "main" +
File.separator + "resources" + File.separator + "Templates" + File.separator;
}
public void start() throws IOException {
serverSocket = new ServerSocket(9091);
running = true;
System.out.println("========================================");
System.out.println("Static Content Server v2.0 Started");
System.out.println("Port: 9091");
System.out.println("========================================");
StaticRequestHandler requestHandler = new StaticRequestHandler();
while (running) {
Socket socket = serverSocket.accept();
socket.setSoTimeout(30000);
threadPool.submit(() -> requestHandler.handleClient(socket));
}
}
public void stop() throws IOException {
running = false;
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
threadPool.shutdown();
}
public static void main(String[] args) {
try {
StaticContentServer server = new StaticContentServer();
server.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
EOF
cat > "$BASE_DIR/staticcontent/StaticRequestHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.StringTokenizer;
public class StaticRequestHandler {
private final RequestParser requestParser;
private final StaticFileResolver fileResolver;
private final StaticResponseWriter responseWriter;
private final ProxyForwarder proxyForwarder;
public StaticRequestHandler() {
this.requestParser = new RequestParser();
this.fileResolver = new StaticFileResolver();
this.responseWriter = new StaticResponseWriter();
this.proxyForwarder = new ProxyForwarder();
}
public void handleClient(Socket socket) {
try {
handleRequest(socket);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
} finally {
try { socket.close(); } catch (IOException ignored) {}
}
}
private void handleRequest(Socket socket) throws IOException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
);
String requestLine = reader.readLine();
if (requestLine == null) return;
System.out.println("Request: " + requestLine);
StringTokenizer tokenizer = new StringTokenizer(requestLine, " ");
if (!tokenizer.hasMoreTokens()) return;
String method = tokenizer.nextToken();
String path = tokenizer.nextToken();
Map<String, String> headers = requestParser.readHeaders(reader);
String requestBody = requestParser.readRequestBody(reader, method, headers);
if (fileResolver.isDynamicRoute(path)) {
proxyForwarder.forwardToDynamicServer(socket, method, path, requestBody, headers);
} else {
responseWriter.serveStaticFile(socket, path);
}
}
}
EOF
cat > "$BASE_DIR/staticcontent/RequestParser.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class RequestParser {
public Map<String, String> readHeaders(BufferedReader reader) throws IOException {
Map<String, String> headers = new HashMap<>();
String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
String[] parts = line.split(": ", 2);
if (parts.length == 2) {
headers.put(parts[0], parts[1]);
}
}
return headers;
}
public String readRequestBody(BufferedReader reader, String method, Map<String, String> headers) throws IOException {
if (!("POST".equals(method) || "PUT".equals(method))) {
return "";
}
int contentLength = 0;
try {
contentLength = Integer.parseInt(headers.getOrDefault("Content-Length", "0"));
} catch (NumberFormatException ignored) {}
if (contentLength <= 0) return "";
char[] bodyChars = new char[contentLength];
int bytesRead = reader.read(bodyChars, 0, contentLength);
return bytesRead > 0 ? new String(bodyChars, 0, bytesRead) : "";
}
}
EOF
cat > "$BASE_DIR/staticcontent/StaticFileResolver.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.File;
public class StaticFileResolver {
public boolean isDynamicRoute(String path) {
String cleanPath = path;
if (path.contains("?")) {
cleanPath = path.substring(0, path.indexOf("?"));
}
if (cleanPath.startsWith("/api/")) {
return true;
}
return false;
}
public String getFileName(String path) {
if (path.contains("?")) {
path = path.substring(0, path.indexOf("?"));
}
if (path.startsWith("/")) {
path = path.substring(1);
}
return path;
}
public File resolveFile(String path) {
String fileName = getFileName(path);
if (fileName.isEmpty() || "/".equals(fileName)) {
fileName = "index.html";
}
fileName = fileName.replace("..", "").replace("//", "/");
File file = new File(StaticContentServer.BASE_PATH + fileName);
if (!file.exists() && !fileName.toLowerCase().endsWith(".html")) {
File htmlFile = new File(StaticContentServer.BASE_PATH + fileName + ".html");
if (htmlFile.exists()) {
file = htmlFile;
}
}
return file;
}
}
EOF
cat > "$BASE_DIR/staticcontent/StaticResponseWriter.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public class StaticResponseWriter {
private final StaticFileResolver fileResolver = new StaticFileResolver();
public void serveStaticFile(Socket socket, String path) throws IOException {
OutputStream out = socket.getOutputStream();
File file = fileResolver.resolveFile(path);
if (file.exists() && !file.isDirectory()) {
byte[] data = Files.readAllBytes(file.toPath());
String extension = getFileExtension(file.getName());
String contentType = MimeTypes.getContentType(extension);
String header = "HTTP/1.1 200 OK\r\n" +
"Content-Type: " + contentType + "\r\n" +
"Content-Length: " + data.length + "\r\n" +
"Connection: close\r\n\r\n";
out.write(header.getBytes(StandardCharsets.UTF_8));
out.write(data);
} else {
sendErrorResponse(out, 404, "File not found");
}
out.flush();
}
public void sendErrorResponse(OutputStream out, int statusCode, String message) throws IOException {
String statusMsg = getStatusMessage(statusCode);
String json = "{\"error\": \"" + message + "\"}";
byte[] data = json.getBytes(StandardCharsets.UTF_8);
String header = "HTTP/1.1 " + statusCode + " " + statusMsg + "\r\n" +
"Content-Type: application/json; charset=UTF-8\r\n" +
"Content-Length: " + data.length + "\r\n" +
"Connection: close\r\n\r\n";
out.write(header.getBytes(StandardCharsets.UTF_8));
out.write(data);
}
private String getFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) return "";
int lastDot = fileName.lastIndexOf('.');
if (lastDot == -1 || lastDot == fileName.length() - 1) return "";
return fileName.substring(lastDot + 1).toLowerCase();
}
private String getStatusMessage(int statusCode) {
switch (statusCode) {
case 200: return "OK";
case 201: return "Created";
case 204: return "No Content";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 500: return "Internal Server Error";
case 503: return "Service Unavailable";
default: return "Unknown";
}
}
}
EOF
cat > "$BASE_DIR/staticcontent/MimeTypes.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.util.HashMap;
import java.util.Map;
public class MimeTypes {
private static final Map<String, String> CONTENT_TYPES = new HashMap<>();
static {
CONTENT_TYPES.put("html", "text/html; charset=UTF-8");
CONTENT_TYPES.put("htm", "text/html; charset=UTF-8");
CONTENT_TYPES.put("css", "text/css; charset=UTF-8");
CONTENT_TYPES.put("js", "application/javascript; charset=UTF-8");
CONTENT_TYPES.put("json", "application/json; charset=UTF-8");
CONTENT_TYPES.put("png", "image/png");
CONTENT_TYPES.put("jpg", "image/jpeg");
CONTENT_TYPES.put("jpeg", "image/jpeg");
CONTENT_TYPES.put("gif", "image/gif");
CONTENT_TYPES.put("svg", "image/svg+xml");
CONTENT_TYPES.put("ico", "image/x-icon");
CONTENT_TYPES.put("pdf", "application/pdf");
CONTENT_TYPES.put("txt", "text/plain; charset=UTF-8");
}
public static String getContentType(String extension) {
return CONTENT_TYPES.getOrDefault(extension, "application/octet-stream");
}
}
EOF
cat > "$BASE_DIR/staticcontent/DynamicProxyResponse.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.util.Map;
public class DynamicProxyResponse {
int statusCode;
String body;
Map<String, String> headers;
public DynamicProxyResponse(int statusCode, String body, Map<String, String> headers) {
this.statusCode = statusCode;
this.body = body;
this.headers = headers;
}
}
EOF
cat > "$BASE_DIR/staticcontent/ProxyForwarder.java" << 'EOF'
package ir.ac.ut.ece.ie.server.staticcontent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class ProxyForwarder {
private final StaticResponseWriter responseWriter = new StaticResponseWriter();
public void forwardToDynamicServer(Socket clientSocket,
String method,
String path,
String requestBody,
Map<String, String> headers) {
try (Socket dynamicSocket = new Socket(
StaticContentServer.DYNAMIC_SERVER_HOST,
StaticContentServer.DYNAMIC_SERVER_PORT)) {
dynamicSocket.setSoTimeout(10000);
OutputStream out = dynamicSocket.getOutputStream();
StringBuilder request = new StringBuilder();
request.append(method).append(" ").append(path).append(" HTTP/1.1\r\n");
request.append("Host: ")
.append(StaticContentServer.DYNAMIC_SERVER_HOST)
.append(":")
.append(StaticContentServer.DYNAMIC_SERVER_PORT)
.append("\r\n");
if (headers.containsKey("Content-Type")) {
request.append("Content-Type: ").append(headers.get("Content-Type")).append("\r\n");
}
if (headers.containsKey("Content-Length")) {
request.append("Content-Length: ").append(headers.get("Content-Length")).append("\r\n");
}
request.append("X-Forwarded-From: static-server\r\n");
request.append("Connection: close\r\n");
if (("POST".equals(method) || "PUT".equals(method)) &&
requestBody != null && !requestBody.isEmpty()) {
request.append("\r\n").append(requestBody);
} else {
request.append("\r\n");
}
out.write(request.toString().getBytes(StandardCharsets.UTF_8));
out.flush();
DynamicProxyResponse response = readDynamicResponse(dynamicSocket);
sendResponseToClient(clientSocket, response);
} catch (IOException e) {
System.err.println("Forward error: " + e.getMessage());
try {
responseWriter.sendErrorResponse(
clientSocket.getOutputStream(),
503,
"Dynamic server unavailable"
);
} catch (IOException ignored) {}
}
}
private DynamicProxyResponse readDynamicResponse(Socket socket) throws IOException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
);
String statusLine = reader.readLine();
int statusCode = 200;
if (statusLine != null && statusLine.length() >= 9) {
try {
statusCode = Integer.parseInt(statusLine.split(" ")[1]);
} catch (Exception ignored) {}
}
Map<String, String> responseHeaders = new HashMap<>();
String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
String[] parts = line.split(": ", 2);
if (parts.length == 2) {
responseHeaders.put(parts[0], parts[1]);
}
}
StringBuilder body = new StringBuilder();
if ("chunked".equalsIgnoreCase(responseHeaders.get("Transfer-Encoding"))) {
String chunk;
while ((chunk = reader.readLine()) != null) {
if (chunk.isEmpty()) break;
int chunkSize = Integer.parseInt(chunk.trim(), 16);
if (chunkSize == 0) break;
char[] chunkData = new char[chunkSize];
reader.read(chunkData, 0, chunkSize);
body.append(chunkData);
reader.readLine(); // CRLF
}
} else {
int contentLength = 0;
try {
contentLength = Integer.parseInt(responseHeaders.getOrDefault("Content-Length", "0"));
} catch (NumberFormatException ignored) {}
if (contentLength > 0) {
char[] bodyChars = new char[contentLength];
int bytesRead = reader.read(bodyChars, 0, contentLength);
if (bytesRead > 0) body.append(bodyChars, 0, bytesRead);
} else {
int ch;
while ((ch = reader.read()) != -1) body.append((char) ch);
}
}
return new DynamicProxyResponse(statusCode, body.toString(), responseHeaders);
}
private void sendResponseToClient(Socket socket, DynamicProxyResponse response) throws IOException {
OutputStream out = socket.getOutputStream();
String statusMsg = getStatusMessage(response.statusCode);
String contentType = response.headers.getOrDefault("Content-Type", "text/html; charset=UTF-8");
byte[] data = response.body.getBytes(StandardCharsets.UTF_8);
String header = "HTTP/1.1 " + response.statusCode + " " + statusMsg + "\r\n" +
"Content-Type: " + contentType + "\r\n" +
"Content-Length: " + data.length + "\r\n" +
"Access-Control-Allow-Origin: *\r\n" +
"Connection: close\r\n\r\n";
out.write(header.getBytes(StandardCharsets.UTF_8));
out.write(data);
out.flush();
}
private String getStatusMessage(int statusCode) {
switch (statusCode) {
case 200: return "OK";
case 201: return "Created";
case 204: return "No Content";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 500: return "Internal Server Error";
case 503: return "Service Unavailable";
default: return "Unknown";
}
}
}
EOF
#################################
# Dynamic server files
#################################
cat > "$BASE_DIR/dynamic/DynamicContentServer.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.repository.ArticleRepository;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.service.ArticleService;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DynamicContentServer {
private ServerSocket serverSocket;
private final int port;
private final ExecutorService threadPool;
private final Router router;
private final ArticleService articleService;
private volatile boolean running = false;
public DynamicContentServer() {
this(9092);
}
public DynamicContentServer(int port) {
this.port = port;
this.threadPool = Executors.newFixedThreadPool(20);
this.router = new Router();
ArticleRepository repo = new ArticleRepository();
this.articleService = new ArticleService(repo);
new DynamicRouterInitializer(router, articleService).setupRoutes();
}
public void start() throws IOException {
serverSocket = new ServerSocket(port);
running = true;
System.out.println("DynamicContentServer started on port " + port);
DynamicRequestHandler handler = new DynamicRequestHandler(router);
while (running) {
Socket socket = serverSocket.accept();
socket.setSoTimeout(30000);
threadPool.submit(() -> handler.handleClient(socket));
}
}
public static void main(String[] args) throws IOException {
new DynamicContentServer().start();
}
}
EOF
cat > "$BASE_DIR/dynamic/DynamicRequestHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.routing.Router;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class DynamicRequestHandler {
private final Router router;
private final HttpRequestReader requestReader;
private final HttpResponseWriter responseWriter;
private final QueryStringParser queryStringParser;
private final CorsHandler corsHandler;
private final ApiErrorFactory errorFactory;
public DynamicRequestHandler(Router router) {
this.router = router;
this.requestReader = new HttpRequestReader();
this.responseWriter = new HttpResponseWriter();
this.queryStringParser = new QueryStringParser();
this.corsHandler = new CorsHandler();
this.errorFactory = new ApiErrorFactory();
}
public void handleClient(Socket socket) {
try {
handleRequest(socket);
} catch (Exception e) {
e.printStackTrace();
} finally {
try { socket.close(); } catch (IOException ignored) {}
}
}
private void handleRequest(Socket socket) throws IOException {
HttpRequestReader.RequestLineAndHeaders info = requestReader.readRequestLineAndHeaders(socket);
if (info == null || info.getRequestLine() == null) return;
String line = info.getRequestLine();
System.out.println("Request: " + line);
StringTokenizer tok = new StringTokenizer(line);
if (tok.countTokens() < 2) return;
String method = tok.nextToken().toUpperCase();
String fullPath = tok.nextToken();
if ("OPTIONS".equals(method)) {
corsHandler.sendCors(socket);
return;
}
String body = requestReader.readBody(socket, method, info.getHeaders());
String path = fullPath;
String query = "";
if (fullPath.contains("?")) {
String[] p = fullPath.split("\\?", 2);
path = p[0];
query = p[1];
}
Map<String, String> qParams = queryStringParser.parseQuery(query);
Router.RouteRequest req = new Router.RouteRequest(
method,
path,
new HashMap<>(),
qParams,
info.getHeaders(),
body
);
Router.RouteResult res = router.findRoute(method, path);
Router.RouteResponse resp;
if (res != null) {
req.getParams().putAll(res.params);
try {
resp = res.handler.handle(req);
} catch (Exception e) {
e.printStackTrace();
resp = errorFactory.error(500, "Internal error");
}
} else {
resp = errorFactory.error(404, "Not found");
}
resp.header("Access-Control-Allow-Origin", "*");
responseWriter.sendResponse(socket, resp);
}
}
EOF
cat > "$BASE_DIR/dynamic/HttpRequestReader.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class HttpRequestReader {
public static class RequestLineAndHeaders {
private final String requestLine;
private final Map<String, String> headers;
private final BufferedReader reader;
public RequestLineAndHeaders(String requestLine,
Map<String, String> headers,
BufferedReader reader) {
this.requestLine = requestLine;
this.headers = headers;
this.reader = reader;
}
public String getRequestLine() { return requestLine; }
public Map<String, String> getHeaders() { return headers; }
public BufferedReader getReader() { return reader; }
}
public RequestLineAndHeaders readRequestLineAndHeaders(Socket socket) throws IOException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
);
String requestLine = reader.readLine();
if (requestLine == null) return null;
Map<String, String> headers = readHeaders(reader);
return new RequestLineAndHeaders(requestLine, headers, reader);
}
private Map<String, String> readHeaders(BufferedReader r) throws IOException {
Map<String, String> h = new HashMap<>();
String line;
while ((line = r.readLine()) != null && !line.isEmpty()) {
String[] kv = line.split(":", 2);
if (kv.length == 2) h.put(kv[0].trim(), kv[1].trim());
}
return h;
}
public String readBody(Socket socket, String method, Map<String, String> h) throws IOException {
if (!("POST".equals(method) || "PUT".equals(method))) return "";
int len = Integer.parseInt(h.getOrDefault("Content-Length", "0"));
if (len <= 0) return "";
BufferedReader r = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
);
char[] buf = new char[len];
int read = 0;
while (read < len) {
int rd = r.read(buf, read, len - read);
if (rd == -1) break;
read += rd;
}
return new String(buf, 0, read);
}
}
EOF
cat > "$BASE_DIR/dynamic/HttpResponseWriter.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.routing.Router;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class HttpResponseWriter {
public void sendResponse(Socket s, Router.RouteResponse r) throws IOException {
OutputStream out = s.getOutputStream();
byte[] body = r.getBody() == null ? new byte[0] : r.getBody();
Map<String, String> h = r.getHeaders();
if (!h.containsKey("Content-Type")) {
h.put("Content-Type", "application/json; charset=UTF-8");
}
h.put("Content-Length", String.valueOf(body.length));
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 ")
.append(r.getStatusCode())
.append(" ")
.append(r.getStatusMessage())
.append("\r\n");
h.forEach((k, v) -> sb.append(k).append(": ").append(v).append("\r\n"));
sb.append("\r\n");
out.write(sb.toString().getBytes(StandardCharsets.UTF_8));
out.write(body);
out.flush();
}
}
EOF
cat > "$BASE_DIR/dynamic/QueryStringParser.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class QueryStringParser {
public Map<String, String> parseQuery(String q) {
Map<String, String> m = new HashMap<>();
if (q == null || q.isEmpty()) return m;
for (String pair : q.split("&")) {
String[] kv = pair.split("=", 2);
try {
m.put(
kv[0],
kv.length > 1
? URLDecoder.decode(kv[1], StandardCharsets.UTF_8)
: ""
);
} catch (Exception e) {
m.put(kv[0], kv.length > 1 ? kv[1] : "");
}
}
return m;
}
}
EOF
cat > "$BASE_DIR/dynamic/CorsHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.routing.Router;
import java.io.IOException;
import java.net.Socket;
public class CorsHandler {
private final HttpResponseWriter responseWriter = new HttpResponseWriter();
public void sendCors(Socket s) throws IOException {
Router.RouteResponse r = new Router.RouteResponse().status(204);
r.header("Access-Control-Allow-Origin", "*");
r.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
responseWriter.sendResponse(s, r);
}
}
EOF
cat > "$BASE_DIR/dynamic/ApiErrorFactory.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import com.fasterxml.jackson.databind.node.ObjectNode;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.util.JsonUtils;
public class ApiErrorFactory {
public Router.RouteResponse error(int code, String msg) {
ObjectNode err = JsonUtils.mapper().createObjectNode();
err.put("error", msg);
return new Router.RouteResponse()
.status(code)
.json(err.toString());
}
}
EOF
cat > "$BASE_DIR/dynamic/JsonResponseHelper.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.routing.Router;
import java.nio.charset.StandardCharsets;
public class JsonResponseHelper {
public Router.RouteResponse json(String json) {
return new Router.RouteResponse().json(json);
}
public Router.RouteResponse html(byte[] content) {
return new Router.RouteResponse().html(new String(content, StandardCharsets.UTF_8));
}
}
EOF
cat > "$BASE_DIR/dynamic/PageRenderer.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import ir.ac.ut.ece.ie.server.dynamic.pages.*;
import java.util.HashMap;
import java.util.Map;
public class PageRenderer {
public byte[] renderHome(Map<String, String> params) {
return new HomePage().pageBody(params);
}
public byte[] renderAddArticle(Map<String, String> params) {
return new AddArticlePage().pageBody(params);
}
public byte[] renderSearch(Map<String, String> params) {
return new SearchPage().pageBody(params);
}
public byte[] renderArticle(Map<String, String> params) {
return new ArticlePage().pageBody(params);
}
public byte[] renderEditArticle(Map<String, String> params) {
return new EditArticlePage().pageBody(params);
}
public Map<String, String> singleParam(String key, String value) {
Map<String, String> map = new HashMap<>();
if (value != null) map.put(key, value);
return map;
}
}
EOF
cat > "$BASE_DIR/dynamic/DynamicRouterInitializer.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import ir.ac.ut.ece.ie.dto.ArticleDTO;
import ir.ac.ut.ece.ie.dto.CreateArticleRequest;
import ir.ac.ut.ece.ie.model.Article;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class DynamicRouterInitializer {
private final Router router;
private final ArticleService articleService;
private final JsonResponseHelper jsonHelper;
private final ApiErrorFactory errorFactory;
private final PageRenderer pageRenderer;
public DynamicRouterInitializer(Router router, ArticleService articleService) {
this.router = router;
this.articleService = articleService;
this.jsonHelper = new JsonResponseHelper();
this.errorFactory = new ApiErrorFactory();
this.pageRenderer = new PageRenderer();
}
public void setupRoutes() {
// Page routes
router.get("/", req -> jsonHelper.html(pageRenderer.renderHome(req.getQueryParams())));
router.get("/addarticle", req -> jsonHelper.html(pageRenderer.renderAddArticle(req.getQueryParams())));
router.get("/search", req -> jsonHelper.html(pageRenderer.renderSearch(req.getQueryParams())));
router.get("/article", req -> {
String id = req.getQueryParam("id");
return jsonHelper.html(pageRenderer.renderArticle(pageRenderer.singleParam("id", id)));
});
router.get("/editarticle", req -> {
String id = req.getQueryParam("id");
return jsonHelper.html(pageRenderer.renderEditArticle(pageRenderer.singleParam("id", id)));
});
// API routes
router.get("/api/articles", new routehandlers.ArticlesApiHandler(articleService)::handleList);
router.get("/api/articles/search", new routehandlers.SearchApiHandler(articleService)::handleSearch);
router.get("/api/stats", new routehandlers.StatsApiHandler(articleService)::handleStats);
router.post("/api/admin/clear", req -> {
articleService.clearAll();
return jsonHelper.json("{\"status\":\"cleared\"}");
});
router.post("/api/articles", new routehandlers.CreateArticleHandler(articleService)::handleCreate);
router.get("/api/articles/{id}", new routehandlers.ArticlesApiHandler(articleService)::handleGetById);
router.put("/api/articles/{id}", new routehandlers.UpdateArticleHandler(articleService)::handleUpdate);
router.delete("/api/articles/{id}", new routehandlers.DeleteArticleHandler(articleService)::handleDelete);
}
// Utility JSON builders (can be shared by handlers if needed)
public static ObjectNode articleToSummaryJson(Article a) {
ObjectNode node = JsonUtils.mapper().createObjectNode();
node.put("id", a.getId());
node.put("title", a.getTitle());
node.put("abstract", a.getAbstract());
node.put("publishedDate", a.getPublishedDate() != null ? a.getPublishedDate().toString() : null);
node.put("relatedCount", a.getRelatedArticles() != null ? a.getRelatedArticles().size() : 0);
return node;
}
public static ObjectNode articleToDetailJson(Article a) {
ObjectNode node = articleToSummaryJson(a);
node.put("body", a.getBody());
ArrayNode rel = JsonUtils.mapper().createArrayNode();
if (a.getRelatedArticles() != null) {
for (Article ra : a.getRelatedArticles()) {
ObjectNode rn = JsonUtils.mapper().createObjectNode();
rn.put("id", ra.getId());
rn.put("title", ra.getTitle());
rel.add(rn);
}
}
node.set("relatedArticles", rel);
return node;
}
public static Long parsePositiveLong(String s) {
try {
Long id = Long.parseLong(s);
return id > 0 ? id : null;
} catch (Exception e) {
return null;
}
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/ArticlesApiHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import com.fasterxml.jackson.databind.node.ArrayNode;
import ir.ac.ut.ece.ie.model.Article;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.server.dynamic.ApiErrorFactory;
import ir.ac.ut.ece.ie.server.dynamic.DynamicRouterInitializer;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.Optional;
public class ArticlesApiHandler {
private final ArticleService articleService;
private final ApiErrorFactory errorFactory = new ApiErrorFactory();
public ArticlesApiHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleList(Router.RouteRequest req) {
ArrayNode arr = JsonUtils.mapper().createArrayNode();
for (Article a : articleService.getAllArticles()) {
arr.add(DynamicRouterInitializer.articleToSummaryJson(a));
}
return new Router.RouteResponse().json(arr.toString());
}
public Router.RouteResponse handleGetById(Router.RouteRequest req) {
Long id = DynamicRouterInitializer.parsePositiveLong(req.getParam("id"));
if (id == null) return errorFactory.error(400, "Invalid id");
Optional<Article> opt = articleService.getArticleById(id);
if (opt.isEmpty()) return errorFactory.error(404, "Article not found");
return new Router.RouteResponse().json(
DynamicRouterInitializer.articleToDetailJson(opt.get()).toString()
);
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/SearchApiHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import com.fasterxml.jackson.databind.node.ArrayNode;
import ir.ac.ut.ece.ie.model.Article;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.server.dynamic.DynamicRouterInitializer;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.List;
public class SearchApiHandler {
private final ArticleService articleService;
public SearchApiHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleSearch(Router.RouteRequest req) {
String q = req.getQueryParam("q");
if (q == null || q.trim().isEmpty()) q = req.getQueryParam("query");
if (q == null || q.trim().isEmpty()) return new Router.RouteResponse().json("[]");
List<Article> results = articleService.searchArticles(q);
ArrayNode arr = JsonUtils.mapper().createArrayNode();
for (Article a : results) {
arr.add(DynamicRouterInitializer.articleToSummaryJson(a));
}
return new Router.RouteResponse().json(arr.toString());
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/StatsApiHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import com.fasterxml.jackson.databind.node.ObjectNode;
import ir.ac.ut.ece.ie.model.Article;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.List;
public class StatsApiHandler {
private final ArticleService articleService;
public StatsApiHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleStats(Router.RouteRequest req) {
List<Article> all = articleService.getAllArticles();
int total = all.size();
int refs = all.stream().mapToInt(a -> a.getRelatedArticles().size()).sum();
ObjectNode node = JsonUtils.mapper().createObjectNode();
node.put("totalArticles", total);
node.put("totalReferences", refs);
return new Router.RouteResponse().json(node.toString());
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/CreateArticleHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import com.fasterxml.jackson.databind.JsonNode;
import ir.ac.ut.ece.ie.dto.ArticleDTO;
import ir.ac.ut.ece.ie.dto.CreateArticleRequest;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.server.dynamic.ApiErrorFactory;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.ArrayList;
import java.util.List;
public class CreateArticleHandler {
private final ArticleService articleService;
private final ApiErrorFactory errorFactory = new ApiErrorFactory();
public CreateArticleHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleCreate(Router.RouteRequest req) {
try {
JsonNode body = JsonUtils.mapper().readTree(req.getBody());
if (!body.hasNonNull("title") || !body.hasNonNull("abstract") || !body.hasNonNull("body"))
return errorFactory.error(400, "Missing required fields");
String title = body.get("title").asText().trim();
if (title.isEmpty()) return errorFactory.error(400, "Title cannot be empty");
if (!articleService.isTitleUnique(title)) return errorFactory.error(409, "Duplicate title");
CreateArticleRequest r = new CreateArticleRequest();
r.setTitle(title);
r.setAbstractText(body.get("abstract").asText().trim());
r.setBody(body.get("body").asText().trim());
if (body.has("relatedArticleIds") && body.get("relatedArticleIds").isArray()) {
List<Long> ids = new ArrayList<>();
for (JsonNode n : body.get("relatedArticleIds")) {
if (!n.canConvertToLong()) return errorFactory.error(400, "Invalid related id");
ids.add(n.asLong());
}
r.setRelatedArticleIds(ids);
}
ArticleDTO created = articleService.create(r);
return new Router.RouteResponse()
.status(201)
.json(JsonUtils.toJson(created));
} catch (Exception e) {
e.printStackTrace();
return errorFactory.error(400, "Invalid request");
}
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/UpdateArticleHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import com.fasterxml.jackson.databind.JsonNode;
import ir.ac.ut.ece.ie.model.Article;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.server.dynamic.ApiErrorFactory;
import ir.ac.ut.ece.ie.server.dynamic.DynamicRouterInitializer;
import ir.ac.ut.ece.ie.service.ArticleService;
import ir.ac.ut.ece.ie.util.JsonUtils;
import java.util.Optional;
public class UpdateArticleHandler {
private final ArticleService articleService;
private final ApiErrorFactory errorFactory = new ApiErrorFactory();
public UpdateArticleHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleUpdate(Router.RouteRequest req) {
Long id = DynamicRouterInitializer.parsePositiveLong(req.getParam("id"));
if (id == null) return errorFactory.error(400, "Invalid id");
Optional<Article> opt = articleService.getArticleById(id);
if (opt.isEmpty()) return errorFactory.error(404, "Article not found");
Article article = opt.get();
JsonNode body;
try {
body = JsonUtils.mapper().readTree(req.getBody());
} catch (Exception e) {
return errorFactory.error(400, "Invalid JSON");
}
if (body.has("title")) {
String t = body.get("title").asText().trim();
if (t.isEmpty()) return errorFactory.error(400, "Title cannot be empty");
if (!t.equals(article.getTitle()) && !articleService.isTitleUnique(t))
return errorFactory.error(409, "Duplicate title");
article.setTitle(t);
}
if (body.has("abstract")) {
String abs = body.get("abstract").asText().trim();
if (abs.isEmpty()) return errorFactory.error(400, "Abstract cannot be empty");
article.setAbstract(abs);
}
if (body.has("body")) {
String b = body.get("body").asText().trim();
if (b.isEmpty()) return errorFactory.error(400, "Body cannot be empty");
article.setBody(b);
}
if (body.has("relatedArticleIds") && body.get("relatedArticleIds").isArray()) {
article.getRelatedArticles().clear();
for (JsonNode n : body.get("relatedArticleIds")) {
if (!n.canConvertToLong()) return errorFactory.error(400, "Invalid related id");
Long rid = n.asLong();
if (!rid.equals(article.getId()))
articleService.getArticleById(rid).ifPresent(article::addRelatedArticle);
}
}
articleService.saveArticle(article);
return new Router.RouteResponse().json(
DynamicRouterInitializer.articleToDetailJson(article).toString()
);
}
}
EOF
cat > "$BASE_DIR/dynamic/routehandlers/DeleteArticleHandler.java" << 'EOF'
package ir.ac.ut.ece.ie.server.dynamic.routehandlers;
import ir.ac.ut.ece.ie.routing.Router;
import ir.ac.ut.ece.ie.server.dynamic.ApiErrorFactory;
import ir.ac.ut.ece.ie.server.dynamic.DynamicRouterInitializer;
import ir.ac.ut.ece.ie.service.ArticleService;
public class DeleteArticleHandler {
private final ArticleService articleService;
private final ApiErrorFactory errorFactory = new ApiErrorFactory();
public DeleteArticleHandler(ArticleService articleService) {
this.articleService = articleService;
}
public Router.RouteResponse handleDelete(Router.RouteRequest req) {
Long id = DynamicRouterInitializer.parsePositiveLong(req.getParam("id"));
if (id == null) return errorFactory.error(400, "Invalid id");
if (articleService.getArticleById(id).isEmpty()) return errorFactory.error(404, "Not found");
articleService.deleteArticle(id);
return new Router.RouteResponse().status(204);
}
}
EOF

Xet Storage Details

Size:
45.4 kB
·
Xet hash:
a819d1ab9416395575691d5a441352079b9f38451af5957ee50222ee620f3796

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.