type Hook func(http.ResponseWriter, *http.Request) bool
type HostCfg struct {
- Root string
- Index string
- TLS *TLSCfg
- DirList bool
- WebDAV bool
- Hooks []Hook
- MIMEOverride map[string]string
-
- DirListReadmes []string
+ Root string
+ TLS *TLSCfg
+ DirList bool
+ WebDAV bool
+ Hooks []Hook
+
+ Indexes []string
+ Readmes []string
+ MIMEs map[string]string
}
var Hosts = make(map[string]*HostCfg)
if fi.IsDir() {
file.IsDir = true
} else {
- file.Type = mediaType(file.Name, cfg.MIMEOverride)
+ file.Type = mediaType(file.Name, cfg.MIMEs)
}
files = append(files, file)
}
with @url{http://www.gostls13.cypherpunks.ru/, gostls13}). SNI supported.
@item HTTP/1.1, @url{https://en.wikipedia.org/wiki/HTTP%2F2, HTTP/2}
-(only when negotiated during ALPN) and keepalives support.
+(only when negotiated during ALPN) and keepalives support. Graceful
+shutdowns.
@item @code{gzip} and @url{https://facebook.github.io/zstd/, Zstandard}
compression on-the-fly.
w http.ResponseWriter, r *http.Request,
host string, cfg *HostCfg,
) {
- if cfg == nil {
+ notFound := func() {
fmt.Printf("%s %s \"%s %s %s\" %d \"%s\"\n",
r.RemoteAddr, host, r.Method, r.URL.Path, r.Proto,
http.StatusNotFound,
r.Header.Get("User-Agent"),
)
http.NotFound(w, r)
+ }
+ if cfg == nil {
+ notFound()
return
}
for _, hook := range cfg.Hooks {
- if hook(w, r) {
+ if done := hook(w, r); done {
return
}
}
}
if cfg.Root == "" {
- fmt.Printf("%s %s \"%s %s %s\" %d \"%s\"\n",
- r.RemoteAddr, host, r.Method, r.URL.Path, r.Proto,
- http.StatusNotFound,
- r.Header.Get("User-Agent"),
- )
- http.NotFound(w, r)
+ notFound()
return
}
var fd *os.File
var contentType string
var etag string
- pth := path.Clean(path.Join(cfg.Root, r.URL.Path))
+ pthOrig := path.Clean(path.Join(cfg.Root, r.URL.Path))
+ pth := pthOrig
IndexLookup:
fi, err := os.Stat(pth)
if err != nil {
- fmt.Printf("%s %s \"%s %s %s\" %d \"%s\"\n",
- r.RemoteAddr, host, r.Method, r.URL.Path, r.Proto,
- http.StatusNotFound,
- r.Header.Get("User-Agent"),
- )
- http.NotFound(w, r)
+ notFound()
return
}
if fi.IsDir() {
return
}
var readme []byte
- for _, f := range append(cfg.DirListReadmes, Readme) {
+ for _, f := range append(cfg.Readmes, Readme) {
readme, _ = ioutil.ReadFile(path.Join(pth, f))
if readme != nil {
break
}
contentType = "text/html; charset=utf-8"
} else {
- if cfg.Index == "" {
- pth = path.Join(pth, Index)
- } else {
- pth = path.Join(pth, cfg.Index)
+ for _, index := range append(cfg.Indexes, Index) {
+ p := path.Join(pth, index)
+ if _, err := os.Stat(p); err == nil {
+ pth = p
+ goto IndexLookup
+ }
}
- goto IndexLookup
+ notFound()
+ return
}
}
}
if contentType == "" {
- contentType = mediaType(path.Base(pth), cfg.MIMEOverride)
+ contentType = mediaType(path.Base(pth), cfg.MIMEs)
}
contentTypeBase := strings.SplitN(contentType, ";", 2)[0]
w.Header().Set("Content-Type", contentType)
var ContentTypes = make(map[string]string)
-func mediaType(fn string, override map[string]string) string {
+func mediaType(fn string, overrides map[string]string) string {
ext := path.Ext(fn)
if ext == "" {
ext = fn
}
- if ct := override[ext]; ct != "" {
+ if ct := overrides[ext]; ct != "" {
return ct
}
if ct := ContentTypes[ext]; ct != "" {
return ct
}
- if ct := override[""]; ct != "" {
+ if ct := overrides[""]; ct != "" {
return ct
}
return OctetStream
)
func init() {
- godlighty.Hosts["www.cryptoanarchy.ru"] = &godlighty.HostCfg{
+ host := "www.cryptoanarchy.ru"
+ godlighty.Hosts[host] = &godlighty.HostCfg{
Hooks: []godlighty.Hook{
func(w http.ResponseWriter, r *http.Request) bool {
- http.Redirect(
- w, r,
+ redirect(
+ host, w, r,
"//www.cypherpunks.ru/Manifesto-cryptoanarchist.html",
- http.StatusTemporaryRedirect,
+ http.StatusMovedPermanently,
)
return true
},
Hooks: []godlighty.Hook{
func(w http.ResponseWriter, r *http.Request) bool {
if r.URL.Path == "/" {
- http.Redirect(w, r, "//"+host+"/", http.StatusMovedPermanently)
+ redirect(host, w, r, "//"+host+"/", http.StatusMovedPermanently)
return true
}
return false
func addGoRepoCfg(host string) {
godlighty.Hosts[host] = &godlighty.HostCfg{
- Root: path.Join(WWW, host),
- TLS: newTLSCfg(host),
- MIMEOverride: map[string]string{"": "text/html"},
+ Root: path.Join(WWW, host),
+ TLS: newTLSCfg(host),
+ MIMEs: map[string]string{"": "text/html"},
}
}
func init() {
addStaticListedDir("www.if.mirror.cypherpunks.ru", "/storage/if")
- godlighty.Hosts["www.if.mirror.cypherpunks.ru"].MIMEOverride = map[string]string{
+ godlighty.Hosts["www.if.mirror.cypherpunks.ru"].MIMEs = map[string]string{
"directory-tree": "text/plain",
"Index": "text/plain",
"Master-Index": "text/plain",
}
- godlighty.Hosts["www.if.mirror.cypherpunks.ru"].DirListReadmes = []string{
- "Index",
- }
+ godlighty.Hosts["www.if.mirror.cypherpunks.ru"].Readmes = []string{"Index"}
}
--- /dev/null
+/*
+godlighty -- highly-customizable HTTP, HTTP/2, HTTPS server
+Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package rc
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func redirect(
+ host string,
+ w http.ResponseWriter, r *http.Request,
+ to string, status int,
+) {
+ http.Redirect(w, r, to, status)
+ fmt.Printf("%s %s \"%s %s %s\" %d \"%s\"\n",
+ r.RemoteAddr, host, r.Method, r.URL.Path, r.Proto,
+ status, r.Header.Get("User-Agent"),
+ )
+}