Skip to Content

Single User Responsive Object Server

The single user responsive object server is a collection of Elisp declarations for operating a web server. It is in the earliest stages of development.

Table of Contents

Introduction

The single user responsive object server is a collection of Elisp declarations for operating a web server. It is in the earliest stages of development.

This document was written by me, emsenn, in the United States. To the extent possible under law, I have waived all copyright and related or neighboring rights to this document. This document was created for the benefit of the public.

Single User Responsive Object Server

The single user responsive object server (hereafter SUROS) is a server implemented in Elisp. Currently it serves HTML documents, which are generically called “objects.” These objects are created using a combination of data serialized as Elisp lists and Org-mode files.

In addition to requiring I believe Emacs version 24 or greater, these instructions require the following Emacs packages

Data

Your SUROS relies on at least having a configuration to look at, currently kept at ems-suros-config. The other kind of data are its templates - collections of mustache templates that are filled in with data to make the rendered objects.

Mustache Functions

(setq ems-suros-mustache-functions
      (ht ("oxfordize-list"
	   (lambda (template context)
	   context))))

Config(uration)

This is the configuration for your SUROS, and has the information about the actor it’s to represent.

(setq ems-suros-config
(ht ("base-url" "suros-emsenn.pagekite.me")
    ("actor-public-key" "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoo+p0r7lPkESdf9JQ/cw\nBFpQQd8NDddM++Q4mqROliq//8U7UBe9c7AQTfnRz3jc5zs8e+kL0L0tFywv2wwS\nDBp4hq5fQuyNMq/XebEveYjtu/Ckw5alcrtwKI+ArdJJ3rGaGzKLtNDRrxoc40u1\nWnGulzsxNzjoJy+OvPgjjdqaBB55k+oKL6jC/MI5WQN86FYEG/uMXkRSXIJbbB2M\nZMOmLjBi3VlVJ/p9robd7dImb4jhP/9YHQ/wFEzRe3ekD4KKPUkhpM63z1WjVh8n\nHPgulhB6OrAu0phwCozA3TZvg368GJKle6GdxHyLH3dnBvwue3AhyNOYXeVuK0fn\nhwIDAQAB\n-----END PUBLIC KEY-----")
    ("preferred-name" "emsenn")
    ("username" "emsenn")
    ("proper-name" "emsenn")
    ("make-verb" "craft")
    ("make-things" '("words" . "computer instructions"))
    ("template-directory" "~/src/suros/mustache/")))
Variables
preferred-name
The name you would prefer to be called in general company.
proper-name
The name you’d prefer to be used in official contexts.
template-directory
The directory where SUROS will look for mustache templates.
make-verb :
make-things :
language-code :

Templates

(I’m not sure how I want to lay out this datastructure since it can have partials too. I’ll probably mimick Hugo’s layout and lookup rules until I have reason not to.) These are parsed by mustache.

CSS
Brutstrap
body {
position: relative;
background-color: #eee;
color: #444;
font-family: serif;
margin: 0 auto;
padding-bottom: 6rem;
min-height: 100%;
font-size: 1.2em;
}
header, h1 {
font-family: sans-serif;
text-align: left;
margin-left: 1em;
width: 80vw;
overflow: hidden;
font-family: sans-serif;
}
.title { font-size: 3.2rem; }
.subtitle { font-size: 2.2rem; }
main, #content {
width: 75vw;
max-width: 40em;
margin: 0 0 0 2em;
line-height: 1.6;
margin-bottom: 8rem;
}
footer, #postamble {
padding: 1em 0;
position: absolute;
right: 0;
bottom: 0;
left: 0;
}
section {
border-bottom: 0.1em solid #444;
margin-bottom: 1em;
}
.outline-3 {
border-bottom: 0.1rem dotted #444;
}
blockquote {
padding: 0.5rem;
border-left: 0.1em solid #444;
}
table {
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 0.1em solid #444;
}
thead { font-family: sans-serif; }
th, td {
padding: 0 0.3em;
border: 0.01em solid #444;
}
img {
max-width: 80vw;
vertical-align: middle;
}
pre, .src {
padding: 0.5em;
border: 0.1em solid #444;
white-space: pre-wrap;
overflow-x: scroll;
text-overflow: clip;
}
dt {
font-weight: normal;
display: inline-block;
border-bottom: 0.1rem dotted #444;
}
#text-index dt { border-bottom: none; }
h2, h3, h4, h5, h6, h7, h8, h9, h10 {
display: block;
font-family: sans-serif;
margin: 0.5em;
}
h2 {
text-align: center;
}
h2 { font-size: 2em; }
h3 {
font-size: 1.8em;
}
h4 {
font-size: 1.6em;
}
h5 {
font-size: 1.4em;
margin: 0.5em 0 0.2em 10%;
}
h6 {
font-size: 1.2em;
margin: 0.5em 0 0.2em 30%;
}
a {
text-decoration: none;
color: #3ac;
display: inline;
position: relative;
border-bottom: 0.1rem dotted;
line-height: 1.2;
transition: border 0.3s;
}
a:hover {
color: #5ce;
outline-style: none;
border-bottom: 0.1rem solid;
}
a:visited { color: #b8c; }
a:visited:hover { color: #dae; }
a:focus {
outline-style: none;
border-bottom: 0.1rem solid;
}
::selection { background-color: #777; color: #eee; }
a::selection { background-color: #ccc; }
.todo {
background-color: #ddd;
color: #333;
font-size: .8rem;
float: right;
}
.org-src-container + .example {
margin-left: 8em;
}
label.org-src-name {
margin: 0 0 0 3em;
font-family: sans-serif;
font-size: 1rem;
line-height: 0;
}
.skipToContentLink {
opacity: 0;
position: absolute;
}
.skipToContentLink:focus{
opacity:1
}
JSON
Actor
{
	"@context": [
		"https://www.w3.org/ns/activitystreams",
		"https://w3id.org/security/v1"
	],

	"id": "https://{{base-url}}/actor",
	"type": "Person",
	"preferredUsername": "{{username}}",
	"inbox": "https://{{base-url}}/inbox",

	"publicKey": {
		"id": "https://{{base-url}}/actor#main-key",
		"owner": "https://{{base-url}}/actor",
		"publicKeyPem": "{{actor-public-key}}"
	}

Webfinger
{
	"subject": "acct:{{username}}@{{base-url}}",

	"links": [
		{
			"rel": "self",
			"type": "application/activity+json",
			"href": "https://{{base-url}}/actor"
		}
	]
}
HTML
Document Declaration
<!DOCTYPE html>
<html
  xmlns="http://www.w3.org/1999/xhtml" {{#language-code}}
  xml:lang="{{language-code}}"
  lang="{{language-code}}"
  {{/language-code}}>
Index Page
{{> document-declaration.html}}
<head>
  {{> head-meta.html}}
  {{> head-links.html}}
  <style>
    {{> brutstrap.css}}
  </style>
</head>
<body>
  <a class="skipToContentLink" href="#content">Skip to Content</a>
  <header>
    <h1>Single User Responsive Object Server.</h1>
  </header>
  <section id="content">
    <p>
      My name is {{preferred-name}} and this is my <em>SUROS</em>, an early-alpha webserver. See the source code and documentation on my <a href="https://emsenn.net/computer-instruction-sets/suros/">personal website</a>, which I hope this will replace.
    </p>
  </section>
</body>
</html>
Contact Page
  <html>
    <body>
fdafdsfs
    </body>
  </html>
Head Meta
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
<meta name="referrer" content="no-referrer">
{{#object-description}}<meta name="description" content={{object-description}}">{{/object-description}}
Text
Maker Brief
I {{#make-verb}}{{make-verb}}{{/make-verb}}{{^make-verb}}make{{/make-verb}}
{{#oxfordize-list}}fasfdasfd{{make-things}}{{/oxfordize-list}}

Functions

Template Exists

(defun ems-suros-template-p (template)
  "Returns true if there is a file at the given template name. Does no checking of validity."
    (file-exists-p (concat (gethash "template-directory" ems-suros-config) template ".mustache")))

Render

(defun ems-suros-render (template)
  "Returns the rendering of template."
  (let ((mustache-partial-paths (list (gethash "template-directory" ems-suros-config)))
	(template (with-temp-buffer (insert-file-contents
				     (concat (gethash "template-directory" ems-suros-config) template ".mustache"))
			    (buffer-string))))
	(mustache-render template ems-suros-config)))

Make Mustache Context

  (defun ems-suros-make-mustache-context ()
    "Returns a hashtable of the root mustache context."
    (let ((mustache-context (ht-create)))
      (ht-map (lambda (key value) (ht-set mustache-context key value)) ems-suros-config)
      (ht-map (lambda (key value) (ht-set mustache-context key value)) ems-suros-mustache-functions)
      mustache-context))
(setq ems-suros-mustache-context (ems-suros-make-mustache-context))

Send HTML

(defun ems-suros-send-html (process rendering)
  (ws-response-header process 200 '("Content-type" . "text/html"))
  (process-send-string process rendering))

Send JSON

(defun ems-suros-send-json (process rendering)
  (ws-response-header process 200 '("Content-type" . "text/json"))
  (process-send-string process rendering))

Start

    (defun ems-suros-start (&optional config)
      "This function starts your SUROS."
      (if (ems-suros-validate-config (if config config ems-suros-config))
	  (ws-start
	   (lambda (request)
	     (with-slots (process headers) request
	       (let ((path (substring (cdr (assoc :GET headers)) 1)))
	 (if (equal path "")
		     (ems-suros-send-html process (ems-suros-render "index.html"))
		   (if (equal path "actor")
		       (ems-suros-send-json process (ems-suros-render "actor.json"))
		     (if (equal path ".well-known/webfinger")
			 (ems-suros-send-json process (ems-suros-render "webfinger.json"))
		   (if (ems-suros-template-p path)
		       (ems-suros-send-html process (ems-suros-render path))
		     (ems-suros-send-html process path))))))))
	   9000)))
(re-search-forward "\\.well-known/webfinger.json")
(equal "path" "path")
https://emsenn.net/.well-known/webfinger.json

Send Webfinger

(defun ems-suros-send-webfinger (process headers)
  (ems-suros-send-json process (ems-suros-render "webfinger.json")))

Validate Config(uration)

(defun ems-suros-validate-config (config)
  "Returns true. Pretty rubbish validator."
  t)