Skip to content

Instantly share code, notes, and snippets.

@J-Moravec
Last active December 8, 2024 05:07
Show Gist options
  • Save J-Moravec/497d71f4a4b7a204235d093b3fa69cc3 to your computer and use it in GitHub Desktop.
Save J-Moravec/497d71f4a4b7a204235d093b3fa69cc3 to your computer and use it in GitHub Desktop.
Implementation of a local server for static website using the R's internal httpd server.
error404 = paste0(
"<!DOCTYPE html>",
"<html lang=\"en\">",
"<head>",
" <meta charset=\"UTF-8\">",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
" <title>Resources not found</title>",
"</head>",
"<body>",
" <div class=\"main\">",
" <h1>404</h1>",
" <div>The page you are looking for is not found</div>",
" <a href=\"/\">Back to home</a>",
" </div>",
"</body>",
"</html>",
collapse = "\n"
)
#' Serve static sites
#'
#' Take the path, such as "/", "/index.html", "posts/learning_r.html"
#' and serve a file with an appropriate mimetype.
#' If file is not found, return the 404 error page.
#'
#' All links within the html pages (such as css, images) are also translated in this way.
#'
#' query and ... are ignored
static_server = function(path, query, ...){
path = sub(pattern = "^/", replace = "", path)
if(path == "") path = "index.html"
if(file.exists(path) && file_test("-f", path)){
list(file = path, "content-type" = tools:::mime_type(path))
} else {
list(payload = error404)
}
}
#' Print arguments
#'
#' this is a toy server for learning how are the arguments
#' that are passed to the browser URL interpreted.
#'
#' For instance, the default the server is launched with:
#' "127.0.0.1:port", which translates to "/" path.
#' If we type into browser "127.0.0.1:port/foo?bar=1&baz=2",
#' then path will be "/foo", and query a named vector:
#' `c("foo" = 1, "bar" = 2)`
#' In all cases, the `...` contains NULL and then a raw vector with
#' user agent information.
print_server = function(path, query, ...){
list(
payload = paste0(
c(
"path:", capture.output(str(path)),
"query:", capture.output(str(query)),
"...", capture.output(str(list(...)))
),
collapse = "<p>"
)
)
}
assign_in_namespace = function(x, f, envir){
old = get(x, envir = envir)
unlockBinding(x, envir)
assign(x, f, envir = envir)
lockBinding(x, envir)
invisible(old)
}
#' Start a server and open it in browser
#'
#' This function will start server `f` in a directory `dir`
#' on a prespecified `port` (or random, by default) on a localhost
#' (loopback device 127.0.0.1)
#'
#' It will then open browser and point at `127.0.0.1:port`
#' and wait. Terminating this process, such as with CTRL+C
#' will close the server.
#'
#' The default server is the `print_server` at current directory.
#' You can explore how the website URL translate to different
#' parameters passed to the server function.
#'
#' To effectively use the `static_server`, you need to specify
#' the `f = static_server` and set `dir` to a path with a static site.
#' This site needs to be generated for local browsing
#' (e.g., "site_url" needs to be empty instead of something like
#' "[email protected]".
#' Feel free to clone https://github.com/j-moravec/cookingrecipes
#' for this purpose and run `Rscript ssg.r -s`.
#' Running this code (serve.r) will then replace `Rscript ssg.r -v` which
#' uses the package `servr` for this purpose.
serve = function(dir = ".", port = 0, f = print_server){
f = match.fun(f)
dir = normalizePath(dir)
if(port)
options(help.ports = port)
old_f = assign_in_namespace("httpd", f, getNamespace("tools"))
old_wd = getwd()
setwd(dir)
on.exit({
port = tools:::httpdPort()
if(port > 0)
tools:::startDynamicHelp(FALSE)
assign_in_namespace("httpd", old_f, getNamespace("tools"))
setwd(old_wd)
})
port = tools:::httpdPort()
if(port > 0){
# server is running, restart it
tools:::startDynamicHelp(FALSE)
}
port = tools:::startDynamicHelp(NA)
url = paste0("http://127.0.0.1:", port)
message("Serving directory: ", dir)
message(paste("Served at:", url))
browser = getOption("browser")
browseURL(url, browser = browser)
Sys.sleep(Inf)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment