FileServer.vala 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. using Astralis;
  2. using Invercargill;
  3. using Invercargill.DataStructures;
  4. /**
  5. * FileServer Example
  6. *
  7. * A static file server that serves files from a directory specified
  8. * on the command line. Supports compression via gzip, brotli, and zstd.
  9. *
  10. * Usage: file-server <directory> [port]
  11. *
  12. * Examples:
  13. * file-server /var/www/html
  14. * file-server ./public 8080
  15. */
  16. // Root endpoint that shows server info
  17. class ServerInfoEndpoint : Object, Endpoint {
  18. private string served_directory;
  19. private int port;
  20. public ServerInfoEndpoint(string served_directory, int port) {
  21. this.served_directory = served_directory;
  22. this.port = port;
  23. }
  24. public string route { get { return "/__server_info"; } }
  25. public Method[] methods { owned get { return new Method[] { Method.GET }; } }
  26. public async HttpResult handle_request(HttpContext http_context, RouteInformation route) throws Error {
  27. var info = @"Astralis File Server
  28. Serving: $served_directory
  29. Port: $port
  30. Available compression: gzip, brotli, zstd
  31. Endpoints:
  32. /** - Serves files from the directory
  33. /__server_info - This information page
  34. Try:
  35. curl http://localhost:$port/
  36. curl --compressed http://localhost:$port/style.css
  37. ";
  38. return new HttpStringResult(info)
  39. .set_header("Content-Type", "text/plain");
  40. }
  41. }
  42. void main(string[] args) {
  43. // Parse command line arguments
  44. if (args.length < 2) {
  45. printerr("Usage: %s <directory> [port]\n", args[0]);
  46. printerr("\nServes files from the specified directory over HTTP.\n");
  47. printerr("Supports compression: gzip, brotli, zstd\n");
  48. printerr("\nExamples:\n");
  49. printerr(" %s /var/www/html\n", args[0]);
  50. printerr(" %s ./public 8080\n", args[0]);
  51. Process.exit(1);
  52. }
  53. string directory = args[1];
  54. int port = args.length > 2 ? int.parse(args[2]) : 8080;
  55. // Validate directory exists
  56. var dir_file = File.new_for_path(directory);
  57. if (!dir_file.query_exists()) {
  58. printerr("Error: Directory '%s' does not exist\n", directory);
  59. Process.exit(1);
  60. }
  61. // Check if it's actually a directory
  62. try {
  63. var info = dir_file.query_info("standard::type", FileQueryInfoFlags.NONE);
  64. if (info.get_file_type() != FileType.DIRECTORY) {
  65. printerr("Error: '%s' is not a directory\n", directory);
  66. Process.exit(1);
  67. }
  68. } catch (Error e) {
  69. printerr("Error checking directory: %s\n", e.message);
  70. Process.exit(1);
  71. }
  72. // Resolve to absolute path
  73. string absolute_path = dir_file.get_path();
  74. // Create the filesystem resource with deep matching
  75. FilesystemResource file_resource;
  76. try {
  77. file_resource = new FilesystemResource("/**", absolute_path);
  78. file_resource.allow_directory_listing = true;
  79. file_resource.index_file = "index.html";
  80. } catch (FilesystemResourceError e) {
  81. printerr("Error creating file resource: %s\n", e.message);
  82. Process.exit(1);
  83. }
  84. // Set up the router with file serving and server info endpoints
  85. var router = new EndpointRouter()
  86. .add_endpoint(new ServerInfoEndpoint(absolute_path, port))
  87. .add_endpoint(file_resource);
  88. // Create compression pipeline components
  89. // Order matters: we try brotli first (best compression), then zstd, then gzip (most compatible)
  90. var brotli = new BrotliCompressor();
  91. var zstd = new ZstdCompressor();
  92. var gzip = new GzipCompressor();
  93. // Build the pipeline with compression support
  94. var pipeline = new Pipeline()
  95. .add_component(gzip)
  96. .add_component(zstd)
  97. .add_component(brotli)
  98. .add_component(router);
  99. // Create and configure the server
  100. var server = new Server(port, pipeline);
  101. // Print startup information
  102. print("╔══════════════════════════════════════════════════════════════╗\n");
  103. print("║ Astralis File Server ║\n");
  104. print("╠══════════════════════════════════════════════════════════════╣\n");
  105. print(@"║ Serving: $(absolute_path)");
  106. if (absolute_path.length < 50) {
  107. for (int i = 0; i < 50 - absolute_path.length; i++) print(" ");
  108. }
  109. print(" ║\n");
  110. print(@"║ Port: $port");
  111. for (int i = 0; i < 50 - port.to_string().length; i++) print(" ");
  112. print(" ║\n");
  113. print("╠══════════════════════════════════════════════════════════════╣\n");
  114. print("║ Compression: gzip, brotli, zstd ║\n");
  115. print("╠══════════════════════════════════════════════════════════════╣\n");
  116. print("║ Try: ║\n");
  117. print(@"║ http://localhost:$port/");
  118. for (int i = 0; i < 50 - 21 - port.to_string().length; i++) print(" ");
  119. print(" ║\n");
  120. print(@"║ http://localhost:$port/__server_info");
  121. for (int i = 0; i < 50 - 35 - port.to_string().length; i++) print(" ");
  122. print(" ║\n");
  123. print("╚══════════════════════════════════════════════════════════════╝\n");
  124. print("\nPress Ctrl+C to stop the server\n\n");
  125. // Run the server
  126. server.run();
  127. }