mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
More refactoring - nearly complete
This commit is contained in:
parent
6029973bdc
commit
e4fdf171c7
14 changed files with 833 additions and 814 deletions
|
@ -9,18 +9,47 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
// The parse package must know which directives
|
// The parse package must know which directives
|
||||||
// are valid, but it must not import the setup
|
// are valid, but it must not import the setup
|
||||||
// or config package.
|
// or config package. To solve this problem, we
|
||||||
|
// fill up this map in our init function here.
|
||||||
|
// The parse package does not need to know the
|
||||||
|
// ordering of the directives.
|
||||||
for _, dir := range directiveOrder {
|
for _, dir := range directiveOrder {
|
||||||
parse.ValidDirectives[dir.name] = struct{}{}
|
parse.ValidDirectives[dir.name] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directives are registered in the order they should be
|
||||||
|
// executed. Middleware (directives that inject a handler)
|
||||||
|
// are executed in the order A-B-C-*-C-B-A, assuming
|
||||||
|
// they all call the Next handler in the chain.
|
||||||
|
//
|
||||||
|
// Ordering is VERY important. Every middleware will
|
||||||
|
// feel the effects of all other middleware below
|
||||||
|
// (after) them during a request, but they must not
|
||||||
|
// care what middleware above them are doing.
|
||||||
|
//
|
||||||
|
// For example, log needs to know the status code and
|
||||||
|
// exactly how many bytes were written to the client,
|
||||||
|
// which every other middleware can affect, so it gets
|
||||||
|
// registered first. The errors middleware does not
|
||||||
|
// care if gzip or log modifies its response, so it
|
||||||
|
// gets registered below them. Gzip, on the other hand,
|
||||||
|
// DOES care what errors does to the response since it
|
||||||
|
// must compress every output to the client, even error
|
||||||
|
// pages, so it must be registered before the errors
|
||||||
|
// middleware and any others that would write to the
|
||||||
|
// response.
|
||||||
var directiveOrder = []directive{
|
var directiveOrder = []directive{
|
||||||
|
// Essential directives that initialize vital configuration settings
|
||||||
{"root", setup.Root},
|
{"root", setup.Root},
|
||||||
{"tls", setup.TLS},
|
{"tls", setup.TLS},
|
||||||
|
|
||||||
|
// Other directives that don't create HTTP handlers
|
||||||
{"startup", setup.Startup},
|
{"startup", setup.Startup},
|
||||||
{"shutdown", setup.Shutdown},
|
{"shutdown", setup.Shutdown},
|
||||||
|
{"git", setup.Git},
|
||||||
|
|
||||||
|
// Directives that inject handlers (middleware)
|
||||||
{"log", setup.Log},
|
{"log", setup.Log},
|
||||||
{"gzip", setup.Gzip},
|
{"gzip", setup.Gzip},
|
||||||
{"errors", setup.Errors},
|
{"errors", setup.Errors},
|
||||||
|
@ -31,15 +60,19 @@ var directiveOrder = []directive{
|
||||||
{"basicauth", setup.BasicAuth},
|
{"basicauth", setup.BasicAuth},
|
||||||
{"proxy", setup.Proxy},
|
{"proxy", setup.Proxy},
|
||||||
{"fastcgi", setup.FastCGI},
|
{"fastcgi", setup.FastCGI},
|
||||||
// {"websocket", setup.WebSocket},
|
{"websocket", setup.WebSocket},
|
||||||
// {"markdown", setup.Markdown},
|
{"markdown", setup.Markdown},
|
||||||
// {"templates", setup.Templates},
|
{"templates", setup.Templates},
|
||||||
// {"browse", setup.Browse},
|
{"browse", setup.Browse},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// directive ties together a directive name with its setup function.
|
||||||
type directive struct {
|
type directive struct {
|
||||||
name string
|
name string
|
||||||
setup setupFunc
|
setup setupFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A setup function takes a setup controller. Its return values may
|
||||||
|
// both be nil. If middleware is not nil, it will be chained into
|
||||||
|
// the HTTP handlers in the order specified in this package.
|
||||||
type setupFunc func(c *setup.Controller) (middleware.Middleware, error)
|
type setupFunc func(c *setup.Controller) (middleware.Middleware, error)
|
||||||
|
|
|
@ -1,4 +1,83 @@
|
||||||
package browse
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/browse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Browse configures a new Browse middleware instance.
|
||||||
|
func Browse(c *Controller) (middleware.Middleware, error) {
|
||||||
|
configs, err := browseParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
browse := browse.Browse{
|
||||||
|
Root: c.Root,
|
||||||
|
Configs: configs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
browse.Next = next
|
||||||
|
return browse
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func browseParse(c *Controller) ([]browse.Config, error) {
|
||||||
|
var configs []browse.Config
|
||||||
|
|
||||||
|
appendCfg := func(bc browse.Config) error {
|
||||||
|
for _, c := range configs {
|
||||||
|
if c.PathScope == bc.PathScope {
|
||||||
|
return fmt.Errorf("Duplicate browsing config for %s", c.PathScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configs = append(configs, bc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var bc browse.Config
|
||||||
|
|
||||||
|
// First argument is directory to allow browsing; default is site root
|
||||||
|
if c.NextArg() {
|
||||||
|
bc.PathScope = c.Val()
|
||||||
|
} else {
|
||||||
|
bc.PathScope = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second argument would be the template file to use
|
||||||
|
var tplText string
|
||||||
|
if c.NextArg() {
|
||||||
|
tplBytes, err := ioutil.ReadFile(c.Val())
|
||||||
|
if err != nil {
|
||||||
|
return configs, err
|
||||||
|
}
|
||||||
|
tplText = string(tplBytes)
|
||||||
|
} else {
|
||||||
|
tplText = defaultTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the template
|
||||||
|
tpl, err := template.New("listing").Parse(tplText)
|
||||||
|
if err != nil {
|
||||||
|
return configs, err
|
||||||
|
}
|
||||||
|
bc.Template = tpl
|
||||||
|
|
||||||
|
// Save configuration
|
||||||
|
err = appendCfg(bc)
|
||||||
|
if err != nil {
|
||||||
|
return configs, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// The default template to use when serving up directory listings
|
// The default template to use when serving up directory listings
|
||||||
const defaultTemplate = `<!DOCTYPE html>
|
const defaultTemplate = `<!DOCTYPE html>
|
171
config/setup/git.go
Normal file
171
config/setup/git.go
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Git configures a new Git service routine.
|
||||||
|
func Git(c *Controller) (middleware.Middleware, error) {
|
||||||
|
repo, err := gitParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Startup = append(c.Startup, func() error {
|
||||||
|
// Startup functions are blocking; start
|
||||||
|
// service routine in background
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(repo.Interval)
|
||||||
|
|
||||||
|
err := repo.Pull()
|
||||||
|
if err != nil {
|
||||||
|
if git.Logger == nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
git.Logger.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Do a pull right away to return error
|
||||||
|
return repo.Pull()
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitParse(c *Controller) (*git.Repo, error) {
|
||||||
|
repo := &git.Repo{Branch: "master", Interval: git.DefaultInterval, Path: c.Root}
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 2:
|
||||||
|
repo.Path = filepath.Clean(c.Root + string(filepath.Separator) + args[1])
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
repo.Url = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
case "repo":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
repo.Url = c.Val()
|
||||||
|
case "path":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
repo.Path = filepath.Clean(c.Root + string(filepath.Separator) + c.Val())
|
||||||
|
case "branch":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
repo.Branch = c.Val()
|
||||||
|
case "key":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
repo.KeyPath = c.Val()
|
||||||
|
case "interval":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
t, _ := strconv.Atoi(c.Val())
|
||||||
|
if t > 0 {
|
||||||
|
repo.Interval = time.Duration(t) * time.Second
|
||||||
|
}
|
||||||
|
case "then":
|
||||||
|
thenArgs := c.RemainingArgs()
|
||||||
|
if len(thenArgs) == 0 {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
repo.Then = strings.Join(thenArgs, " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if repo is not specified, return error
|
||||||
|
if repo.Url == "" {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if private key is not specified, convert repository url to https
|
||||||
|
// to avoid ssh authentication
|
||||||
|
// else validate git url
|
||||||
|
// Note: private key support not yet available on Windows
|
||||||
|
var err error
|
||||||
|
if repo.KeyPath == "" {
|
||||||
|
repo.Url, repo.Host, err = sanitizeHttp(repo.Url)
|
||||||
|
} else {
|
||||||
|
repo.Url, repo.Host, err = sanitizeGit(repo.Url)
|
||||||
|
// TODO add Windows support for private repos
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return nil, fmt.Errorf("Private repository not yet supported on Windows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate git availability in PATH
|
||||||
|
if err = git.InitGit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo, repo.Prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitizeHttp cleans up repository url and converts to https format
|
||||||
|
// if currently in ssh format.
|
||||||
|
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
||||||
|
// and possible error
|
||||||
|
func sanitizeHttp(repoUrl string) (string, string, error) {
|
||||||
|
url, err := url.Parse(repoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.Host == "" && strings.HasPrefix(url.Path, "git@") {
|
||||||
|
url.Path = url.Path[len("git@"):]
|
||||||
|
i := strings.Index(url.Path, ":")
|
||||||
|
if i < 0 {
|
||||||
|
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
||||||
|
}
|
||||||
|
url.Host = url.Path[:i]
|
||||||
|
url.Path = "/" + url.Path[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUrl = "https://" + url.Host + url.Path
|
||||||
|
return repoUrl, url.Host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitizeGit cleans up repository url and validate ssh format.
|
||||||
|
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
||||||
|
// and possible error
|
||||||
|
func sanitizeGit(repoUrl string) (string, string, error) {
|
||||||
|
repoUrl = strings.TrimSpace(repoUrl)
|
||||||
|
if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") {
|
||||||
|
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
||||||
|
}
|
||||||
|
hostUrl := repoUrl[len("git@"):]
|
||||||
|
i := strings.Index(hostUrl, ":")
|
||||||
|
host := hostUrl[:i]
|
||||||
|
return repoUrl, host, nil
|
||||||
|
}
|
74
config/setup/markdown.go
Normal file
74
config/setup/markdown.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/markdown"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Markdown configures a new Markdown middleware instance.
|
||||||
|
func Markdown(c *Controller) (middleware.Middleware, error) {
|
||||||
|
mdconfigs, err := markdownParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
md := markdown.Markdown{
|
||||||
|
Root: c.Root,
|
||||||
|
Configs: mdconfigs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
md.Next = next
|
||||||
|
return md
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func markdownParse(c *Controller) ([]markdown.Config, error) {
|
||||||
|
var mdconfigs []markdown.Config
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
md := markdown.Config{
|
||||||
|
Renderer: blackfriday.HtmlRenderer(0, "", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path scope
|
||||||
|
if !c.NextArg() || c.Val() == "{" {
|
||||||
|
return mdconfigs, c.ArgErr()
|
||||||
|
}
|
||||||
|
md.PathScope = c.Val()
|
||||||
|
|
||||||
|
// Load any other configuration parameters
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
case "ext":
|
||||||
|
exts := c.RemainingArgs()
|
||||||
|
if len(exts) == 0 {
|
||||||
|
return mdconfigs, c.ArgErr()
|
||||||
|
}
|
||||||
|
md.Extensions = append(md.Extensions, exts...)
|
||||||
|
case "css":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return mdconfigs, c.ArgErr()
|
||||||
|
}
|
||||||
|
md.Styles = append(md.Styles, c.Val())
|
||||||
|
case "js":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return mdconfigs, c.ArgErr()
|
||||||
|
}
|
||||||
|
md.Scripts = append(md.Scripts, c.Val())
|
||||||
|
default:
|
||||||
|
return mdconfigs, c.Err("Expected valid markdown configuration property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no extensions were specified, assume .md
|
||||||
|
if len(md.Extensions) == 0 {
|
||||||
|
md.Extensions = []string{".md"}
|
||||||
|
}
|
||||||
|
|
||||||
|
mdconfigs = append(mdconfigs, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mdconfigs, nil
|
||||||
|
}
|
54
config/setup/templates.go
Normal file
54
config/setup/templates.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Templates configures a new Templates middleware instance.
|
||||||
|
func Templates(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rules, err := templatesParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpls := templates.Templates{
|
||||||
|
Root: c.Root,
|
||||||
|
Rules: rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
tmpls.Next = next
|
||||||
|
return tmpls
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func templatesParse(c *Controller) ([]templates.Rule, error) {
|
||||||
|
var rules []templates.Rule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var rule templates.Rule
|
||||||
|
|
||||||
|
if c.NextArg() {
|
||||||
|
// First argument would be the path
|
||||||
|
rule.Path = c.Val()
|
||||||
|
|
||||||
|
// Any remaining arguments are extensions
|
||||||
|
rule.Extensions = c.RemainingArgs()
|
||||||
|
if len(rule.Extensions) == 0 {
|
||||||
|
rule.Extensions = defaultExtensions
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rule.Path = defaultPath
|
||||||
|
rule.Extensions = defaultExtensions
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPath = "/"
|
||||||
|
|
||||||
|
var defaultExtensions = []string{".html", ".htm", ".txt"}
|
82
config/setup/websocket.go
Normal file
82
config/setup/websocket.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/websockets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocket configures a new WebSockets middleware instance.
|
||||||
|
func WebSocket(c *Controller) (middleware.Middleware, error) {
|
||||||
|
var websocks []websockets.Config
|
||||||
|
var respawn bool
|
||||||
|
|
||||||
|
optionalBlock := func() (hadBlock bool, err error) {
|
||||||
|
for c.NextBlock() {
|
||||||
|
hadBlock = true
|
||||||
|
if c.Val() == "respawn" {
|
||||||
|
respawn = true
|
||||||
|
} else {
|
||||||
|
return true, c.Err("Expected websocket configuration parameter in block")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var val, path, command string
|
||||||
|
|
||||||
|
// Path or command; not sure which yet
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
val = c.Val()
|
||||||
|
|
||||||
|
// Extra configuration may be in a block
|
||||||
|
hadBlock, err := optionalBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hadBlock {
|
||||||
|
// The next argument on this line will be the command or an open curly brace
|
||||||
|
if c.NextArg() {
|
||||||
|
path = val
|
||||||
|
command = c.Val()
|
||||||
|
} else {
|
||||||
|
path = "/"
|
||||||
|
command = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, check again for optional block
|
||||||
|
hadBlock, err = optionalBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split command into the actual command and its arguments
|
||||||
|
cmd, args, err := middleware.SplitCommandAndArgs(command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
websocks = append(websocks, websockets.Config{
|
||||||
|
Path: path,
|
||||||
|
Command: cmd,
|
||||||
|
Arguments: args,
|
||||||
|
Respawn: respawn, // TODO: This isn't used currently
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
websockets.GatewayInterface = envGatewayInterface
|
||||||
|
websockets.ServerSoftware = envServerSoftware
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return websockets.WebSockets{Next: next, Sockets: websocks}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
envGatewayInterface = "caddy-CGI/1.1"
|
||||||
|
envServerSoftware = "caddy/" // TODO: Version
|
||||||
|
)
|
|
@ -4,9 +4,7 @@ package browse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -23,11 +21,11 @@ import (
|
||||||
type Browse struct {
|
type Browse struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Root string
|
Root string
|
||||||
Configs []BrowseConfig
|
Configs []Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// BrowseConfig is a configuration for browsing in a particular path.
|
// Config is a configuration for browsing in a particular path.
|
||||||
type BrowseConfig struct {
|
type Config struct {
|
||||||
PathScope string
|
PathScope string
|
||||||
Template *template.Template
|
Template *template.Template
|
||||||
}
|
}
|
||||||
|
@ -72,24 +70,6 @@ var IndexPages = []string{
|
||||||
"default.htm",
|
"default.htm",
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new instance of browse middleware.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
configs, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
browse := Browse{
|
|
||||||
Root: c.Root(),
|
|
||||||
Configs: configs,
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
browse.Next = next
|
|
||||||
return browse
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements the middleware.Handler interface.
|
// ServeHTTP implements the middleware.Handler interface.
|
||||||
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
filename := b.Root + r.URL.Path
|
filename := b.Root + r.URL.Path
|
||||||
|
@ -196,56 +176,3 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Didn't qualify; pass-thru
|
// Didn't qualify; pass-thru
|
||||||
return b.Next.ServeHTTP(w, r)
|
return b.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse returns a list of browsing configurations
|
|
||||||
func parse(c middleware.Controller) ([]BrowseConfig, error) {
|
|
||||||
var configs []BrowseConfig
|
|
||||||
|
|
||||||
appendCfg := func(bc BrowseConfig) error {
|
|
||||||
for _, c := range configs {
|
|
||||||
if c.PathScope == bc.PathScope {
|
|
||||||
return fmt.Errorf("Duplicate browsing config for %s", c.PathScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configs = append(configs, bc)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
var bc BrowseConfig
|
|
||||||
|
|
||||||
// First argument is directory to allow browsing; default is site root
|
|
||||||
if c.NextArg() {
|
|
||||||
bc.PathScope = c.Val()
|
|
||||||
} else {
|
|
||||||
bc.PathScope = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second argument would be the template file to use
|
|
||||||
var tplText string
|
|
||||||
if c.NextArg() {
|
|
||||||
tplBytes, err := ioutil.ReadFile(c.Val())
|
|
||||||
if err != nil {
|
|
||||||
return configs, err
|
|
||||||
}
|
|
||||||
tplText = string(tplBytes)
|
|
||||||
} else {
|
|
||||||
tplText = defaultTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the template
|
|
||||||
tpl, err := template.New("listing").Parse(tplText)
|
|
||||||
if err != nil {
|
|
||||||
return configs, err
|
|
||||||
}
|
|
||||||
bc.Template = tpl
|
|
||||||
|
|
||||||
// Save configuration
|
|
||||||
err = appendCfg(bc)
|
|
||||||
if err != nil {
|
|
||||||
return configs, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return configs, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// Package extension is middleware for clean URLs. The root path
|
// Package extension is middleware for clean URLs.
|
||||||
// of the site is passed in as well as possible extensions to try
|
//
|
||||||
// internally for paths requested that don't match an existing
|
// The root path of the site is passed in as well as possible extensions
|
||||||
// resource. The first path+ext combination that matches a valid
|
// to try internally for paths requested that don't match an existing
|
||||||
// file will be used.
|
// resource. The first path+ext combination that matches a valid file
|
||||||
|
// will be used.
|
||||||
package extensions
|
package extensions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -14,25 +15,6 @@ import (
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new instance of middleware that assumes extensions
|
|
||||||
// so the site can use cleaner, extensionless URLs
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
root := c.Root()
|
|
||||||
|
|
||||||
extensions, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
return Ext{
|
|
||||||
Next: next,
|
|
||||||
Extensions: extensions,
|
|
||||||
Root: root,
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ext can assume an extension from clean URLs.
|
// Ext can assume an extension from clean URLs.
|
||||||
// It tries extensions in the order listed in Extensions.
|
// It tries extensions in the order listed in Extensions.
|
||||||
type Ext struct {
|
type Ext struct {
|
||||||
|
@ -60,25 +42,6 @@ func (e Ext) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
return e.Next.ServeHTTP(w, r)
|
return e.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse sets up an instance of extension middleware
|
|
||||||
// from a middleware controller and returns a list of extensions.
|
|
||||||
func parse(c middleware.Controller) ([]string, error) {
|
|
||||||
var extensions []string
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
// At least one extension is required
|
|
||||||
if !c.NextArg() {
|
|
||||||
return extensions, c.ArgErr()
|
|
||||||
}
|
|
||||||
extensions = append(extensions, c.Val())
|
|
||||||
|
|
||||||
// Tack on any other extensions that may have been listed
|
|
||||||
extensions = append(extensions, c.RemainingArgs()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// resourceExists returns true if the file specified at
|
// resourceExists returns true if the file specified at
|
||||||
// root + path exists; false otherwise.
|
// root + path exists; false otherwise.
|
||||||
func resourceExists(root, path string) bool {
|
func resourceExists(root, path string) bool {
|
||||||
|
|
|
@ -1,178 +1,36 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultInterval is the minimum interval to delay before
|
||||||
|
// requesting another git pull
|
||||||
|
const DefaultInterval time.Duration = time.Hour * 1
|
||||||
|
|
||||||
|
// Number of retries if git pull fails
|
||||||
|
const numRetries = 3
|
||||||
|
|
||||||
|
// gitBinary holds the absolute path to git executable
|
||||||
|
var gitBinary string
|
||||||
|
|
||||||
|
// initMutex prevents parallel attempt to validate
|
||||||
|
// git availability in PATH
|
||||||
|
var initMutex sync.Mutex = sync.Mutex{}
|
||||||
|
|
||||||
// Logger is used to log errors; if nil, the default log.Logger is used.
|
// Logger is used to log errors; if nil, the default log.Logger is used.
|
||||||
var Logger *log.Logger
|
var Logger *log.Logger
|
||||||
|
|
||||||
// New creates a new instance of git middleware.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
repo, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Startup(func() error {
|
|
||||||
// Startup functions are blocking; start
|
|
||||||
// service routine in background
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(repo.Interval)
|
|
||||||
|
|
||||||
err := repo.Pull()
|
|
||||||
if err != nil {
|
|
||||||
if Logger == nil {
|
|
||||||
log.Println(err)
|
|
||||||
} else {
|
|
||||||
Logger.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Do a pull right away to return error
|
|
||||||
return repo.Pull()
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(c middleware.Controller) (*Repo, error) {
|
|
||||||
repo := &Repo{Branch: "master", Interval: DefaultInterval, Path: c.Root()}
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
args := c.RemainingArgs()
|
|
||||||
|
|
||||||
switch len(args) {
|
|
||||||
case 2:
|
|
||||||
repo.Path = filepath.Clean(c.Root() + string(filepath.Separator) + args[1])
|
|
||||||
fallthrough
|
|
||||||
case 1:
|
|
||||||
repo.Url = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "repo":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
repo.Url = c.Val()
|
|
||||||
case "path":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
repo.Path = filepath.Clean(c.Root() + string(filepath.Separator) + c.Val())
|
|
||||||
case "branch":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
repo.Branch = c.Val()
|
|
||||||
case "key":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
repo.KeyPath = c.Val()
|
|
||||||
case "interval":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
t, _ := strconv.Atoi(c.Val())
|
|
||||||
if t > 0 {
|
|
||||||
repo.Interval = time.Duration(t) * time.Second
|
|
||||||
}
|
|
||||||
case "then":
|
|
||||||
thenArgs := c.RemainingArgs()
|
|
||||||
if len(thenArgs) == 0 {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
repo.Then = strings.Join(thenArgs, " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if repo is not specified, return error
|
|
||||||
if repo.Url == "" {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// if private key is not specified, convert repository url to https
|
|
||||||
// to avoid ssh authentication
|
|
||||||
// else validate git url
|
|
||||||
// Note: private key support not yet available on Windows
|
|
||||||
var err error
|
|
||||||
if repo.KeyPath == "" {
|
|
||||||
repo.Url, repo.Host, err = sanitizeHttp(repo.Url)
|
|
||||||
} else {
|
|
||||||
repo.Url, repo.Host, err = sanitizeGit(repo.Url)
|
|
||||||
// TODO add Windows support for private repos
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return nil, fmt.Errorf("Private repository not yet supported on Windows")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate git availability in PATH
|
|
||||||
if err = initGit(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, repo.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeHttp cleans up repository url and converts to https format
|
|
||||||
// if currently in ssh format.
|
|
||||||
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
|
||||||
// and possible error
|
|
||||||
func sanitizeHttp(repoUrl string) (string, string, error) {
|
|
||||||
url, err := url.Parse(repoUrl)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if url.Host == "" && strings.HasPrefix(url.Path, "git@") {
|
|
||||||
url.Path = url.Path[len("git@"):]
|
|
||||||
i := strings.Index(url.Path, ":")
|
|
||||||
if i < 0 {
|
|
||||||
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
|
||||||
}
|
|
||||||
url.Host = url.Path[:i]
|
|
||||||
url.Path = "/" + url.Path[i+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
repoUrl = "https://" + url.Host + url.Path
|
|
||||||
return repoUrl, url.Host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeGit cleans up repository url and validate ssh format.
|
|
||||||
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
|
|
||||||
// and possible error
|
|
||||||
func sanitizeGit(repoUrl string) (string, string, error) {
|
|
||||||
repoUrl = strings.TrimSpace(repoUrl)
|
|
||||||
if !strings.HasPrefix(repoUrl, "git@") || strings.Index(repoUrl, ":") < len("git@a:") {
|
|
||||||
return "", "", fmt.Errorf("Invalid git url %s", repoUrl)
|
|
||||||
}
|
|
||||||
hostUrl := repoUrl[len("git@"):]
|
|
||||||
i := strings.Index(hostUrl, ":")
|
|
||||||
host := hostUrl[:i]
|
|
||||||
return repoUrl, host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logger is an helper function to retrieve the available logger
|
// logger is an helper function to retrieve the available logger
|
||||||
func logger() *log.Logger {
|
func logger() *log.Logger {
|
||||||
if Logger == nil {
|
if Logger == nil {
|
||||||
|
@ -180,3 +38,302 @@ func logger() *log.Logger {
|
||||||
}
|
}
|
||||||
return Logger
|
return Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repo is the structure that holds required information
|
||||||
|
// of a git repository.
|
||||||
|
type Repo struct {
|
||||||
|
Url string // Repository URL
|
||||||
|
Path string // Directory to pull to
|
||||||
|
Host string // Git domain host e.g. github.com
|
||||||
|
Branch string // Git branch
|
||||||
|
KeyPath string // Path to private ssh key
|
||||||
|
Interval time.Duration // Interval between pulls
|
||||||
|
Then string // Command to execute after successful git pull
|
||||||
|
pulled bool // true if there was a successful pull
|
||||||
|
lastPull time.Time // time of the last successful pull
|
||||||
|
lastCommit string // hash for the most recent commit
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull attempts a git clone.
|
||||||
|
// It retries at most numRetries times if error occurs
|
||||||
|
func (r *Repo) Pull() error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
// if it is less than interval since last pull, return
|
||||||
|
if time.Since(r.lastPull) <= r.Interval {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep last commit hash for comparison later
|
||||||
|
lastCommit := r.lastCommit
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// Attempt to pull at most numRetries times
|
||||||
|
for i := 0; i < numRetries; i++ {
|
||||||
|
if err = r.pull(); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logger().Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are new changes,
|
||||||
|
// then execute post pull command
|
||||||
|
if r.lastCommit == lastCommit {
|
||||||
|
logger().Println("No new changes.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.postPullCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull performs git clone, or git pull if repository exists
|
||||||
|
func (r *Repo) pull() error {
|
||||||
|
params := []string{"clone", "-b", r.Branch, r.Url, r.Path}
|
||||||
|
if r.pulled {
|
||||||
|
params = []string{"pull", "origin", r.Branch}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if key is specified, pull using ssh key
|
||||||
|
if r.KeyPath != "" {
|
||||||
|
return r.pullWithKey(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := ""
|
||||||
|
if r.pulled {
|
||||||
|
dir = r.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if err = runCmd(gitBinary, params, dir); err == nil {
|
||||||
|
r.pulled = true
|
||||||
|
r.lastPull = time.Now()
|
||||||
|
logger().Printf("%v pulled.\n", r.Url)
|
||||||
|
r.lastCommit, err = r.getMostRecentCommit()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// pullWithKey is used for private repositories and requires an ssh key.
|
||||||
|
// Note: currently only limited to Linux and OSX.
|
||||||
|
func (r *Repo) pullWithKey(params []string) error {
|
||||||
|
var gitSsh, script *os.File
|
||||||
|
// ensure temporary files deleted after usage
|
||||||
|
defer func() {
|
||||||
|
if gitSsh != nil {
|
||||||
|
os.Remove(gitSsh.Name())
|
||||||
|
}
|
||||||
|
if script != nil {
|
||||||
|
os.Remove(script.Name())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// write git.sh script to temp file
|
||||||
|
gitSsh, err = writeScriptFile(gitWrapperScript(gitBinary))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write git clone bash script to file
|
||||||
|
script, err = writeScriptFile(bashScript(gitSsh.Name(), r, params))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := ""
|
||||||
|
if r.pulled {
|
||||||
|
dir = r.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = runCmd(script.Name(), nil, dir); err == nil {
|
||||||
|
r.pulled = true
|
||||||
|
r.lastPull = time.Now()
|
||||||
|
logger().Printf("%v pulled.\n", r.Url)
|
||||||
|
r.lastCommit, err = r.getMostRecentCommit()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare prepares for a git pull
|
||||||
|
// and validates the configured directory
|
||||||
|
func (r *Repo) Prepare() error {
|
||||||
|
// check if directory exists or is empty
|
||||||
|
// if not, create directory
|
||||||
|
fs, err := ioutil.ReadDir(r.Path)
|
||||||
|
if err != nil || len(fs) == 0 {
|
||||||
|
return os.MkdirAll(r.Path, os.FileMode(0755))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate git repo
|
||||||
|
isGit := false
|
||||||
|
for _, f := range fs {
|
||||||
|
if f.IsDir() && f.Name() == ".git" {
|
||||||
|
isGit = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isGit {
|
||||||
|
// check if same repository
|
||||||
|
var repoUrl string
|
||||||
|
if repoUrl, err = r.getRepoUrl(); err == nil && repoUrl == r.Url {
|
||||||
|
r.pulled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Another git repo '%v' exists at %v", repoUrl, r.Path)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Cannot git clone into %v, directory not empty.", r.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMostRecentCommit gets the hash of the most recent commit to the
|
||||||
|
// repository. Useful for checking if changes occur.
|
||||||
|
func (r *Repo) getMostRecentCommit() (string, error) {
|
||||||
|
command := gitBinary + ` --no-pager log -n 1 --pretty=format:"%H"`
|
||||||
|
c, args, err := middleware.SplitCommandAndArgs(command)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return runCmdOutput(c, args, r.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRepoUrl retrieves remote origin url for the git repository at path
|
||||||
|
func (r *Repo) getRepoUrl() (string, error) {
|
||||||
|
_, err := os.Stat(r.Path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
args := []string{"config", "--get", "remote.origin.url"}
|
||||||
|
return runCmdOutput(gitBinary, args, r.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postPullCommand executes r.Then.
|
||||||
|
// It is trigged after successful git pull
|
||||||
|
func (r *Repo) postPullCommand() error {
|
||||||
|
if r.Then == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c, args, err := middleware.SplitCommandAndArgs(r.Then)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = runCmd(c, args, r.Path); err == nil {
|
||||||
|
logger().Printf("Command %v successful.\n", r.Then)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitGit validates git installation and locates the git executable
|
||||||
|
// binary in PATH
|
||||||
|
func InitGit() error {
|
||||||
|
// prevent concurrent call
|
||||||
|
initMutex.Lock()
|
||||||
|
defer initMutex.Unlock()
|
||||||
|
|
||||||
|
// if validation has been done before and binary located in
|
||||||
|
// PATH, return.
|
||||||
|
if gitBinary != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate git binary in path
|
||||||
|
var err error
|
||||||
|
gitBinary, err = exec.LookPath("git")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCmd is a helper function to run commands.
|
||||||
|
// It runs command with args from directory at dir.
|
||||||
|
// The executed process outputs to os.Stderr
|
||||||
|
func runCmd(command string, args []string, dir string) error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
cmd.Dir = dir
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCmdOutput is a helper function to run commands and return output.
|
||||||
|
// It runs command with args from directory at dir.
|
||||||
|
// If successful, returns output and nil error
|
||||||
|
func runCmdOutput(command string, args []string, dir string) (string, error) {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Dir = dir
|
||||||
|
var err error
|
||||||
|
if output, err := cmd.Output(); err == nil {
|
||||||
|
return string(bytes.TrimSpace(output)), nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeScriptFile writes content to a temporary file.
|
||||||
|
// It changes the temporary file mode to executable and
|
||||||
|
// closes it to prepare it for execution.
|
||||||
|
func writeScriptFile(content []byte) (file *os.File, err error) {
|
||||||
|
if file, err = ioutil.TempFile("", "caddy"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = file.Write(content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = file.Chmod(os.FileMode(0755)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// gitWrapperScript forms content for git.sh script
|
||||||
|
var gitWrapperScript = func(gitBinary string) []byte {
|
||||||
|
return []byte(fmt.Sprintf(`#!/bin/bash
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
# Copyright (c) 2013 Alvin Abad
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Git wrapper script that can specify an ssh-key file
|
||||||
|
Usage:
|
||||||
|
git.sh -i ssh-key-file git-command
|
||||||
|
"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# remove temporary file on exit
|
||||||
|
trap 'rm -f /tmp/.git_ssh.$$' 0
|
||||||
|
|
||||||
|
if [ "$1" = "-i" ]; then
|
||||||
|
SSH_KEY=$2; shift; shift
|
||||||
|
echo "ssh -i $SSH_KEY \$@" > /tmp/.git_ssh.$$
|
||||||
|
chmod +x /tmp/.git_ssh.$$
|
||||||
|
export GIT_SSH=/tmp/.git_ssh.$$
|
||||||
|
fi
|
||||||
|
|
||||||
|
# in case the git command is repeated
|
||||||
|
[ "$1" = "git" ] && shift
|
||||||
|
|
||||||
|
# Run the git command
|
||||||
|
%v "$@"
|
||||||
|
|
||||||
|
`, gitBinary))
|
||||||
|
}
|
||||||
|
|
||||||
|
// bashScript forms content of bash script to clone or update a repo using ssh
|
||||||
|
var bashScript = func(gitShPath string, repo *Repo, params []string) []byte {
|
||||||
|
return []byte(fmt.Sprintf(`#!/bin/bash
|
||||||
|
|
||||||
|
mkdir -p ~/.ssh;
|
||||||
|
touch ~/.ssh/known_hosts;
|
||||||
|
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
||||||
|
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
||||||
|
%v -i %v %v;
|
||||||
|
`, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " ")))
|
||||||
|
}
|
||||||
|
|
|
@ -1,328 +0,0 @@
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultInterval is the minimum interval to delay before
|
|
||||||
// requesting another git pull
|
|
||||||
const DefaultInterval time.Duration = time.Hour * 1
|
|
||||||
|
|
||||||
// Number of retries if git pull fails
|
|
||||||
const numRetries = 3
|
|
||||||
|
|
||||||
// gitBinary holds the absolute path to git executable
|
|
||||||
var gitBinary string
|
|
||||||
|
|
||||||
// initMutex prevents parallel attempt to validate
|
|
||||||
// git availability in PATH
|
|
||||||
var initMutex sync.Mutex = sync.Mutex{}
|
|
||||||
|
|
||||||
// Repo is the structure that holds required information
|
|
||||||
// of a git repository.
|
|
||||||
type Repo struct {
|
|
||||||
Url string // Repository URL
|
|
||||||
Path string // Directory to pull to
|
|
||||||
Host string // Git domain host e.g. github.com
|
|
||||||
Branch string // Git branch
|
|
||||||
KeyPath string // Path to private ssh key
|
|
||||||
Interval time.Duration // Interval between pulls
|
|
||||||
Then string // Command to execute after successful git pull
|
|
||||||
pulled bool // true if there was a successful pull
|
|
||||||
lastPull time.Time // time of the last successful pull
|
|
||||||
lastCommit string // hash for the most recent commit
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull attempts a git clone.
|
|
||||||
// It retries at most numRetries times if error occurs
|
|
||||||
func (r *Repo) Pull() error {
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
// if it is less than interval since last pull, return
|
|
||||||
if time.Since(r.lastPull) <= r.Interval {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep last commit hash for comparison later
|
|
||||||
lastCommit := r.lastCommit
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// Attempt to pull at most numRetries times
|
|
||||||
for i := 0; i < numRetries; i++ {
|
|
||||||
if err = r.pull(); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
logger().Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are new changes,
|
|
||||||
// then execute post pull command
|
|
||||||
if r.lastCommit == lastCommit {
|
|
||||||
logger().Println("No new changes.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return r.postPullCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull performs git clone, or git pull if repository exists
|
|
||||||
func (r *Repo) pull() error {
|
|
||||||
params := []string{"clone", "-b", r.Branch, r.Url, r.Path}
|
|
||||||
if r.pulled {
|
|
||||||
params = []string{"pull", "origin", r.Branch}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if key is specified, pull using ssh key
|
|
||||||
if r.KeyPath != "" {
|
|
||||||
return r.pullWithKey(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := ""
|
|
||||||
if r.pulled {
|
|
||||||
dir = r.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if err = runCmd(gitBinary, params, dir); err == nil {
|
|
||||||
r.pulled = true
|
|
||||||
r.lastPull = time.Now()
|
|
||||||
logger().Printf("%v pulled.\n", r.Url)
|
|
||||||
r.lastCommit, err = r.getMostRecentCommit()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// pullWithKey is used for private repositories and requires an ssh key.
|
|
||||||
// Note: currently only limited to Linux and OSX.
|
|
||||||
func (r *Repo) pullWithKey(params []string) error {
|
|
||||||
var gitSsh, script *os.File
|
|
||||||
// ensure temporary files deleted after usage
|
|
||||||
defer func() {
|
|
||||||
if gitSsh != nil {
|
|
||||||
os.Remove(gitSsh.Name())
|
|
||||||
}
|
|
||||||
if script != nil {
|
|
||||||
os.Remove(script.Name())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// write git.sh script to temp file
|
|
||||||
gitSsh, err = writeScriptFile(gitWrapperScript(gitBinary))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// write git clone bash script to file
|
|
||||||
script, err = writeScriptFile(bashScript(gitSsh.Name(), r, params))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := ""
|
|
||||||
if r.pulled {
|
|
||||||
dir = r.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = runCmd(script.Name(), nil, dir); err == nil {
|
|
||||||
r.pulled = true
|
|
||||||
r.lastPull = time.Now()
|
|
||||||
logger().Printf("%v pulled.\n", r.Url)
|
|
||||||
r.lastCommit, err = r.getMostRecentCommit()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare prepares for a git pull
|
|
||||||
// and validates the configured directory
|
|
||||||
func (r *Repo) prepare() error {
|
|
||||||
// check if directory exists or is empty
|
|
||||||
// if not, create directory
|
|
||||||
fs, err := ioutil.ReadDir(r.Path)
|
|
||||||
if err != nil || len(fs) == 0 {
|
|
||||||
return os.MkdirAll(r.Path, os.FileMode(0755))
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate git repo
|
|
||||||
isGit := false
|
|
||||||
for _, f := range fs {
|
|
||||||
if f.IsDir() && f.Name() == ".git" {
|
|
||||||
isGit = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isGit {
|
|
||||||
// check if same repository
|
|
||||||
var repoUrl string
|
|
||||||
if repoUrl, err = r.getRepoUrl(); err == nil && repoUrl == r.Url {
|
|
||||||
r.pulled = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Cannot retrieve repo url for %v Error: %v", r.Path, err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Another git repo '%v' exists at %v", repoUrl, r.Path)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Cannot git clone into %v, directory not empty.", r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMostRecentCommit gets the hash of the most recent commit to the
|
|
||||||
// repository. Useful for checking if changes occur.
|
|
||||||
func (r *Repo) getMostRecentCommit() (string, error) {
|
|
||||||
command := gitBinary + ` --no-pager log -n 1 --pretty=format:"%H"`
|
|
||||||
c, args, err := middleware.SplitCommandAndArgs(command)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return runCmdOutput(c, args, r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRepoUrl retrieves remote origin url for the git repository at path
|
|
||||||
func (r *Repo) getRepoUrl() (string, error) {
|
|
||||||
_, err := os.Stat(r.Path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
args := []string{"config", "--get", "remote.origin.url"}
|
|
||||||
return runCmdOutput(gitBinary, args, r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// postPullCommand executes r.Then.
|
|
||||||
// It is trigged after successful git pull
|
|
||||||
func (r *Repo) postPullCommand() error {
|
|
||||||
if r.Then == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c, args, err := middleware.SplitCommandAndArgs(r.Then)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = runCmd(c, args, r.Path); err == nil {
|
|
||||||
logger().Printf("Command %v successful.\n", r.Then)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// initGit validates git installation and locates the git executable
|
|
||||||
// binary in PATH
|
|
||||||
func initGit() error {
|
|
||||||
// prevent concurrent call
|
|
||||||
initMutex.Lock()
|
|
||||||
defer initMutex.Unlock()
|
|
||||||
|
|
||||||
// if validation has been done before and binary located in
|
|
||||||
// PATH, return.
|
|
||||||
if gitBinary != "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate git binary in path
|
|
||||||
var err error
|
|
||||||
gitBinary, err = exec.LookPath("git")
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// runCmd is a helper function to run commands.
|
|
||||||
// It runs command with args from directory at dir.
|
|
||||||
// The executed process outputs to os.Stderr
|
|
||||||
func runCmd(command string, args []string, dir string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdout = os.Stderr
|
|
||||||
cmd.Dir = dir
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// runCmdOutput is a helper function to run commands and return output.
|
|
||||||
// It runs command with args from directory at dir.
|
|
||||||
// If successful, returns output and nil error
|
|
||||||
func runCmdOutput(command string, args []string, dir string) (string, error) {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.Dir = dir
|
|
||||||
var err error
|
|
||||||
if output, err := cmd.Output(); err == nil {
|
|
||||||
return string(bytes.TrimSpace(output)), nil
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeScriptFile writes content to a temporary file.
|
|
||||||
// It changes the temporary file mode to executable and
|
|
||||||
// closes it to prepare it for execution.
|
|
||||||
func writeScriptFile(content []byte) (file *os.File, err error) {
|
|
||||||
if file, err = ioutil.TempFile("", "caddy"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err = file.Write(content); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = file.Chmod(os.FileMode(0755)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return file, file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// gitWrapperScript forms content for git.sh script
|
|
||||||
var gitWrapperScript = func(gitBinary string) []byte {
|
|
||||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
|
||||||
|
|
||||||
# The MIT License (MIT)
|
|
||||||
# Copyright (c) 2013 Alvin Abad
|
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
echo "Git wrapper script that can specify an ssh-key file
|
|
||||||
Usage:
|
|
||||||
git.sh -i ssh-key-file git-command
|
|
||||||
"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# remove temporary file on exit
|
|
||||||
trap 'rm -f /tmp/.git_ssh.$$' 0
|
|
||||||
|
|
||||||
if [ "$1" = "-i" ]; then
|
|
||||||
SSH_KEY=$2; shift; shift
|
|
||||||
echo "ssh -i $SSH_KEY \$@" > /tmp/.git_ssh.$$
|
|
||||||
chmod +x /tmp/.git_ssh.$$
|
|
||||||
export GIT_SSH=/tmp/.git_ssh.$$
|
|
||||||
fi
|
|
||||||
|
|
||||||
# in case the git command is repeated
|
|
||||||
[ "$1" = "git" ] && shift
|
|
||||||
|
|
||||||
# Run the git command
|
|
||||||
%v "$@"
|
|
||||||
|
|
||||||
`, gitBinary))
|
|
||||||
}
|
|
||||||
|
|
||||||
// bashScript forms content of bash script to clone or update a repo using ssh
|
|
||||||
var bashScript = func(gitShPath string, repo *Repo, params []string) []byte {
|
|
||||||
return []byte(fmt.Sprintf(`#!/bin/bash
|
|
||||||
|
|
||||||
mkdir -p ~/.ssh;
|
|
||||||
touch ~/.ssh/known_hosts;
|
|
||||||
ssh-keyscan -t rsa,dsa %v 2>&1 | sort -u - ~/.ssh/known_hosts > ~/.ssh/tmp_hosts;
|
|
||||||
cat ~/.ssh/tmp_hosts >> ~/.ssh/known_hosts;
|
|
||||||
%v -i %v %v;
|
|
||||||
`, repo.Host, gitShPath, repo.KeyPath, strings.Join(params, " ")))
|
|
||||||
}
|
|
|
@ -24,11 +24,11 @@ type Markdown struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
|
||||||
// The list of markdown configurations
|
// The list of markdown configurations
|
||||||
Configs []MarkdownConfig
|
Configs []Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownConfig stores markdown middleware configurations.
|
// Config stores markdown middleware configurations.
|
||||||
type MarkdownConfig struct {
|
type Config struct {
|
||||||
// Markdown renderer
|
// Markdown renderer
|
||||||
Renderer blackfriday.Renderer
|
Renderer blackfriday.Renderer
|
||||||
|
|
||||||
|
@ -45,25 +45,6 @@ type MarkdownConfig struct {
|
||||||
Scripts []string
|
Scripts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new instance of Markdown middleware that
|
|
||||||
// renders markdown to HTML on-the-fly.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
mdconfigs, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
md := Markdown{
|
|
||||||
Root: c.Root(),
|
|
||||||
Configs: mdconfigs,
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
md.Next = next
|
|
||||||
return md
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler interface.
|
// ServeHTTP implements the http.Handler interface.
|
||||||
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for _, m := range md.Configs {
|
for _, m := range md.Configs {
|
||||||
|
@ -125,56 +106,6 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
return md.Next.ServeHTTP(w, r)
|
return md.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse creates new instances of Markdown middleware.
|
|
||||||
func parse(c middleware.Controller) ([]MarkdownConfig, error) {
|
|
||||||
var mdconfigs []MarkdownConfig
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
md := MarkdownConfig{
|
|
||||||
Renderer: blackfriday.HtmlRenderer(0, "", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the path scope
|
|
||||||
if !c.NextArg() || c.Val() == "{" {
|
|
||||||
return mdconfigs, c.ArgErr()
|
|
||||||
}
|
|
||||||
md.PathScope = c.Val()
|
|
||||||
|
|
||||||
// Load any other configuration parameters
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "ext":
|
|
||||||
exts := c.RemainingArgs()
|
|
||||||
if len(exts) == 0 {
|
|
||||||
return mdconfigs, c.ArgErr()
|
|
||||||
}
|
|
||||||
md.Extensions = append(md.Extensions, exts...)
|
|
||||||
case "css":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return mdconfigs, c.ArgErr()
|
|
||||||
}
|
|
||||||
md.Styles = append(md.Styles, c.Val())
|
|
||||||
case "js":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return mdconfigs, c.ArgErr()
|
|
||||||
}
|
|
||||||
md.Scripts = append(md.Scripts, c.Val())
|
|
||||||
default:
|
|
||||||
return mdconfigs, c.Err("Expected valid markdown configuration property")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no extensions were specified, assume .md
|
|
||||||
if len(md.Extensions) == 0 {
|
|
||||||
md.Extensions = []string{".md"}
|
|
||||||
}
|
|
||||||
|
|
||||||
mdconfigs = append(mdconfigs, md)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mdconfigs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
htmlTemplate = `<!DOCTYPE html>
|
htmlTemplate = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
|
@ -10,24 +10,6 @@ import (
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New constructs a new Templates middleware instance.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
rules, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpls := Templates{
|
|
||||||
Root: c.Root(),
|
|
||||||
Rules: rules,
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
tmpls.Next = next
|
|
||||||
return tmpls
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements the middleware.Handler interface.
|
// ServeHTTP implements the middleware.Handler interface.
|
||||||
func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for _, rule := range t.Rules {
|
for _, rule := range t.Rules {
|
||||||
|
@ -64,32 +46,6 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
return t.Next.ServeHTTP(w, r)
|
return t.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(c middleware.Controller) ([]Rule, error) {
|
|
||||||
var rules []Rule
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
var rule Rule
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
// First argument would be the path
|
|
||||||
rule.Path = c.Val()
|
|
||||||
|
|
||||||
// Any remaining arguments are extensions
|
|
||||||
rule.Extensions = c.RemainingArgs()
|
|
||||||
if len(rule.Extensions) == 0 {
|
|
||||||
rule.Extensions = defaultExtensions
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rule.Path = defaultPath
|
|
||||||
rule.Extensions = defaultExtensions
|
|
||||||
}
|
|
||||||
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates is middleware to render templated files as the HTTP response.
|
// Templates is middleware to render templated files as the HTTP response.
|
||||||
type Templates struct {
|
type Templates struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
@ -104,7 +60,3 @@ type Rule struct {
|
||||||
Path string
|
Path string
|
||||||
Extensions []string
|
Extensions []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultPath = "/"
|
|
||||||
|
|
||||||
var defaultExtensions = []string{".html", ".htm", ".txt"}
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
// WebSocket represents a web socket server instance. A WebSocket
|
// WebSocket represents a web socket server instance. A WebSocket
|
||||||
// is instantiated for each new websocket request/connection.
|
// is instantiated for each new websocket request/connection.
|
||||||
type WebSocket struct {
|
type WebSocket struct {
|
||||||
WSConfig
|
Config
|
||||||
*http.Request
|
*http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ type (
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
|
||||||
// Sockets holds all the web socket endpoint configurations
|
// Sockets holds all the web socket endpoint configurations
|
||||||
Sockets []WSConfig
|
Sockets []Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// WSConfig holds the configuration for a single websocket
|
// WSConfig holds the configuration for a single websocket
|
||||||
// endpoint which may serve multiple websocket connections.
|
// endpoint which may serve multiple websocket connections.
|
||||||
WSConfig struct {
|
Config struct {
|
||||||
Path string
|
Path string
|
||||||
Command string
|
Command string
|
||||||
Arguments []string
|
Arguments []string
|
||||||
|
@ -37,7 +37,7 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
|
||||||
for _, sockconfig := range ws.Sockets {
|
for _, sockconfig := range ws.Sockets {
|
||||||
if middleware.Path(r.URL.Path).Matches(sockconfig.Path) {
|
if middleware.Path(r.URL.Path).Matches(sockconfig.Path) {
|
||||||
socket := WebSocket{
|
socket := WebSocket{
|
||||||
WSConfig: sockconfig,
|
Config: sockconfig,
|
||||||
Request: r,
|
Request: r,
|
||||||
}
|
}
|
||||||
websocket.Handler(socket.Handle).ServeHTTP(w, r)
|
websocket.Handler(socket.Handle).ServeHTTP(w, r)
|
||||||
|
@ -49,77 +49,6 @@ func (ws WebSockets) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
|
||||||
return ws.Next.ServeHTTP(w, r)
|
return ws.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New constructs and configures a new websockets middleware instance.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
var websocks []WSConfig
|
|
||||||
var respawn bool
|
|
||||||
|
|
||||||
optionalBlock := func() (hadBlock bool, err error) {
|
|
||||||
for c.NextBlock() {
|
|
||||||
hadBlock = true
|
|
||||||
if c.Val() == "respawn" {
|
|
||||||
respawn = true
|
|
||||||
} else {
|
|
||||||
return true, c.Err("Expected websocket configuration parameter in block")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
var val, path, command string
|
|
||||||
|
|
||||||
// Path or command; not sure which yet
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
val = c.Val()
|
|
||||||
|
|
||||||
// Extra configuration may be in a block
|
|
||||||
hadBlock, err := optionalBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hadBlock {
|
|
||||||
// The next argument on this line will be the command or an open curly brace
|
|
||||||
if c.NextArg() {
|
|
||||||
path = val
|
|
||||||
command = c.Val()
|
|
||||||
} else {
|
|
||||||
path = "/"
|
|
||||||
command = val
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okay, check again for optional block
|
|
||||||
hadBlock, err = optionalBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split command into the actual command and its arguments
|
|
||||||
cmd, args, err := middleware.SplitCommandAndArgs(command)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
websocks = append(websocks, WSConfig{
|
|
||||||
Path: path,
|
|
||||||
Command: cmd,
|
|
||||||
Arguments: args,
|
|
||||||
Respawn: respawn, // TODO: This isn't used currently
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
GatewayInterface = envGatewayInterface
|
|
||||||
ServerSoftware = envServerSoftware
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
return WebSockets{Next: next, Sockets: websocks}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// See CGI spec, 4.1.4
|
// See CGI spec, 4.1.4
|
||||||
GatewayInterface string
|
GatewayInterface string
|
||||||
|
@ -127,8 +56,3 @@ var (
|
||||||
// See CGI spec, 4.1.17
|
// See CGI spec, 4.1.17
|
||||||
ServerSoftware string
|
ServerSoftware string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
envGatewayInterface = "caddy-CGI/1.1"
|
|
||||||
envServerSoftware = "caddy/" // TODO: Version
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in a new issue