156 lines
6.4 KiB
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";
|
|
}
|
|
}
|
|
}
|