Files
sisvietnamvn_01/appointment/AppointmentWebServer.java

156 lines
6.4 KiB
Java

package vn.sis.appointment;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Executors;
final class AppointmentWebServer {
private final HttpServer server;
private final SisApiClient apiClient = new SisApiClient();
AppointmentWebServer(int port) throws IOException {
server = HttpServer.create(new InetSocketAddress("0.0.0.0", port), 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/api/health", exchange -> {
if (!requireMethod(exchange, "GET")) {
return;
}
sendJson(exchange, 200, Json.stringify(Map.of(
"ok", true,
"application", "SisAppointmentJavaWeb",
"javaVersion", System.getProperty("java.version"))));
});
server.createContext("/api/token", postHandler((form, config) -> apiClient.checkToken(config)));
server.createContext("/api/doctors", postHandler((form, config) -> apiClient.getDoctors(config)));
server.createContext("/api/schedule", postHandler((form, config) ->
apiClient.getSchedule(config, form.getOrDefault("doctorCode", ""))));
server.createContext("/api/clear-token", postHandler((form, config) -> {
apiClient.clearToken(config);
return new ProxyResponse(true, 200, "OK", "-", "-", 0,
"", "", "", null, "Da xoa access token trong bo nho may chu.");
}));
server.createContext("/", new StaticHandler());
}
void start() {
server.start();
}
private HttpHandler postHandler(ApiOperation operation) {
return exchange -> {
if (!requireMethod(exchange, "POST")) {
return;
}
try {
Map<String, String> form = FormData.read(exchange);
ApiConfig config = ApiConfig.fromForm(form);
ProxyResponse result = operation.execute(form, config);
sendJson(exchange, 200, result.toJson());
} catch (IllegalArgumentException ex) {
ProxyResponse error = new ProxyResponse(false, 400, "Bad Request", "-", "-", 0,
"", "", "", null, ex.getMessage());
sendJson(exchange, 400, error.toJson());
} catch (Exception ex) {
ProxyResponse error = new ProxyResponse(false, 500, "Internal Server Error", "-", "-", 0,
"", "", "", null,
ex.getMessage() == null ? ex.getClass().getSimpleName() : ex.getMessage());
sendJson(exchange, 500, error.toJson());
}
};
}
private static boolean requireMethod(HttpExchange exchange, String expected) throws IOException {
if (exchange.getRequestMethod().equalsIgnoreCase(expected)) {
return true;
}
exchange.getResponseHeaders().set("Allow", expected);
sendJson(exchange, 405, Json.stringify(Map.of(
"ok", false,
"message", "Phuong thuc khong duoc ho tro.")));
return false;
}
private static void sendJson(HttpExchange exchange, int status, String json) throws IOException {
byte[] data = json.getBytes(StandardCharsets.UTF_8);
applySecurityHeaders(exchange.getResponseHeaders());
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
exchange.getResponseHeaders().set("Cache-Control", "no-store");
exchange.sendResponseHeaders(status, data.length);
exchange.getResponseBody().write(data);
exchange.close();
}
private static void applySecurityHeaders(Headers headers) {
headers.set("X-Content-Type-Options", "nosniff");
headers.set("X-Frame-Options", "DENY");
headers.set("Referrer-Policy", "no-referrer");
headers.set("Content-Security-Policy",
"default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; connect-src 'self'");
}
@FunctionalInterface
private interface ApiOperation {
ProxyResponse execute(Map<String, String> form, ApiConfig config);
}
private static final class StaticHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("GET")) {
exchange.getResponseHeaders().set("Allow", "GET");
exchange.sendResponseHeaders(405, -1);
exchange.close();
return;
}
String path = exchange.getRequestURI().getPath();
if (path.equals("/")) {
path = "/index.html";
}
if (path.contains("..")) {
exchange.sendResponseHeaders(400, -1);
exchange.close();
return;
}
String resource = "web" + path;
try (InputStream input = AppointmentWebServer.class.getClassLoader().getResourceAsStream(resource)) {
if (input == null) {
exchange.sendResponseHeaders(404, -1);
exchange.close();
return;
}
byte[] data = input.readAllBytes();
Headers headers = exchange.getResponseHeaders();
applySecurityHeaders(headers);
headers.set("Content-Type", contentType(path));
headers.set("Cache-Control", path.endsWith("index.html") ? "no-cache" : "public, max-age=3600");
exchange.sendResponseHeaders(200, data.length);
exchange.getResponseBody().write(data);
exchange.close();
}
}
private static String contentType(String path) {
if (path.endsWith(".html")) return "text/html; charset=utf-8";
if (path.endsWith(".css")) return "text/css; charset=utf-8";
if (path.endsWith(".js")) return "application/javascript; charset=utf-8";
if (path.endsWith(".svg")) return "image/svg+xml";
if (path.endsWith(".png")) return "image/png";
return "application/octet-stream";
}
}
}