mirror of
https://code.rocketnine.space/tslocum/twins.git
synced 2024-11-27 16:48:14 +01:00
Support proxying requests
This commit is contained in:
parent
d66e5d6384
commit
29c059c43f
5 changed files with 83 additions and 12 deletions
|
@ -2,6 +2,12 @@ Paths may be defined as fixed strings or regular expressions (starting with `^`)
|
||||||
|
|
||||||
Fixed string paths will match with and without a trailing slash.
|
Fixed string paths will match with and without a trailing slash.
|
||||||
|
|
||||||
|
Serve entries have either a `root` path or `proxy` URL. When a `root` path is
|
||||||
|
provided static files and directories are served from that location. When a
|
||||||
|
`proxy` URL is provided requests are forwarded to the Gemini server at that URL.
|
||||||
|
|
||||||
|
When accessing a directory `index.gemini` or `index.gmi` is served.
|
||||||
|
|
||||||
# config.yaml
|
# config.yaml
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -9,7 +15,7 @@ Fixed string paths will match with and without a trailing slash.
|
||||||
cert: /home/twins/data/certfile.crt
|
cert: /home/twins/data/certfile.crt
|
||||||
key: /home/twins/data/keyfile.key
|
key: /home/twins/data/keyfile.key
|
||||||
|
|
||||||
# Paths to serve
|
# Server paths
|
||||||
serve:
|
serve:
|
||||||
-
|
-
|
||||||
path: /sites
|
path: /sites
|
||||||
|
@ -17,6 +23,9 @@ serve:
|
||||||
-
|
-
|
||||||
path: ^/(help|info)$
|
path: ^/(help|info)$
|
||||||
root: /home/twins/data/help
|
root: /home/twins/data/help
|
||||||
|
-
|
||||||
|
path: ^/proxy-example$
|
||||||
|
proxy: gemini://gemini.rocks
|
||||||
-
|
-
|
||||||
path: /
|
path: /
|
||||||
root: /home/twins/data/home
|
root: /home/twins/data/home
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Serve static files
|
- Serve static files
|
||||||
|
- Reverse proxy support
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
@ -11,7 +12,9 @@ import (
|
||||||
|
|
||||||
type serveConfig struct {
|
type serveConfig struct {
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
Root string
|
Root string
|
||||||
|
Proxy string
|
||||||
|
|
||||||
r *regexp.Regexp
|
r *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
@ -46,7 +49,9 @@ func readconfig(configPath string) error {
|
||||||
|
|
||||||
for _, serve := range config.Serve {
|
for _, serve := range config.Serve {
|
||||||
if serve.Path == "" {
|
if serve.Path == "" {
|
||||||
continue
|
log.Fatal("path must be specified in serve entry")
|
||||||
|
} else if serve.Root != "" && serve.Proxy != "" {
|
||||||
|
log.Fatal("only one root or reverse proxy may defined for a serve entry")
|
||||||
}
|
}
|
||||||
|
|
||||||
if serve.Path[0] == '^' {
|
if serve.Path[0] == '^' {
|
||||||
|
|
7
main.go
7
main.go
|
@ -7,8 +7,15 @@ import (
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
var verbose bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configFile := flag.String("config", "", "path to configuration file")
|
configFile := flag.String("config", "", "path to configuration file")
|
||||||
|
flag.BoolVar(&verbose, "verbose", false, "print request and response information")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *configFile == "" {
|
if *configFile == "" {
|
||||||
|
|
69
server.go
69
server.go
|
@ -12,13 +12,20 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/h2non/filetype"
|
"github.com/h2non/filetype"
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const readTimeout = 30 * time.Second
|
||||||
|
|
||||||
func writeHeader(c net.Conn, code int, meta string) {
|
func writeHeader(c net.Conn, code int, meta string) {
|
||||||
fmt.Fprintf(c, "%d %s\r\n", code, meta)
|
fmt.Fprintf(c, "%d %s\r\n", code, meta)
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("< %d %s\n", code, meta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStatus(c net.Conn, code int) {
|
func writeStatus(c net.Conn, code int) {
|
||||||
|
@ -26,6 +33,8 @@ func writeStatus(c net.Conn, code int) {
|
||||||
switch code {
|
switch code {
|
||||||
case gemini.StatusTemporaryFailure:
|
case gemini.StatusTemporaryFailure:
|
||||||
meta = "Temporary failure"
|
meta = "Temporary failure"
|
||||||
|
case gemini.StatusProxyError:
|
||||||
|
meta = "Proxy error"
|
||||||
case gemini.StatusBadRequest:
|
case gemini.StatusBadRequest:
|
||||||
meta = "Bad request"
|
meta = "Bad request"
|
||||||
case gemini.StatusNotFound:
|
case gemini.StatusNotFound:
|
||||||
|
@ -34,6 +43,35 @@ func writeStatus(c net.Conn, code int) {
|
||||||
writeHeader(c, code, meta)
|
writeHeader(c, code, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveProxy(c net.Conn, proxyURL, requestData string) {
|
||||||
|
original := proxyURL
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
if strings.HasPrefix(proxyURL, "gemini://") {
|
||||||
|
proxyURL = proxyURL[9:]
|
||||||
|
} else if strings.HasPrefix(proxyURL, "gemini-insecure://") {
|
||||||
|
proxyURL = proxyURL[18:]
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
proxy, err := tls.Dial("tcp", proxyURL, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
writeStatus(c, gemini.StatusProxyError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer proxy.Close()
|
||||||
|
|
||||||
|
// Forward request
|
||||||
|
proxy.Write([]byte(requestData))
|
||||||
|
proxy.Write([]byte("\r\n"))
|
||||||
|
|
||||||
|
// Forward response
|
||||||
|
io.Copy(c, proxy)
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Printf("< %s\n", original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func serveFile(c net.Conn, filePath string) {
|
func serveFile(c net.Conn, filePath string) {
|
||||||
fi, err := os.Stat(filePath)
|
fi, err := os.Stat(filePath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
@ -111,6 +149,8 @@ func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
func handleConn(c net.Conn) {
|
func handleConn(c net.Conn) {
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
c.SetReadDeadline(time.Now().Add(readTimeout))
|
||||||
|
|
||||||
var requestData string
|
var requestData string
|
||||||
scanner := bufio.NewScanner(c)
|
scanner := bufio.NewScanner(c)
|
||||||
scanner.Split(scanCRLF)
|
scanner.Split(scanCRLF)
|
||||||
|
@ -135,19 +175,28 @@ func handleConn(c net.Conn) {
|
||||||
if strippedPath[0] == '/' {
|
if strippedPath[0] == '/' {
|
||||||
strippedPath = strippedPath[1:]
|
strippedPath = strippedPath[1:]
|
||||||
}
|
}
|
||||||
|
if verbose {
|
||||||
var realPath string
|
log.Printf("> %s\n", request)
|
||||||
for _, serve := range config.Serve {
|
|
||||||
if serve.r != nil && serve.r.Match(pathBytes) {
|
|
||||||
realPath = path.Join(serve.Root, strippedPath)
|
|
||||||
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
|
||||||
realPath = path.Join(serve.Root, request.Path[len(serve.Path):])
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serveFile(c, realPath)
|
for _, serve := range config.Serve {
|
||||||
|
if serve.Proxy != "" {
|
||||||
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
||||||
|
serveProxy(c, serve.Proxy, requestData)
|
||||||
return
|
return
|
||||||
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
||||||
|
serveProxy(c, serve.Proxy, requestData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
||||||
|
serveFile(c, path.Join(serve.Root, strippedPath))
|
||||||
|
return
|
||||||
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
||||||
|
serveFile(c, path.Join(serve.Root, strippedPath[len(serve.Path)-1:]))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStatus(c, gemini.StatusNotFound)
|
writeStatus(c, gemini.StatusNotFound)
|
||||||
|
|
Loading…
Reference in a new issue