A simple file server

Introduced properties

fun answer_default: HttpResponse

nitcorn :: FileServer :: answer_default

Answer the default_file if any.
fun answer_directory_listing(request: HttpRequest, turi: String, local_file: String): HttpResponse

nitcorn :: FileServer :: answer_directory_listing

Answer with a directory listing for files within local_files.
fun answer_file(local_file: String): HttpResponse

nitcorn :: FileServer :: answer_file

Build a reponse containing a single local_file.
fun answer_redirection(location: String): HttpResponse

nitcorn :: FileServer :: answer_redirection

Answer a 303 redirection to location.
fun cache_control: String

nitcorn :: FileServer :: cache_control

Caching attributes of served files, used as the cache-control field in response headers
fun cache_control=(cache_control: String)

nitcorn :: FileServer :: cache_control=

Caching attributes of served files, used as the cache-control field in response headers
fun default_file: nullable String

nitcorn :: FileServer :: default_file

Default file returned when no static file matches the requested URI.
fun default_file=(default_file: nullable String)

nitcorn :: FileServer :: default_file=

Default file returned when no static file matches the requested URI.
fun error_page(code: Int): Writable

nitcorn :: FileServer :: error_page

Error page template for a given code
fun header: nullable Writable

nitcorn :: FileServer :: header

Header of each directory page
fun header=(header: nullable Writable)

nitcorn :: FileServer :: header=

Header of each directory page
fun javascript_header: nullable Writable

nitcorn :: FileServer :: javascript_header

Custom JavaScript code added within a <script> block to each page
fun javascript_header=(javascript_header: nullable Writable)

nitcorn :: FileServer :: javascript_header=

Custom JavaScript code added within a <script> block to each page
fun root: String

nitcorn :: FileServer :: root

Root folder of self file system
protected fun root=(root: String)

nitcorn :: FileServer :: root=

Root folder of self file system
fun show_directory_listing=(show_directory_listing: Bool)

nitcorn :: FileServer :: show_directory_listing=

Show directory listing?

Redefined properties

redef type SELF: FileServer

nitcorn $ FileServer :: SELF

Type of this instance, automatically specialized in every class
redef fun answer(request: HttpRequest, turi: String): HttpResponse

nitcorn $ FileServer :: answer

Prepare a HttpResponse destined to the client in response to the request
redef init init

nitcorn $ FileServer :: init

All properties

fun !=(other: nullable Object): Bool

core :: Object :: !=

Have self and other different values?
fun ==(other: nullable Object): Bool

core :: Object :: ==

Have self and other the same value?
type CLASS: Class[SELF]

core :: Object :: CLASS

The type of the class of self.
type SELF: Object

core :: Object :: SELF

Type of this instance, automatically specialized in every class
abstract fun answer(request: HttpRequest, truncated_uri: String): HttpResponse

nitcorn :: Action :: answer

Prepare a HttpResponse destined to the client in response to the request
fun answer_default: HttpResponse

nitcorn :: FileServer :: answer_default

Answer the default_file if any.
fun answer_directory_listing(request: HttpRequest, turi: String, local_file: String): HttpResponse

nitcorn :: FileServer :: answer_directory_listing

Answer with a directory listing for files within local_files.
fun answer_file(local_file: String): HttpResponse

nitcorn :: FileServer :: answer_file

Build a reponse containing a single local_file.
fun answer_redirection(location: String): HttpResponse

nitcorn :: FileServer :: answer_redirection

Answer a 303 redirection to location.
fun cache_control: String

nitcorn :: FileServer :: cache_control

Caching attributes of served files, used as the cache-control field in response headers
fun cache_control=(cache_control: String)

nitcorn :: FileServer :: cache_control=

Caching attributes of served files, used as the cache-control field in response headers
protected fun class_factory(name: String): CLASS

core :: Object :: class_factory

Implementation used by get_class to create the specific class.
fun class_name: String

core :: Object :: class_name

The class name of the object.
fun default_file: nullable String

nitcorn :: FileServer :: default_file

Default file returned when no static file matches the requested URI.
fun default_file=(default_file: nullable String)

nitcorn :: FileServer :: default_file=

Default file returned when no static file matches the requested URI.
fun error_page(code: Int): Writable

nitcorn :: FileServer :: error_page

Error page template for a given code
fun get_class: CLASS

core :: Object :: get_class

The meta-object representing the dynamic type of self.
fun hash: Int

core :: Object :: hash

The hash code of the object.
fun header: nullable Writable

nitcorn :: FileServer :: header

Header of each directory page
fun header=(header: nullable Writable)

nitcorn :: FileServer :: header=

Header of each directory page
init init

core :: Object :: init

fun inspect: String

core :: Object :: inspect

Developer readable representation of self.
protected fun inspect_head: String

core :: Object :: inspect_head

Return "CLASSNAME:#OBJECTID".
intern fun is_same_instance(other: nullable Object): Bool

core :: Object :: is_same_instance

Return true if self and other are the same instance (i.e. same identity).
fun is_same_serialized(other: nullable Object): Bool

core :: Object :: is_same_serialized

Is self the same as other in a serialization context?
intern fun is_same_type(other: Object): Bool

core :: Object :: is_same_type

Return true if self and other have the same dynamic type.
fun javascript_header: nullable Writable

nitcorn :: FileServer :: javascript_header

Custom JavaScript code added within a <script> block to each page
fun javascript_header=(javascript_header: nullable Writable)

nitcorn :: FileServer :: javascript_header=

Custom JavaScript code added within a <script> block to each page
intern fun object_id: Int

core :: Object :: object_id

An internal hash code for the object based on its identity.
fun output

core :: Object :: output

Display self on stdout (debug only).
intern fun output_class_name

core :: Object :: output_class_name

Display class name on stdout (debug only).
protected fun prepare_respond_and_close(request: HttpRequest, truncated_uri: String, http_server: HttpServer)

nitcorn :: Action :: prepare_respond_and_close

Full to a request with sending the response and closing of the http_server
fun root: String

nitcorn :: FileServer :: root

Root folder of self file system
protected fun root=(root: String)

nitcorn :: FileServer :: root=

Root folder of self file system
fun serialization_hash: Int

core :: Object :: serialization_hash

Hash value use for serialization
fun show_directory_listing=(show_directory_listing: Bool)

nitcorn :: FileServer :: show_directory_listing=

Show directory listing?
intern fun sys: Sys

core :: Object :: sys

Return the global sys object, the only instance of the Sys class.
abstract fun to_jvalue(env: JniEnv): JValue

core :: Object :: to_jvalue

fun to_s: String

core :: Object :: to_s

User readable representation of self.
package_diagram nitcorn::FileServer FileServer nitcorn::Action Action nitcorn::FileServer->nitcorn::Action core::Object Object nitcorn::Action->core::Object ...core::Object ... ...core::Object->core::Object

Ancestors

interface Object

core :: Object

The root of the class hierarchy.

Parents

abstract class Action

nitcorn :: Action

Action executed to answer a request

Class definitions

nitcorn $ FileServer
# A simple file server
class FileServer
	super Action

	# Root folder of `self` file system
	var root: String

	init
	do
		var root = self.root

		# Simplify the root path as each file requested will also be simplified
		root = root.simplify_path

		# Make sure the root ends with '/', this makes a difference in the security
		# check on each file access.
		root = root + "/"

		self.root = root
	end

	# Error page template for a given `code`
	fun error_page(code: Int): Writable do return new ErrorTemplate(code)

	# Header of each directory page
	var header: nullable Writable = null is writable

	# Custom JavaScript code added within a `<script>` block to each page
	var javascript_header: nullable Writable = null is writable

	# Caching attributes of served files, used as the `cache-control` field in response headers
	var cache_control = "public, max-age=360" is writable

	# Show directory listing?
	var show_directory_listing = true is writable

	# Default file returned when no static file matches the requested URI.
	#
	# If no `default_file` is provided, the FileServer responds 404 error to
	# unmatched queries.
	var default_file: nullable String = null is writable

	redef fun answer(request, turi)
	do
		var response

		var local_file = root.join_path(turi.strip_start_slashes)
		local_file = local_file.simplify_path

		# Is it reachable?
		#
		# This make sure that the requested file is within the root folder.
		if (local_file + "/").has_prefix(root) then
			# Does it exists?
			var file_stat = local_file.file_stat
			if file_stat != null then
				if file_stat.is_dir then
					# If we target a directory without an ending `/`,
					# redirect to the directory ending with `/`.
					var uri = request.uri
					if not uri.is_empty and uri.chars.last != '/' then
						return answer_redirection(request.uri + "/")
					end

					# Show index file instead of the directory listing
					# only if `index.html` or `index.htm` is available
					var index_file = local_file.join_path("index.html")
					if index_file.file_exists then
						local_file = index_file
					else
						index_file = local_file.join_path("index.htm")
						if index_file.file_exists then local_file = index_file
					end
				end

				file_stat = local_file.file_stat
				if show_directory_listing and file_stat != null and file_stat.is_dir then
					response = answer_directory_listing(request, turi, local_file)
				else if file_stat != null and not file_stat.is_dir then # It's a single file
					response = answer_file(local_file)
				else response = answer_default
			else response = answer_default
		else response = new HttpResponse(403)

		if response.status_code != 200 then
			var tmpl = error_page(response.status_code)
			if header != null and tmpl isa ErrorTemplate then tmpl.header = header
			response.body = tmpl
		end

		return response
	end

	# Answer the `default_file` if any.
	fun answer_default: HttpResponse do
		var default_file = self.default_file
		if default_file == null then
			return new HttpResponse(404)
		end

		var local_file = (root / default_file).simplify_path
		return answer_file(local_file)
	end

	# Answer a 303 redirection to `location`.
	fun answer_redirection(location: String): HttpResponse do
		var response = new HttpResponse(303)
		response.header["Location"] = location
		return response
	end

	# Build a reponse containing a single `local_file`.
	#
	# Returns a 404 error if local_file does not exists.
	fun answer_file(local_file: String): HttpResponse do
		if not local_file.file_exists then return new HttpResponse(404)

		var response = new HttpResponse(200)
		response.files.add local_file

		# Set Content-Type depending on the file extension
		var ext = local_file.file_extension
		if ext != null then
			var media_type = media_types[ext]
			if media_type != null then
				response.header["Content-Type"] = media_type
			else response.header["Content-Type"] = "application/octet-stream"
		end

		# Cache control
		response.header["cache-control"] = cache_control
		return response
	end

	# Answer with a directory listing for files within `local_files`.
	fun answer_directory_listing(request: HttpRequest, turi, local_file: String): HttpResponse do
		# Show the directory listing
		var title = turi
		var files = local_file.files

		alpha_comparator.sort files

		var links = new Array[String]
		if turi.length > 1 then
			var path = (request.uri + "/..").simplify_path
			links.add "<a href=\"{path}/\">..</a>"
		end
		for file in files do
			var local_path = local_file.join_path(file).simplify_path
			var web_path = file.simplify_path
			var file_stat = local_path.file_stat
			if file_stat != null and file_stat.is_dir then web_path = web_path + "/"
			links.add "<a href=\"{web_path}\">{file}</a>"
		end

		var header = self.header
		var header_code
		if header != null then
			header_code = header.write_to_string
		else header_code = ""

		var response = new HttpResponse(200)
		response.body = """
<!DOCTYPE html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
	<script>
		{{{javascript_header or else ""}}}
	</script>
	<title>{{{title}}}</title>
</head>
<body>
	{{{header_code}}}
	<div class="container">
		<h1>{{{title}}}</h1>
		<ul>
			<li>{{{links.join("</li>\n\t\t\t<li>")}}}</li>
		</ul>
	</div>
</body>
</html>"""

		response.header["Content-Type"] = media_types["html"].as(not null)
		return response
	end
end
lib/nitcorn/file_server.nit:41,1--228,3