Documentation ¶
Overview ¶
Package site defines a Kisipar web site, and contains most of its actual server logic.
Index ¶
- Variables
- type Dot
- type PatternHandler
- type SaneEntry
- type SaneFeed
- type Site
- func (s *Site) Feed(ps *pageset.Pageset) *SaneFeed
- func (s *Site) Href(p *page.Page) string
- func (s *Site) LoadPages() error
- func (s *Site) LoadTemplates() error
- func (s *Site) MainHandler() func(w http.ResponseWriter, req *http.Request)
- func (s *Site) NewServeMux(handlers ...*PatternHandler) *http.ServeMux
- func (s *Site) PageForPath(rpath string) (*page.Page, error)
- func (s *Site) PageURL(p *page.Page) string
- func (s *Site) Serve() error
- func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Site) SetHandler(h http.Handler)
- func (s *Site) URL(path string) string
- type SiteServer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DEFAULT_FEED_ITEMS = 20
var DEFAULT_FEED_PATH = "/feed.xml"
var DEFAULT_MAX_HEADER_BYTES = 1 << 20
var DEFAULT_NAME = "Anonymous Kisipar Site"
var DEFAULT_OWNER = "Anonymous Kisipar Fan"
var DEFAULT_PAGE_EXTENSIONS = []string{
".md",
".MD",
".markdown",
".MARKDOWN",
".txt",
".TXT",
}
var DEFAULT_PORT = 8020
var DEFAULT_READ_TIMEOUT = 10 * time.Second
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.)
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 ¶
SetRegister sets the Register to the provided value and returns the previous value. It is mostly useful in templates.
func (*Dot) 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>
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 ¶
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 LoadVirtual ¶
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 ¶
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 ¶
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 ¶
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) LoadPages ¶
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 ¶
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.
- Static paths containing no extension look for an "index.html" file under the implied directory, e.g.: "/foo" will match "/foo/index.html".
- 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.
- 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.
- 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.
- 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 ¶
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) Serve ¶
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 ¶
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.