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
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
@ -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.
##### 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
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
convention is carried over from protocol specifications **first written in the
1970s**. This requirement is antithetic to the spirit of Gemini (to improve
upon the Finger and Gopher protocols) because it unnecessarily tacks on ancient
baggage. This baggage has caused (and continues to cause) increased complexity in
client and server implementations, which naturally gives rise to more bugs.
upon the Finger and Gopher protocols), increasing the complexity of client and
server implementations unnecessarily.
In anticipation of an improvement to the Gemini specification, administrators
may configure twins to send standard `\n` (LF) line endings by setting
@ -157,6 +163,7 @@ hosts:
-
path: /sites
root: /home/geminirocks/data
cache: 604800 # Cache for 1 week
listdirectory: true
-
path: ^/(help|info)$
@ -167,6 +174,7 @@ hosts:
-
path: ^/cmd-example$
command: uname -a
cache: 0 # Do not cache
-
path: /
root: /home/geminirocks/data/home

View file

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

2
go.mod
View file

@ -6,6 +6,6 @@ require (
github.com/h2non/filetype v1.1.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
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
)

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-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-20201104193857-22bd85271a8b h1:ILx+nYAUTq4JtXxrXKQ82j7nruEwlXRfUn+kxtsnElg=
golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201110030525-169ad6d6ecb2 h1:5GmCe1Mc5HsGGl6E0kOVQRzVp+AgZf4Ffw4DadiVpd4=
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-20191011141410-1b5146add898/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"
)
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
if len(command) > 0 {
args = command[1:]
@ -34,7 +34,7 @@ func serveCommand(c net.Conn, request *url.URL, command []string) {
return
}
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8")
writeSuccess(c, serve, geminiType, int64(buf.Len()))
c.Write(buf.Bytes())
if verbose {

View file

@ -13,7 +13,7 @@ import (
"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 (
files []os.FileInfo
numDirs int
@ -50,7 +50,7 @@ func serveDirList(c net.Conn, request *url.URL, dirPath string) {
return i < j
})
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8")
writeSuccess(c, serve, geminiType, -1)
fmt.Fprintf(c, "# %s%s", request.Path, newLine)
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
file, _ := os.Open(filePath)
defer file.Close()
@ -103,32 +103,26 @@ func serveFile(c net.Conn, filePath string) {
n, _ := file.Read(buf)
// 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)
info, err := file.Stat()
if err == nil {
size = info.Size()
}
var mimeType string
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)
writeSuccess(c, serve, contentType, size)
// Write body
c.Write(buf[:n])

View file

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