forked from tslocum/twins
Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Trevor Slocum | c026f5c7b1 | |
Ivan Vilata-i-Balaguer | 668e099ffa | |
Ivan Vilata-i-Balaguer | 066fe276eb | |
Trevor Slocum | 24f3196a61 | |
Trevor Slocum | 0c4c7e8ecb | |
Trevor Slocum | 12aa024671 | |
tslocum | 30d4097086 | |
Aaron Fischer | 5632fd1ba4 | |
Trevor Slocum | 3500533ecf | |
tslocum | 9cd235c469 | |
f | 2252fc45dd | |
Aaron Fischer | daf4b2b1a2 | |
Aaron Fischer | 8654f0ca1b | |
Aaron Fischer | 83453281bc | |
Trevor Slocum | f8a14016f0 |
|
@ -1,21 +0,0 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
|
||||
fmt:
|
||||
stage: validate
|
||||
script:
|
||||
- gofmt -l -s -e .
|
||||
- exit $(gofmt -l -s -e . | wc -l)
|
||||
|
||||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- go test -race -v ./...
|
|
@ -10,19 +10,29 @@ via the `--config` argument.
|
|||
|
||||
Address to listen for connections on in the format of `interface:port`.
|
||||
|
||||
### Listen on localhost
|
||||
|
||||
`localhost:1965`
|
||||
|
||||
### Listen on all interfaces
|
||||
### Listen on all addresses (IPv4 and IPv6)
|
||||
|
||||
`:1965`
|
||||
|
||||
### Listen on a specific address (IPv4 or IPv6)
|
||||
|
||||
`192.0.2.1:1965` or `"[2001:db8::1]:1965"` (please note the quotes and brackets
|
||||
for IPv6 addresses).
|
||||
|
||||
### Listen on localhost (IPv4 only)
|
||||
|
||||
`localhost:1965`
|
||||
|
||||
## Types
|
||||
|
||||
Content types may be defined by file extension. When a type is not defined for
|
||||
the requested file extension, content type is detected automatically.
|
||||
|
||||
## ShowImages
|
||||
|
||||
When enabled, clients accessing gemini pages via HTTPS will see links to images
|
||||
as inline images.
|
||||
|
||||
## Hosts
|
||||
|
||||
Hosts are defined by their hostname followed by one or more paths to serve.
|
||||
|
@ -46,18 +56,35 @@ enable an attribute by default and then disable it for individual paths.
|
|||
|
||||
A certificate and private key must be specified.
|
||||
|
||||
#### localhost certificate
|
||||
#### Self-signed domain certificate (recommended)
|
||||
|
||||
Use `openssl` generate a certificate for localhost.
|
||||
Use `openssl` generate a certificate for a domain.
|
||||
|
||||
Replace `rocketnine.space` and `twins.rocketnine.space` with your domain and subdomain.
|
||||
|
||||
```bash
|
||||
openssl req -x509 -out localhost.crt -keyout localhost.key \
|
||||
-newkey rsa:2048 -nodes -sha256 \
|
||||
-days 36500 \
|
||||
-subj '/CN=localhost' -extensions EXT -config <( \
|
||||
printf "[dn]\nCN=rocketnine.space\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:rocketnine.space,DNS:twins.rocketnine.space\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
|
||||
```
|
||||
|
||||
#### Self-signed localhost certificate
|
||||
|
||||
Use `openssl` generate a certificate for localhost.
|
||||
|
||||
Some Gemini clients will not accept such a certificate when accessing a server via domain or subdomain.
|
||||
|
||||
```bash
|
||||
openssl req -x509 -out localhost.crt -keyout localhost.key \
|
||||
-newkey rsa:2048 -nodes -sha256 \
|
||||
-days 36500 \
|
||||
-subj '/CN=localhost' -extensions EXT -config <( \
|
||||
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
|
||||
```
|
||||
|
||||
#### Domain certificate
|
||||
#### Signed certificate from Let's Encrypt
|
||||
|
||||
Use [certbot](https://certbot.eff.org) to get a certificate from [Let's Encrypt](https://letsencrypt.org) for a domain.
|
||||
|
||||
|
@ -74,7 +101,13 @@ certbot certonly --config-dir /home/www/certs \
|
|||
Provide the path to the certificate file at `certs/live/$DOMAIN/fullchain.pem`
|
||||
and the private key file at `certs/live/$DOMAIN/privkey.pem` to twins.
|
||||
|
||||
## DisableHTTPS
|
||||
### StyleSheet
|
||||
|
||||
Provide the path to a style sheet to serve instead of the default style sheet.
|
||||
|
||||
This option only applies when serving HTTPS connections.
|
||||
|
||||
### DisableHTTPS
|
||||
|
||||
Pages are also available via HTTPS on the same port by default.
|
||||
Set this option to `true` to disable this feature.
|
||||
|
@ -211,6 +244,7 @@ listen: :1965
|
|||
# Custom content types
|
||||
types:
|
||||
.json: application/json; charset=UTF-8
|
||||
.js: application/javascript; charset=UTF-8
|
||||
|
||||
# Hosts and paths to serve
|
||||
hosts:
|
||||
|
@ -223,6 +257,7 @@ hosts:
|
|||
gemini.rocks:
|
||||
cert: /srv/gemini.rocks/data/cert.crt
|
||||
key: /srv/gemini.rocks/data/cert.key
|
||||
stylesheet: /srv/gemini.rocks/style.css
|
||||
paths:
|
||||
-
|
||||
path: ^/.*\.php$
|
||||
|
|
32
config.go
32
config.go
|
@ -11,9 +11,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml"
|
||||
"github.com/kballard/go-shellquote"
|
||||
"github.com/yookoala/gofast"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type pathConfig struct {
|
||||
|
@ -66,12 +67,17 @@ type hostConfig struct {
|
|||
Key string
|
||||
Paths []*pathConfig
|
||||
|
||||
// Custom CSS styles. If specified, it will be used for all paths in that host/domain.
|
||||
StyleSheet string
|
||||
|
||||
cert *tls.Certificate
|
||||
css []byte
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
Listen string
|
||||
Types map[string]string
|
||||
ShowImages bool
|
||||
Hosts map[string]*hostConfig
|
||||
DisableHTTPS bool
|
||||
DisableSize bool
|
||||
|
@ -121,13 +127,13 @@ func readconfig(configPath string) error {
|
|||
newLine = "\r\n"
|
||||
}
|
||||
|
||||
split := strings.Split(config.Listen, ":")
|
||||
if len(split) != 2 {
|
||||
listenRe := regexp.MustCompile("(.*):([0-9]+)$")
|
||||
if !listenRe.MatchString(config.Listen) {
|
||||
config.hostname = config.Listen
|
||||
config.Listen += ":1965"
|
||||
} else {
|
||||
config.hostname = split[0]
|
||||
config.port, err = strconv.Atoi(split[1])
|
||||
config.hostname = listenRe.ReplaceAllString(config.Listen, "$1")
|
||||
config.port, err = strconv.Atoi(listenRe.ReplaceAllString(config.Listen, "$2"))
|
||||
if err != nil {
|
||||
log.Fatalf("invalid port specified: %s", err)
|
||||
}
|
||||
|
@ -147,6 +153,8 @@ func readconfig(configPath string) error {
|
|||
config.Types[".gemini"] = geminiType
|
||||
}
|
||||
|
||||
gmitohtml.Config.ConvertImages = config.ShowImages
|
||||
|
||||
defaultHost := config.Hosts["default"]
|
||||
delete(config.Hosts, "default")
|
||||
|
||||
|
@ -210,6 +218,20 @@ func readconfig(configPath string) error {
|
|||
}
|
||||
host.cert = &cert
|
||||
|
||||
// Custom CSS stylesheets are precached in customCSS and used on HTTPS requests.
|
||||
if host.StyleSheet != "" {
|
||||
_, err := os.Stat(host.StyleSheet)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("error: stylesheet '%s' not found", host.StyleSheet)
|
||||
} else {
|
||||
host.css, err = ioutil.ReadFile(host.StyleSheet)
|
||||
if err != nil {
|
||||
host.css = nil
|
||||
log.Printf("error: failed to read stylesheet %s: %s", host.StyleSheet, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, serve := range host.Paths {
|
||||
if serve.Path == "" {
|
||||
log.Fatal("a path must be specified in each serve entry")
|
||||
|
|
6
go.mod
6
go.mod
|
@ -3,10 +3,10 @@ module code.rocketnine.space/tslocum/twins
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/gmitohtml v1.0.4
|
||||
code.rocketnine.space/tslocum/gmitohtml v1.0.5
|
||||
github.com/h2non/filetype v1.1.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/yookoala/gofast v0.6.0
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
golang.org/x/tools v0.1.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
|
26
go.sum
26
go.sum
|
@ -1,5 +1,6 @@
|
|||
code.rocketnine.space/tslocum/gmitohtml v1.0.4 h1:EZsXfnP1sZx85PYNQb9XWFuF4WxSgeeTN3vxPsdGKyY=
|
||||
code.rocketnine.space/tslocum/gmitohtml v1.0.4/go.mod h1:1FWEBFtkgRZgRE7ktApyFe29nm3MCNANvH/Mp8hyG54=
|
||||
code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a/go.mod h1:SQrM+bQ4eZdyAVTxuF2BNnyAnojHP6Kcmm2vMszoFWw=
|
||||
code.rocketnine.space/tslocum/gmitohtml v1.0.5 h1:cc4MdgoGS7zBahUJovz6qDLa94eZpc2ncby4gxmckNc=
|
||||
code.rocketnine.space/tslocum/gmitohtml v1.0.5/go.mod h1:QOy0pJQltuKQQDupfkmtN1DOlrUBCg55xFaQwoKQwlM=
|
||||
github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
|
@ -14,37 +15,40 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
|||
github.com/yookoala/gofast v0.6.0 h1:E5x2acfUD7GkzCf8bmIMwnV10VxDy5tUCHc5LGhluwc=
|
||||
github.com/yookoala/gofast v0.6.0/go.mod h1:OJU201Q6HCaE1cASckaTbMm3KB6e0cZxK0mgqfwOKvQ=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
2
main.go
2
main.go
|
@ -14,12 +14,14 @@ func init() {
|
|||
}
|
||||
|
||||
var quiet bool
|
||||
var debug bool
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
configFile := flag.String("config", "", "path to configuration file")
|
||||
flag.BoolVar(&quiet, "quiet", false, "do not print access log")
|
||||
flag.BoolVar(&debug, "debug", false, "print debug information")
|
||||
flag.Parse()
|
||||
|
||||
if *configFile == "" {
|
||||
|
|
|
@ -30,16 +30,6 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) {
|
|||
status := http.StatusTemporaryRedirect
|
||||
http.Redirect(w, r, u.String(), status)
|
||||
return status, -1, ""
|
||||
} else if r.URL.Path == "/assets/style.css" {
|
||||
status := http.StatusOK
|
||||
w.Header().Set("Content-Type", cssType)
|
||||
w.WriteHeader(status)
|
||||
|
||||
if r.Method == "HEAD" {
|
||||
return status, 0, ""
|
||||
}
|
||||
w.Write(cssBytes)
|
||||
return status, int64(len(cssBytes)), ""
|
||||
}
|
||||
|
||||
pathBytes := []byte(r.URL.Path)
|
||||
|
@ -49,6 +39,23 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) {
|
|||
}
|
||||
|
||||
if host, ok := config.Hosts[r.URL.Hostname()]; ok {
|
||||
if strings.HasSuffix(r.URL.Path, "/assets/style.css") {
|
||||
status := http.StatusOK
|
||||
w.Header().Set("Content-Type", cssType)
|
||||
w.WriteHeader(status)
|
||||
|
||||
if r.Method == "HEAD" {
|
||||
return status, 0, ""
|
||||
}
|
||||
|
||||
if host.css != nil {
|
||||
w.Write(host.css)
|
||||
} else {
|
||||
w.Write(cssBytes)
|
||||
}
|
||||
return status, int64(len(cssBytes)), ""
|
||||
}
|
||||
|
||||
for _, serve := range host.Paths {
|
||||
matchedRegexp := serve.r != nil && serve.r.Match(pathBytes)
|
||||
matchedPrefix := serve.r == nil && strings.HasPrefix(r.URL.Path, serve.Path)
|
||||
|
@ -176,11 +183,15 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) {
|
|||
fileExt := strings.ToLower(filepath.Ext(filePath))
|
||||
if fileExt == ".gmi" || fileExt == ".gemini" {
|
||||
result = gmitohtml.Convert([]byte(data), r.URL.String())
|
||||
} else if fileExt == ".htm" || fileExt == ".html" {
|
||||
result = data
|
||||
} else {
|
||||
result = data
|
||||
contentType = plainType
|
||||
if fileExt == ".htm" || fileExt == ".html" {
|
||||
// HTML content type already set
|
||||
} else if customType := config.Types[filepath.Ext(filePath)]; customType != "" {
|
||||
contentType = customType
|
||||
} else {
|
||||
contentType = plainType
|
||||
}
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
|
|
Loading…
Reference in New Issue