Is it possible to create a simple web server with Crystal that can serve HTML, CSS and JS pages?
My current code is:
require "http/server"
Port = 8080
Mime = "text/html"
server = HTTP::Server.new([HTTP::ErrorHandler.new, HTTP::LogHandler.new]) do |context|
req = context.request
if req.method == "GET"
filename = File.join(Dir.current, "index.html")
context.response.content_type = Mime
context.response.content_length = File.size(filename)
File.open(filename) { |file| IO.copy(file, context.response) }
next
end
context.response.content_type = Mime
end
puts "\e[1;33mStarted Listening on Port #{Port}\e[0m"
server.listen(Port)
When I run compile and run the program, it initializes the server, but there are a couple of problems:
The stylesheet http://127.0.0.1:8080/styling.css was not loaded because its MIME type, "text/html", is not "text/css". 127.0.0.1:8080
The script from “http://127.0.0.1:8080/javascript.js” was loaded even though its MIME type (“text/html”) is not a valid JavaScript MIME type. 127.0.0.1:8080
SyntaxError: expected expression, got '<' javascript.js:1
The server just shows only the content of index.html
.
The codes HTML, CSS and JS is perfectly valid when I run using WEBrick or load up the index.html directly to the browser.
Many thanks to @Johannes Müller which solved my problem. Here, I am sharing the code for what I wanted exactly.
#!/usr/bin/env crystal
require "http/server"
# Get the Address
ADDR = (ARGV.find { |x| x.split(".").size == 4 } || "0.0.0.0").tap { |x| ARGV.delete(x) }
.split(".").map { |x| x.to_i { 0 } }.join(".")
# Get the Port
PORT = ARGV.find { |x| x.to_i { 0 } > 0 }.tap { |x| ARGV.delete(x) }.to_s.to_i { 8080 }
# Get the path
d = Dir.current
dir = ARGV[0] rescue d
path = Dir.exists?(dir) ? dir : Dir.exists?(File.join(d, dir)) ? File.join(d, dir) : d
listing = !!Dir.children(path).find { |x| x == "index.html" }
actual_path = listing ? File.join(path, "index.html") : path
server = HTTP::Server.new([
HTTP::ErrorHandler.new,
HTTP::LogHandler.new,
HTTP::StaticFileHandler.new(path, directory_listing: !listing)
]) do |context|
context.response.content_type = "text/html"
File.open(actual_path) { |file| IO.copy(file, context.response) }
end
puts "\e[1;33m:: Starting Sharing \e[38;5;75m#{actual_path}\e[1;31m on \e[38;5;226mhttp://#{ADDR}:#{PORT}\e[0m"
server.listen(::ADDR, ::PORT)
This code looks for a "index.html" file to the provided path (default Dir.current), if found, it shares the index.html file to the IP address (default 0.0.0.0) and port (default 8080) provided, else it just shares the current directory contents.
crystal code.cr /tmp/ 5020 127.0.0.1
The options can be shuffled. For example:
crystal code.cr 5020 /tmp/ 127.0.0.1
Or
crystal code.cr 5020 127.0.0.1 /tmp
This will start the server and share the /tmp direcotory. If the index.html file is found inside the /tmp/ directory, the requested browser will display the index.html content, or it will work similar to an FTP (although it's not).
crystal build code.cr
./code [options]