site

package
v0.0.0-...-cf5c4a9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2023 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package site defines a Kisipar web site, and contains most of its actual server logic.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DEFAULT_FEED_ITEMS = 20
View Source
var DEFAULT_FEED_PATH = "/feed.xml"
View Source
var DEFAULT_MAX_HEADER_BYTES = 1 << 20
View Source
var DEFAULT_NAME = "Anonymous Kisipar Site"
View Source
var DEFAULT_OWNER = "Anonymous Kisipar Fan"
View Source
var DEFAULT_PAGE_EXTENSIONS = []string{
	".md",
	".MD",
	".markdown",
	".MARKDOWN",
	".txt",
	".TXT",
}
View Source
var DEFAULT_PORT = 8020
View Source
var DEFAULT_READ_TIMEOUT = 10 * time.Second
View Source
var DEFAULT_TEMPLATE = assets.MustAssetString("demosite/templates/default.html")

TODO: instead of just having one default template, have a whole set of them that can be available to other templates, under the "kisipar" namespace in the loaded templates. (Still have the minimalist default though.)

View Source
var DEFAULT_WRITE_TIMEOUT = 10 * time.Second

Functions

This section is empty.

Types

type Dot

type Dot struct {

	// The full Request used to retrieve the page.  Use with caution when
	// caching rendered pages.
	Request *http.Request

	// The Page being rendered; nil if rendering an anonymous index.
	Page *page.Page

	// The Pageset if we are rendering an index; nil if we are rendering a
	// final page.
	Pageset *pageset.Pageset

	// The Site, which of course contains its own Pageset and so on.
	Site *Site

	// Now hold the timestamp of the Dot's creation.
	Now time.Time

	// Register is useful in templates when one needs, say, to keep track
	// of indent levels.  It is set via - ta-da! - SetRegister.
	Register int
}

A Dot is the "dot" available in a template for rendering a page.

func (*Dot) SetRegister

func (d *Dot) SetRegister(v int) int

SetRegister sets the Register to the provided value and returns the previous value. It is mostly useful in templates.

func (*Dot) Template

func (d *Dot) Template() *template.Template

Template returns the template in which to render the Dot, based on the following logic.

TODO: possibly start with a site-level configured override set in case you want to say /oranges/* uses /fruit.html or whatever.

If the Page defines a Template property in its Meta, and that template exists as an exact match, then it is used. This allows per-page template overrides.

If no override is present, a match is sought based on the Request URL. An exact match of the normalized request path to the template name is preferred; failing that, a template named "index" (if the Dot has a Pageset) or "single" is sought at the same level. This is repeated up the path chain until we hit or miss at the top-level "index" or "single" template. The fallback is the top-level (default) template.

Thus a request for /foo/bar with a Pageset present matches:

foo/bar
foo/bar/index
foo
foo/index
index
<default>

func (*Dot) URL

func (d *Dot) URL() string

URL returns the full URL string of the request.

type PatternHandler

type PatternHandler struct {
	Pattern  string
	Function func(http.ResponseWriter, *http.Request)
	Handler  http.Handler
}

PatternHandler defines an HTTP handler or handler function together with the pattern used by its multiplexer (Mux). Use this to wrap arguments to NewServeMux, via NewPatternHandler and NewPatternHandlerFunc. Function and Handler should not both be defined, and the Pattern may not be an empty string.

type SaneEntry

type SaneEntry struct {
	atom.Entry
	XMLBase string `xml:"xml:base,attr"`
}

SaneEntry extends a Google Atom Entry to support an xml:base attribute, thus making it minimally sane. WTF, GOOG?

type SaneFeed

type SaneFeed struct {
	XMLName xml.Name     `xml:"http://www.w3.org/2005/Atom feed"`
	Title   string       `xml:"title"`
	ID      string       `xml:"id"`
	Link    []atom.Link  `xml:"link"`
	Updated atom.TimeStr `xml:"updated"`
	Author  *atom.Person `xml:"author"`
	Entry   []*SaneEntry `xml:"entry"`
}

SaneFeed allows a Google-style Feed to have an xml:base attribute per entry, and thus not be COMPLETELY EFFING INSANE. (TODO: write own, this shit is not so fancy.)

type Site

type Site struct {

	// The Path is the base path of the site.  Kisipar sites are contained in
	// single directories.
	Path string

	// PagePath is the path under which Page content is located, i.e. the
	// Markdown files and (optionally) their resources.
	PagePath string

	// PageExtensions are the recognized page content file extensions.  Files
	// with these extensions are parsed as Pages.
	PageExtensions []string

	// UnlistedPaths are the path prefixes under which to automatically set
	// Pages to Unlisted.
	UnlistedPaths []string

	// ServePageSources determines whether the source files (e.g. "foo.md")
	// can be served as assets when requested directly.  If false, page assets
	// with extensions listed in PageExtensions will be treated as Not Found.
	ServePageSources bool

	// TemplatePath is the path under which Templates are located.
	TemplatePath string

	// StaticPath is the path under which Static content is located.
	StaticPath string

	// The Name can be anything you want, and is the primary identifier of the
	// site in the logs.  It is also used in templates and news feeds.
	Name string

	// The Owner is the display name of the site owner, e.g. "John Q. Doe".
	Owner string

	// The Email is where contact notices, if any, are sent.
	Email string

	// The Host should be the *external* host name, such as "example.com" --
	// this is used to construct a standard BaseURL if none is specified.
	Host string

	// The BaseURL is used for generating links to the site's pages.
	BaseURL string

	// FeedPath is the URL path to the site's Atom feed, if available.
	// FeedTitle is the Title to use in the Feed, if not the site's Name.
	// FeedItems specifies the maximum number of items to include in the
	// feed.  To turn off this feature, set NoFeed to a true value.
	// NOTE: the feed excludes unlisted pages; cf. UnlistedPaths.
	// TODO: (maybe) support multiple feeds, e.g. "/foo/*" vs "/bar/*"
	FeedPath  string
	FeedTitle string
	FeedItems int
	NoFeed    bool

	// The Port determines where the server will listen, and ServeTLS dictates
	// whether we listen on HTTP or HTTPS.
	Port     int
	ServeTLS bool

	// If ServeTLS is true, then CertFile and KeyFile must contain paths to
	// a valid cert and key, respectively.
	CertFile string
	KeyFile  string

	// Timeout and header-reading limits for the Server include:
	ReadTimeout    time.Duration // Config is in seconds.
	WriteTimeout   time.Duration // Config is in seconds.
	MaxHeaderBytes int

	// The Config is used to set the properties above.
	Config *config.Config

	// The Server is used to serve the Site; by default it will be set to
	// a standard http.Server using the configurable properties above.
	Server SiteServer

	// The Pageset contains all the known Pages for dynamic generation.
	Pageset *pageset.Pageset

	// The Template containing all the shared templates as well as all the
	// specific page templates.
	Template *template.Template
}

A Site represents a Kisipar web site ready for serving.

func Load

func Load(path string) (*Site, error)

Load initializes a Site at the given directory path and loads its pages and templates.

func LoadVirtual

func LoadVirtual(cfg *config.Config, pages []*page.Page,
	tmpl *template.Template) (*Site, error)

Load initializes a virtual site containing the provided pages, with cfg as its Config. A nil Config is acceptable, as is an empty array of pages and a nil template.

func LoadVirtualYaml

func LoadVirtualYaml(yaml string) (*Site, error)

LoadVirtualYaml initializes a virtual site from a YAML input file that contains the configuration and, optionally, maps of the Pages and Templates. All Pages will have the same ModTime.

func New

func New(path string) (*Site, error)

New initializes a Site at the given directory path. A config file in YAML format, named "config.yaml", is sought under the path. If path is the empty string or no config file is present then a blank Config is used.

Expected config values are:

Name           # Name of the site; default: Anonymous Kisipar Site
Owner          # Owner of the site; default: Anonymous Kisipar Fan
Host           # Host from which we're serving; default: localhost
Port           # Port on which to serve; default: 8020
BaseURL        # BaseURL for all site URLs; default: derived.
CertFile       # TLS only: path to the cert file
KeyFile        # TLS only: path to the key file
PagePath       # relative path for pages; default: pages
UnlistedPaths  # path (prefixes) for unlisted pages
TemplatePath   # relative path for templates; default: templates
StaticPath     # relative path for static content; default: static
FeedPath       # URL path for Atom feed; standard default: /feed.xml
FeedTitle      # Title for the Atom feed, if not the site Name
FeedItems      # Number of items in the Atom feed; standard default: 20
NoFeed         # boolean switch to disable the Atom feed

Sensible, but not necessarily perfect, defaults are calculated as needed; most can be overridden via the package variables.

All configs are available via the site's Config property.

func (*Site) Feed

func (s *Site) Feed(ps *pageset.Pageset) *SaneFeed

Feed returns an Atom Feed (as a SaneFeed) for the Site based on its configuration. If a Pageset is provided, that is used for the Entries; otherwise the main Site Pageset is used. Pages are used in descending Time order, i.e. Updated > Created; ModTime is the fallback. The Feed's Updated field is the time of the newest entry. TODO: An optional Template for the Feed. TODO: Support more parts of the Person for author.

func (*Site) Href

func (s *Site) Href(p *page.Page) string

Href returns the URL path (realtive URL) for a Page.

func (*Site) LoadPages

func (s *Site) LoadPages() error

LoadPages loads the pages at the Site's PagePath into the Pageset. If the Site already has a Pageset, it will be replaced. Any file under the PagePath whose extension matches one of the PageExtensions will be loaded.

func (*Site) LoadTemplates

func (s *Site) LoadTemplates() error

LoadTemplates loads the templates under the Site's TemplatePath, putting them all into the Site's Template property. The template names are the filepaths, lowercased and stripped of both the TemplatePath prefix and file extension. Files may have any extension, but the cleaned path must be unique or an error will be returned.

func (*Site) MainHandler

func (s *Site) MainHandler() func(w http.ResponseWriter, req *http.Request)

MainHandler returns an HTTP handler function applying the Kisipar logic to the current Site. That logic, in a nutshell, is:

1. Static files take priority, followed by Pages, then page assets.

  1. Static paths containing no extension look for an "index.html" file under the implied directory, e.g.: "/foo" will match "/foo/index.html".
  1. Requests containing a dot (".") anywhere in the cleaned path are not considered potential Pages; those not containing any extension are not considered potential page-asset files.
  1. Pages are sought at the path key first, then at its index, e.g.: "foo/bar" -> "foo/bar.md" OR "foo/bar/index.md" (where the ".md" extension is the first match from the Site's PageExtensions).

5. Directories are treated as not-found.

  1. Page source files are available as page assets *only* if the Site's ServePageSources property is set to true; otherwise no page asset with an extension matching the Site's PageExtensions will be found.
  1. If the top of the site is not otherwise handled, a simple default page is served.

func (*Site) NewServeMux

func (s *Site) NewServeMux(handlers ...*PatternHandler) *http.ServeMux

NewServeMux creates and returns a multiplexing HTTP request handler -- an http.ServeMux -- based on the Site's properties. This is set in the default Server created by the New function, but it may also be called in order to use the standard request-handling logic elsewhere.

If no handler is provided for the pattern "/" then one will be obtained from the MainHandler function.

Panics if duplicate patterns are passed in the handlers, or if any passed PatternHandler is malformed. (Such a case would unambiguously indicate programmer error.)

Example
package main

import (
	"github.com/biztos/kisipar/site"

	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
)

func main() {

	// Create a Site with a custom override.  We start with a standard site,
	// in this case an empty one:
	s, err := site.New("")
	if err != nil {
		log.Fatal(err)
	}

	// This is the override we want: /world -> HELLO CRUEL WORLD
	h := &site.PatternHandler{
		Pattern: "/world",
		Function: func(w http.ResponseWriter, req *http.Request) {
			fmt.Fprintf(w, "HELLO CRUEL WORLD\n")
		}}

	// We already have a ServeMux set up as the Server's Handler in our Site,
	// but we don't know what's in it and we don't want to put in something
	// that conflicts with the existing multiplexer. So we give it a new
	// one built with our override.
	mux := s.NewServeMux(h)

	// Here we attach it back to the Site's Server, though we could of course
	// go do something else with it, such as serve a very Kisipar-like site
	// from some other server framework.
	s.SetHandler(mux)

	// Let's prove our Mux is doing what we want:
	req, err := http.NewRequest("GET", "http://example.com/world", nil)
	if err != nil {
		log.Fatal(err)
	}
	w := httptest.NewRecorder()
	s.ServeHTTP(w, req)

	fmt.Println(w.Code)
	fmt.Println(w.Body.String())

}
Output:

200
HELLO CRUEL WORLD

func (*Site) PageForPath

func (s *Site) PageForPath(rpath string) (*page.Page, error)

PageForPath returns a Page from the Site's Pageset for the given cleaned request Path.

Exact matches on virtual pages take precedence, but they *must* be on virtual pages: a non-virtual page with a key matching a request path would be a dangerous coincidence, as the Site's PagePath is part of the key.

Otherwise a match is sought in the Pageset after first refreshing the path key. First the path itself is sought, then the path's index: "foo/bar" then "foo/bar/index".

Pages not found will result in os.IsNotExist style errors; parse or filesystem errors are returned as-is.

TODO: subject this to some kind of PagesetTTL so we don't hit the disk all the time for duplicate or, worse, random requests. ...but don't call it that! Because it implies -- and we might want this -- that we would reload the whole Pageset every so often. (Odd use-case vs. bouncing the server.) PageRefreshInterval? AND ALSO TODO: whether to check disk at all. PagesRefresh, PageRefreshInterval

TODO: have a set of prefixes that are always not-found, or maybe always something else, in case there are persistent annoying attacks that get 404 errors (which you'd otherwise want to monitor, to watch for broken incoming links)

func (*Site) PageURL

func (s *Site) PageURL(p *page.Page) string

PageURL returns the full URL for a Page, based on the Site's BaseURL.

func (*Site) Serve

func (s *Site) Serve() error

Serve serves the Site, either in TLS mode or insecure. For most purposes insecure will be preferable, as you should have another layer between Kisipar and the open internet.

An error is always returned, as per http.ListenAndServe.

Serve does NOT block to wait for the server to finish, as one may serve multiple Kisipar sites from a single application. In order to block in the normal fashion, wrap the final Serve call in log.Fatal or similar.

func (*Site) ServeHTTP

func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP calls the ServeHTTP method on the Site's Server's Handler. Panics if the Server is not an http.Server. This function exists mostly to support testing, but may also be useful in exotic use-cases.

func (*Site) SetHandler

func (s *Site) SetHandler(h http.Handler)

SetHandler sets the http.Handler in the Site's Server. Panics if the Server is not an http.Server. This function should be used for overriding the Handler in an otherwise standard Server.

func (*Site) URL

func (s *Site) URL(path string) string

URL returns a full URL for the given path, based on the Site's BaseURL.

type SiteServer

type SiteServer interface {
	ListenAndServe() error
	ListenAndServeTLS(string, string) error
}

A SiteServer can be a custom implementation as long as it provides the standard APIs for serving with and without Transport Layer Security.

Directories

Path Synopsis
Package assets provides embedded asset data for the Kisipar site package.
Package assets provides embedded asset data for the Kisipar site package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL