Add cache attribute

This commit is contained in:
Trevor Slocum 2020-11-09 20:10:53 -08:00
parent e8af67c813
commit 8d02c6d779
7 changed files with 63 additions and 39 deletions

View file

@ -65,7 +65,8 @@ and the private key file at `certs/live/$DOMAIN/privkey.pem` to twins.
### DisableSize ### DisableSize
The size of the response body is included in the media type header by default. The size of the response body is included in the media type header by default.
Set this option to `true` to disable this feature. Set this option to `true` to disable this feature. See [PROPOSALS.md](https://gitlab.com/tslocum/twins/blob/master/PROPOSALS.md)
for more information.
### Path ### Path
@ -95,6 +96,12 @@ argument to the command, otherwise user input is passed via standard input.
Any number of attributes may be defined for a path. Any number of attributes may be defined for a path.
##### Cache
Cache duration (in seconds). Set to `0` to disable caching entirely. This is an
out-of-spec feature. See [PROPOSALS.md](https://gitlab.com/tslocum/twins/blob/master/PROPOSALS.md)
for more information.
##### ListDirectory ##### ListDirectory
Directory listing may be enabled by adding `listdirectory: true`. Directory listing may be enabled by adding `listdirectory: true`.
@ -124,9 +131,8 @@ A `Root` attribute must also be specified to use `FastCGI`.
The Gemini protocol requires `\r\n` (CRLF) as the end-of-line indicator. This The Gemini protocol requires `\r\n` (CRLF) as the end-of-line indicator. This
convention is carried over from protocol specifications **first written in the convention is carried over from protocol specifications **first written in the
1970s**. This requirement is antithetic to the spirit of Gemini (to improve 1970s**. This requirement is antithetic to the spirit of Gemini (to improve
upon the Finger and Gopher protocols) because it unnecessarily tacks on ancient upon the Finger and Gopher protocols), increasing the complexity of client and
baggage. This baggage has caused (and continues to cause) increased complexity in server implementations unnecessarily.
client and server implementations, which naturally gives rise to more bugs.
In anticipation of an improvement to the Gemini specification, administrators In anticipation of an improvement to the Gemini specification, administrators
may configure twins to send standard `\n` (LF) line endings by setting may configure twins to send standard `\n` (LF) line endings by setting
@ -157,6 +163,7 @@ hosts:
- -
path: /sites path: /sites
root: /home/geminirocks/data root: /home/geminirocks/data
cache: 604800 # Cache for 1 week
listdirectory: true listdirectory: true
- -
path: ^/(help|info)$ path: ^/(help|info)$
@ -167,6 +174,7 @@ hosts:
- -
path: ^/cmd-example$ path: ^/cmd-example$
command: uname -a command: uname -a
cache: 0 # Do not cache
- -
path: / path: /
root: /home/geminirocks/data/home root: /home/geminirocks/data/home

View file

@ -37,6 +37,9 @@ type pathConfig struct {
// Content type // Content type
Type string Type string
// Cache duration
Cache int64 `default:"-1965"`
// FastCGI server address // FastCGI server address
FastCGI string FastCGI string

2
go.mod
View file

@ -6,6 +6,6 @@ require (
github.com/h2non/filetype v1.1.0 github.com/h2non/filetype v1.1.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/yookoala/gofast v0.4.1-0.20201013050739-975113c54107 github.com/yookoala/gofast v0.4.1-0.20201013050739-975113c54107
golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b // indirect golang.org/x/tools v0.0.0-20201110030525-169ad6d6ecb2 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
) )

4
go.sum
View file

@ -34,8 +34,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b h1:ILx+nYAUTq4JtXxrXKQ82j7nruEwlXRfUn+kxtsnElg= golang.org/x/tools v0.0.0-20201110030525-169ad6d6ecb2 h1:5GmCe1Mc5HsGGl6E0kOVQRzVp+AgZf4Ffw4DadiVpd4=
golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110030525-169ad6d6ecb2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -9,7 +9,7 @@ import (
"strings" "strings"
) )
func serveCommand(c net.Conn, request *url.URL, command []string) { func serveCommand(c net.Conn, serve *pathConfig, request *url.URL, command []string) {
var args []string var args []string
if len(command) > 0 { if len(command) > 0 {
args = command[1:] args = command[1:]
@ -34,7 +34,7 @@ func serveCommand(c net.Conn, request *url.URL, command []string) {
return return
} }
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") writeSuccess(c, serve, geminiType, int64(buf.Len()))
c.Write(buf.Bytes()) c.Write(buf.Bytes())
if verbose { if verbose {

View file

@ -13,7 +13,7 @@ import (
"github.com/h2non/filetype" "github.com/h2non/filetype"
) )
func serveDirList(c net.Conn, request *url.URL, dirPath string) { func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) {
var ( var (
files []os.FileInfo files []os.FileInfo
numDirs int numDirs int
@ -50,7 +50,7 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) {
return i < j return i < j
}) })
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") writeSuccess(c, serve, geminiType, -1)
fmt.Fprintf(c, "# %s%s", request.Path, newLine) fmt.Fprintf(c, "# %s%s", request.Path, newLine)
if numDirs == 1 { if numDirs == 1 {
@ -93,7 +93,7 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) {
} }
} }
func serveFile(c net.Conn, filePath string) { func serveFile(c net.Conn, serve *pathConfig, filePath string) {
// Open file // Open file
file, _ := os.Open(filePath) file, _ := os.Open(filePath)
defer file.Close() defer file.Close()
@ -103,32 +103,26 @@ func serveFile(c net.Conn, filePath string) {
n, _ := file.Read(buf) n, _ := file.Read(buf)
// Write response header // Write response header
var contentType string
if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") {
contentType = "text/html; charset=utf-8"
} else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") {
contentType = "text/plain; charset=utf-8"
} else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") {
kind, _ := filetype.Match(buf[:n])
if kind != filetype.Unknown {
contentType = kind.MIME.Value
}
}
if contentType == "" {
contentType = geminiType
}
size := int64(-1) size := int64(-1)
info, err := file.Stat() info, err := file.Stat()
if err == nil { if err == nil {
size = info.Size() size = info.Size()
} }
var mimeType string writeSuccess(c, serve, contentType, size)
if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") {
mimeType = "text/html; charset=utf-8"
} else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") {
mimeType = "text/plain; charset=utf-8"
} else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") {
kind, _ := filetype.Match(buf[:n])
if kind != filetype.Unknown {
mimeType = kind.MIME.Value
}
}
if mimeType == "" {
mimeType = "text/gemini; charset=utf-8"
}
var meta string
if !config.DisableSize && size >= 0 {
meta = fmt.Sprintf("%s; size=%d", mimeType, size)
} else {
meta = mimeType
}
writeHeader(c, statusSuccess, meta)
// Write body // Write body
c.Write(buf[:n]) c.Write(buf[:n])

View file

@ -22,6 +22,8 @@ const (
readTimeout = 30 * time.Second readTimeout = 30 * time.Second
urlMaxLength = 1024 urlMaxLength = 1024
geminiType = "text/gemini; charset=utf-8"
) )
const ( const (
@ -74,6 +76,23 @@ func writeStatus(c net.Conn, code int) {
writeHeader(c, code, meta) writeHeader(c, code, meta)
} }
func writeSuccess(c net.Conn, serve *pathConfig, contentType string, size int64) {
meta := contentType
if serve.Type != "" {
meta = serve.Type
}
if !config.DisableSize && size >= 0 {
meta += fmt.Sprintf("; size=%d", size)
}
if serve.Cache != -1965 {
meta += fmt.Sprintf("; cache=%d", serve.Cache)
}
writeHeader(c, statusSuccess, meta)
}
func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) { func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 { if atEOF && len(data) == 0 {
return 0, nil, nil return 0, nil, nil
@ -138,11 +157,11 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
return return
} }
contentType := "text/gemini; charset=utf-8" contentType := geminiType
if serve.Type != "" { if serve.Type != "" {
contentType = serve.Type contentType = serve.Type
} }
writeHeader(c, statusSuccess, contentType) writeSuccess(c, serve, contentType, -1)
serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath) serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath)
return return
@ -151,11 +170,11 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
if requireInput { if requireInput {
newCommand := replaceWithUserInput(serve.cmd, request) newCommand := replaceWithUserInput(serve.cmd, request)
if newCommand != nil { if newCommand != nil {
serveCommand(c, request, newCommand) serveCommand(c, serve, request, newCommand)
return return
} }
} }
serveCommand(c, request, serve.cmd) serveCommand(c, serve, request, serve.cmd)
return return
} }
@ -183,7 +202,7 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
_, err := os.Stat(path.Join(filePath, "index.gemini")) _, err := os.Stat(path.Join(filePath, "index.gemini"))
if err != nil { if err != nil {
if serve.ListDirectory { if serve.ListDirectory {
serveDirList(c, request, filePath) serveDirList(c, serve, request, filePath)
return return
} }
writeStatus(c, statusNotFound) writeStatus(c, statusNotFound)
@ -199,7 +218,7 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) {
return return
} }
serveFile(c, filePath) serveFile(c, serve, filePath)
} }
func serveConn(c *tls.Conn) { func serveConn(c *tls.Conn) {