FileServer.vala 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 async HttpResult handle_request(HttpContext http_context, RouteContext route) throws Error {
  25. var info = @"Astralis File Server
  26. Serving: $served_directory
  27. Port: $port
  28. Available compression: gzip, brotli, zstd
  29. Endpoints:
  30. /** - Serves files from the directory
  31. /__server_info - This information page
  32. Try:
  33. curl http://localhost:$port/
  34. curl --compressed http://localhost:$port/style.css
  35. ";
  36. return new HttpStringResult(info)
  37. .set_header("Content-Type", "text/plain");
  38. }
  39. }
  40. void main(string[] args) {
  41. // Parse command line arguments
  42. if (args.length < 2) {
  43. printerr("Usage: %s <directory> [port]\n", args[0]);
  44. printerr("\nServes files from the specified directory over HTTP.\n");
  45. printerr("Supports compression: gzip, brotli, zstd\n");
  46. printerr("\nExamples:\n");
  47. printerr(" %s /var/www/html\n", args[0]);
  48. printerr(" %s ./public 8080\n", args[0]);
  49. Process.exit(1);
  50. }
  51. string directory = args[1];
  52. int port = args.length > 2 ? int.parse(args[2]) : 8080;
  53. // Validate directory exists
  54. var dir_file = File.new_for_path(directory);
  55. if (!dir_file.query_exists()) {
  56. printerr("Error: Directory '%s' does not exist\n", directory);
  57. Process.exit(1);
  58. }
  59. // Check if it's actually a directory
  60. try {
  61. var info = dir_file.query_info("standard::type", FileQueryInfoFlags.NONE);
  62. if (info.get_file_type() != FileType.DIRECTORY) {
  63. printerr("Error: '%s' is not a directory\n", directory);
  64. Process.exit(1);
  65. }
  66. } catch (Error e) {
  67. printerr("Error checking directory: %s\n", e.message);
  68. Process.exit(1);
  69. }
  70. // Resolve to absolute path
  71. string absolute_path = dir_file.get_path();
  72. // Create the filesystem resource with deep matching
  73. FilesystemResource file_resource;
  74. try {
  75. file_resource = new FilesystemResource("/**", absolute_path);
  76. file_resource.allow_directory_listing = true;
  77. file_resource.index_file = "index.html";
  78. } catch (FilesystemResourceError e) {
  79. printerr("Error creating file resource: %s\n", e.message);
  80. Process.exit(1);
  81. }
  82. // Print startup information
  83. print("╔══════════════════════════════════════════════════════════════╗\n");
  84. print("║ Astralis File Server ║\n");
  85. print("╠══════════════════════════════════════════════════════════════╣\n");
  86. print(@"║ Serving: $(absolute_path)");
  87. if (absolute_path.length < 50) {
  88. for (int i = 0; i < 50 - absolute_path.length; i++) print(" ");
  89. }
  90. print(" ║\n");
  91. print(@"║ Port: $port");
  92. for (int i = 0; i < 50 - port.to_string().length; i++) print(" ");
  93. print(" ║\n");
  94. print("╠══════════════════════════════════════════════════════════════╣\n");
  95. print("║ Compression: gzip, brotli, zstd ║\n");
  96. print("╠══════════════════════════════════════════════════════════════╣\n");
  97. print("║ Try: ║\n");
  98. print(@"║ http://localhost:$port/");
  99. for (int i = 0; i < 50 - 21 - port.to_string().length; i++) print(" ");
  100. print(" ║\n");
  101. print(@"║ http://localhost:$port/__server_info");
  102. for (int i = 0; i < 50 - 35 - port.to_string().length; i++) print(" ");
  103. print(" ║\n");
  104. print("╚══════════════════════════════════════════════════════════════╝\n");
  105. print("\nPress Ctrl+C to stop the server\n\n");
  106. // Create the application and register endpoints
  107. var application = new WebApplication(port);
  108. // Register compression components
  109. application.container.register_singleton<GzipCompressor>(() => new GzipCompressor());
  110. application.container.register_singleton<ZstdCompressor>(() => new ZstdCompressor());
  111. application.container.register_singleton<BrotliCompressor>(() => new BrotliCompressor());
  112. // Register server info endpoint
  113. application.container.register_scoped<Endpoint>(() => new ServerInfoEndpoint(absolute_path, port))
  114. .with_metadata<EndpointRoute>(new EndpointRoute("/__server_info"));
  115. // Register filesystem resource endpoint as singleton (holds configuration)
  116. // The factory creates the instance and the container caches it
  117. application.container.register_singleton<Endpoint>(() => file_resource)
  118. .with_metadata<EndpointRoute>(new EndpointRoute("/**"));
  119. // Run the server
  120. application.run();
  121. }