15 Volts
15 Volts

Reputation: 2077

A WEBrick like server with Crystal

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:

  1. In the Firefox browser's "Inspect Element" console, I see:
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.

  1. My server isn't accessible from any other devices on the local network.

Upvotes: 1

Views: 504

Answers (3)

Ujjwal Kumar Gupta
Ujjwal Kumar Gupta

Reputation: 2376

If you don't want to deep dive into native code & wants a simple solution - You should use any framework which provides file server.

You can use Shivneri framework for this. Shivneri has inbuilt file server which is easy to configure -

Shivneri.folders = [{
    path: "/",
    folder:  File.join(Dir.current, "assets"),
}]

You can add as many folder as you want to use. Every folder will be mapped with provided path.

For more info read doc - https://shivneriforcrystal.com/tutorial/file-server/

Upvotes: 0

15 Volts
15 Volts

Reputation: 2077

Many thanks to @Johannes Müller which solved my problem. Here, I am sharing the code for what I wanted exactly.

Code:

#!/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.

Running:

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).

Compiling and Running:

crystal build code.cr
./code [options]

Upvotes: 2

Johannes M&#252;ller
Johannes M&#252;ller

Reputation: 5661

You might want to use HTTP::StaticFileHandler for this. It serves files from a local directory.

  1. In the handler you always read file index.html whatever the request is. This can't work.
  2. HTTP::Server#listen listens on 127.0.0.1, so it's only available from localhost. In order to be accessible from the network, you need to listen to an address that is available on the network. For example server.listen("0.0.0.0", Port) will listen on all interfaces.

Upvotes: 4

Related Questions